From 3d0914881d45cf158cedde137d3fcc56db667784 Mon Sep 17 00:00:00 2001 From: e-salad Date: Fri, 22 Nov 2024 20:52:46 -0600 Subject: [PATCH 1/3] implement script selection load --- .../TheRecommendationSelector.vue | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/presentation/components/Scripts/Menu/Recommendation/TheRecommendationSelector.vue b/src/presentation/components/Scripts/Menu/Recommendation/TheRecommendationSelector.vue index fdac58ca..d5d16510 100644 --- a/src/presentation/components/Scripts/Menu/Recommendation/TheRecommendationSelector.vue +++ b/src/presentation/components/Scripts/Menu/Recommendation/TheRecommendationSelector.vue @@ -81,6 +81,22 @@ /> + + + + + + @@ -97,6 +113,10 @@ import { setCurrentRecommendationStatus, getCurrentRecommendationStatus } from ' import { RecommendationStatusType } from './RecommendationStatusType'; import RecommendationDocumentation from './RecommendationDocumentation.vue'; +interface SavedSelection { + selectedScripts: string[]; +} + export default defineComponent({ components: { MenuOptionList, @@ -134,10 +154,64 @@ export default defineComponent({ }); } + async function loadFromFile() { + try { + // Use file input to load the JSON file + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + + // Create a promise to handle the file selection + const file = await new Promise((resolve, reject) => { + input.onchange = (event) => { + const { files } = (event.target as HTMLInputElement); + if (files && files.length > 0) { + resolve(files[0]); + } else { + reject(new Error('No file selected')); + } + }; + input.click(); + }); + + // Read the file content + const content = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = () => reject(reader.error); + reader.readAsText(file); + }); + + const savedSelection = JSON.parse(content) as SavedSelection; + + // Update the current selection state + modifyCurrentSelection((selection) => { + // First deselect all scripts + selection.scripts.deselectAll(); + + // Then select all scripts from the saved selection + savedSelection.selectedScripts.forEach((scriptId) => { + selection.scripts.processChanges({ + changes: [{ + scriptId, + newStatus: { + isSelected: true, + isReverted: false, + }, + }], + }); + }); + }); + } catch (error) { + console.error('Failed to load selection:', error); + } + } + return { RecommendationStatusType, currentRecommendationStatusType, selectRecommendationStatusType, + loadFromFile, }; }, }); From 2c2c742584989e518a71f891fcba3a7f7b23bd79 Mon Sep 17 00:00:00 2001 From: e-salad Date: Fri, 22 Nov 2024 21:29:24 -0600 Subject: [PATCH 2/3] implement script selection export --- .../Electron/NodeElectronSaveFileDialog.ts | 6 +++ src/presentation/common/Dialog.ts | 1 + .../CodeButtons/Save/SaveSelectionButton.vue | 50 +++++++++++++++++++ .../Code/CodeButtons/TheCodeButtons.vue | 3 ++ 4 files changed, 60 insertions(+) create mode 100644 src/presentation/components/Code/CodeButtons/Save/SaveSelectionButton.vue diff --git a/src/infrastructure/Dialog/Electron/NodeElectronSaveFileDialog.ts b/src/infrastructure/Dialog/Electron/NodeElectronSaveFileDialog.ts index 6eef3ef0..4364411e 100644 --- a/src/infrastructure/Dialog/Electron/NodeElectronSaveFileDialog.ts +++ b/src/infrastructure/Dialog/Electron/NodeElectronSaveFileDialog.ts @@ -164,6 +164,12 @@ const FileTypeSpecificFilters: Record = { extensions: ['sh', 'bash', 'zsh'], }, ], + [FileType.Json]: [ + { + name: 'JSON Files', + extensions: ['json'], + }, + ], }; type SaveDialogOutcome = diff --git a/src/presentation/common/Dialog.ts b/src/presentation/common/Dialog.ts index 2882c5b8..e79f53a8 100644 --- a/src/presentation/common/Dialog.ts +++ b/src/presentation/common/Dialog.ts @@ -6,6 +6,7 @@ export interface Dialog { export enum FileType { BatchFile, ShellScript, + Json, } export type SaveFileOutcome = SuccessfulSaveFile | FailedSaveFile; diff --git a/src/presentation/components/Code/CodeButtons/Save/SaveSelectionButton.vue b/src/presentation/components/Code/CodeButtons/Save/SaveSelectionButton.vue new file mode 100644 index 00000000..624bdc7f --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Save/SaveSelectionButton.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue b/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue index 9cc1dc91..71493e53 100644 --- a/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue +++ b/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue @@ -2,6 +2,7 @@
+
@@ -14,12 +15,14 @@ import { injectKey } from '@/presentation/injectionSymbols'; import CodeRunButton from './CodeRunButton.vue'; import CodeCopyButton from './CodeCopyButton.vue'; import CodeSaveButton from './Save/CodeSaveButton.vue'; +import SaveSelectionButton from './Save/SaveSelectionButton.vue'; export default defineComponent({ components: { CodeRunButton, CodeCopyButton, CodeSaveButton, + SaveSelectionButton, }, setup() { const { currentCode } = injectKey((keys) => keys.useCurrentCode); From 7aa31e54188af51a14ec193c4f88839946061315 Mon Sep 17 00:00:00 2001 From: e-salad Date: Sat, 23 Nov 2024 00:47:15 -0600 Subject: [PATCH 3/3] improve script selection functionality - Improve validation for configuration import - Refactor SaveSelectionButton.vue implementation - Add floppy-disk-gear icon for SaveSelectionButton --- .../Dialog/Browser/FileSaverDialog.ts | 2 + .../assets/icons/floppy-disk-gear.svg | 4 ++ .../CodeButtons/Save/SaveSelectionButton.vue | 30 +++++--- .../TheRecommendationSelector.vue | 70 ++++++++++++++++--- .../components/Shared/Icon/IconName.ts | 1 + 5 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 src/presentation/assets/icons/floppy-disk-gear.svg diff --git a/src/infrastructure/Dialog/Browser/FileSaverDialog.ts b/src/infrastructure/Dialog/Browser/FileSaverDialog.ts index b29e565d..4d77961c 100644 --- a/src/infrastructure/Dialog/Browser/FileSaverDialog.ts +++ b/src/infrastructure/Dialog/Browser/FileSaverDialog.ts @@ -39,4 +39,6 @@ const MimeTypes: Record = { // otherwise they ignore extension and save the file as text. [FileType.BatchFile]: 'application/bat', // https://en.wikipedia.org/wiki/Batch_file [FileType.ShellScript]: 'text/x-shellscript', // https://de.wikipedia.org/wiki/Shellskript#MIME-Typ + [FileType.Json]: 'application/json', // https://en.wikipedia.org/wiki/JSON + } as const; diff --git a/src/presentation/assets/icons/floppy-disk-gear.svg b/src/presentation/assets/icons/floppy-disk-gear.svg new file mode 100644 index 00000000..3f5b8e79 --- /dev/null +++ b/src/presentation/assets/icons/floppy-disk-gear.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/presentation/components/Code/CodeButtons/Save/SaveSelectionButton.vue b/src/presentation/components/Code/CodeButtons/Save/SaveSelectionButton.vue index 624bdc7f..32bb205e 100644 --- a/src/presentation/components/Code/CodeButtons/Save/SaveSelectionButton.vue +++ b/src/presentation/components/Code/CodeButtons/Save/SaveSelectionButton.vue @@ -1,7 +1,7 @@ @@ -11,6 +11,13 @@ import { defineComponent } from 'vue'; import { injectKey } from '@/presentation/injectionSymbols'; import { FileType } from '@/presentation/common/Dialog'; import IconButton from '../IconButton.vue'; +import { createScriptErrorDialog } from '../ScriptErrorDialog'; + +interface SelectionConfig { + version: string; + timestamp: string; + selectedScripts: string[]; +} export default defineComponent({ components: { @@ -20,25 +27,28 @@ export default defineComponent({ const { currentSelection } = injectKey((keys) => keys.useUserSelectionState); const { dialog } = injectKey((keys) => keys.useDialog); const { projectDetails } = injectKey((keys) => keys.useApplication); + const { scriptDiagnosticsCollector } = injectKey((keys) => keys.useScriptDiagnosticsCollector); async function saveSelection() { - // Create a config object with the current selection state - const config = { - version: projectDetails.version, - selectedScripts: currentSelection.value.scripts.selectedScripts.map((script) => script.id), + const config: SelectionConfig = { + version: projectDetails.version.toString(), timestamp: new Date().toISOString(), + selectedScripts: currentSelection.value.scripts.selectedScripts.map((script) => script.id), }; - const configJson = JSON.stringify(config, null, 2); - const { success, error } = await dialog.saveFile( - configJson, + JSON.stringify(config, null, 2), 'privacy-selection.json', FileType.Json, ); - if (!success && error) { - console.error('Failed to save selection:', error); + if (!success) { + dialog.showError(...(await createScriptErrorDialog({ + errorContext: 'save', + errorType: error.type, + errorMessage: error.message, + isFileReadbackError: error.type === 'FileReadbackVerificationError', + }, scriptDiagnosticsCollector))); } } diff --git a/src/presentation/components/Scripts/Menu/Recommendation/TheRecommendationSelector.vue b/src/presentation/components/Scripts/Menu/Recommendation/TheRecommendationSelector.vue index d5d16510..c2fcb320 100644 --- a/src/presentation/components/Scripts/Menu/Recommendation/TheRecommendationSelector.vue +++ b/src/presentation/components/Scripts/Menu/Recommendation/TheRecommendationSelector.vue @@ -86,7 +86,7 @@ @@ -102,7 +106,7 @@