From d56cf96455f54ade7275762b2a51d9f3480ac467 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 15 Apr 2022 14:02:53 +0300 Subject: [PATCH 1/4] feat(main: dialog): pass config from renderer --- src/main/services/ipc/dialog.ts | 9 ++++++--- src/shared/types/main/index.d.ts | 8 +++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/services/ipc/dialog.ts b/src/main/services/ipc/dialog.ts index 196ee4dc..f2ccc188 100644 --- a/src/main/services/ipc/dialog.ts +++ b/src/main/services/ipc/dialog.ts @@ -1,11 +1,14 @@ -import type { MessageBoxRequest } from '@shared/types/main' +import type { DialogRequest, MessageBoxRequest } from '@shared/types/main' import { dialog, ipcMain } from 'electron' export const subscribeToDialog = () => { - ipcMain.handle('main:open-dialog', () => { + ipcMain.handle('main:open-dialog', (event, payload) => { return new Promise(resolve => { + const { properties, filters } = payload + const dir = dialog.showOpenDialogSync({ - properties: ['openDirectory', 'createDirectory'] + properties: properties || ['openDirectory', 'createDirectory'], + filters: filters || [{ name: '*', extensions: ['json'] }] }) if (dir) { diff --git a/src/shared/types/main/index.d.ts b/src/shared/types/main/index.d.ts index 9020dfb9..1bcd0b64 100644 --- a/src/shared/types/main/index.d.ts +++ b/src/shared/types/main/index.d.ts @@ -1,4 +1,4 @@ -import type { IpcRendererEvent } from 'electron' +import type { IpcRendererEvent, OpenDialogOptions } from 'electron' import type { TrackEvents } from '../main/analytics' import type { AppStore, PreferencesStore } from './store' @@ -77,6 +77,11 @@ export interface MessageBoxRequest { buttons: string[] } +export interface DialogRequest { + properties?: OpenDialogOptions['properties'] + filters?: OpenDialogOptions['filters'] +} + interface EventCallback { (event?: IpcRendererEvent, ...args: any[]): void } @@ -107,6 +112,7 @@ export interface ElectronBridge { } db: { migrate: (path: string) => Promise + migrateFromSnippetsLab: (path: string) => void move: (from: string, to: string) => Promise isExist: (path: string) => boolean } From f3a264f709493989743aae0a3d24327d759a9abe Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 15 Apr 2022 14:05:04 +0300 Subject: [PATCH 2/4] feat(main: db): add migrate from SnippetsLab --- package.json | 1 + src/main/preload.ts | 3 +- src/main/services/db/index.ts | 118 +++++++++++++++++- .../components/preferences/Storage.vue | 82 ++++++++++-- 4 files changed, 188 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index a0247edc..85ad5c50 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "fs-extra": "^10.0.1", "highlight.js": "^11.5.1", "interactjs": "^1.10.11", + "lodash": "^4.17.21", "lowdb": "^3.0.0", "markdown-it": "^12.3.2", "markdown-it-link-attributes": "^4.0.0", diff --git a/src/main/preload.ts b/src/main/preload.ts index 03d2ba57..adfd1c5a 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -1,5 +1,5 @@ import { contextBridge, ipcRenderer } from 'electron' -import { isDbExist, migrate, move } from './services/db' +import { isDbExist, migrate, migrateFromSnippetsLab, move } from './services/db' import { store } from './store' import type { ElectronBridge } from '@shared/types/main' import { version } from '../../package.json' @@ -29,6 +29,7 @@ contextBridge.exposeInMainWorld('electron', { }, db: { migrate: path => migrate(path), + migrateFromSnippetsLab: path => migrateFromSnippetsLab(path), move: (from, to) => move(from, to), isExist: path => isDbExist(path) }, diff --git a/src/main/services/db/index.ts b/src/main/services/db/index.ts index 92b9d20a..8934d1c1 100644 --- a/src/main/services/db/index.ts +++ b/src/main/services/db/index.ts @@ -3,8 +3,12 @@ import fs from 'fs-extra' import readline from 'readline' import { nestedToFlat } from '../../utils' import { nanoid } from 'nanoid' -import type { Folder, Snippet, Tag } from '@shared/types/main/db' -import { oldLanguageMap } from '../../../renderer/components/editor/languages' +import type { DB, Folder, Snippet, Tag } from '@shared/types/main/db' +import { + oldLanguageMap, + languages +} from '../../../renderer/components/editor/languages' +import { snakeCase } from 'lodash' const DB_NAME = 'db.json' @@ -207,3 +211,113 @@ export const migrate = async (path: string) => { writeToFile(db) console.log('Migrate is done') } + +export const migrateFromSnippetsLab = (path: string) => { + interface SLFragment { + Content: string + 'Date Created': string + 'Date Modified': string + Note: string + Title: string + Language: string + } + interface SLSnippet { + 'Date Created': string + 'Date Modified': string + Folder: string + Title: string + Fragments: SLFragment[] + Tags: string[] + } + + interface SnippetsLabDbJSON { + Snippets: SLSnippet[] + } + + const INBOX = 'Uncategorized' + + const file = fs.readFileSync(path, 'utf-8') + const json = JSON.parse(file) as SnippetsLabDbJSON + + const folders = new Set() + const tags = new Set() + + const db: DB = { + folders: [...DEFAULT_SYSTEM_FOLDERS], + snippets: [], + tags: [] + } + + json.Snippets.forEach(i => { + if (i.Folder) folders.add(i.Folder) + + if (i.Tags.length) { + i.Tags.forEach(t => tags.add(t)) + } + }) + + folders.forEach(i => { + if (i === INBOX) return + db.folders.push({ + id: nanoid(8), + name: i, + defaultLanguage: 'plain_text', + parentId: null, + isOpen: false, + isSystem: false, + createdAt: new Date().valueOf(), + updatedAt: new Date().valueOf() + }) + }) + + tags.forEach(i => { + db.tags.push({ + id: nanoid(8), + name: i, + createdAt: new Date().valueOf(), + updatedAt: new Date().valueOf() + }) + }) + + json.Snippets.forEach(i => { + const folderId = db.folders.find(f => f.name === i.Folder)?.id || '' + const tagsIds: string[] = [] + + if (i.Tags.length) { + i.Tags.forEach(t => { + const id = db.tags.find(_t => _t.name === t)?.id + if (id) tagsIds.push(id) + }) + } + + const snippet: Snippet = { + id: nanoid(8), + name: i.Title, + content: [], + folderId, + tagsIds, + isDeleted: false, + isFavorites: false, + createdAt: new Date(i['Date Created']).valueOf(), + updatedAt: new Date(i['Date Modified']).valueOf() + } + + if (i.Fragments.length) { + i.Fragments.forEach(f => { + const _language = snakeCase(f.Language.toLowerCase()) + const language = languages.find(i => i.value === _language)?.value + + snippet.content.push({ + label: f.Title, + value: f.Content, + language: language || 'plain_text' + }) + }) + } + + db.snippets.push(snippet) + }) + + writeToFile(db) + console.log('Migrate is done') +} diff --git a/src/renderer/components/preferences/Storage.vue b/src/renderer/components/preferences/Storage.vue index 536a7312..66022352 100644 --- a/src/renderer/components/preferences/Storage.vue +++ b/src/renderer/components/preferences/Storage.vue @@ -19,10 +19,21 @@ - Open folder + From massCode v1.0 + + + From SnippetsLab @@ -36,7 +47,7 @@ import { ipc, store, db, track } from '@/electron' import { useFolderStore } from '@/store/folders' import { useSnippetStore } from '@/store/snippets' -import type { MessageBoxRequest } from '@shared/types/main' +import type { MessageBoxRequest, DialogRequest } from '@shared/types/main' import { ref } from 'vue' const snippetStore = useSnippetStore() @@ -96,11 +107,16 @@ const onClickMigrate = async () => { try { const path = await ipc.invoke('main:open-dialog', {}) await db.migrate(path) + ipc.invoke('main:restart-api', {}) + + resetStore() + await snippetStore.getSnippets() + ipc.invoke('main:notification', { body: 'DB successfully migrated.' }) - snippetStore.getSnippets() + track('app/migrate') } catch (err) { const e = err as Error @@ -111,22 +127,62 @@ const onClickMigrate = async () => { } } +const onClickMigrateFromSnippetsLab = async () => { + const state = await ipc.invoke( + 'main:open-message-box', + { + message: 'Are you sure you want to migrate from SnippetsLab', + detail: 'During migrate, the current library will be overwritten.', + buttons: ['Confirm', 'Cancel'] + } + ) + + if (!state) return + + try { + const path = await ipc.invoke('main:open-dialog', { + properties: ['openFile'] + }) + + db.migrateFromSnippetsLab(path) + + ipc.invoke('main:restart-api', {}) + + resetStore() + await snippetStore.getSnippets() + + ipc.invoke('main:notification', { + body: 'DB successfully migrated.' + }) + + track('app/migrate', 'from-snippets-lab') + } catch (err) { + const e = err as Error + ipc.invoke('main:notification', { + body: e.message + }) + console.error(err) + } +} + const setStorageAndRestartApi = (path: string, reset?: boolean) => { storagePath.value = path store.preferences.set('storagePath', path) - if (reset) { - store.app.delete('selectedFolderAlias') - store.app.delete('selectedFolderId') - store.app.delete('selectedFolderIds') - store.app.delete('selectedSnippetId') - - snippetStore.$reset() - folderStore.$reset() - } + if (reset) resetStore() ipc.invoke('main:restart-api', {}) } + +const resetStore = () => { + store.app.delete('selectedFolderAlias') + store.app.delete('selectedFolderId') + store.app.delete('selectedFolderIds') + store.app.delete('selectedSnippetId') + + snippetStore.$reset() + folderStore.$reset() +} From 1db2ef5786bf2010409a1429f1576c2e295158c3 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 15 Apr 2022 14:07:52 +0300 Subject: [PATCH 3/4] refactor: rename Storage -> StoragePreferences --- .../preferences/{Storage.vue => StoragePreferences.vue} | 0 src/renderer/views/Preferences.vue | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/renderer/components/preferences/{Storage.vue => StoragePreferences.vue} (100%) diff --git a/src/renderer/components/preferences/Storage.vue b/src/renderer/components/preferences/StoragePreferences.vue similarity index 100% rename from src/renderer/components/preferences/Storage.vue rename to src/renderer/components/preferences/StoragePreferences.vue diff --git a/src/renderer/views/Preferences.vue b/src/renderer/views/Preferences.vue index 551044b1..fedaea2e 100644 --- a/src/renderer/views/Preferences.vue +++ b/src/renderer/views/Preferences.vue @@ -12,7 +12,7 @@ name="Storage" value="storage" > - + Date: Fri, 15 Apr 2022 14:11:27 +0300 Subject: [PATCH 4/4] fix(preferences): empty path by cancel in dialog via migrate --- src/renderer/components/preferences/StoragePreferences.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderer/components/preferences/StoragePreferences.vue b/src/renderer/components/preferences/StoragePreferences.vue index 66022352..075fb447 100644 --- a/src/renderer/components/preferences/StoragePreferences.vue +++ b/src/renderer/components/preferences/StoragePreferences.vue @@ -106,6 +106,9 @@ const onClickMigrate = async () => { try { const path = await ipc.invoke('main:open-dialog', {}) + + if (!path) return + await db.migrate(path) ipc.invoke('main:restart-api', {}) @@ -144,6 +147,8 @@ const onClickMigrateFromSnippetsLab = async () => { properties: ['openFile'] }) + if (!path) return + db.migrateFromSnippetsLab(path) ipc.invoke('main:restart-api', {})