diff --git a/package.json b/package.json index 9c6e6d2..10143b2 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@nuxt/devalue": "^2.0.0", + "add": "^2.0.6", "add-filename-increment": "^1.0.0", "autoprefixer": "^10.4", "electron-settings": "^4.0", @@ -55,10 +56,13 @@ "unused-filename": "^4.0.0", "v8-compile-cache": "^2.3.0", "vue": "^3.2", - "vue-router": "^4.0" + "vue-router": "^4.0", + "wavesurfer.js": "^6.0.1", + "yarn": "^1.22.17" }, "devDependencies": { "@types/electron-devtools-installer": "^2.2", + "@types/wavesurfer.js": "^6.0.0", "@typescript-eslint/eslint-plugin": "^5.11", "@typescript-eslint/parser": "^5.11", "@vitejs/plugin-vue": "^2.2", diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts index b8440f8..01f78c7 100644 --- a/packages/main/src/index.ts +++ b/packages/main/src/index.ts @@ -1,10 +1,11 @@ -import { app, BrowserWindow, ipcMain, dialog } from 'electron' +import { app, BrowserWindow, ipcMain, dialog, protocol } from 'electron' import { join } from 'path' -import { mkdir } from 'fs/promises' +import { mkdir, unlink } from 'fs/promises' import { URL } from 'url' // import devalue from '@nuxt/devalue' import Store from 'electron-store' import 'v8-compile-cache' +import type { Sample } from 'root/types' import { enforceApplicationFolder, @@ -25,6 +26,7 @@ const store = new Store({ const isSingleInstance = app.requestSingleInstanceLock() const userDataPath = app.getPath('userData') const samplePath = join(userDataPath, 'samples') +const protocolName = 'slip-board' console.log('userDataPath', userDataPath) pathAvailable(samplePath).then((available) => { if (available) { @@ -46,6 +48,13 @@ const mainPageUrl = ? import.meta.env.VITE_DEV_SERVER_URL : new URL('../renderer/dist/index.html', 'file://' + __dirname).toString() +protocol.registerSchemesAsPrivileged([ + { + scheme: 'slip-board', + privileges: { secure: true, standard: true, supportFetchAPI: true }, + }, +]) + if (!isSingleInstance) { app.quit() process.exit(0) @@ -105,8 +114,44 @@ ipcMain.on('openSamplesFilepicker', () => { }) }) +ipcMain.on('processDroppedSamples', (event, fileList: string[]) => { + console.log('processing dropped samples') + + filepathsToSamples(samplePath, fileList).then((samples) => { + console.log('got samples: ', samples) + mainWindow.webContents.send('addedSamples', samples) + }) +}) + +ipcMain.on('confirmSampleDelete', (event, sample: Sample) => { + console.log('confirmSampleDelete payload: ', sample) + + const answer = dialog.showMessageBoxSync({ + message: 'Are you sure?', + type: 'warning', + detail: 'This action cannot be undone.', + buttons: ['Yes', 'No'], + }) + + if (answer === 0) { + console.log('deleting sample: ', sample) + mainWindow.webContents.send('deletedSample', sample.id) + unlink(sample.path).then(() => { + console.log('succesfully deleted sample') + }) + } +}) + function initMain() { return new Promise((resolve) => { + protocol.registerFileProtocol(protocolName, (request, callback) => { + const url = request.url.replace(`${protocolName}://`, '') + try { + return callback(decodeURIComponent(url)) + } catch (error) { + console.error(error) + } + }) resolve() }) } diff --git a/packages/renderer/assets/main.postcss b/packages/renderer/assets/main.postcss index 39bdcad..95534dd 100644 --- a/packages/renderer/assets/main.postcss +++ b/packages/renderer/assets/main.postcss @@ -3,7 +3,7 @@ @tailwind utilities; html { - @apply bg-black text-gray-200; + @apply bg-[#1C1E20] text-gray-200; font-size: 14px; } diff --git a/packages/renderer/src/App.vue b/packages/renderer/src/App.vue index 0116eea..699b8eb 100644 --- a/packages/renderer/src/App.vue +++ b/packages/renderer/src/App.vue @@ -9,17 +9,48 @@ store.initApp() const uiMode = computed(() => store.ui.mode) const headerTextColor = computed(() => store.headerTextColor) const headerBgColor = computed(() => store.headerBgColor) +const dragMode = computed(() => store.ui.dragMode) function toggleUiMode() { if (uiMode.value === 'play') { store.ui.mode = 'edit' - console.log('changed uiMode to edit') } else { store.ui.mode = 'play' - console.log('changed uiMode to play') + store.setActiveSample('') } } +document.addEventListener('drop', (event) => { + event.preventDefault() + event.stopPropagation() + store.ui.dragMode = false + const filePaths: string[] = [] + // @ts-expect-error + for (const f of event.dataTransfer.files) { + // Using the path attribute to get absolute file path + // @ts-expect-error + filePaths.push(f.path) + // @ts-expect-error + console.log('File Path of dragged files: ', f.path) + } + window.api.send('processDroppedSamples', filePaths) +}) + +document.addEventListener('dragover', (e) => { + e.preventDefault() + e.stopPropagation() +}) + +document.addEventListener('dragenter', () => { + store.ui.dragMode = true + console.log('File is in the Drop Space') +}) + +document.addEventListener('dragleave', () => { + store.ui.dragMode = false + console.log('File has left the Drop Space') +}) + window.api.receive('blur', () => { store.changeFocus(false) }) @@ -32,7 +63,9 @@ window.api.receive('addedSamples', (samples: Sample[]) => { store.addSamples(samples) }) -console.log('Renderer setup DONE') +window.api.receive('deletedSample', (id: string) => { + store.deleteSample(id) +}) diff --git a/packages/renderer/src/components/SampleCard.vue b/packages/renderer/src/components/SampleCard.vue new file mode 100644 index 0000000..02555b6 --- /dev/null +++ b/packages/renderer/src/components/SampleCard.vue @@ -0,0 +1,82 @@ + + diff --git a/packages/renderer/src/components/SampleEditor.vue b/packages/renderer/src/components/SampleEditor.vue new file mode 100644 index 0000000..238f28c --- /dev/null +++ b/packages/renderer/src/components/SampleEditor.vue @@ -0,0 +1,29 @@ + + diff --git a/packages/renderer/src/pages/About.vue b/packages/renderer/src/pages/About.vue deleted file mode 100644 index 4f8ea8f..0000000 --- a/packages/renderer/src/pages/About.vue +++ /dev/null @@ -1,17 +0,0 @@ - - diff --git a/packages/renderer/src/pages/EditMode.vue b/packages/renderer/src/pages/EditMode.vue deleted file mode 100644 index 29868ca..0000000 --- a/packages/renderer/src/pages/EditMode.vue +++ /dev/null @@ -1,66 +0,0 @@ - - - diff --git a/packages/renderer/src/pages/Home.vue b/packages/renderer/src/pages/Home.vue deleted file mode 100644 index f18c9e0..0000000 --- a/packages/renderer/src/pages/Home.vue +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/packages/renderer/src/pages/PlayMode.vue b/packages/renderer/src/pages/PlayMode.vue deleted file mode 100644 index 86d1688..0000000 --- a/packages/renderer/src/pages/PlayMode.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/packages/renderer/src/router.ts b/packages/renderer/src/router.ts index 56c318e..e1ffb6c 100644 --- a/packages/renderer/src/router.ts +++ b/packages/renderer/src/router.ts @@ -1,14 +1,7 @@ import { createRouter, createWebHashHistory } from 'vue-router' -import Home from '@/pages/Home.vue' +import GridView from '@/views/GridView.vue' -const routes = [ - { path: '/', name: 'Home', component: Home }, - { - path: '/about', - name: 'About', - component: () => import('@/pages/About.vue'), - }, // Lazy load route component -] +const routes = [{ path: '/', name: 'GridView', component: GridView }] export default createRouter({ routes, diff --git a/packages/renderer/src/store.ts b/packages/renderer/src/store.ts index aa043d0..4757edf 100644 --- a/packages/renderer/src/store.ts +++ b/packages/renderer/src/store.ts @@ -1,7 +1,7 @@ import { toRaw } from 'vue' import { defineStore } from 'pinia' import type { Sample, Board, UiMode } from 'root/types' -import { find, filter } from 'rambda' +import { find, filter, findIndex } from 'rambda' export const useStore = defineStore('main', { state: () => ({ @@ -13,6 +13,8 @@ export const useStore = defineStore('main', { firstStart: true, mode: 'play' as UiMode, inFocus: true, + activeSample: '', + dragMode: false, }, boards: [] as Board[], samples: [] as Sample[], @@ -76,6 +78,34 @@ export const useStore = defineStore('main', { } } this.saveStore() + this.ui.firstStart = false + this.ui.mode = 'play' + }, + + setActiveSample(id: string) { + this.ui.activeSample = id + }, + + deleteSample(id: string) { + this.ui.activeSample = '' + + // delete from all boards + const bpredicate = (sid: string) => sid === id + this.boards.forEach((board, bindex) => { + const index = findIndex(bpredicate, board.sampleIds) + if (index !== -1) { + this.boards[bindex].sampleIds.splice(index, 1) + } + }) + + // delete from samples + const predicate = (sample: Sample) => sample.id === id + const index = findIndex(predicate, this.samples) + if (index !== -1) { + this.samples.splice(index, 1) + } + + this.saveStore() }, }, getters: { @@ -84,7 +114,7 @@ export const useStore = defineStore('main', { if (state.ui.mode === 'play') { return '#333537' } else { - return '#6A3832' + return '#3e3562' } } else { return '#25282B' @@ -110,5 +140,10 @@ export const useStore = defineStore('main', { return filter(samplePredicate, state.samples) } }, + + getSelectedSample(state) { + const predicate = (sample: Sample) => sample.id === state.ui.activeSample + return find(predicate, state.samples) + }, }, }) diff --git a/packages/renderer/src/views/GridView.vue b/packages/renderer/src/views/GridView.vue new file mode 100644 index 0000000..bb3a508 --- /dev/null +++ b/packages/renderer/src/views/GridView.vue @@ -0,0 +1,94 @@ + + + diff --git a/types/.gitkeep b/types/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/yarn.lock b/yarn.lock index 6aa6bc1..f7f262d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -194,6 +194,11 @@ mkdirp "^1.0.4" path-browserify "^1.0.1" +"@types/debounce@*": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.1.tgz#79b65710bc8b6d44094d286aecf38e44f9627852" + integrity sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA== + "@types/debug@^4.1.6": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" @@ -279,6 +284,13 @@ resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.5.tgz#2a1413aded46e67a1fe2386800e291123ed75eb1" integrity sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw== +"@types/wavesurfer.js@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@types/wavesurfer.js/-/wavesurfer.js-6.0.0.tgz#aa7813402b4cda3abe80a257ae8d0470ece37af4" + integrity sha512-w5yP3KLMhbBC4JvjWD5Yf9pUNLqZvuT4o/RIFJc0XQ8flbxdg+VOZ558PBQpczBsDAqUlfPkPfkBAUE4uItU3Q== + dependencies: + "@types/debounce" "*" + "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -601,6 +613,11 @@ add-filename-increment@^1.0.0: dependencies: strip-filename-increment "^2.0.1" +add@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/add/-/add-2.0.6.tgz#248f0a9f6e5a528ef2295dbeec30532130ae2235" + integrity sha1-JI8Kn25aUo7yKV2+7DBTITCuIjU= + agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -4596,6 +4613,11 @@ vue@^3.2: "@vue/server-renderer" "3.2.31" "@vue/shared" "3.2.31" +wavesurfer.js@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/wavesurfer.js/-/wavesurfer.js-6.0.1.tgz#a511611b2f4d04d098da8166f1fd2a818db20a82" + integrity sha512-igFjn9Zuo/vqcKdNKP90GZnKMGgder3Xz1cCR1UBw6ifdXTrfQDHpIte7GuddupQIEGoLajhvIgv1OJRXeU0XQ== + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -4723,6 +4745,11 @@ yargs@^17.0.1: y18n "^5.0.5" yargs-parser "^21.0.0" +yarn@^1.22.17: + version "1.22.17" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.17.tgz#bf910747d22497b573131f7341c0e1d15c74036c" + integrity sha512-H0p241BXaH0UN9IeH//RT82tl5PfNraVpSpEoW+ET7lmopNC61eZ+A+IDvU8FM6Go5vx162SncDL8J1ZjRBriQ== + yauzl@2.10.0, yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"