Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions apps/playground/src/components/Editor/DeleteFileDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button, Dialog } from '@douglasneuroinformatics/libui/components';
import { useTranslation } from '@douglasneuroinformatics/libui/hooks';

import { useAppStore } from '@/store';

Expand All @@ -10,12 +11,23 @@ export type DeleteFileDialogProps = {

export const DeleteFileDialog = ({ filename, isOpen, setIsOpen }: DeleteFileDialogProps) => {
const deleteFile = useAppStore((store) => store.deleteFile);
const { t } = useTranslation();
return filename ? (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>{`Are you sure you want to delete "${filename}"?`}</Dialog.Title>
<Dialog.Description>Once deleted, this file cannot be restored</Dialog.Description>
<Dialog.Title>
{t({
en: `Are you sure you want to delete "${filename}"?`,
fr: `Êtes-vous sûr de vouloir supprimer "${filename}" ?`
})}
</Dialog.Title>
<Dialog.Description>
{t({
en: 'Once deleted, this file cannot be restored',
fr: 'Une fois supprimé, ce fichier ne peut plus être restauré'
})}
</Dialog.Description>
</Dialog.Header>
<Dialog.Footer>
<Button
Expand All @@ -25,10 +37,10 @@ export const DeleteFileDialog = ({ filename, isOpen, setIsOpen }: DeleteFileDial
setIsOpen(false);
}}
>
Delete
{t({ en: 'Delete', fr: 'Supprimer' })}
</Button>
<Button type="button" variant="outline" onClick={() => setIsOpen(false)}>
Cancel
{t({ en: 'Cancel', fr: 'Annuler' })}
</Button>
</Dialog.Footer>
</Dialog.Content>
Expand Down
23 changes: 17 additions & 6 deletions apps/playground/src/components/Editor/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';

import { extractInputFileExtension } from '@opendatacapture/instrument-bundler';
import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
import { Columns3Icon, FilePlusIcon, FileUpIcon } from 'lucide-react';
import { motion } from 'motion/react';
import { useShallow } from 'zustand/react/shallow';
Expand Down Expand Up @@ -29,6 +30,7 @@ export const Editor = () => {

const [isFileUploadDialogOpen, setIsFileUploadDialogOpen] = useState(false);
const [isDeleteFileDialogOpen, setIsDeleteFileDialogOpen] = useState(false);
const { t } = useTranslation();

const addFile = useAppStore((store) => store.addFile);
const addFiles = useAppStore((store) => store.addFiles);
Expand Down Expand Up @@ -77,18 +79,22 @@ export const Editor = () => {
return (
<div className="flex h-full w-full flex-col border border-r-0 bg-slate-50 dark:bg-slate-800">
<div className="flex w-full border-b">
<EditorButton icon={<Columns3Icon />} tip="View Files" onClick={() => setIsSidebarOpen(!isSidebarOpen)} />
<EditorButton
icon={<Columns3Icon />}
tip={t({ en: 'View Files', fr: 'Voir les fichiers' })}
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
/>
<EditorButton
icon={<FilePlusIcon />}
tip="Add File"
tip={t({ en: 'Add File', fr: 'Ajouter un fichier' })}
onClick={() => {
setIsSidebarOpen(true);
setIsAddingFile(true);
}}
/>
<EditorButton
icon={<FileUpIcon />}
tip="Upload Files"
tip={t({ en: 'Upload Files', fr: 'Téléverser des fichiers' })}
onClick={() => {
setIsFileUploadDialogOpen(true);
}}
Expand Down Expand Up @@ -135,7 +141,9 @@ export const Editor = () => {
{openFilenames.length ? (
<EditorPane ref={editorPaneRef} onEditorMount={() => setIsEditorMounted(true)} />
) : (
<EditorPanePlaceholder>No File Selected</EditorPanePlaceholder>
<EditorPanePlaceholder>
{t({ en: 'No File Selected', fr: 'Aucun fichier sélectionné' })}
</EditorPanePlaceholder>
)}
</div>
<DeleteFileDialog
Expand All @@ -157,7 +165,7 @@ export const Editor = () => {
}}
isOpen={isFileUploadDialogOpen}
setIsOpen={setIsFileUploadDialogOpen}
title="Upload Files"
title={t({ en: 'Upload Files', fr: 'Téléverser des fichiers' })}
onSubmit={async (files) => {
const editorFiles = await loadEditorFilesFromNative(files);
addFiles(editorFiles);
Expand All @@ -166,7 +174,10 @@ export const Editor = () => {
onValidate={(files: File[]) => {
for (const file of files) {
if (filenames.includes(file.name)) {
return { message: `File already exists: ${file.name}`, result: 'error' };
return {
message: t({ en: `File already exists: ${file.name}`, fr: `Le fichier existe déjà : ${file.name}` }),
result: 'error'
};
}
}
return { result: 'success' };
Expand Down
7 changes: 4 additions & 3 deletions apps/playground/src/components/Editor/EditorFileButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';

import { useOnClickOutside } from '@douglasneuroinformatics/libui/hooks';
import { useOnClickOutside, useTranslation } from '@douglasneuroinformatics/libui/hooks';
import { cn } from '@douglasneuroinformatics/libui/utils';
import { PencilIcon, TrashIcon } from 'lucide-react';

Expand All @@ -23,6 +23,7 @@ export const EditorFileButton = ({ filename, isActive, onDelete }: EditorFileBut
const [displayFilename, setDisplayFilename] = useState(filename);
const [isRenaming, setIsRenaming] = useState(false);
const renameFile = useAppStore((store) => store.renameFile);
const { t } = useTranslation();

const rename = () => {
renameFile(filename, displayFilename);
Expand Down Expand Up @@ -79,15 +80,15 @@ export const EditorFileButton = ({ filename, isActive, onDelete }: EditorFileBut
}}
>
<TrashIcon />
<span className="ml-2 text-sm">Delete</span>
<span className="ml-2 text-sm">{t({ en: 'Delete', fr: 'Supprimer' })}</span>
</ContextMenu.Item>
<ContextMenu.Item
onSelect={() => {
setIsRenaming(true);
}}
>
<PencilIcon />
<span className="ml-2 text-sm">Rename</span>
<span className="ml-2 text-sm">{t({ en: 'Rename', fr: 'Renommer' })}</span>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu>
Expand Down
22 changes: 18 additions & 4 deletions apps/playground/src/components/Editor/EditorPane.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useImperativeHandle, useRef, useState } from 'react';

import { useTheme } from '@douglasneuroinformatics/libui/hooks';
import { useTheme, useTranslation } from '@douglasneuroinformatics/libui/hooks';
import MonacoEditor from '@monaco-editor/react';

import { useFilesRef } from '@/hooks/useFilesRef';
Expand Down Expand Up @@ -29,6 +29,7 @@ export const EditorPane = React.forwardRef<EditorPaneRef, EditorPaneProps>(funct

const [theme] = useTheme();
const [isMounted, setIsMounted] = useState(false);
const { t } = useTranslation();
const { libs } = useRuntime('v1');

const editorRef = useRef<MonacoEditorType | null>(null);
Expand Down Expand Up @@ -112,12 +113,21 @@ export const EditorPane = React.forwardRef<EditorPaneRef, EditorPaneProps>(funct
};

if (!defaultFile) {
return <EditorPanePlaceholder>No File Selected</EditorPanePlaceholder>;
return (
<EditorPanePlaceholder>{t({ en: 'No File Selected', fr: 'Aucun fichier sélectionné' })}</EditorPanePlaceholder>
);
}

const fileType = inferFileType(defaultFile.name);
if (!fileType) {
return <EditorPanePlaceholder>{`Error: Invalid file type "${fileType}"`}</EditorPanePlaceholder>;
return (
<EditorPanePlaceholder>
{t({
en: `Error: Invalid file type for "${defaultFile.name}"`,
fr: `Erreur : Type de fichier invalide pour "${defaultFile.name}"`
})}
</EditorPanePlaceholder>
);
} else if (fileType === 'asset') {
if (isBase64EncodedFileType(defaultFile.name)) {
return (
Expand All @@ -130,7 +140,11 @@ export const EditorPane = React.forwardRef<EditorPaneRef, EditorPaneProps>(funct
</div>
);
}
return <EditorPanePlaceholder>Cannot Display Binary Asset</EditorPanePlaceholder>;
return (
<EditorPanePlaceholder>
{t({ en: 'Cannot Display Binary Asset', fr: "Impossible d'afficher l'actif binaire" })}
</EditorPanePlaceholder>
);
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react';

import { Button, Dialog } from '@douglasneuroinformatics/libui/components';
import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
import { CloudUploadIcon } from 'lucide-react';
import { useDropzone } from 'react-dropzone';
import type { FileRejection } from 'react-dropzone';
Expand All @@ -20,11 +21,12 @@ export type FileUploadDialogProps = {
export const FileUploadDialog = ({ accept, isOpen, onSubmit, onValidate, setIsOpen, title }: FileUploadDialogProps) => {
const [files, setFiles] = useState<File[]>([]);
const [errorMessage, setErrorMessage] = useState<null | string>(null);
const { t } = useTranslation();

const handleDrop = useCallback(
(acceptedFiles: File[], rejections: FileRejection[]) => {
for (const { errors, file } of rejections) {
setErrorMessage(`Invalid file type: ${file.name} `);
setErrorMessage(t({ en: `Invalid file type: ${file.name} `, fr: `Type de fichier invalide : ${file.name} ` }));
console.error(errors);
return;
}
Expand Down Expand Up @@ -65,9 +67,15 @@ export const FileUploadDialog = ({ accept, isOpen, onSubmit, onValidate, setIsOp
} else if (files.length === 1) {
dropzoneText = files[0]!.name;
} else if (isDragActive) {
dropzoneText = 'Release your cursor to upload file(s)';
dropzoneText = t({
en: 'Release your cursor to upload file(s)',
fr: 'Relâchez votre curseur pour téléverser le(s) fichier(s)'
});
} else {
dropzoneText = 'Click here to upload, or drag and drop files into this area';
dropzoneText = t({
en: 'Click here to upload, or drag and drop files into this area',
fr: 'Cliquez ici pour téléverser, ou glissez et déposez des fichiers dans cette zone'
});
}

return (
Expand All @@ -94,7 +102,7 @@ export const FileUploadDialog = ({ accept, isOpen, onSubmit, onValidate, setIsOp
type="button"
onClick={() => void submitFiles(files)}
>
Submit
{t({ en: 'Submit', fr: 'Soumettre' })}
</Button>
</Dialog.Footer>
</Dialog.Content>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';

import { Button, DropdownMenu } from '@douglasneuroinformatics/libui/components';
import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
import { EllipsisVerticalIcon } from 'lucide-react';

import { useAppStore } from '@/store';
Expand All @@ -21,6 +22,7 @@ export const ActionsDropdown = () => {
const [showStorageUsageDialog, setShowStorageUsageDialog] = useState(false);

const selectedInstrument = useAppStore((store) => store.selectedInstrument);
const { t } = useTranslation();

return (
<React.Fragment>
Expand All @@ -43,22 +45,22 @@ export const ActionsDropdown = () => {
</DropdownMenu.Item>
<DropdownMenu.Item asChild onSelect={() => setShowLoginDialog(true)}>
<button className="w-full cursor-pointer disabled:cursor-not-allowed disabled:opacity-50" type="button">
Login
{t({ en: 'Login', fr: 'Se connecter' })}
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild onSelect={() => setShowUploadBundleDialog(true)}>
<button className="w-full cursor-pointer disabled:cursor-not-allowed disabled:opacity-50" type="button">
Upload Bundle
{t({ en: 'Upload Bundle', fr: 'Téléverser le paquet' })}
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild onSelect={() => setShowUserSettingsDialog(true)}>
<button className="w-full cursor-pointer disabled:cursor-not-allowed disabled:opacity-50" type="button">
User Settings
{t({ en: 'User Settings', fr: 'Paramètres utilisateur' })}
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild onSelect={() => setShowStorageUsageDialog(true)}>
<button className="w-full cursor-pointer disabled:cursor-not-allowed disabled:opacity-50" type="button">
Storage Usage
{t({ en: 'Storage Usage', fr: 'Utilisation du stockage' })}
</button>
</DropdownMenu.Item>
<DropdownMenu.Separator />
Expand All @@ -68,15 +70,15 @@ export const ActionsDropdown = () => {
disabled={selectedInstrument.category !== 'Saved'}
type="button"
>
Delete Instrument
{t({ en: 'Delete Instrument', fr: "Supprimer l'instrument" })}
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild onSelect={() => setShowRestoreDefaultsDialog(true)}>
<button
className="w-full cursor-pointer text-red-600 disabled:cursor-not-allowed disabled:opacity-50 dark:text-red-400"
type="button"
>
Restore Defaults
{t({ en: 'Restore Defaults', fr: 'Restaurer les paramètres par défaut' })}
</button>
</DropdownMenu.Item>
</DropdownMenu.Content>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, Dialog } from '@douglasneuroinformatics/libui/components';
import { useNotificationsStore } from '@douglasneuroinformatics/libui/hooks';
import { useNotificationsStore, useTranslation } from '@douglasneuroinformatics/libui/hooks';

import { useAppStore } from '@/store';

Expand All @@ -12,27 +12,36 @@ export const DeleteInstrumentDialog = ({ isOpen, setIsOpen }: DeleteInstrumentDi
const addNotification = useNotificationsStore((store) => store.addNotification);
const removeInstrument = useAppStore((store) => store.removeInstrument);
const selectedInstrument = useAppStore((store) => store.selectedInstrument);
const { t } = useTranslation();

return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Are you absolutely sure?</Dialog.Title>
<Dialog.Description>This instrument will be deleted from local storage.</Dialog.Description>
<Dialog.Title>{t({ en: 'Are you absolutely sure?', fr: 'Êtes-vous absolument sûr ?' })}</Dialog.Title>
<Dialog.Description>
{t({
en: 'This instrument will be deleted from local storage.',
fr: 'Cet instrument sera supprimé du stockage local.'
})}
</Dialog.Description>
</Dialog.Header>
<Dialog.Footer>
<Button
variant="danger"
onClick={() => {
removeInstrument(selectedInstrument.id);
addNotification({ message: 'This instrument has been deleted', type: 'success' });
addNotification({
message: t({ en: 'This instrument has been deleted', fr: 'Cet instrument a été supprimé' }),
type: 'success'
});
setIsOpen(false);
}}
>
Delete
{t({ en: 'Delete', fr: 'Supprimer' })}
</Button>
<Button type="button" variant="outline" onClick={() => setIsOpen(false)}>
Cancel
{t({ en: 'Cancel', fr: 'Annuler' })}
</Button>
</Dialog.Footer>
</Dialog.Content>
Expand Down
Loading
Loading