From cfdabc706ee587ada3c04c5f16310b0b737cb03c Mon Sep 17 00:00:00 2001 From: Ash Keel Date: Tue, 31 Jan 2023 17:52:28 +0100 Subject: [PATCH] feat: renaming --- frontend/src/lib/workers/tsconfig.json | 16 ---- frontend/src/locale/en/translation.json | 9 +- frontend/src/store/extensions/reducer.ts | 37 +++++++- frontend/src/ui/pages/Extensions.tsx | 106 ++++++++++++++++++++++- 4 files changed, 143 insertions(+), 25 deletions(-) delete mode 100644 frontend/src/lib/workers/tsconfig.json diff --git a/frontend/src/lib/workers/tsconfig.json b/frontend/src/lib/workers/tsconfig.json deleted file mode 100644 index 0c714f8..0000000 --- a/frontend/src/lib/workers/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "module": "es2022", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "jsx": "react-jsx", - "lib": ["es2019", "WebWorker"], - "resolveJsonModule": true, - "baseUrl": ".", - "paths": { - "@wailsapp/*": ["./wailsjs/*"], - "~/*": ["./src/*"] - } - } -} diff --git a/frontend/src/locale/en/translation.json b/frontend/src/locale/en/translation.json index b904817..87daf68 100644 --- a/frontend/src/locale/en/translation.json +++ b/frontend/src/locale/en/translation.json @@ -318,7 +318,11 @@ "search": "Search by name", "tab-manage": "Manage", "tab-editor": "Editor", - "remove-alert": "Remove extension \"{{name}}\"?" + "remove-alert": "Remove extension \"{{name}}\"?", + "rename": "Rename extension", + "rename-dialog": "Rename extension \"{{name}}\"", + "name-already-in-use": "Name already in use", + "rename-new-name": "New name" } }, "form-actions": { @@ -339,7 +343,8 @@ "password-reveal": "Reveal", "password-hide": "Hide", "start": "Start", - "stop": "Stop" + "stop": "Stop", + "rename": "Rename" }, "debug": { "dev-build": "Development build" diff --git a/frontend/src/store/extensions/reducer.ts b/frontend/src/store/extensions/reducer.ts index ce5228f..f1b81e2 100644 --- a/frontend/src/store/extensions/reducer.ts +++ b/frontend/src/store/extensions/reducer.ts @@ -185,10 +185,16 @@ export const initializeExtensions = createAsyncThunk( // Become reactive to extension changes await api.client.subscribePrefix(extensionPrefix, (newValue, newKey) => { + const name = newKey.substring(extensionPrefix.length); + // Check for deleted + if (!newValue || newValue === '') { + void dispatch(extensionsReducer.actions.extensionRemoved(name)); + return; + } void dispatch( refreshExtensionInstance({ ...(JSON.parse(newValue) as ExtensionEntry), - name: newKey.substring(extensionPrefix.length), + name, }), ); }); @@ -256,12 +262,37 @@ export const saveExtension = createAsyncThunk( export const removeExtension = createAsyncThunk( 'extensions/remove', - async (name: string, { getState, dispatch }) => { + async (name: string, { getState }) => { // Get kv client const { api } = getState() as RootState; - dispatch(extensionsReducer.actions.extensionRemoved(name)); await api.client.deleteKey(extensionPrefix + name); }, ); +export const renameExtension = createAsyncThunk( + 'extensions/rename', + async (payload: { from: string; to: string }, { getState, dispatch }) => { + const { extensions } = getState() as RootState; + + // Save old entries + const unsaved = extensions.unsaved[payload.from]; + const entry = extensions.installed[payload.from]; + + // Remove and re-add under new name + await dispatch(removeExtension(payload.from)); + await dispatch( + saveExtension({ + ...entry, + name: payload.to, + }), + ); + + // Set unsaved and current file + dispatch(extensionsReducer.actions.editorSelectedFile(payload.to)); + if (unsaved) { + dispatch(extensionsReducer.actions.extensionSourceChanged(unsaved)); + } + }, +); + export default extensionsReducer; diff --git a/frontend/src/ui/pages/Extensions.tsx b/frontend/src/ui/pages/Extensions.tsx index b353787..a7a88ce 100644 --- a/frontend/src/ui/pages/Extensions.tsx +++ b/frontend/src/ui/pages/Extensions.tsx @@ -1,5 +1,5 @@ import Editor from '@monaco-editor/react'; -import { InputIcon, PlusIcon } from '@radix-ui/react-icons'; +import { InputIcon, PlusIcon, ZoomInIcon } from '@radix-ui/react-icons'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { blankTemplate } from '~/lib/extensions/extension'; @@ -9,18 +9,24 @@ import { useAppDispatch, useAppSelector } from '~/store'; import extensionsReducer, { ExtensionEntry, removeExtension, + renameExtension, saveExtension, startExtension, stopExtension, } from '~/store/extensions/reducer'; import AlertContent from '../components/AlertContent'; +import DialogContent from '../components/DialogContent'; import Loading from '../components/Loading'; import { Button, ComboBox, + ControlledInputBox, + Dialog, + DialogActions, Field, FlexRow, InputBox, + Label, MultiButton, PageContainer, PageHeader, @@ -232,15 +238,25 @@ const EditorDropdown = styled(ComboBox, { }); function ExtensionEditor() { + const [dialogRename, setDialogRename] = useState({ open: false, name: '' }); const extensions = useAppSelector((state) => state.extensions); + const { t } = useTranslation(); const dispatch = useAppDispatch(); + + // Normally you can't navigate here without this being set but there is an instant + // where you can and it messes up the dropdown, so don't render anything for that + // split second + if (!extensions.editorCurrentFile) { + return <>; + } + const isUnsaved = extensions.editorCurrentFile in extensions.unsaved && extensions.unsaved[extensions.editorCurrentFile] !== extensions.installed[extensions.editorCurrentFile]?.source; const currentFile = isUnsaved ? extensions.unsaved[extensions.editorCurrentFile] - : extensions.installed[extensions.editorCurrentFile].source; + : extensions.installed[extensions.editorCurrentFile]?.source; return (
))} - + + setDialogRename({ open: true, name: extensions.editorCurrentFile }) + } + > - Save + {t('form-actions.save')} + + setDialogRename({ ...dialogRename, open: state }) + } + > + +
{ + e.preventDefault(); + if (!(e.target as HTMLFormElement).checkValidity()) { + return; + } + + // Only rename if it changed + if (extensions.editorCurrentFile !== dialogRename.name) { + void dispatch( + renameExtension({ + from: extensions.editorCurrentFile, + to: dialogRename.name, + }), + ); + } + + setDialogRename({ ...dialogRename, open: false }); + }} + > + + + { + setDialogRename({ + ...dialogRename, + name: e.target.value, + }); + if ( + Object.values(extensions.installed).find( + (r) => r.name === e.target.value, + ) + ) { + (e.target as HTMLInputElement).setCustomValidity( + t('pages.extensions.name-already-in-use'), + ); + } else { + (e.target as HTMLInputElement).setCustomValidity(''); + } + }} + /> + + + + + +
+
+
); }