diff --git a/package.json b/package.json index 49a71f8..4e3500b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dicionario", - "version": "2.6.4", + "version": "2.7.0", "description": "Dicionário simples onde você pode cadastrar as palavras que você aprendeu recentemente, fácil e prático.", "main": "./dist/main/main.js", "scripts": { @@ -21,16 +21,16 @@ "license": "MIT", "devDependencies": { "@arthur-lobo/eslint-config": "^1.0.0", - "@electron-forge/cli": "^6.4.1", - "@electron-forge/maker-deb": "^6.4.1", - "@electron-forge/maker-rpm": "^6.4.1", - "@electron-forge/maker-squirrel": "^6.4.1", - "@electron-forge/maker-zip": "^6.4.1", - "@electron-forge/publisher-github": "^6.4.1", + "@electron-forge/cli": "^6.4.2", + "@electron-forge/maker-deb": "^6.4.2", + "@electron-forge/maker-rpm": "^6.4.2", + "@electron-forge/maker-squirrel": "^6.4.2", + "@electron-forge/maker-zip": "^6.4.2", + "@electron-forge/publisher-github": "^6.4.2", "@types/deep-equal": "^1.0.1", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", - "electron": "^26.1.0", + "electron": "^26.2.4", "eslint": "^8.44.0", "sass-compiler": "^1.3.3", "svg-react-icon": "^1.0.3", @@ -40,16 +40,16 @@ "@arthur-lobo/react-onclickout": "^1.1.1", "@electron-fonts/nunito": "^1.1.0", "@electron-fonts/open-sans": "^1.1.0", - "@hookform/resolvers": "^3.2.0", + "@hookform/resolvers": "^3.3.1", "deep-equal": "^2.2.2", "electron-frame": "^1.4.5", "electron-squirrel-startup": "^1.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hook-form": "^7.45.4", - "react-router-dom": "^6.15.0", + "react-hook-form": "^7.46.2", + "react-router-dom": "^6.16.0", "update-electron-app": "^2.0.1", - "zod": "^3.20.6", - "zod-electron-store": "^1.0.2" + "zod": "^3.22.2", + "zod-electron-store": "^1.0.3" } } \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 55b1c0a..51ddacd 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -4,7 +4,7 @@ import UpdateListener from "update-electron-app" import Store from "zod-electron-store" import { createJumpList } from "./windowsJumpList" -import { options } from "../store/Store/options" +import { optionsStore } from "../store/Store/options" import "electron-frame/main" import "./events" @@ -55,7 +55,7 @@ function createWindow() { height: 600, minWidth: 800, minHeight: 600, - frame: isLinux && options.store.linux.useSystemTitleBar, + frame: isLinux && optionsStore.store.linux.useSystemTitleBar, autoHideMenuBar: true, show: false, icon: path.join(appPath, "assets", "icon.png"), diff --git a/src/renderer/components/modals/ExportData.tsx b/src/renderer/components/modals/ExportData.tsx new file mode 100644 index 0000000..7f78b31 --- /dev/null +++ b/src/renderer/components/modals/ExportData.tsx @@ -0,0 +1,92 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { ipcRenderer } from "electron" +import { useForm } from "react-hook-form" +import { z } from "zod" +import { SuccessModal } from "." +import { api } from "../../../store/Api" +import { defaultErrorHandler, hookformOnErrorFactory } from "../../ErrorHandler" +import { useModal } from "../../hooks/useModal" +import { FormModal } from "./FormModal" + +interface ExportDataModalProps { + onClose: () => void +} + +const dataSchema = z.object({ + path: z.string().refine((value) => value !== "", { + message: "Escolha um local para exportar o dicionário", + }), + compress: z.boolean().default(true), +}) + +type data = z.infer + +function getDefaulPath(): string { + return ipcRenderer.sendSync("get-path", "documents") +} + +export function ExportDataModal(props: ExportDataModalProps) { + const { setValue, watch, handleSubmit, register } = useForm({ + resolver: zodResolver(dataSchema), + defaultValues: { + path: getDefaulPath(), + compress: true, + }, + }) + + const path = watch("path") + const modal = useModal() + + function handleExport(data: data) { + try { + api.exporter.exportData(data.path, data.compress) + + modal.open( + , + ) + } catch (error) { + defaultErrorHandler(error, modal) + } + } + + const handleExportError = hookformOnErrorFactory(modal) + + function handlePickFolder() { + const folder = ipcRenderer.sendSync("get-folder") + if (folder === "canceled") return + setValue("path", folder) + } + + return ( + + {modal.content} +
+
+ Salvar em: + + + + Comprimir arquivo + +
+
+
+ ) +} diff --git a/src/renderer/components/modals/ImportData.tsx b/src/renderer/components/modals/ImportData.tsx new file mode 100644 index 0000000..9ac462a --- /dev/null +++ b/src/renderer/components/modals/ImportData.tsx @@ -0,0 +1,170 @@ +import { ipcRenderer } from "electron/renderer" +import fs from "node:fs" +import { FormEvent, useState } from "react" +import { ErrorModal, SuccessModal, WarningModal } from "." +import { api } from "../../../store/Api" +import { + backupData, + backupDataSchema, +} from "../../../store/ZodSchemas/exportdata" +import { defaultErrorHandler } from "../../ErrorHandler" +import { useModal } from "../../hooks/useModal" +import { If } from "../base" +import { WarningIcon } from "../icons" +import { FormModal } from "./FormModal" + +function CheckFileContent(filePath: string) { + const fileContent = fs.readFileSync(filePath, "utf-8") + const json = JSON.parse(fileContent) + const isValid = backupDataSchema.safeParse(json) + + if (isValid.success) { + return { isValid: true, data: isValid.data } + } + + return { isValid: false } +} + +interface ImportDataModalProps { + onClose: () => void +} + +export function ImportDataModal(props: ImportDataModalProps) { + const [selectedFile, setSelectedFile] = useState({ + path: "", + content: {} as backupData, + }) + + const [action, setAction] = useState<"merge" | "replace">("merge") + + const modal = useModal() + + function HandlePickFile() { + setSelectedFile({ + path: "", + content: {} as backupData, + }) + + const file = ipcRenderer.sendSync("get-file") + + if (file === "canceled") { + return + } + + const check = CheckFileContent(file) + + if (!check.isValid) { + return modal.open( + , + ) + } + + setSelectedFile({ + path: file, + content: check.data as backupData, + }) + + setAction("merge") + } + + const readyToImport = !!selectedFile.path + + function HandleSubmit(event: FormEvent) { + event.preventDefault() + event.stopPropagation() + + if (!readyToImport) { + return + } + + try { + if (action === "merge") { + api.importer.importData(selectedFile.content, action) + + modal.open( + , + ) + } + + if (action === "replace") { + modal.open( + { + modal.close() + + if (res) { + api.importer.importData(selectedFile.content, action) + + modal.open( + , + ) + } + }} + />, + ) + } + } catch (error) { + defaultErrorHandler(error, modal) + } + } + + return ( + <> + {modal.content} + +
+ Arquivo: +
+ + + +
+ + +
+ Tipo de importação: + +
+ + + + + Ao substituir os dados, todos seus dados anteriores serão + apagados. + + +
+
+
+ + ) +} diff --git a/src/renderer/components/modals/dictionary/ExportDictionary.tsx b/src/renderer/components/modals/dictionary/ExportDictionary.tsx index 0be574c..01f5605 100644 --- a/src/renderer/components/modals/dictionary/ExportDictionary.tsx +++ b/src/renderer/components/modals/dictionary/ExportDictionary.tsx @@ -44,7 +44,7 @@ export function ExportDictionaryModal(props: ExportDictionaryModalProps) { function handleExport(data: data) { try { - api.dictionaries.exportDictionary(dictionary.name, data.path) + api.exporter.exportDictionary(dictionary.name, data.path) modal.open( ("rename") const [newName, setNewName] = useState("") @@ -99,7 +99,7 @@ export function ImportDictionaryModal(props: ImportDictionaryModalProps) { try { if (alreadyExists) { if (action === "merge") { - DictionariesController.mergeDictionary(selectedFile.content) + api.importer.mergeDictionary(selectedFile.content) return modal.open( , ) } else { - DictionariesController.importDictionary(selectedFile.content) + api.importer.importDictionary(selectedFile.content) modal.open( @@ -95,6 +96,7 @@ export function DictionaryInfoModal(props: DictionaryInfoModalProps) { @@ -111,6 +113,7 @@ export function DictionaryInfoModal(props: DictionaryInfoModalProps) { @@ -128,6 +131,7 @@ export function DictionaryInfoModal(props: DictionaryInfoModalProps) { diff --git a/src/renderer/pages/Config/components/DictionarySection.tsx b/src/renderer/pages/Config/components/DictionarySection.tsx index b737f25..8e1d215 100644 --- a/src/renderer/pages/Config/components/DictionarySection.tsx +++ b/src/renderer/pages/Config/components/DictionarySection.tsx @@ -1,6 +1,8 @@ import { hoverFocus } from "../../../Util" import { LineTitle } from "../../../components/base" -import { AddIcon, DonwloadIcon } from "../../../components/icons" +import { AddIcon, DonwloadIcon, UploadIcon } from "../../../components/icons" +import { ExportDataModal } from "../../../components/modals/ExportData" +import { ImportDataModal } from "../../../components/modals/ImportData" import { useModal } from "../../../hooks/useModal" import { @@ -23,6 +25,14 @@ export function DictionarySection(props: DictionarySectionsProps) { modal.open() } + function HandleExportData() { + modal.open() + } + + function HandleImportData() { + modal.open() + } + return ( <> @@ -30,9 +40,9 @@ export function DictionarySection(props: DictionarySectionsProps) { Adicionar dicionário + + Exportar Dados + + + Importar Dados +