mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-18 01:50:50 +00:00
feat: renaming
This commit is contained in:
parent
196ab184e6
commit
cfdabc706e
4 changed files with 143 additions and 25 deletions
|
@ -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/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -318,7 +318,11 @@
|
||||||
"search": "Search by name",
|
"search": "Search by name",
|
||||||
"tab-manage": "Manage",
|
"tab-manage": "Manage",
|
||||||
"tab-editor": "Editor",
|
"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": {
|
"form-actions": {
|
||||||
|
@ -339,7 +343,8 @@
|
||||||
"password-reveal": "Reveal",
|
"password-reveal": "Reveal",
|
||||||
"password-hide": "Hide",
|
"password-hide": "Hide",
|
||||||
"start": "Start",
|
"start": "Start",
|
||||||
"stop": "Stop"
|
"stop": "Stop",
|
||||||
|
"rename": "Rename"
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"dev-build": "Development build"
|
"dev-build": "Development build"
|
||||||
|
|
|
@ -185,10 +185,16 @@ export const initializeExtensions = createAsyncThunk(
|
||||||
|
|
||||||
// Become reactive to extension changes
|
// Become reactive to extension changes
|
||||||
await api.client.subscribePrefix(extensionPrefix, (newValue, newKey) => {
|
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(
|
void dispatch(
|
||||||
refreshExtensionInstance({
|
refreshExtensionInstance({
|
||||||
...(JSON.parse(newValue) as ExtensionEntry),
|
...(JSON.parse(newValue) as ExtensionEntry),
|
||||||
name: newKey.substring(extensionPrefix.length),
|
name,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -256,12 +262,37 @@ export const saveExtension = createAsyncThunk(
|
||||||
|
|
||||||
export const removeExtension = createAsyncThunk(
|
export const removeExtension = createAsyncThunk(
|
||||||
'extensions/remove',
|
'extensions/remove',
|
||||||
async (name: string, { getState, dispatch }) => {
|
async (name: string, { getState }) => {
|
||||||
// Get kv client
|
// Get kv client
|
||||||
const { api } = getState() as RootState;
|
const { api } = getState() as RootState;
|
||||||
dispatch(extensionsReducer.actions.extensionRemoved(name));
|
|
||||||
await api.client.deleteKey(extensionPrefix + 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;
|
export default extensionsReducer;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Editor from '@monaco-editor/react';
|
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 React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { blankTemplate } from '~/lib/extensions/extension';
|
import { blankTemplate } from '~/lib/extensions/extension';
|
||||||
|
@ -9,18 +9,24 @@ import { useAppDispatch, useAppSelector } from '~/store';
|
||||||
import extensionsReducer, {
|
import extensionsReducer, {
|
||||||
ExtensionEntry,
|
ExtensionEntry,
|
||||||
removeExtension,
|
removeExtension,
|
||||||
|
renameExtension,
|
||||||
saveExtension,
|
saveExtension,
|
||||||
startExtension,
|
startExtension,
|
||||||
stopExtension,
|
stopExtension,
|
||||||
} from '~/store/extensions/reducer';
|
} from '~/store/extensions/reducer';
|
||||||
import AlertContent from '../components/AlertContent';
|
import AlertContent from '../components/AlertContent';
|
||||||
|
import DialogContent from '../components/DialogContent';
|
||||||
import Loading from '../components/Loading';
|
import Loading from '../components/Loading';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ComboBox,
|
ComboBox,
|
||||||
|
ControlledInputBox,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
Field,
|
Field,
|
||||||
FlexRow,
|
FlexRow,
|
||||||
InputBox,
|
InputBox,
|
||||||
|
Label,
|
||||||
MultiButton,
|
MultiButton,
|
||||||
PageContainer,
|
PageContainer,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
|
@ -232,15 +238,25 @@ const EditorDropdown = styled(ComboBox, {
|
||||||
});
|
});
|
||||||
|
|
||||||
function ExtensionEditor() {
|
function ExtensionEditor() {
|
||||||
|
const [dialogRename, setDialogRename] = useState({ open: false, name: '' });
|
||||||
const extensions = useAppSelector((state) => state.extensions);
|
const extensions = useAppSelector((state) => state.extensions);
|
||||||
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
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 =
|
const isUnsaved =
|
||||||
extensions.editorCurrentFile in extensions.unsaved &&
|
extensions.editorCurrentFile in extensions.unsaved &&
|
||||||
extensions.unsaved[extensions.editorCurrentFile] !==
|
extensions.unsaved[extensions.editorCurrentFile] !==
|
||||||
extensions.installed[extensions.editorCurrentFile]?.source;
|
extensions.installed[extensions.editorCurrentFile]?.source;
|
||||||
const currentFile = isUnsaved
|
const currentFile = isUnsaved
|
||||||
? extensions.unsaved[extensions.editorCurrentFile]
|
? extensions.unsaved[extensions.editorCurrentFile]
|
||||||
: extensions.installed[extensions.editorCurrentFile].source;
|
: extensions.installed[extensions.editorCurrentFile]?.source;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -276,7 +292,13 @@ function ExtensionEditor() {
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</EditorDropdown>
|
</EditorDropdown>
|
||||||
<EditorButton size="small" title="rename script">
|
<EditorButton
|
||||||
|
size="small"
|
||||||
|
title={t('pages.extensions.rename')}
|
||||||
|
onClick={() =>
|
||||||
|
setDialogRename({ open: true, name: extensions.editorCurrentFile })
|
||||||
|
}
|
||||||
|
>
|
||||||
<InputIcon />
|
<InputIcon />
|
||||||
</EditorButton>
|
</EditorButton>
|
||||||
<EditorButton
|
<EditorButton
|
||||||
|
@ -295,7 +317,7 @@ function ExtensionEditor() {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Save
|
{t('form-actions.save')}
|
||||||
</EditorButton>
|
</EditorButton>
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
<Editor
|
<Editor
|
||||||
|
@ -307,6 +329,82 @@ function ExtensionEditor() {
|
||||||
void dispatch(extensionsReducer.actions.extensionSourceChanged(ev));
|
void dispatch(extensionsReducer.actions.extensionSourceChanged(ev));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Dialog
|
||||||
|
open={dialogRename.open}
|
||||||
|
onOpenChange={(state) =>
|
||||||
|
setDialogRename({ ...dialogRename, open: state })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DialogContent
|
||||||
|
title={t('pages.extensions.rename-dialog', {
|
||||||
|
name: extensions.editorCurrentFile,
|
||||||
|
})}
|
||||||
|
closeButton={true}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
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 });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Field size="fullWidth" spacing="narrow">
|
||||||
|
<Label htmlFor="renamed">
|
||||||
|
{t('pages.extensions.rename-new-name')}
|
||||||
|
</Label>
|
||||||
|
<ControlledInputBox
|
||||||
|
id="renamed"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={dialogRename.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
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('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variation="primary" type="submit">
|
||||||
|
{t('form-actions.rename')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setDialogRename({ ...dialogRename, open: false })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('form-actions.cancel')}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue