1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

feat: renaming

This commit is contained in:
Ash Keel 2023-01-31 17:52:28 +01:00
parent 196ab184e6
commit cfdabc706e
No known key found for this signature in database
GPG key ID: BAD8D93E7314ED3E
4 changed files with 143 additions and 25 deletions

View file

@ -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/*"]
}
}
}

View file

@ -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"

View file

@ -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;

View file

@ -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>
); );
} }