├── .github
└── ISSUE_TEMPLATE
│ ├── ---bug-report.md
│ ├── ---feature-request.md
│ └── ---module-request.md
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── LICENSE.md
├── README.md
├── core
├── .eslintrc
├── .gitignore
├── LICENSE.md
├── README.md
├── config
│ ├── bin.tsconfig.json
│ └── lib.tsconfig.json
├── package.json
├── public
│ ├── assets
│ │ ├── patchcab.png
│ │ ├── patchcab.svg
│ │ ├── preview@2x.png
│ │ ├── routed-gothic-wide.woff2
│ │ └── routed-gothic.woff2
│ ├── index.html
│ └── robots.txt
├── rollup.config.js
├── src
│ ├── Patchcab.svelte
│ ├── actions
│ │ ├── clickOutside.ts
│ │ ├── drag.ts
│ │ ├── index.ts
│ │ └── pan.ts
│ ├── bin
│ │ ├── bin.ts
│ │ ├── lib
│ │ │ ├── copyFiles.ts
│ │ │ ├── helpers.ts
│ │ │ ├── parseLibs.ts
│ │ │ ├── rollupConfig.ts
│ │ │ ├── screenshot.ts
│ │ │ └── types.ts
│ │ └── libs.json
│ ├── components
│ │ ├── Faceplate.svelte
│ │ ├── Knob.svelte
│ │ ├── Label.svelte
│ │ ├── Logo.svelte
│ │ ├── Patch.svelte
│ │ ├── Switch.svelte
│ │ ├── Volume.svelte
│ │ └── index.ts
│ ├── contstants.ts
│ ├── helpers
│ │ ├── Catenary.ts
│ │ ├── Point.ts
│ │ ├── energy.ts
│ │ ├── helpers.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── lib.ts
│ ├── nodes
│ │ ├── Bang.ts
│ │ └── index.ts
│ ├── rack
│ │ ├── Bar.svelte
│ │ ├── Cables.svelte
│ │ ├── Container.svelte
│ │ ├── Dialog.svelte
│ │ ├── Help.svelte
│ │ ├── Loading.svelte
│ │ ├── Menu.svelte
│ │ ├── Preview.svelte
│ │ ├── Share.svelte
│ │ ├── Shelf.svelte
│ │ └── index.ts
│ ├── state
│ │ ├── helpers.ts
│ │ ├── libraries.ts
│ │ ├── modules.ts
│ │ └── patches.ts
│ └── types.ts
└── tsconfig.json
├── modules
├── .gitignore
├── .prettierrc
├── LICENSE.md
├── README.md
├── modules
│ ├── adsr.png
│ ├── clock.png
│ ├── fm.png
│ ├── lfo.png
│ ├── midi.png
│ ├── noise.png
│ ├── notes.png
│ ├── osc.png
│ ├── out.png
│ ├── revrb.png
│ ├── scope.png
│ ├── seq.png
│ ├── vcf.png
│ └── vol.png
├── package.json
├── src
│ ├── ADSR.svelte
│ ├── Clock.svelte
│ ├── FM.svelte
│ ├── LFO.svelte
│ ├── MIDI.svelte
│ ├── NOISE.svelte
│ ├── Notes.svelte
│ ├── OSC.svelte
│ ├── OUT.svelte
│ ├── REVRB.svelte
│ ├── SCOPE.svelte
│ ├── SEQ.svelte
│ ├── VCF.svelte
│ └── VOL.svelte
└── tsconfig.json
├── package.json
└── yarn.lock
/.github/ISSUE_TEMPLATE/---bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41E Bug report"
3 | about: Something is not working correctly
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear description of what the bug is.
12 |
13 | **To Reproduce**
14 | If possible:
15 | - provide a link to a [https://patch.cab](Patch.cab) patch where the bug can be reproduced
16 | - or steps to reproduce the behavior
17 |
18 | **Environment:**
19 | - OS: [e.g. Windows, macOS]
20 | - Browser [e.g. Chrome, Safari, Firefox]
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F680 Feature request"
3 | about: This would be nice to have
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is this feature request for a module or Patchcab in general?**
11 | - [ ] Module
12 | - [ ] General
13 |
14 | **Describe the solution you'd like**
15 | A clear and concise description of what you want to happen.
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---module-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F39A Module request"
3 | about: I want this on my Patchcab rack
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the the module you would like to have**
11 | A clear and concise description of what the module should do.
12 |
13 | **References**
14 | Are there any Eurorack, Buchla, VCV Rack or any other modules that do the same?
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [v1.1.3](https://github.com/spectrome/patchcab/compare/1.1.1...1.1.3)
2 |
3 | > 21 November 2021
4 |
5 | ### Fixes
6 |
7 | - Fix rack reset refreshes whole page instead of resetting the modules [#18](https://github.com/spectrome/patchcab/pull/18)
8 |
9 | ## [v1.1.0](https://github.com/spectrome/patchcab/compare/1.0.5...1.1.0)
10 |
11 | > 19 November 2021
12 |
13 | ### Updates
14 |
15 | - Add synth reset confirmation dialog
16 | - Add mouse wheel support on pan actions [#15](https://github.com/spectrome/patchcab/pull/15)
17 |
18 | ### Modules
19 |
20 | - Add internal trigger sweep to `OSC` module [#13](https://github.com/spectrome/patchcab/pull/13)
21 |
22 | ## [v1.0.5](https://github.com/spectrome/patchcab/compare/1.0.4...1.0.5)
23 |
24 | > 22 January 2021
25 |
26 | ### Fixes
27 |
28 | - Fix empty space calculation on module addition [#7](https://github.com/spectrome/patchcab/pull/7)
29 | - Fix drag action to calculate cursor position offset [#8](https://github.com/spectrome/patchcab/pull/8)
30 | - Skip existing patches on state import [#9](https://github.com/spectrome/patchcab/pull/9)
31 | - Fix duplicate connection callbacks triggered [#10](https://github.com/spectrome/patchcab/pull/10)
32 |
33 | ## [v1.0.4](https://github.com/spectrome/patchcab/compare/1.0.3...1.0.4)
34 |
35 | > 22 January 2021
36 |
37 | ### Updates
38 |
39 | - Interface UX updates [#6](https://github.com/spectrome/patchcab/pull/6)
40 |
41 | ### Modules
42 |
43 | - ADSR, LFO, VOL and OUT module parameter range tweaks [#5](https://github.com/spectrome/patchcab/pull/5)
44 |
45 | ## [v1.0.3](https://github.com/spectrome/patchcab/compare/1.0.2...1.0.3)
46 |
47 | > 20 January 2021
48 |
49 | ### Fixes
50 |
51 | - Straight patch cable drawn incorrectly [#1](https://github.com/spectrome/patchcab/pull/1)
52 | - Context menu shows under cables [#2](https://github.com/spectrome/patchcab/pull/2)
53 | - Connecting input patch to input patch results in error [#3](https://github.com/spectrome/patchcab/pull/3)
54 |
55 | ## [v1.0.2](https://github.com/spectrome/patchcab/compare/1.0.1...1.0.2)
56 |
57 | > 19 January 2021
58 |
59 | - Fix module filename resolution
60 |
61 | ## [v1.0.1](https://github.com/spectrome/patchcab/compare/1.0.0...1.0.1)
62 |
63 | > 19 January 2021
64 |
65 | - Fix patch sharing
66 |
67 | ## [1.0.0](https://github.com/spectrome/patchcab/tree/1.0.0)
68 |
69 | > 19 January 2021
70 |
71 | - Initial release 🤘
72 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Spectrome
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Spectrome
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🎛 Patchcab
2 |
3 |
4 |
5 | ### Patchcab is a modular Eurorack style synthesizer made with Web Audio.
6 |
7 | Modules are built using [Tone.js Web Audio framework](https://github.com/Tonejs/Tone.js/) and [Svelte Javascript framework](https://github.com/sveltejs/svelte). Patchcab is heavily inspired by [VCV Eurorack Simulator](https://vcvrack.com).
8 |
9 | ---
10 |
11 | ### 🎛 Patch and play
12 |
13 | Go to **https://patch.cab** to create, share and remix synths with community made modules.
14 |
15 | ---
16 |
17 | ### 💾 Run locally
18 |
19 | Install [Node.js](https://nodejs.org) and the latest version of Patchcab core:
20 |
21 | ```bash
22 | npm install @patchcab/core
23 | ```
24 |
25 | Add some modules:
26 |
27 | ```bash
28 | npm install @patchcab/modules
29 | ```
30 |
31 | Start Patchcab:
32 |
33 | ```bash
34 | npx patchcab
35 | ```
36 |
37 | And finally - open the address http://localhost:3000 in your browser 🤘
38 |
39 | ---
40 |
41 | ### 🎚 Build modules
42 |
43 | - Read the ~~Patchcab documentation~~ (coming soon 🤞)
44 | - Read the [Tone.js documentation](https://tonejs.github.io/)
45 | - Browse [Patchcab default modules](https://github.com/spectrome/patchcab/tree/master/modules/src) for basic examples
46 | - Fork the [Patchcab module template](https://github.com/spectrome/patchcab-module-template)
47 | - Share your modules on [Patch.cab](https://patch.cab) by making a pull request to [spectrome/patch-dot-cab](https://github.com/spectrome/patch-dot-cab)
--------------------------------------------------------------------------------
/core/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "plugins": ["@typescript-eslint"],
4 | "extends": ["prettier", "plugin:@typescript-eslint/recommended"]
5 | }
6 |
--------------------------------------------------------------------------------
/core/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /lib
3 | /bin
4 | public/modules*
5 | public/js*
--------------------------------------------------------------------------------
/core/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Spectrome
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/core/README.md:
--------------------------------------------------------------------------------
1 | # 🎛 Patchcab
2 |
3 |
4 |
5 | ### Patchcab is a modular Eurorack style synthesizer made with Web Audio.
6 |
7 | Modules are built using [Tone.js Web Audio framework](https://github.com/Tonejs/Tone.js/) and [Svelte Javascript framework](https://github.com/sveltejs/svelte). Patchcab is heavily inspired by [VCV Eurorack Simulator](https://vcvrack.com).
8 |
9 | ---
10 |
11 | ### 🎛 Patch and play
12 |
13 | Go to **https://patch.cab** to create, share and remix synths with community made modules.
14 |
15 | ---
16 |
17 | ### 💾 Run locally
18 |
19 | Install [Node.js](https://nodejs.org) and the latest version of Patchcab core:
20 |
21 | ```bash
22 | npm install @patchcab/core
23 | ```
24 |
25 | Add some modules:
26 |
27 | ```bash
28 | npm install @patchcab/modules
29 | ```
30 |
31 | Start Patchcab:
32 |
33 | ```bash
34 | npx patchcab
35 | ```
36 |
37 | And finally - open the address http://localhost:3000 in your browser 🤘
38 |
39 | ---
40 |
41 | ### 🎚 Build modules
42 |
43 | - Read the ~~Patchcab documentation~~ (coming soon 🤞)
44 | - Read the [Tone.js documentation](https://tonejs.github.io/)
45 | - Browse [Patchcab default modules](https://github.com/spectrome/patchcab/tree/master/modules/src) for basic examples
46 | - Fork the [Patchcab module template](https://github.com/spectrome/patchcab-module-template)
47 | - Share your modules on [Patch.cab](https://patch.cab) by making a pull request to [spectrome/patch-dot-cab](https://github.com/spectrome/patch-dot-cab)
48 |
--------------------------------------------------------------------------------
/core/config/bin.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "../bin",
4 | "module": "commonjs",
5 | "esModuleInterop": true,
6 | "resolveJsonModule": true,
7 | "skipLibCheck": true
8 | },
9 | "include": ["../src/bin/bin.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/core/config/lib.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../lib",
5 | "declaration": true
6 | },
7 | "include": ["../src/lib.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@patchcab/core",
3 | "version": "1.1.3",
4 | "description": "Modular Eurorack style synthesizer made with Web Audio",
5 | "license": "MIT",
6 | "author": "Spectrome ",
7 | "files": [
8 | "bin",
9 | "lib",
10 | "public"
11 | ],
12 | "main": "lib/lib.js",
13 | "types": "lib/lib.d.ts",
14 | "bin": {
15 | "patchcab": "./bin/bin.js"
16 | },
17 | "scripts": {
18 | "dev": "rollup -c -w",
19 | "test": "svelte-check && tsc --noEmit",
20 | "build": "yarn build:lib && yarn build:web && yarn build:bin",
21 | "build:web": "rollup -c",
22 | "build:lib": "tsc --project ./config/lib.tsconfig.json && cpy './src/components/*.svelte' './lib/components/'",
23 | "build:bin": "tsc --project ./config/bin.tsconfig.json",
24 | "lint": "eslint './src/**/*.ts' --max-warnings 0",
25 | "prettier": "prettier --write './src/**/*.{ts,svelte}'",
26 | "prepublish": "yarn build",
27 | "cli": "patchcab"
28 | },
29 | "dependencies": {
30 | "@rollup/plugin-commonjs": "^21.0.1",
31 | "@rollup/plugin-node-resolve": "^13.0.6",
32 | "dotenv": "^10.0.0",
33 | "file-dialog": "^0.0.8",
34 | "file-saver": "^2.0.5",
35 | "html-minifier-terser": "^6.0.2",
36 | "imagemin": "^7.0.1",
37 | "imagemin-pngquant": "^9.0.2",
38 | "ncp": "^2.0.0",
39 | "parse-author": "^2.0.0",
40 | "prettier": "^2.4.1",
41 | "prettier-plugin-svelte": "^2.5.0",
42 | "puppeteer": "^11.0.0",
43 | "rimraf": "^3.0.2",
44 | "rollup": "^2.60.0",
45 | "rollup-plugin-external-globals": "^0.6.1",
46 | "rollup-plugin-glslify": "^1.2.1",
47 | "rollup-plugin-livereload": "^2.0.5",
48 | "rollup-plugin-serve": "^1.1.0",
49 | "rollup-plugin-svelte": "^7.1.0",
50 | "rollup-plugin-terser": "^7.0.2",
51 | "rollup-plugin-typescript2": "^0.31.0",
52 | "standardized-audio-context": "^25.3.15",
53 | "svelte": "^3.44.2",
54 | "svelte-check": "^2.2.10",
55 | "svelte-preprocess": "^4.9.8",
56 | "tone": "14.7.77",
57 | "typescript": "^4.1.3"
58 | },
59 | "prettier": {
60 | "useTabs": false,
61 | "arrowParens": "always",
62 | "semi": true,
63 | "bracketSpacing": true,
64 | "singleQuote": true,
65 | "printWidth": 120,
66 | "svelteSortOrder": "options-scripts-styles-markup"
67 | },
68 | "devDependencies": {
69 | "@types/file-saver": "^2.0.4",
70 | "@typescript-eslint/eslint-plugin": "^5.4.0",
71 | "@typescript-eslint/parser": "^5.4.0",
72 | "cpy-cli": "^3.1.1",
73 | "eslint": "^8.2.0",
74 | "eslint-config-prettier": "^8.3.0",
75 | "eslint-plugin-prettier": "^4.0.0"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/core/public/assets/patchcab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spectrome/patchcab/0fdb815e35542f798e8b74499ac125a11ea65e6a/core/public/assets/patchcab.png
--------------------------------------------------------------------------------
/core/public/assets/patchcab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/public/assets/preview@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spectrome/patchcab/0fdb815e35542f798e8b74499ac125a11ea65e6a/core/public/assets/preview@2x.png
--------------------------------------------------------------------------------
/core/public/assets/routed-gothic-wide.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spectrome/patchcab/0fdb815e35542f798e8b74499ac125a11ea65e6a/core/public/assets/routed-gothic-wide.woff2
--------------------------------------------------------------------------------
/core/public/assets/routed-gothic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spectrome/patchcab/0fdb815e35542f798e8b74499ac125a11ea65e6a/core/public/assets/routed-gothic.woff2
--------------------------------------------------------------------------------
/core/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Patchcab
6 |
7 |
8 |
9 |
10 |
11 |
106 |
107 |
108 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/core/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
--------------------------------------------------------------------------------
/core/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { nodeResolve } from '@rollup/plugin-node-resolve';
2 | import typescript from 'rollup-plugin-typescript2';
3 | import livereload from 'rollup-plugin-livereload';
4 | import autoPreprocess from 'svelte-preprocess';
5 | import { terser } from 'rollup-plugin-terser';
6 | import common from '@rollup/plugin-commonjs';
7 | import svelte from 'rollup-plugin-svelte';
8 | import serve from 'rollup-plugin-serve';
9 | import path from 'path';
10 |
11 | const DIR = path.resolve(process.cwd(), 'public/js');
12 | const DEV = process.env.ROLLUP_WATCH;
13 |
14 | module.exports = () => {
15 | return {
16 | input: {
17 | core: path.resolve(__dirname, './src/index.ts'),
18 | },
19 | external: ['@patchcab/core'],
20 | output: {
21 | name: 'patchcab',
22 | format: 'es',
23 | dir: DIR,
24 | paths: {
25 | '@patchcab/core': '/js/core.js',
26 | },
27 | },
28 | plugins: [
29 | common(),
30 | nodeResolve({
31 | extensions: ['.ts', '.js', '.json', '.svelte', '.mjs'],
32 | }),
33 | svelte({
34 | emitCss: false,
35 | preprocess: autoPreprocess(),
36 | }),
37 | typescript(),
38 | DEV &&
39 | serve({
40 | open: true,
41 | contentBase: path.resolve(DIR, '../'),
42 | historyApiFallback: true,
43 | port: '3000',
44 | headers: {
45 | 'Access-Control-Allow-Origin': '*',
46 | },
47 | }),
48 | DEV && livereload(),
49 | !DEV && terser(),
50 | ],
51 | };
52 | };
53 |
--------------------------------------------------------------------------------
/core/src/Patchcab.svelte:
--------------------------------------------------------------------------------
1 |
57 |
58 | {#if loading}
59 |
60 | {:else}
61 |
62 |
63 |
64 |
65 | {#each $modulesAll as module (module.id)}
66 |
67 | {/each}
68 |
69 | {/if}
70 |
--------------------------------------------------------------------------------
/core/src/actions/clickOutside.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from '../types';
2 |
3 | export type OnClickOutside = (e: MouseEvent) => void;
4 |
5 | /**
6 | * Detect a click outside of the target element or it's children
7 | *
8 | * @example
9 | *
10 | *
11 | *
12 | * @category Actions
13 | */
14 | const useClickOutside: Action = (node, onClickOutside) => {
15 | if (typeof onClickOutside !== 'function') {
16 | return;
17 | }
18 |
19 | const onMousedown = (event: MouseEvent) => {
20 | let parent = event.target as HTMLElement;
21 | let isChild = false;
22 |
23 | while (parent) {
24 | if (parent === node) {
25 | isChild = true;
26 | break;
27 | }
28 | parent = parent.parentNode as HTMLElement;
29 | }
30 |
31 | if (!isChild) {
32 | onClickOutside(event);
33 | }
34 | };
35 |
36 | document.addEventListener('mousedown', onMousedown, { passive: true });
37 | document.addEventListener('touchstart', onMousedown, { passive: true });
38 |
39 | return {
40 | destroy() {
41 | document.removeEventListener('mousedown', onMousedown);
42 | document.removeEventListener('touchstart', onMousedown);
43 | },
44 | };
45 | };
46 |
47 | export default useClickOutside;
48 |
--------------------------------------------------------------------------------
/core/src/actions/drag.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from '../types';
2 | import { BAR_HEIGHT } from '../contstants';
3 |
4 | export type OnDrag = (x: number, y: number, box: DOMRect) => void;
5 |
6 | /**
7 | * Element drag event
8 | *
9 | * @example
10 | *
11 | *
12 | *
13 | * @category Actions
14 | */
15 | const useDrag: Action = (node, onDrag) => {
16 | if (typeof onDrag !== 'function') {
17 | return;
18 | }
19 |
20 | let offset: number;
21 |
22 | const onMousedown = (event: MouseEvent | TouchEvent) => {
23 | if (event.target !== node && (event.target as HTMLElement).getAttribute('draggable') === null) {
24 | return;
25 | }
26 |
27 | const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX;
28 | offset = clientX - node.getBoundingClientRect().left;
29 |
30 | window.addEventListener('mousemove', onMousemove, { passive: true });
31 | window.addEventListener('touchmove', onMousemove, { passive: true });
32 | window.addEventListener('mouseup', onMouseup, { passive: true });
33 | window.addEventListener('touchend', onMouseup, { passive: true });
34 | };
35 |
36 | const onMousemove = (event: MouseEvent | TouchEvent) => {
37 | const box = node.getBoundingClientRect();
38 |
39 | const scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
40 | const scrollY = document.documentElement.scrollTop || document.body.scrollTop;
41 |
42 | const clientX = 'clientX' in event ? event.clientX : event.touches[0].clientX;
43 | const clientY = 'clientY' in event ? event.clientY : event.touches[0].clientY;
44 |
45 | const x = clientX + scrollX - offset;
46 | const y = clientY + scrollY - BAR_HEIGHT;
47 |
48 | onDrag(x, y, box);
49 | };
50 |
51 | const onMouseup = () => {
52 | window.removeEventListener('mousemove', onMousemove);
53 | window.removeEventListener('touchmove', onMousemove);
54 | window.removeEventListener('mouseup', onMouseup);
55 | window.removeEventListener('touchend', onMouseup);
56 | };
57 |
58 | node.addEventListener('mousedown', onMousedown, { passive: true });
59 | node.addEventListener('touchstart', onMousedown, { passive: true });
60 |
61 | return {
62 | destroy() {
63 | node.removeEventListener('mousedown', onMousedown);
64 | node.removeEventListener('touchstart', onMousedown);
65 | },
66 | };
67 | };
68 |
69 | export default useDrag;
70 |
--------------------------------------------------------------------------------
/core/src/actions/index.ts:
--------------------------------------------------------------------------------
1 | export { default as usePan } from './pan';
2 | export type { OnPan } from './pan';
3 |
4 | export { default as useDrag } from './drag';
5 | export type { OnDrag } from './drag';
6 |
7 | export { default as useClickOutside } from './clickOutside';
8 | export type { OnClickOutside } from './clickOutside';
9 |
--------------------------------------------------------------------------------
/core/src/actions/pan.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from '../types';
2 |
3 | export type OnPan = (value: { x: number; y: number; dx?: number; dy?: number }) => void;
4 |
5 | /**
6 | * Element pan event
7 | *
8 | * @example
9 | *
10 | *
11 | *
12 | * @category Actions
13 | */
14 | const usePan: Action = (node, onMove) => {
15 | let x: number;
16 | let y: number;
17 |
18 | if (typeof onMove !== 'function') {
19 | return;
20 | }
21 |
22 | const onMousedown = (event: MouseEvent | TouchEvent) => {
23 | x = 'clientX' in event ? event.clientX : event.touches[0].clientX;
24 | y = 'clientY' in event ? event.clientY : event.touches[0].clientY;
25 |
26 | window.addEventListener('mousemove', onMousemove, { passive: true });
27 | window.addEventListener('touchmove', onMousemove, { passive: true });
28 | window.addEventListener('mouseup', onMouseup, { passive: true });
29 | window.addEventListener('touchend', onMouseup, { passive: true });
30 | };
31 |
32 | const onMousemove = (event: MouseEvent | TouchEvent) => {
33 | const newX = 'clientX' in event ? event.clientX : event.touches[0].clientX;
34 | const newY = 'clientY' in event ? event.clientY : event.touches[0].clientY;
35 |
36 | const dx = newX - x;
37 | const dy = newY - y;
38 |
39 | x = newX;
40 | y = newY;
41 |
42 | onMove({ x, y, dx, dy });
43 | };
44 |
45 | const onMouseup = () => {
46 | window.removeEventListener('mousemove', onMousemove);
47 | window.removeEventListener('touchmove', onMousemove);
48 | window.removeEventListener('mouseup', onMouseup);
49 | window.removeEventListener('touchend', onMouseup);
50 | };
51 |
52 | const onWheel = (event: WheelEvent) => {
53 | event.preventDefault();
54 | onMove({ x: event.clientX, y: event.clientY, dx: event.deltaX, dy: event.deltaY });
55 | };
56 |
57 | node.addEventListener('mousedown', onMousedown, { passive: true });
58 | node.addEventListener('touchstart', onMousedown, { passive: true });
59 | node.addEventListener('wheel', onWheel, { passive: false });
60 |
61 | return {
62 | destroy() {
63 | node.removeEventListener('mousedown', onMousedown);
64 | node.removeEventListener('touchstart', onMousedown);
65 | node.removeEventListener('wheel', onWheel);
66 | },
67 | };
68 | };
69 |
70 | export default usePan;
71 |
--------------------------------------------------------------------------------
/core/src/bin/bin.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { copyFileSync, existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
4 | import { extname, resolve } from 'path';
5 | import * as rimraf from 'rimraf';
6 | import { rollup, watch } from 'rollup';
7 | import parseAuthor from 'parse-author';
8 | import copyFiles from './lib/copyFiles';
9 | import parseLibs from './lib/parseLibs';
10 | import rollupConfig from './lib/rollupConfig';
11 | import defaultLibs from './libs.json';
12 | import { minify } from 'html-minifier-terser';
13 | import screenshot from './lib/screenshot';
14 | import { safeName } from './lib/helpers';
15 | import dotenv from 'dotenv';
16 | import type { Module, Library } from './lib/types';
17 |
18 | dotenv.config();
19 |
20 | const BUILD = process.argv.indexOf('--build') > -1;
21 | const DIR = process.cwd();
22 | const CONFIG = JSON.parse(readFileSync(resolve(DIR, 'package.json'), 'utf8'));
23 |
24 | if (!CONFIG) {
25 | throw Error('No package.json file found');
26 | }
27 |
28 | const PATH_BUILD = resolve(DIR, './modules');
29 | const PATH_PUBLIC = resolve(DIR, './public');
30 | const PATH_MODULES = resolve(DIR, `./public/modules/${CONFIG.name.replace(/\//g, '-')}`);
31 |
32 | const bin = async (): Promise => {
33 | const libs: Record = defaultLibs;
34 |
35 | // Remove build outputs
36 | rimraf.sync(PATH_BUILD);
37 | rimraf.sync(PATH_PUBLIC);
38 |
39 | // Copy public asset files
40 | await copyFiles(resolve(__dirname, '../public'), PATH_PUBLIC);
41 |
42 | // Update index file
43 | let indexFile: string = readFileSync(resolve(PATH_PUBLIC, `index.html`), 'utf8');
44 |
45 | // Add optional head content
46 | if (process.env.PATCHCAB_HEAD) {
47 | indexFile = indexFile.replace(``, `${process.env.PATCHCAB_HEAD}`);
48 | }
49 |
50 | // Add optional body content
51 | if (process.env.PATCHCAB_BODY) {
52 | indexFile = indexFile.replace(`