├── .husky └── pre-commit ├── src ├── csound-templates │ ├── default.sco │ ├── default.orc │ ├── default.csd │ └── index.js ├── store │ ├── index.ts │ └── root-reducer.tsx ├── styles │ ├── index.ts │ ├── constants.ts │ ├── utils.ts │ ├── _tooltip.ts │ └── _perfect-scrollbar.ts ├── components │ ├── csound │ │ ├── index.ts │ │ ├── selectors.ts │ │ ├── utils.tsx │ │ └── reducer.ts │ ├── editor │ │ ├── index.ts │ │ ├── types.tsx │ │ └── actions.tsx │ ├── hot-keys │ │ ├── index.ts │ │ ├── utils.ts │ │ ├── reducer.ts │ │ └── default-bindings.ts │ ├── profile │ │ ├── face.png │ │ ├── tabs │ │ │ └── styles.ts │ │ └── delete-project-modal.tsx │ ├── themes │ │ ├── types.ts │ │ └── action.ts │ ├── interfaces.ts │ ├── project-last-modified │ │ ├── types.tsx │ │ ├── selectors.ts │ │ ├── reducer.tsx │ │ ├── subscribers.tsx │ │ └── actions.tsx │ ├── file-tree │ │ ├── selectors.ts │ │ ├── types.ts │ │ └── actions.ts │ ├── console │ │ ├── types.ts │ │ ├── actions.ts │ │ ├── styles.ts │ │ ├── reducer.ts │ │ └── console.tsx │ ├── audio-editor │ │ ├── styles.ts │ │ └── audio-editor.tsx │ ├── site-documents │ │ ├── actions.tsx │ │ └── styles.ts │ ├── login │ │ ├── styles.ts │ │ ├── types.ts │ │ ├── selectors.tsx │ │ └── subscribers.tsx │ ├── menu-bar │ │ └── types.ts │ ├── modal │ │ ├── actions.ts │ │ ├── styles.tsx │ │ └── reducer.tsx │ ├── projects │ │ └── selectors.ts │ ├── bottom-tabs │ │ ├── types.ts │ │ ├── selectors.ts │ │ └── actions.ts │ ├── snackbar │ │ ├── actions.ts │ │ ├── types.ts │ │ ├── selectors.ts │ │ └── reducer.ts │ ├── social-controls │ │ ├── styles.ts │ │ └── subscribers.tsx │ ├── manual │ │ └── styles.tsx │ ├── page-404 │ │ └── styles.ts │ ├── project-editor │ │ ├── utils.tsx │ │ └── types.ts │ ├── target-controls │ │ ├── types.ts │ │ └── config-dialog │ │ │ └── styles.ts │ └── router │ │ └── router.tsx ├── tabtab │ ├── helpers │ │ ├── move.js │ │ └── delete.js │ ├── utils │ │ ├── is-type.js │ │ └── count-tab.js │ ├── sort-method.js │ ├── index.js │ ├── drag-tab.jsx │ ├── close-button.jsx │ ├── panel-list.jsx │ ├── panel.jsx │ ├── extra-button.jsx │ └── icon-svg.jsx ├── svgs │ ├── fad-armrecording.svg │ ├── fad-hardclipcurve.svg │ ├── fad-filter-bypass.svg │ ├── fad-record.svg │ ├── fad-drumpad.svg │ ├── fad-mono.svg │ ├── fad-digital-dot.svg │ ├── fad-cutter.svg │ ├── fad-arrows-horz.svg │ ├── fad-arrows-vert.svg │ ├── fad-modsawup.svg │ ├── fad-modsawdown.svg │ ├── fad-play.svg │ ├── fad-modtri.svg │ ├── fad-digital-colon.svg │ ├── fad-modsine.svg │ ├── fad-pen.svg │ ├── fad-phase.svg │ ├── fad-punch-in.svg │ ├── fad-punch-out.svg │ ├── fad-softclipcurve.svg │ ├── fad-filter-lowpass.svg │ ├── fad-timeselect.svg │ ├── fad-filter-highpass.svg │ ├── fad-thunderbolt.svg │ ├── fad-logo-cubase.svg │ ├── fad-pointer.svg │ ├── fad-AR.svg │ ├── fad-filter-notch.svg │ ├── fad-zoomout.svg │ ├── fad-filter-rez-highpass.svg │ ├── fad-filter-bandpass.svg │ ├── fad-filter-rez-lowpass.svg │ ├── fad-modsquare.svg │ ├── fad-caret-right.svg │ ├── fad-h-expand.svg │ ├── fad-caret-down.svg │ ├── fad-caret-up.svg │ ├── fad-v-expand.svg │ ├── fad-caret-left.svg │ ├── fad-automation-2p.svg │ ├── fad-digital1.svg │ ├── fad-modsh.svg │ ├── fad-zoomin.svg │ ├── fad-undo.svg │ ├── fad-redo.svg │ ├── fad-ADR.svg │ ├── fad-sliderhandle-2.svg │ ├── fad-mute.svg │ ├── fad-powerswitch.svg │ ├── fad-xlrplug.svg │ ├── fad-preset-a.svg │ ├── fad-sliderhandle-1.svg │ ├── fad-ADSR.svg │ ├── fad-loop.svg │ ├── fad-AHDSR.svg │ ├── fad-stop.svg │ ├── fad-logo-reaper.svg │ ├── fad-logo-protools.svg │ ├── fad-logo-studioone.svg │ ├── fad-digital7.svg │ ├── fad-modularplug.svg │ ├── fad-scissors.svg │ ├── fad-hardclip.svg │ ├── fad-next.svg │ ├── fad-automation-3p.svg │ ├── fad-diskio.svg │ ├── fad-prev.svg │ ├── fad-keyboard.svg │ ├── fad-arpdown.svg │ ├── fad-arpdownup.svg │ ├── fad-filter-shelving-lo.svg │ ├── fad-arprandom.svg │ ├── fad-arpupdown.svg │ ├── fad-arpup.svg │ ├── fad-filter-shelving-hi.svg │ ├── fad-lock.svg │ ├── fad-midiplug.svg │ ├── fad-unlock.svg │ ├── fad-microphone.svg │ ├── fad-close.svg │ ├── fad-forward.svg │ ├── fad-stereo.svg │ ├── fad-backward.svg │ ├── fad-speaker.svg │ ├── fad-ram.svg │ ├── fad-logo-waveform.svg │ ├── fad-modrandom.svg │ ├── fad-arpchord.svg │ ├── fad-arpdownandup.svg │ ├── fad-arpplayorder.svg │ ├── fad-arpupandown.svg │ ├── fad-cpu.svg │ ├── fad-metronome.svg │ ├── fad-squareswitch-off.svg │ ├── fad-squareswitch-on.svg │ ├── fad-vsquareswitch-off.svg │ ├── fad-vsquareswitch-on.svg │ ├── fad-eraser.svg │ ├── fad-automation-4p.svg │ ├── fad-filter-bell.svg │ ├── fad-duplicate.svg │ ├── fad-digital4.svg │ ├── fad-logo-tracktion.svg │ ├── fad-usb.svg │ ├── fad-foldback.svg │ ├── fad-random-2dice.svg │ ├── fad-repeat.svg │ ├── fad-solo.svg │ ├── fad-pause.svg │ ├── fad-shuffle.svg │ ├── fad-logo-audacity.svg │ ├── fad-ffwd.svg │ ├── fad-rew.svg │ ├── fad-logo-lv2.svg │ ├── fad-open.svg │ ├── fad-digital5.svg │ ├── fad-bluetooth.svg │ ├── fad-waveform.svg │ ├── fad-digital3.svg │ ├── fad-softclip.svg │ ├── fad-digital2.svg │ ├── fad-paste.svg │ ├── fad-logo-live.svg │ ├── fad-headphones.svg │ ├── fad-preset-b.svg │ ├── fad-slider-round-2.svg │ ├── fad-digital0.svg │ ├── fad-copy.svg │ └── fad-save.svg ├── index.tsx ├── db │ ├── search.ts │ ├── types.ts │ └── id-reducer.ts └── global.d.ts ├── .eslintignore ├── functions ├── .gitignore ├── .env.example ├── tsconfig.json ├── src │ ├── popular_projects.ts │ ├── main.ts │ ├── random_projects.ts │ ├── followers_counter.ts │ ├── following_counter.ts │ └── project_file_storage_delete.ts ├── README.md └── package.json ├── search └── main.ts ├── __mocks__ ├── styleMock.js ├── fileMock.js └── svgMock.js ├── public ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── img │ ├── project_editor.png │ └── ios_notification_warning.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── manifest.json └── electron.js ├── vite-env.d.ts ├── .firebaserc ├── config ├── cors.json ├── pnpTs.js └── lezer-loader.js ├── .huskyrc ├── README.md ├── .prettierrc.json ├── shell.nix ├── .gitignore ├── scripts ├── setupJest.js └── uploadSourcemaps.js ├── .babelrc ├── .github └── workflows │ ├── production.yaml │ └── develop.yaml ├── vite.config.ts ├── tsconfig.json └── jest.config.js /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run precommit-hook 2 | -------------------------------------------------------------------------------- /src/csound-templates/default.sco: -------------------------------------------------------------------------------- 1 | i1 0 2 8.00 -12 -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./store"; 2 | -------------------------------------------------------------------------------- /src/styles/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./_tooltip"; 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | config/* 2 | scripts/* 3 | public/* 4 | *.test.tsx" -------------------------------------------------------------------------------- /functions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | index.html 3 | dist 4 | _local -------------------------------------------------------------------------------- /search/main.ts: -------------------------------------------------------------------------------- 1 | import { onRequest } from "firebase-functions/v2/https"; 2 | -------------------------------------------------------------------------------- /src/components/csound/index.ts: -------------------------------------------------------------------------------- 1 | export { csoundInstance } from "./actions"; 2 | -------------------------------------------------------------------------------- /src/components/editor/index.ts: -------------------------------------------------------------------------------- 1 | export { openEditors } from "./editor"; 2 | -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | // __mocks__/styleMock.js 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /src/components/hot-keys/index.ts: -------------------------------------------------------------------------------- 1 | export const keyboardCallbacks = new Map(); 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csound/web-ide/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | // __mocks__/fileMock.js 2 | 3 | module.exports = "test-file-stub"; 4 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csound/web-ide/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csound/web-ide/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /src/styles/constants.ts: -------------------------------------------------------------------------------- 1 | export const tabListHeight = 38; 2 | 3 | export const headerHeight = 64; 4 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csound/web-ide/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/project_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csound/web-ide/HEAD/public/img/project_editor.png -------------------------------------------------------------------------------- /src/components/profile/face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csound/web-ide/HEAD/src/components/profile/face.png -------------------------------------------------------------------------------- /vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csound/web-ide/HEAD/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csound/web-ide/HEAD/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "csound-ide", 4 | "develop": "csound-ide-dev" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /config/cors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "origin": ["*"], 4 | "method": ["GET"], 5 | "maxAgeSeconds": 3600 6 | } 7 | ] -------------------------------------------------------------------------------- /public/img/ios_notification_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csound/web-ide/HEAD/public/img/ios_notification_warning.png -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "yarn lint:check && REACT_APP_DATABASE=DEV yarn test --watchAll=false" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/tabtab/helpers/move.js: -------------------------------------------------------------------------------- 1 | import { arrayMove } from "array-move"; 2 | 3 | export default { 4 | simpleSwitch: arrayMove, 5 | }; 6 | -------------------------------------------------------------------------------- /functions/.env.example: -------------------------------------------------------------------------------- 1 | # Firebase Storage Bucket URL 2 | # Replace with your actual storage bucket URL 3 | STORAGE_BUCKET_URL=your-storage-bucket-url -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Csound Web IDE 2 | 3 | To start run ```yarn``` to download dependencies. 4 | Then run ```yarn start``` to live reload as files are edited. 5 | 6 | -------------------------------------------------------------------------------- /src/components/themes/types.ts: -------------------------------------------------------------------------------- 1 | export const THEMES_CHANGE_THEME = "THEMES.CHANGE_THEME"; 2 | 3 | export type CsoundTheme = "monokai" | "github" | "bluepunk"; 4 | -------------------------------------------------------------------------------- /src/svgs/fad-armrecording.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/editor/types.tsx: -------------------------------------------------------------------------------- 1 | export const SIGNIN_FAIL = "SIGNIN_FAIL"; 2 | export const SIGNIN_SUCCESS = "SIGNIN_SUCCESS"; 3 | export const SIGNIN_REQUEST = "SIGNIN_REQUEST"; 4 | -------------------------------------------------------------------------------- /src/tabtab/helpers/delete.js: -------------------------------------------------------------------------------- 1 | function deleteHelper(sequence, deleteIndex) { 2 | return sequence.filter((_, i) => i !== deleteIndex); 3 | } 4 | 5 | export default deleteHelper; 6 | -------------------------------------------------------------------------------- /src/components/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IPanel { 2 | type: string; 3 | component: string; 4 | panelTitle?: string; 5 | panelType?: string; 6 | panelPayload?: any; 7 | } 8 | -------------------------------------------------------------------------------- /src/svgs/fad-hardclipcurve.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/project-last-modified/types.tsx: -------------------------------------------------------------------------------- 1 | const PREFIX = "PROJECT_LAST_MODIFIED."; 2 | export const UPDATE_PROJECT_LAST_MODIFIED_LOCALLY = 3 | PREFIX + "UPDATE_PROJECT_LAST_MODIFIED_LOCALLY"; 4 | -------------------------------------------------------------------------------- /src/components/file-tree/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@root/store"; 2 | 3 | export const selectNonCloudFiles = (store: RootState): string[] => { 4 | return store.FileTreeReducer.nonCloudFiles || []; 5 | }; 6 | -------------------------------------------------------------------------------- /src/styles/utils.ts: -------------------------------------------------------------------------------- 1 | import hexRgb from "hex-rgb"; 2 | 3 | export const rgba = (hex: string, alpha: number | string) => { 4 | const rgb = hexRgb(hex, { format: "array" }).slice(0, -1).join(","); 5 | return `${rgb},${alpha}`; 6 | }; 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 80, 4 | "tabWidth": 4, 5 | "arrowParens": "always", 6 | "singleQuote": false, 7 | "trailingComma": "none", 8 | "jsxBracketSameLine": false 9 | } 10 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-bypass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/console/types.ts: -------------------------------------------------------------------------------- 1 | const PREFIX = "CONSOLE."; 2 | 3 | // ACTION TYPES 4 | export const SET_CLEAR_CONSOLE_CALLBACK = PREFIX + "SET_CLEAR_CONSOLE_CALLBACK"; 5 | export const SET_PRINT_TO_CONSOLE_CALLBACK = 6 | PREFIX + "SET_PRINT_TO_CONSOLE_CALLBACK"; 7 | -------------------------------------------------------------------------------- /src/svgs/fad-record.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/csound-templates/default.orc: -------------------------------------------------------------------------------- 1 | sr=44100 2 | ksmps=32 3 | 0dbfs=1 4 | nchnls=2 5 | 6 | instr 1 7 | 8 | iamp = ampdbfs(p5) 9 | ipch = cps2pch(p4,12) 10 | ipan = 0.5 11 | 12 | asig = vco2(iamp, ipch) 13 | al, ar pan2 asig, ipan 14 | 15 | out(al, ar) 16 | endin -------------------------------------------------------------------------------- /src/svgs/fad-drumpad.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-mono.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-digital-dot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/audio-editor/styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react"; 2 | 3 | export const rootStyle = css({ 4 | width: "100%", 5 | height: "100%", 6 | display: "flex", 7 | "& audio": { 8 | borderRightWidth: 30, 9 | margin: "auto" 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/components/site-documents/actions.tsx: -------------------------------------------------------------------------------- 1 | import { openSimpleModal } from "../modal/actions"; 2 | 3 | export const showKeyboardShortcuts = (): ((dispatch: any) => Promise) => { 4 | return async (dispatch: any) => { 5 | dispatch(openSimpleModal("keyboard-shortcuts", {})); 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/login/styles.ts: -------------------------------------------------------------------------------- 1 | import { css, SerializedStyles, Theme } from "@emotion/react"; 2 | 3 | export const errorBox = (theme: Theme): SerializedStyles => css` 4 | color: ${theme.errorText}; 5 | `; 6 | 7 | export const centerLink = css` 8 | text-align: center; 9 | margin-bottom: 10px; 10 | `; 11 | -------------------------------------------------------------------------------- /src/components/csound/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@root/store"; 2 | import { ICsoundReducer } from "./reducer"; 3 | import { ICsoundStatus } from "./types"; 4 | 5 | export const selectCsoundStatus = (store: RootState): ICsoundStatus => { 6 | const state: ICsoundReducer = store.csound; 7 | return state.status; 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/hot-keys/utils.ts: -------------------------------------------------------------------------------- 1 | import { isMac } from "@root/utils"; 2 | import { replace, pipe, when } from "ramda"; 3 | 4 | export const humanizeKeySequence = (keySequence: string): string => 5 | pipe( 6 | when(() => isMac, replace("command", "⌘")), 7 | when(() => isMac, replace("opt", "⌥")) 8 | )(keySequence); 9 | -------------------------------------------------------------------------------- /src/components/menu-bar/types.ts: -------------------------------------------------------------------------------- 1 | // type MenuItemType = "normal" | "separator"; 2 | 3 | export interface MenuItemDef { 4 | callback?: () => void; 5 | disabled?: boolean; 6 | doc?: string; 7 | checked?: boolean; 8 | hotKey?: string; 9 | label?: string; 10 | seperator?: boolean; 11 | submenu?: MenuItemDef[]; 12 | } 13 | -------------------------------------------------------------------------------- /src/svgs/fad-cutter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-arrows-horz.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-arrows-vert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-modsawup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/csound-templates/default.csd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0dbfs=1 6 | nchnls=2 7 | 8 | instr 1 9 | asig vco2 p4, p5 10 | aenv linenr asig,0.01,0.1,0.01 11 | out aenv, aenv 12 | endin 13 | 14 | event_i "i", 1, 0, 2, 0dbfs/2, A4 15 | event_i "e", 0, 2.5 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/svgs/fad-modsawdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "esModuleInterop": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true 10 | }, 11 | "include": ["src/**/*.ts"], 12 | "exclude": ["node_modules", "../node_modules"] 13 | } 14 | -------------------------------------------------------------------------------- /src/svgs/fad-play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/modal/actions.ts: -------------------------------------------------------------------------------- 1 | export const closeModal = () => { 2 | return { 3 | type: "MODAL_CLOSE" 4 | }; 5 | }; 6 | 7 | export const openSimpleModal = ( 8 | modalComponentName: string, 9 | properties: Record | undefined 10 | ) => { 11 | return { 12 | type: "MODAL_OPEN_SIMPLE", 13 | modalComponentName, 14 | properties 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/svgs/fad-modtri.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-digital-colon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-modsine.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/themes/action.ts: -------------------------------------------------------------------------------- 1 | // import React from "react"; 2 | import { CsoundTheme, THEMES_CHANGE_THEME } from "./types"; 3 | 4 | export const changeTheme = ( 5 | themeName: CsoundTheme 6 | ): ((dispatch: any) => Promise) => { 7 | return async (dispatch: any) => { 8 | dispatch({ 9 | type: THEMES_CHANGE_THEME, 10 | newTheme: themeName 11 | }); 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | # A shellfile for nixos users 2 | with import {}; 3 | 4 | stdenv.mkDerivation { 5 | name = "web-ide"; 6 | buildInputs = with pkgs; [ electron ]; 7 | shellHook = '' 8 | # cmd: sh -c patchElectron 9 | patchElectron () { 10 | stat node_modules/electron/dist/electron &>/dev/null 11 | cp -f ${electron}/bin/electron node_modules/electron/dist/electron 12 | } 13 | export -f patchElectron 14 | ''; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/projects/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@root/store"; 2 | import { IProject, IProjectsReducer } from "./types"; 3 | 4 | export const selectActiveProject = (store: RootState): IProject | undefined => { 5 | const state: IProjectsReducer = store.ProjectsReducer; 6 | const activeProjectUid = state?.activeProjectUid; 7 | 8 | return activeProjectUid ? state.projects?.[activeProjectUid] : undefined; 9 | }; 10 | -------------------------------------------------------------------------------- /src/svgs/fad-pen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-phase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-punch-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-punch-out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-softclipcurve.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-lowpass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-timeselect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-highpass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/csound/utils.tsx: -------------------------------------------------------------------------------- 1 | import { ICsoundFileType } from "./types"; 2 | 3 | export function filenameToCsoundType( 4 | filename: string 5 | ): ICsoundFileType | undefined { 6 | if (filename.endsWith(".csd")) { 7 | return "csd"; 8 | } else if (filename.endsWith(".sco")) { 9 | return "sco"; 10 | } else if (filename.endsWith(".orc")) { 11 | return "orc"; 12 | } else if (filename.endsWith(".udo")) { 13 | return "udo"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/styles/_tooltip.ts: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react"; 2 | 3 | // the absolute pos elem 4 | const tooltipPopper = css` 5 | position: absolute !important; 6 | top: -10px !important; 7 | left: 0 !important; 8 | `; 9 | 10 | const tooltipElement = css` 11 | font-size: 13px !important; 12 | padding: 6px 12px !important; 13 | `; 14 | 15 | // inject into classes prop 16 | export const tooltipClasses = { 17 | popper: tooltipPopper, 18 | tooltip: tooltipElement 19 | }; 20 | -------------------------------------------------------------------------------- /src/svgs/fad-thunderbolt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-logo-cubase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-pointer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/console/actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SET_CLEAR_CONSOLE_CALLBACK, 3 | SET_PRINT_TO_CONSOLE_CALLBACK 4 | } from "./types"; 5 | 6 | type CB = (text: string) => void; 7 | 8 | export const setClearConsoleCallback = (callback: CB): Record => ({ 9 | type: SET_CLEAR_CONSOLE_CALLBACK, 10 | callback 11 | }); 12 | 13 | export const setPrintToConsoleCallback = ( 14 | callback?: CB 15 | ): Record => ({ 16 | type: SET_PRINT_TO_CONSOLE_CALLBACK, 17 | callback: callback 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/bottom-tabs/types.ts: -------------------------------------------------------------------------------- 1 | const PREFIX = "BOTTOM_TABS."; 2 | 3 | // ACTION TYPES 4 | export const OPEN_BOTTOM_TAB = PREFIX + "OPEN_BOTTOM_TAB"; 5 | export const CLOSE_BOTTOM_TAB = PREFIX + "CLOSE_BOTTOM_TAB"; 6 | export const REORDER_TABS = PREFIX + "REORDER_TABS"; 7 | export const SET_BOTTOM_TAB_INDEX = PREFIX + "SET_BOTTOM_TAB_INDEX"; 8 | 9 | export type BottomTab = "console" | "spectralAnalyzer" | "piano"; 10 | 11 | export interface IBottomTabsReducer { 12 | index: number; 13 | openTabs: BottomTab[]; 14 | } 15 | -------------------------------------------------------------------------------- /src/svgs/fad-AR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /__mocks__/svgMock.js: -------------------------------------------------------------------------------- 1 | // svgMock.js 2 | import React from "react"; 3 | 4 | const createComponent = function (name) { 5 | return class extends React.Component { 6 | // overwrite the displayName, since this is a class created dynamically 7 | static displayName = name; 8 | 9 | render() { 10 | return React.createElement(name, this.props, this.props.children); 11 | } 12 | }; 13 | }; 14 | 15 | export const ReactComponent = createComponent("svg"); 16 | export default `""`; 17 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-notch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-zoomout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/tabtab/utils/is-type.js: -------------------------------------------------------------------------------- 1 | export function isTabList(element) { 2 | return ( 3 | element.type && 4 | (element.type.displayName === "TabList" || 5 | element.type.displayName === "DragTabList") 6 | ); 7 | } 8 | 9 | export function isTab(element) { 10 | return ( 11 | element.type && 12 | (element.type.displayName === "Tab" || 13 | element.type.displayName === "DragTab") 14 | ); 15 | } 16 | 17 | export function isNumber(number) { 18 | return !Number.isNaN(Number.parseInt(number, 10)); 19 | } 20 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-rez-highpass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Csound WebIDE", 3 | "name": "Csound WebIDE", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-bandpass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-rez-lowpass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-modsquare.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/project-last-modified/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@root/store"; 2 | import { createSelector } from "@reduxjs/toolkit"; 3 | 4 | export const selectProjectLastModified = (projectUid: string | undefined) => 5 | createSelector( 6 | [ 7 | () => projectUid, 8 | (state: RootState) => state.ProjectLastModifiedReducer 9 | ], 10 | (projectUid, projectLastModifiedReducer) => { 11 | if (!projectUid) return undefined; 12 | return projectLastModifiedReducer[projectUid]; 13 | } 14 | ); 15 | -------------------------------------------------------------------------------- /src/csound-templates/index.js: -------------------------------------------------------------------------------- 1 | import * as defaultCsdTxt from "./default.csd?raw"; 2 | import * as defaultOrcTxt from "./default.orc?raw"; 3 | import * as defaultScoTxt from "./default.sco?raw"; 4 | 5 | export const defaultCsd = { 6 | name: "project.csd", 7 | value: defaultCsdTxt.default, 8 | type: "txt" 9 | }; 10 | 11 | export const defaultOrc = { 12 | name: "project.orc", 13 | value: defaultOrcTxt.default, 14 | type: "txt" 15 | }; 16 | 17 | export const defaultSco = { 18 | name: "project.sco", 19 | value: defaultScoTxt.default, 20 | type: "txt" 21 | }; 22 | -------------------------------------------------------------------------------- /src/svgs/fad-caret-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-h-expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-caret-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-caret-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-v-expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-caret-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-automation-2p.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/svgs/fad-digital1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-modsh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-zoomin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Provider } from "react-redux"; 3 | import { createRoot } from "react-dom/client"; 4 | import Main from "./components/main/main"; 5 | import { store } from "@root/store"; 6 | 7 | import "./config/firestore"; // import for sideffects 8 | import "react-perfect-scrollbar/dist/css/styles.css"; 9 | 10 | const container = document.getElementById("root"); 11 | const root = createRoot(container as any); 12 | 13 | root.render( 14 | 15 | 16 |
17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /src/components/bottom-tabs/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@root/store"; 2 | import { BottomTab, IBottomTabsReducer } from "./types"; 3 | // import { path, prop } from "ramda"; 4 | 5 | export const selectOpenBottomTabs = ( 6 | store: RootState 7 | ): BottomTab[] | undefined => { 8 | const state: IBottomTabsReducer | undefined = store.BottomTabsReducer; 9 | return state ? state.openTabs : ([] as BottomTab[]); 10 | }; 11 | 12 | export const selectBottomTabIndex = (store: RootState): number => { 13 | const state: IBottomTabsReducer | undefined = store.BottomTabsReducer; 14 | return state ? state.index : -1; 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/snackbar/actions.ts: -------------------------------------------------------------------------------- 1 | // import firebase from "firebase/app"; 2 | import { 3 | OPEN_SNACKBAR, 4 | CLOSE_SNACKBAR, 5 | SnackbarType, 6 | ISnackbar 7 | } from "./types"; 8 | 9 | export const openSnackbar = ( 10 | text: string, 11 | type: SnackbarType, 12 | timeout = 6000 13 | ) => { 14 | const payload: ISnackbar = { 15 | text, 16 | type, 17 | timeout 18 | }; 19 | return { 20 | type: OPEN_SNACKBAR, 21 | payload 22 | }; 23 | }; 24 | 25 | export const closeSnackbar = () => { 26 | return { 27 | type: CLOSE_SNACKBAR 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /src/svgs/fad-undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/tabtab/sort-method.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const useSort = ({ 4 | activeIndex, 5 | handleTabChange, 6 | handleTabSequence 7 | }) => { 8 | const onSortEnd = React.useCallback( 9 | ({ oldIndex, newIndex }) => { 10 | if (oldIndex === newIndex) { 11 | if (activeIndex !== oldIndex) { 12 | handleTabChange(oldIndex); 13 | } 14 | } else { 15 | handleTabSequence({ oldIndex, newIndex }); 16 | } 17 | }, 18 | [activeIndex, handleTabChange, handleTabSequence] 19 | ); 20 | 21 | return { onSortEnd }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/site-documents/styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react"; 2 | import { headerHeight } from "@styles/constants"; 3 | 4 | export const rootStyle = css({ 5 | backgroundColor: "#e8e8e8", 6 | bottom: "0px", 7 | top: `${headerHeight}px`, 8 | left: 0, 9 | right: 0, 10 | position: "relative" 11 | }); 12 | 13 | export const mainStyle = css({ 14 | maxWidth: "1024px", 15 | padding: 16, 16 | margin: "auto", 17 | fontSize: 16, 18 | "& h1": { 19 | margin: "16px 0" 20 | }, 21 | "& h2": { 22 | margin: "40px 0 16px" 23 | }, 24 | "& h3": { 25 | margin: "40px 0 16px" 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /src/svgs/fad-ADR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-sliderhandle-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-mute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-powerswitch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/modal/styles.tsx: -------------------------------------------------------------------------------- 1 | import { css, SerializedStyles, Theme } from "@emotion/react"; 2 | import { shadow, _scrollbars } from "@styles/_common"; 3 | 4 | export const content = (theme: Theme): SerializedStyles => css` 5 | position: absolute; 6 | outline: none; 7 | & > div { 8 | max-height: 80vh; 9 | overflow-y: scroll; 10 | color: ${theme.textColor}; 11 | position: relative; 12 | border: 2px solid ${theme.line}; 13 | border-radius: 6px; 14 | background-color: ${theme.background}; 15 | ${shadow} 16 | box-sizing: content-box; 17 | padding: 16px 32px 24px; 18 | ${_scrollbars(theme)} 19 | } 20 | `; 21 | -------------------------------------------------------------------------------- /src/svgs/fad-xlrplug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/csound/reducer.ts: -------------------------------------------------------------------------------- 1 | import { assoc } from "ramda"; 2 | import { ICsoundStatus, SET_CSOUND_PLAY_STATE } from "./types"; 3 | 4 | export interface ICsoundReducer { 5 | status: ICsoundStatus; 6 | } 7 | 8 | const CsoundReducer = ( 9 | state: ICsoundReducer | undefined, 10 | action: Record 11 | ): ICsoundReducer => { 12 | if (!state) { 13 | return { status: "initialized" }; 14 | } 15 | switch (action.type) { 16 | case SET_CSOUND_PLAY_STATE: { 17 | return assoc("status", action.status, state); 18 | } 19 | default: { 20 | return state; 21 | } 22 | } 23 | }; 24 | 25 | export default CsoundReducer; 26 | -------------------------------------------------------------------------------- /src/svgs/fad-preset-a.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-sliderhandle-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-ADSR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-loop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/login/types.ts: -------------------------------------------------------------------------------- 1 | const PREFIX = "LOGIN."; 2 | export const SET_REQUESTING_STATUS = PREFIX + "SET_REQUESTING_STATUS"; 3 | export const SIGNIN_FAIL = PREFIX + "SIGNIN_FAIL"; 4 | export const SIGNIN_SUCCESS = PREFIX + "SIGNIN_SUCCESS"; 5 | export const SIGNIN_REQUEST = PREFIX + "SIGNIN_REQUEST"; 6 | export const UPDATE_USER_PROFILE = PREFIX + "UPDATE_USER_PROFILE"; 7 | export const OPEN_DIALOG = PREFIX + "OPEN_DIALOG"; 8 | export const CLOSE_DIALOG = PREFIX + "CLOSE_DIALOG"; 9 | export const LOG_OUT = PREFIX + "LOG_OUT"; 10 | export const CREATE_USER_FAIL = PREFIX + "CREATE_USER_FAIL"; 11 | export const CREATE_USER_SUCCESS = PREFIX + "CREATE_USER_SUCCESS"; 12 | export const CREATE_CLEAR_ERROR = PREFIX + "CREATE_USER_CLEAR_ERROR"; 13 | -------------------------------------------------------------------------------- /src/components/snackbar/types.ts: -------------------------------------------------------------------------------- 1 | import { UnknownAction } from "redux"; 2 | 3 | export const OPEN_SNACKBAR = "SNACKBAR.OPEN_SNACKBAR"; 4 | export const CLOSE_SNACKBAR = "SNACKBAR.CLOSE_SNACKBAR"; 5 | 6 | export interface ISnackbar { 7 | type: SnackbarType; 8 | text: string; 9 | timeout: number | typeof Number.POSITIVE_INFINITY; 10 | } 11 | export interface OpenSnackbar { 12 | type: typeof OPEN_SNACKBAR; 13 | payload: ISnackbar; 14 | } 15 | 16 | export interface CloseSnackbar { 17 | type: typeof CLOSE_SNACKBAR; 18 | } 19 | 20 | export enum SnackbarType { 21 | Error, 22 | Info, 23 | Warning, 24 | Success 25 | } 26 | 27 | export type SnackbarActionTypes = OpenSnackbar | CloseSnackbar | UnknownAction; 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Firebase 4 | .firebase 5 | firebase-debug.log 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | dist 16 | 17 | # misc 18 | .DS_Store 19 | .env 20 | .env.prod 21 | .env.dev 22 | .env.local 23 | .env.development.local 24 | .env.test.local 25 | .env.production.local 26 | 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | .vscode 31 | .dir-locals.el 32 | 33 | # Tmp files 34 | *# 35 | 36 | # Search app 37 | search/service-key.json 38 | search/node_modules 39 | dev-database.json 40 | prod-database.json 41 | database.json 42 | service-key-dev.json 43 | service-key-prod.json 44 | -------------------------------------------------------------------------------- /src/svgs/fad-AHDSR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/social-controls/styles.ts: -------------------------------------------------------------------------------- 1 | import { css, SerializedStyles, Theme } from "@emotion/react"; 2 | import { shadow } from "@styles/_common"; 3 | 4 | export const buttonContainer = (theme: Theme): SerializedStyles => css` 5 | position: relative; 6 | top: 0; 7 | color: ${theme.headerTextColor}; 8 | border: 2px solid ${theme.highlightBackground}; 9 | cursor: pointer; 10 | border-radius: 3px; 11 | ${shadow} 12 | height: 42px; 13 | width: auto; 14 | margin: 0; 15 | margin-right: 6px; 16 | &:hover { 17 | cursor: pointer; 18 | border: 2px solid ${theme.line}; 19 | & > button { 20 | border-color: transparent transparent transparent ${theme.line}; 21 | } 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /src/svgs/fad-stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/manual/styles.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react"; 2 | 3 | export const opcodeContainer = css` 4 | display: inline-block; 5 | padding: 0; 6 | margin: 0 12px; 7 | height: 100%; 8 | `; 9 | 10 | export const entry = css` 11 | display: block; 12 | font-size: 14px; 13 | text-decoration: none; 14 | margin: 3px 0; 15 | white-space: nowrap; 16 | text-overflow: ellipsis; 17 | overflow: hidden; 18 | color: white; 19 | &:hover { 20 | background-color: rgba(255, 255, 255, 0.5); 21 | } 22 | 23 | span { 24 | font-weight: 500; 25 | display: inline-block; 26 | } 27 | 28 | p { 29 | padding: 0; 30 | margin: 0; 31 | display: initial; 32 | } 33 | `; 34 | -------------------------------------------------------------------------------- /src/svgs/fad-logo-reaper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/console/styles.ts: -------------------------------------------------------------------------------- 1 | import { css, SerializedStyles, Theme } from "@emotion/react"; 2 | import { _scrollbars } from "@styles/_common"; 3 | // import { css as classCss } from "emotion"; 4 | 5 | export const ConsoleContainer = (theme: Theme): SerializedStyles => css` 6 | height: calc(100% - 42px); 7 | width: 100%; 8 | bottom: 0; 9 | left: 0; 10 | position: absolute; 11 | white-space: break-spaces; 12 | font-family: ${theme.font.monospace}; 13 | color: ${theme.console}; 14 | padding: 0 6px; 15 | padding-top: 6px; 16 | outline: none; 17 | overflow-x: hidden; 18 | overflow-y: scroll; 19 | ${_scrollbars(theme)} 20 | code { 21 | height: 100%; 22 | width: 100%; 23 | position: relative; 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /src/svgs/fad-logo-protools.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-logo-studioone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-digital7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-modularplug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/profile/tabs/styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react"; 2 | 3 | export const starItemContainer = css` 4 | & > div { 5 | min-height: unset; 6 | display: flex; 7 | } 8 | `; 9 | 10 | export const starItemIcon = css` 11 | width: 52px; 12 | display: flex; 13 | align-items: center; 14 | align-self: center; 15 | margin-right: 24px; 16 | & > div { 17 | box-sizing: content-box; 18 | height: 34px; 19 | width: 34px; 20 | padding: 5px; 21 | margin: 0 9px; 22 | cursor: default; 23 | border-radius: 50%; 24 | & svg { 25 | height: 20px !important; 26 | width: 20px !important; 27 | margin-top: 1px; 28 | margin-left: 1px; 29 | } 30 | } 31 | `; 32 | -------------------------------------------------------------------------------- /src/svgs/fad-scissors.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/tabtab/index.js: -------------------------------------------------------------------------------- 1 | export { default as Tabs } from "./tabs.jsx"; 2 | export { 3 | TabListComponent, 4 | ActionButtonStyle, 5 | TabListStyle 6 | } from "./tab-list.jsx"; 7 | export { Tab, TabStyle } from "./tab.jsx"; 8 | export { default as DragTabList } from "./drag-tab-list.jsx"; 9 | export { DragTab } from "./drag-tab.jsx"; 10 | export { default as PanelList } from "./panel-list.jsx"; 11 | export { default as Panel, PanelStyle } from "./panel.jsx"; 12 | export { default as AsyncPanel } from "./async-panel.jsx"; 13 | export { default as ExtraButton } from "./extra-button.jsx"; 14 | 15 | // export { 16 | // Tabs, 17 | // TabList, 18 | // Tab, 19 | // DragTabList, 20 | // DragTab, 21 | // PanelList, 22 | // Panel, 23 | // AsyncPanel, 24 | // ExtraButton, 25 | // styled 26 | // }; 27 | -------------------------------------------------------------------------------- /config/lezer-loader.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const { buildParserFile } = require("@lezer/generator"); 3 | 4 | exports.default = function loader() { 5 | const isRequestingTerms = this.resource.includes(".terms"); 6 | const resourcePath = isRequestingTerms 7 | ? this.resource.replace(".terms.js", ".grammar") 8 | : this.resource; 9 | const code = fs.readFileSync(resourcePath).toString(); 10 | 11 | const parser = buildParserFile(code, { 12 | fileName: "syntax.grammar", 13 | moduleStyle: "es", 14 | warn: (message) => { 15 | if (!isRequestingTerms) { 16 | console.error(message); 17 | throw new Error(message); 18 | } 19 | } 20 | }); 21 | 22 | return isRequestingTerms ? parser.terms : parser.parser; 23 | }; 24 | -------------------------------------------------------------------------------- /src/db/search.ts: -------------------------------------------------------------------------------- 1 | import firestore from "firebase/firestore"; 2 | 3 | export interface ISearchResultItem { 4 | id: string; 5 | created: firestore.Timestamp; 6 | description: string; 7 | iconBackgroundColor: string; 8 | iconForegroundColor: string; 9 | iconName: string; 10 | name: string; 11 | public: boolean; 12 | userUid: string; 13 | } 14 | 15 | export interface ISearchResult { 16 | data: ISearchResultItem[]; 17 | totalRecords: number; 18 | } 19 | 20 | interface IStarredProjectMeta { 21 | id: string; 22 | count: number; 23 | } 24 | 25 | type IStar = { [userUid: string]: firestore.Timestamp }; 26 | 27 | export type IStarredProject = IStarredProjectMeta & IStar; 28 | 29 | export type IStarredProjectSearchResult = { 30 | data: IStarredProject[]; 31 | totalRecords: number; 32 | }; 33 | -------------------------------------------------------------------------------- /scripts/setupJest.js: -------------------------------------------------------------------------------- 1 | require("jest-fetch-mock").enableMocks(); 2 | 3 | Object.defineProperty(window.document, "styleSheets", { 4 | writable: true, 5 | value: "" 6 | }); 7 | 8 | Object.defineProperty(window, "scrollTo", { 9 | writable: true, 10 | value: () => {} 11 | }); 12 | 13 | fetch.mockResponse((req) => { 14 | if ( 15 | req.url.endsWith("list/stars/8/0/count/desc") || 16 | req.url.includes("random/projects/") 17 | ) { 18 | return { 19 | then: function () { 20 | return { 21 | json: function () { 22 | return { data: [], totalRecords: 0 }; 23 | } 24 | }; 25 | } 26 | }; 27 | } else { 28 | Promise.reject(new Error(`bad url ${req.url}`)); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/svgs/fad-hardclip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-automation-3p.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/tabtab/drag-tab.jsx: -------------------------------------------------------------------------------- 1 | import { useSortable } from "@dnd-kit/sortable"; 2 | import { CSS } from "@dnd-kit/utilities"; 3 | import { Tab } from "./tab.jsx"; 4 | 5 | export function DragTab({ children, closeCallback, id, ...props }) { 6 | const { attributes, listeners, setNodeRef, transform, transition } = 7 | useSortable({ id }); 8 | 9 | const style = { 10 | transform: CSS.Transform.toString(transform), 11 | transition, 12 | ...props.style // Allow custom styles to be passed 13 | }; 14 | 15 | return ( 16 | 24 | {children} 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/svgs/fad-diskio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-keyboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/editor/actions.tsx: -------------------------------------------------------------------------------- 1 | import { EditorView } from "@codemirror/view"; 2 | 3 | export const manualEntryAtPoint = (editorReference: EditorView) => { 4 | return async (): Promise => { 5 | console.log("FIXME", editorReference); 6 | // if (!editorReference || !window.csoundSynopsis) { 7 | // return; 8 | // } 9 | // const cursor = editorReference.getCursor(); 10 | // const token = editorReference 11 | // .getTokenAt(cursor) 12 | // .string.replace(/:.*/, ""); 13 | // const manualEntry = window.csoundSynopsis.find( 14 | // (opc) => opc.opname === token 15 | // ); 16 | // if (manualEntry) { 17 | // dispatch(lookupManualString()); 18 | // setTimeout(() => dispatch(lookupManualString(manualEntry.id)), 10); 19 | // } 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/svgs/fad-arpdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-arpdownup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-shelving-lo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-arprandom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-arpupdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-arpup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-shelving-hi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/hot-keys/reducer.ts: -------------------------------------------------------------------------------- 1 | import defaultBindings from "./default-bindings"; 2 | import { assoc } from "ramda"; 3 | import { HotKeysActionTypes, BindingsMap, UPDATE_COUNTER } from "./types"; 4 | 5 | export interface IHotKeys { 6 | bindings: BindingsMap; 7 | updateCounter: number; 8 | } 9 | 10 | const INITIAL_STATE: IHotKeys = { 11 | updateCounter: 0, 12 | bindings: defaultBindings 13 | }; 14 | 15 | const HotKeysReducer = ( 16 | state: IHotKeys | undefined, 17 | action: HotKeysActionTypes 18 | ): IHotKeys => { 19 | if (!state) { 20 | return INITIAL_STATE; 21 | } 22 | 23 | switch (action.type) { 24 | case UPDATE_COUNTER: { 25 | return assoc("updateCounter", state.updateCounter + 1, state); 26 | } 27 | default: { 28 | return state; 29 | } 30 | } 31 | }; 32 | 33 | export default HotKeysReducer; 34 | -------------------------------------------------------------------------------- /src/tabtab/close-button.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { CloseIcon } from "./icon-svg.js"; 3 | import styled from "@emotion/styled"; 4 | 5 | const CloseWrapper = styled.button` 6 | display: inline-block; 7 | color: #777; 8 | margin-left: 5px; 9 | padding: 0; 10 | vertical-align: middle; 11 | border: 0; 12 | padding: 2px; 13 | outline: 0; 14 | z-index: 10; 15 | &:hover { 16 | color: black; 17 | background-color: #eee; 18 | cursor: pointer; 19 | border-radius: 50%; 20 | } 21 | > svg { 22 | vertical-align: middle; 23 | } 24 | `; 25 | 26 | export default class CloseButton extends React.PureComponent { 27 | render() { 28 | return ( 29 | 30 | 31 | 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/snackbar/selectors.ts: -------------------------------------------------------------------------------- 1 | // import { createSelector } from "reselect"; 2 | import { ISnackbarReducer } from "./reducer"; 3 | import { SnackbarType } from "./types"; 4 | import { RootState } from "@root/store"; 5 | 6 | export const selectSnackbarOpen = (store: RootState): boolean => { 7 | const state: ISnackbarReducer = store.SnackbarReducer; 8 | return state.open; 9 | }; 10 | 11 | export const selectSnackbarType = (store: RootState): SnackbarType => { 12 | const state: ISnackbarReducer = store.SnackbarReducer; 13 | return state.type; 14 | }; 15 | 16 | export const selectSnackbarText = (store: RootState): string => { 17 | const state: ISnackbarReducer = store.SnackbarReducer; 18 | return state.text; 19 | }; 20 | 21 | export const selectSnackbarTimeout = (store: RootState): number => { 22 | const state: ISnackbarReducer = store.SnackbarReducer; 23 | return state.timeout; 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/page-404/styles.ts: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react"; 2 | 3 | export const rootStyle = css({ 4 | backgroundColor: "#e8e8e8", 5 | fontFamily: "'Space Mono', monospace", 6 | width: "100%", 7 | height: "auto", 8 | bottom: "0px", 9 | top: "0px", 10 | left: 0, 11 | position: "absolute" 12 | }); 13 | 14 | export const centerBoxStyle = css({ 15 | position: "absolute", 16 | width: "600px", 17 | height: "50px", 18 | top: "120px", 19 | left: "50%", 20 | marginTop: "-25px", 21 | marginLeft: "-50px" 22 | }); 23 | 24 | export const startCodingButtonStyle = css({ 25 | fontSize: "22px", 26 | border: "4px solid #518C82", 27 | borderRadius: "80%", 28 | display: "flex", 29 | alignItems: "center", 30 | justifyContent: "center", 31 | width: "220px", 32 | height: "220px", 33 | textDecoration: "none", 34 | background: "#00DFCB" 35 | }); 36 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "mime"; 2 | declare module "react-piano"; 3 | declare module "d3-scale"; 4 | declare module "file-saver"; 5 | declare module "react-beforeunload"; 6 | declare module "react-iframe-comm"; 7 | declare module "react-beautiful-dnd"; 8 | declare module "@hlolli/codemirror-lang-csound"; 9 | 10 | declare module "history" { 11 | export * from "history"; 12 | const forward: () => void; 13 | const back: () => void; 14 | export const createBrowserHistory: () => void; 15 | export type Action = any; 16 | export type Location = any; 17 | export type History = any; 18 | } 19 | 20 | declare module "*.csd" { 21 | const content: string; 22 | export default content; 23 | } 24 | 25 | declare module "*.orc" { 26 | const content: string; 27 | export default content; 28 | } 29 | 30 | declare module "*.sco" { 31 | const content: string; 32 | export default content; 33 | } 34 | -------------------------------------------------------------------------------- /src/svgs/fad-midiplug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/svgs/fad-unlock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-microphone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/tabtab/panel-list.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default class PanelList extends React.PureComponent { 4 | render() { 5 | const { children, activeIndex, customStyle, style } = this.props; 6 | if (!children || activeIndex === undefined) { 7 | return undefined; 8 | } 9 | 10 | let props = {}; 11 | if (customStyle && customStyle.Panel) { 12 | props = { ...props, CustomPanelStyle: customStyle.Panel }; 13 | } 14 | 15 | // to prevent the type of one children is object type 16 | const result = React.Children.toArray(children).map((child, index) => 17 | React.cloneElement(child, { 18 | key: index, 19 | active: index === activeIndex, 20 | index, 21 | ...props 22 | }) 23 | ); 24 | return
{result}
; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/login/selectors.tsx: -------------------------------------------------------------------------------- 1 | import { RootState } from "@root/store"; 2 | 3 | export const selectLoginRequesting = ({ LoginReducer }: RootState): boolean => { 4 | return LoginReducer.requesting; 5 | }; 6 | 7 | export const selectErrorCode = ({ 8 | LoginReducer 9 | }: RootState): number | undefined => { 10 | return LoginReducer.errorCode; 11 | }; 12 | 13 | export const selectErrorMessage = ({ 14 | LoginReducer 15 | }: RootState): string | undefined => { 16 | return LoginReducer.errorMessage; 17 | }; 18 | 19 | export const selectLoginFail = ({ LoginReducer }: RootState): boolean => { 20 | return LoginReducer.failed; 21 | }; 22 | 23 | export const selectAuthenticated = ({ LoginReducer }: RootState): boolean => { 24 | return LoginReducer.authenticated; 25 | }; 26 | 27 | export const selectLoggedInUid = ({ 28 | LoginReducer 29 | }: RootState): string | undefined => { 30 | return LoginReducer.loggedInUid; 31 | }; 32 | -------------------------------------------------------------------------------- /src/svgs/fad-stereo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/tabtab/panel.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | 4 | const PanelStyle = styled.div` 5 | text-align: left; 6 | padding: 20px 15px; 7 | ${(props) => (props.active ? "" : `display: none;`)} 8 | height: 100%; 9 | width: 100%; 10 | `; 11 | 12 | export default class PanelComponent extends React.PureComponent { 13 | render() { 14 | const { CustomPanelStyle, active, index, children } = this.props; 15 | const Panel = CustomPanelStyle || PanelStyle; 16 | return ( 17 | 24 | {active && children} 25 | 26 | ); 27 | } 28 | } 29 | 30 | export { PanelStyle }; 31 | -------------------------------------------------------------------------------- /src/svgs/fad-backward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-speaker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/tabtab/utils/count-tab.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react"; 3 | import { isTab, isTabList } from "./is-type.js"; 4 | 5 | function loopTabList(tabList, cb) { 6 | React.Children.forEach(tabList, (tab) => { 7 | if (isTab(tab)) { 8 | cb(); 9 | } 10 | }); 11 | } 12 | 13 | function deepLoop(children, cb) { 14 | React.Children.forEach(children, (child) => { 15 | if (isTabList(child)) { 16 | if (child.props && child.props.children) { 17 | return loopTabList(child.props.children, cb); 18 | } else { 19 | throw new Error("You need to provide `Tab` children"); 20 | } 21 | } else if (child.props && child.props.children) { 22 | deepLoop(child.props.children, cb); 23 | } 24 | }); 25 | } 26 | 27 | export default function countTab(children) { 28 | let count = 0; 29 | deepLoop(children, () => count++); 30 | return count; 31 | } 32 | -------------------------------------------------------------------------------- /src/svgs/fad-ram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /functions/src/popular_projects.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | import { onCall } from "firebase-functions/v2/https"; 3 | import { log } from "firebase-functions/logger"; 4 | import { shuffle } from "lodash-es"; 5 | 6 | let lastUpdate = Date.now(); 7 | let projects: any[] = []; 8 | 9 | export const popularProjects = onCall<{ count: number }>(async ({ data }) => { 10 | if (projects.length === 0 || Date.now() - lastUpdate > 1000 * 60 * 5) { 11 | lastUpdate = Date.now(); 12 | const db = admin.firestore(); 13 | const randomStars = await db.collection("stars").limit(200).get(); 14 | // log("randomStarsLength: " + randomStars.docs.length); 15 | const projectUids = randomStars.docs.map((doc) => doc.id); 16 | // log("projectUids: " + JSON.stringify(projectUids, null, 2)); 17 | // log("projectsLength: " + projects.length); 18 | projects = projectUids; 19 | } 20 | 21 | return shuffle(projects).slice(0, data.count); 22 | }); 23 | -------------------------------------------------------------------------------- /scripts/uploadSourcemaps.js: -------------------------------------------------------------------------------- 1 | // uploadSourcemaps.js 2 | const path = require("path"); 3 | const { upload } = require("sentry-files"); 4 | const { version } = require("../package.json"); 5 | 6 | upload({ 7 | version: version, 8 | organization: "csound", 9 | project: process.env.SENTRY_PROJECT, 10 | token: process.env.SENTRY_AUTH_TOKEN, 11 | files: getFiles() 12 | }) 13 | .then(data => console.log("----- SUCCESS ----\n", data)) 14 | .catch(error => console.log("---- ERROR ----\n", error)); 15 | function getFiles() { 16 | const BUILD_DIR = "dist"; 17 | const assetsFile = path.resolve(BUILD_DIR, "asset-manifest.json"); 18 | const filePaths = require(assetsFile); 19 | const jsFilesRegex = /(\.js(.map)?)$/; 20 | const files = Object.keys(filePaths) 21 | .filter(f => jsFilesRegex.test(f)) 22 | .map(f => ({ 23 | name: `${filePaths[f]}`, 24 | path: `build${filePaths[f]}` 25 | })); 26 | 27 | return files; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/project-editor/utils.tsx: -------------------------------------------------------------------------------- 1 | import { IDocument } from "@comp/projects/types"; 2 | import { IOpenDocument } from "./types"; 3 | 4 | export const sortByStoredTabOrder = ( 5 | tabOrder: string[], 6 | allDocuments: IDocument[] 7 | ): IOpenDocument[] => { 8 | return tabOrder.reduce( 9 | (accumulator: IOpenDocument[], documentUid: string) => { 10 | console.log("Document uid", allDocuments, documentUid); 11 | const maybeDocument: IDocument | undefined = allDocuments.find( 12 | (doc) => doc.documentUid === documentUid 13 | ); 14 | return maybeDocument 15 | ? [ 16 | ...accumulator, 17 | { 18 | editorInstance: undefined, 19 | uid: maybeDocument.documentUid 20 | } as IOpenDocument 21 | ] 22 | : accumulator; 23 | }, 24 | [] 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/bottom-tabs/actions.ts: -------------------------------------------------------------------------------- 1 | import { AppThunkDispatch } from "@root/store"; 2 | import { 3 | BottomTab, 4 | CLOSE_BOTTOM_TAB, 5 | OPEN_BOTTOM_TAB, 6 | REORDER_TABS, 7 | SET_BOTTOM_TAB_INDEX 8 | } from "./types"; 9 | 10 | export const setBottomTabIndex = (newIndex: number) => { 11 | return async (dispatch: AppThunkDispatch) => 12 | dispatch({ type: SET_BOTTOM_TAB_INDEX, newIndex }); 13 | }; 14 | 15 | export const reorderBottomTabs = (newOrder: BottomTab[], newIndex: number) => { 16 | return async (dispatch: AppThunkDispatch) => 17 | dispatch({ type: REORDER_TABS, newIndex, newOrder }); 18 | }; 19 | 20 | export const openBottomTab = (tab: BottomTab) => { 21 | return async (dispatch: AppThunkDispatch) => 22 | dispatch({ type: OPEN_BOTTOM_TAB, tab }); 23 | }; 24 | 25 | export const closeBottomTab = (closeTab: BottomTab) => { 26 | return async (dispatch: AppThunkDispatch) => 27 | dispatch({ type: CLOSE_BOTTOM_TAB, closeTab }); 28 | }; 29 | -------------------------------------------------------------------------------- /src/svgs/fad-logo-waveform.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-modrandom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-arpchord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/project-last-modified/reducer.tsx: -------------------------------------------------------------------------------- 1 | import { UPDATE_PROJECT_LAST_MODIFIED_LOCALLY } from "./types"; 2 | import { assocPath } from "ramda"; 3 | 4 | export interface IProjectLastModified { 5 | timestamp: number | undefined; 6 | } 7 | 8 | export type IProjectLastModifiedReducer = { 9 | [projectUid: string]: IProjectLastModified; 10 | }; 11 | 12 | const ProjectLastModifiedReducer = ( 13 | state: IProjectLastModifiedReducer | undefined, 14 | action: Record 15 | ): IProjectLastModifiedReducer => { 16 | switch (action.type) { 17 | case UPDATE_PROJECT_LAST_MODIFIED_LOCALLY: { 18 | return { 19 | ...state, 20 | [action.projectUid]: { 21 | ...(state?.[action.projectUid] || {}), 22 | timestamp: action.timestamp 23 | } 24 | }; 25 | } 26 | default: { 27 | return state || {}; 28 | } 29 | } 30 | }; 31 | 32 | export default ProjectLastModifiedReducer; 33 | -------------------------------------------------------------------------------- /src/svgs/fad-arpdownandup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-arpplayorder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-arpupandown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /functions/src/main.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp } from "firebase-admin/app"; 2 | initializeApp({}); 3 | 4 | export { addProjectFileOnStorageUploadCallback as add_project_file_on_storage_upload_callback } from "./add_project_file_on_storage_upload.js"; 5 | export { deleteUserCallback as delete_user_callback } from "./delete_user.js"; 6 | export { followersCounter as followers_counter } from "./followers_counter.js"; 7 | export { followingCounter as following_counter } from "./following_counter.js"; 8 | export { host } from "./host.js"; 9 | export { newUserCallback as new_user_callback } from "./new_user.js"; 10 | export { projectFileStorageDeleteCallback as project_file_storage_delete_callback } from "./project_file_storage_delete.js"; 11 | export { projectsCounter as projects_counter } from "./projects_counter.js"; 12 | export { randomProjects as random_projects } from "./random_projects.js"; 13 | export { popularProjects as popular_projects } from "./popular_projects.js"; 14 | export { searchProjects as search_projects } from "./search_projects.js"; 15 | -------------------------------------------------------------------------------- /src/tabtab/extra-button.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | 4 | const Wrapper = styled.button` 5 | float: right; 6 | border: 1px solid #eee; 7 | border-radius: 2px; 8 | padding: 3px; 9 | margin-top: 10px; 10 | margin-left: 2px; 11 | display: inline-block; 12 | color: #777; 13 | vertical-align: middle; 14 | &:hover { 15 | color: black; 16 | cursor: pointer; 17 | } 18 | &:disabled, 19 | &[disabled] { 20 | border: 1px solid grey; 21 | background-color: #e7e7e7; 22 | cursor: not-allowed; 23 | } 24 | `; 25 | 26 | export default class ExtraButton extends React.PureComponent { 27 | static defaultProps = { 28 | disabled: false 29 | }; 30 | 31 | render() { 32 | const { disabled, onClick, children } = this.props; 33 | return ( 34 | 35 | {children} 36 | 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/svgs/fad-cpu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/electron.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | const app = electron.app; 3 | const BrowserWindow = electron.BrowserWindow; 4 | 5 | const path = require('path'); 6 | const isDev = require('electron-is-dev'); 7 | 8 | let mainWindow; 9 | console.log("IS DEV", isDev); 10 | function createWindow() { 11 | mainWindow = new BrowserWindow({width: 900, height: 680}); 12 | mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../dist/index.html')}`); 13 | if (isDev) { 14 | // Open the DevTools. 15 | //BrowserWindow.addDevToolsExtension(''); 16 | mainWindow.webContents.openDevTools(); 17 | } 18 | mainWindow.on('closed', () => mainWindow = null); 19 | } 20 | 21 | app.on('ready', createWindow); 22 | 23 | app.on('window-all-closed', () => { 24 | if (process.platform !== 'darwin') { 25 | app.quit(); 26 | } 27 | }); 28 | 29 | app.on('activate', () => { 30 | if (mainWindow === null) { 31 | createWindow(); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/svgs/fad-metronome.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-squareswitch-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-squareswitch-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-vsquareswitch-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-vsquareswitch-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-eraser.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/project-last-modified/subscribers.tsx: -------------------------------------------------------------------------------- 1 | import { doc, onSnapshot } from "firebase/firestore"; 2 | import { store } from "@root/store"; 3 | import { projectLastModified } from "@config/firestore"; 4 | import { updateProjectLastModifiedLocally } from "./actions"; 5 | 6 | export const subscribeToProjectLastModified = async ( 7 | projectUid: string 8 | ): Promise<() => void> => { 9 | const unsubscribe: () => void = onSnapshot( 10 | doc(projectLastModified, projectUid), 11 | async (timestampDocument) => { 12 | if (!timestampDocument.exists()) { 13 | return; 14 | } 15 | 16 | const { timestamp } = timestampDocument.data(); 17 | timestamp && 18 | (await store.dispatch( 19 | updateProjectLastModifiedLocally( 20 | projectUid, 21 | timestamp.toMillis() 22 | ) 23 | )); 24 | }, 25 | (error: any) => console.error(error) 26 | ); 27 | return unsubscribe; 28 | }; 29 | -------------------------------------------------------------------------------- /src/svgs/fad-automation-4p.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/svgs/fad-filter-bell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/db/types.ts: -------------------------------------------------------------------------------- 1 | import firestore, { DocumentData } from "firebase/firestore"; 2 | 3 | export interface IUserProfile { 4 | name: string; 5 | email: string; 6 | userid?: number; 7 | photoUrl?: string; 8 | } 9 | 10 | export interface IFirestoreDocument extends DocumentData { 11 | lastModified: firestore.Timestamp; 12 | name: string; 13 | type: string; 14 | userUid: string; 15 | value: string; 16 | } 17 | 18 | export interface IFirestoreProject extends DocumentData { 19 | created: firestore.Timestamp; 20 | description: string; 21 | iconBackgroundColor: string; 22 | iconForegroundColor: string; 23 | iconName: string; 24 | name: string; 25 | public: boolean; 26 | userUid: string; 27 | id?: string; 28 | } 29 | 30 | export interface IFirestoreProfile { 31 | backgroundIndex?: number; 32 | bio: string; 33 | displayName: string; 34 | link1: string; 35 | link2: string; 36 | link3: string; 37 | photoUrl: string; 38 | userJoinDate: firestore.Timestamp; 39 | userUid: string; 40 | username: string; 41 | } 42 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "compact": false 5 | } 6 | }, 7 | "presets": [ 8 | "@babel/preset-env", 9 | "@babel/preset-typescript", 10 | [ 11 | "@babel/preset-react", 12 | { 13 | "runtime": "automatic" 14 | } 15 | ], 16 | [ 17 | "@emotion/babel-preset-css-prop", 18 | { 19 | "autoLabel": "dev-only", 20 | "labelFormat": "[local]" 21 | } 22 | ] 23 | ], 24 | "plugins": [ 25 | "@emotion", 26 | "@babel/plugin-transform-runtime", 27 | "@babel/proposal-class-properties", 28 | "@babel/proposal-object-rest-spread", 29 | [ 30 | "babel-plugin-named-asset-import", 31 | { 32 | "loaderMap": { 33 | "svg": { 34 | "ReactComponent": "@svgr/webpack?-svgo,+titleProp,+ref![path]" 35 | } 36 | } 37 | } 38 | ] 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/components/console/reducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SET_CLEAR_CONSOLE_CALLBACK, 3 | SET_PRINT_TO_CONSOLE_CALLBACK 4 | } from "./types"; 5 | import { assoc } from "ramda"; 6 | 7 | type ClearConsole = () => void; 8 | type PrintToConsole = (text: string) => void; 9 | 10 | export interface IConsoleReducer { 11 | clearConsole: ClearConsole | undefined; 12 | printToConsole: PrintToConsole | undefined; 13 | } 14 | 15 | const initState: IConsoleReducer = { 16 | clearConsole: undefined, 17 | printToConsole: undefined 18 | }; 19 | 20 | const Console = ( 21 | state: IConsoleReducer | undefined, 22 | action: Record 23 | ): IConsoleReducer => { 24 | switch (action.type) { 25 | case SET_CLEAR_CONSOLE_CALLBACK: { 26 | return assoc("clearConsole", action.callback, state); 27 | } 28 | case SET_PRINT_TO_CONSOLE_CALLBACK: { 29 | return assoc("printToConsole", action.callback, state); 30 | } 31 | default: { 32 | return state || initState; 33 | } 34 | } 35 | }; 36 | 37 | export default Console; 38 | -------------------------------------------------------------------------------- /.github/workflows/production.yaml: -------------------------------------------------------------------------------- 1 | name: "Deploy Production" 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | deploy-prod: 8 | name: deploy-production 9 | runs-on: ubuntu-latest 10 | env: 11 | REACT_APP_DATABASE: PROD 12 | steps: 13 | - name: Checkout Repo 14 | uses: actions/checkout@master 15 | - name: Build 16 | run: | 17 | yarn install 18 | echo "STORAGE_BUCKET_URL=gs://csound-ide.appspot.com" >> functions/.env 19 | PUBLIC_URL="https://ide.csound.com" PRODUCTION=1 yarn build 20 | cp dist/index.html functions 21 | cd functions 22 | yarn install 23 | yarn build 24 | - name: Deploy to Firebase 25 | uses: w9jds/firebase-action@master 26 | with: 27 | args: deploy -P default 28 | env: 29 | GCP_SA_KEY: ${{ secrets.GCP_SA_KEY_PROD }} 30 | REACT_APP_DATABASE: PROD 31 | -------------------------------------------------------------------------------- /src/components/file-tree/types.ts: -------------------------------------------------------------------------------- 1 | import { UnknownAction } from "redux"; 2 | 3 | export const ADD_NON_CLOUD_FILE = "FILE_TREE.ADD_NON_CLOUD_FILE"; 4 | export const DELETE_NON_CLOUD_FILE = "FILE_TREE.DELETE_NON_CLOUD_FILE"; 5 | export const CLEANUP_NON_CLOUD_FILES = "FILE_TREE.CLEANUP_NON_CLOUD_FILES"; 6 | 7 | // AKA in-memory files (ex. from render) 8 | export interface NonCloudFile { 9 | createdAt: Date; 10 | name: string; 11 | buffer: Uint8Array; 12 | } 13 | 14 | export interface NonCloudFileTreeEntry { 15 | createdAt: number; 16 | name: string; 17 | } 18 | 19 | export interface AddNonCloudFileAction { 20 | type: typeof ADD_NON_CLOUD_FILE; 21 | file: NonCloudFileTreeEntry; 22 | } 23 | 24 | export interface CleanupNonCloudFileAction { 25 | type: typeof CLEANUP_NON_CLOUD_FILES; 26 | } 27 | 28 | export interface DeleteNonCloudFileAction { 29 | type: typeof DELETE_NON_CLOUD_FILE; 30 | filename: string; 31 | } 32 | 33 | export type FileTreeActionTypes = 34 | | UnknownAction 35 | | AddNonCloudFileAction 36 | | CleanupNonCloudFileAction 37 | | DeleteNonCloudFileAction; 38 | -------------------------------------------------------------------------------- /src/svgs/fad-duplicate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-digital4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-logo-tracktion.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-usb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/console/console.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useRef } from "react"; 2 | import { useConsole } from "./context"; 3 | import * as SS from "./styles"; 4 | 5 | const Console = (): React.ReactElement => { 6 | const logs = useConsole(); 7 | const consoleReference = useRef(null); 8 | 9 | const onMessage = useCallback(() => { 10 | if (consoleReference && consoleReference.current) { 11 | const { 12 | clientHeight = 0, 13 | scrollHeight = 0, 14 | scrollTop = 0 15 | } = consoleReference.current; 16 | if (scrollTop + clientHeight < scrollHeight) { 17 | consoleReference.current.scrollTop = scrollHeight; 18 | } 19 | } 20 | }, [consoleReference]); 21 | 22 | useEffect(() => { 23 | onMessage(); 24 | }, [logs, onMessage]); 25 | 26 | return ( 27 |
28 | {((logs || []) as string[]).join("")} 29 |
30 | ); 31 | }; 32 | 33 | export default Console; 34 | -------------------------------------------------------------------------------- /src/db/id-reducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SIGNIN_SUCCESS, 3 | LOG_OUT, 4 | UPDATE_USER_PROFILE 5 | } from "../components/login/types"; 6 | import { mergeAll } from "ramda"; 7 | import { IUserProfile } from "./types"; 8 | 9 | const IdReducer = ( 10 | state: IUserProfile = { 11 | name: "", 12 | email: "", 13 | photoUrl: "" 14 | }, 15 | action: { type: string; user?: any; profile?: string } 16 | ): IUserProfile => { 17 | switch (action.type) { 18 | case SIGNIN_SUCCESS: { 19 | return { 20 | name: action.user.displayName, 21 | email: action.user.email, 22 | photoUrl: action.user.photoURL 23 | }; 24 | } 25 | case UPDATE_USER_PROFILE: { 26 | return action.profile 27 | ? (mergeAll([state, action.profile]) as IUserProfile) 28 | : state; 29 | } 30 | case LOG_OUT: { 31 | return {} as IUserProfile; 32 | } 33 | default: { 34 | return state as IUserProfile; 35 | } 36 | } 37 | }; 38 | 39 | export default IdReducer; 40 | -------------------------------------------------------------------------------- /functions/src/random_projects.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | import { onCall } from "firebase-functions/v2/https"; 3 | import { log } from "firebase-functions/logger"; 4 | import { shuffle } from "lodash-es"; 5 | 6 | let lastUpdate = Date.now(); 7 | let projects: any[] = []; 8 | 9 | export const randomProjects = onCall<{ count: number }>(async ({ data }) => { 10 | if (projects.length === 0 || Date.now() - lastUpdate > 1000 * 60 * 5) { 11 | lastUpdate = Date.now(); 12 | const db = admin.firestore(); 13 | const projectsCollection = await db 14 | .collection("projects") 15 | .where("public", "==", true) 16 | .orderBy("created", "desc") 17 | .limit(100) 18 | .get(); 19 | const nextProjects = projectsCollection.docs.map((doc) => ({ 20 | ...doc.data(), 21 | projectUid: doc.id 22 | })); 23 | if (nextProjects.length > 0) { 24 | projects = nextProjects; 25 | } 26 | log("projectsLength: " + projects.length); 27 | } 28 | 29 | return shuffle(projects).slice(0, data.count); 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/social-controls/subscribers.tsx: -------------------------------------------------------------------------------- 1 | import { STORE_PROJECT_STARS } from "@comp/projects/types"; 2 | import { stars } from "@config/firestore"; 3 | import { Timestamp, doc, onSnapshot } from "firebase/firestore"; 4 | 5 | export const subscribeToProjectStars = ( 6 | projectUid: string, 7 | dispatch: (store: any) => void 8 | ): (() => void) => { 9 | const unsubscribe: () => void = onSnapshot( 10 | doc(stars, projectUid), 11 | (stars) => { 12 | const starsData = stars.data(); 13 | const starsDataSerializeable: Record = {}; 14 | for (const sdk in starsData) { 15 | const sdkd: Timestamp = starsData[sdk]; 16 | if (typeof sdkd === "object") { 17 | starsDataSerializeable[sdk] = sdkd.toMillis(); 18 | } 19 | } 20 | dispatch({ 21 | type: STORE_PROJECT_STARS, 22 | projectUid, 23 | stars: starsDataSerializeable 24 | }); 25 | }, 26 | (error: any) => console.error(error) 27 | ); 28 | return unsubscribe; 29 | }; 30 | -------------------------------------------------------------------------------- /src/styles/_perfect-scrollbar.ts: -------------------------------------------------------------------------------- 1 | import { css, Theme } from "@emotion/react"; 2 | 3 | export const perfectScrollbarStyleSheet = (theme: Theme) => css` 4 | & .ps__rail-x:hover, 5 | & .ps__rail-y:hover, 6 | & .ps__rail-x:focus, 7 | & .ps__rail-y:focus, 8 | & .ps__rail-x.ps--clicking, 9 | & .ps__rail-y.ps--clicking { 10 | background-color: ${theme.highlightBackground}; 11 | width: 9px; 12 | opacity: 1; 13 | z-index: 2; 14 | } 15 | 16 | & .ps:hover > .ps__rail-x, 17 | & .ps:hover > .ps__rail-y, 18 | & .ps--focus > .ps__rail-x, 19 | & .ps--focus > .ps__rail-y, 20 | & .ps--scrolling-x > .ps__rail-x, 21 | & .ps--scrolling-y > .ps__rail-y { 22 | opacity: 1; 23 | } 24 | .ps__rail-y { 25 | width: 9px; 26 | } 27 | .ps__thumb-y { 28 | background-color: ${theme.scrollbar}; 29 | width: 4px; 30 | opacity: 1; 31 | } 32 | .ps__rail-y:hover > .ps__thumb-y, 33 | .ps__rail-y:focus > .ps__thumb-y, 34 | .ps__rail-y.ps--clicking .ps__thumb-y { 35 | background-color: ${theme.scrollbarHover}; 36 | width: 8px; 37 | } 38 | `; 39 | -------------------------------------------------------------------------------- /src/svgs/fad-foldback.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/target-controls/types.ts: -------------------------------------------------------------------------------- 1 | import { ICsoundOptions } from "@comp/csound/types"; 2 | 3 | const PREFIX = "TARGET_CONTROL."; 4 | 5 | // ACTION TYPES 6 | export const SET_SELECTED_TARGET = PREFIX + "SET_SELECTED_TARGET"; 7 | export const UPDATE_ALL_TARGETS_LOCALLY = PREFIX + "UPDATE_ALL_TARGETS_LOCALLY"; 8 | export const UPDATE_TARGET_LOCALLY = PREFIX + "UPDATE_TARGET_LOCALLY"; 9 | export const UPDATE_DEFAULT_TARGET_LOCALLY = 10 | PREFIX + "UPDATE_DEFAULT_TARGET_LOCALLY"; 11 | 12 | export interface ITarget { 13 | csoundOptions: ICsoundOptions; 14 | useCsound7: boolean; 15 | targetName: string; 16 | targetType: string; 17 | targetDocumentUid?: string; 18 | playlistDocumentsUid?: string[]; 19 | } 20 | 21 | export type ITargetMap = { [targetName: string]: ITarget }; 22 | 23 | export interface ITargetFromInput { 24 | targetName: string; 25 | oldTargetName: string; 26 | targetType: string; 27 | isDefaultTarget: boolean; 28 | isNameValid: boolean; 29 | isTypeValid: boolean; 30 | isOtherwiseValid: boolean; 31 | useCsound7: boolean; 32 | csoundOptions?: ICsoundOptions; 33 | targetDocumentUid?: string; 34 | } 35 | -------------------------------------------------------------------------------- /src/svgs/fad-random-2dice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/svgs/fad-repeat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-solo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.github/workflows/develop.yaml: -------------------------------------------------------------------------------- 1 | name: "Deploy Develop" 2 | on: 3 | push: 4 | branches: 5 | - develop 6 | jobs: 7 | deploy-dev: 8 | name: deploy-develop 9 | runs-on: ubuntu-latest 10 | env: 11 | REACT_APP_DATABASE: DEV 12 | steps: 13 | - name: Checkout Repo 14 | uses: actions/checkout@master 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: 20 18 | cache: yarn 19 | - name: Build 20 | run: | 21 | yarn install 22 | echo "STORAGE_BUCKET_URL=gs://csound-ide-dev.appspot.com" >> functions/.env.dev 23 | PUBLIC_URL="https://csound-ide-dev.web.app" yarn build:dev 24 | cp dist/index.html functions 25 | cd functions 26 | yarn install 27 | yarn build 28 | - name: Deploy to Firebase 29 | uses: w9jds/firebase-action@master 30 | with: 31 | args: deploy -P develop 32 | env: 33 | GCP_SA_KEY: ${{ secrets.GCP_SA_KEY_DEV }} 34 | -------------------------------------------------------------------------------- /src/svgs/fad-shuffle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-logo-audacity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-ffwd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-rew.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/tabtab/icon-svg.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | // The svg path is from react-icons: https://github.com/gorangajic/react-icons/ 3 | const Svg = ({ d }) => ( 4 | 11 | 12 | 13 | 14 | 15 | ); 16 | 17 | const CloseIcon = () => ( 18 | 19 | ); 20 | 21 | const LeftIcon = () => ( 22 | 23 | ); 24 | 25 | const RightIcon = () => ( 26 | 27 | ); 28 | 29 | const BulletIcon = () => ( 30 | 31 | ); 32 | 33 | export { CloseIcon, LeftIcon, RightIcon, BulletIcon }; 34 | -------------------------------------------------------------------------------- /functions/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 1. `npm install -g firebase-tools` (provides firebase-cli) 3 | 2. Login via google OAuth by typing `firebase login` 4 | 3. confirm that you're logged in by typing `firebase list` 5 | 4. Activate a default project from your google-appengine projects by typing `firebase use --add`. 6 | 7 | ## Useage 8 | ### Scope 9 | 10 | ``` 11 | # All functions 12 | $ firebase deploy --only functions 13 | ``` 14 | 15 | ``` 16 | # Some functions 17 | $ firebase deploy --only functions:new_user_callback 18 | ``` 19 | 20 | When developing run `firebase serve --only functions` 21 | When deploying run `firebase deploy --only functions` 22 | Watch the logs from the command line with firebase functions:log 23 | 24 | 25 | ## Understanding the firebase-cli 26 | Firebase is pretty strict on the directory structure. All cloud functions must be uploaded from one .js file from the functions directory. 27 | 28 | ## Troubleshoot 29 | - Read the firebase-debug.log file when something crashes 30 | - Common tip is to try `npm install -g @google-cloud/functions-emulator` for strange errors 31 | 32 | ## Online resources 33 | - https://github.com/firebase/functions-samples 34 | - https://firebase.google.com/docs/functions/firestore-events 35 | -------------------------------------------------------------------------------- /src/components/hot-keys/default-bindings.ts: -------------------------------------------------------------------------------- 1 | import { isMac } from "@root/utils"; 2 | import { BindingsMap } from "./types"; 3 | 4 | const defaultBindings: BindingsMap = { 5 | // project 6 | add_file: isMac ? "command+alt+u" : "ctrl+alt+u", 7 | new_document: isMac ? "command+alt+n" : "ctrl+alt+n", 8 | open_target_config_dialog: isMac ? "command+alt+t" : "ctrl+alt+t", 9 | pause_playback: isMac ? "command+p" : "ctrl+p", 10 | run_project: isMac ? "command+r" : "ctrl+r", 11 | save_document: isMac ? "command+s" : "ctrl+s", 12 | save_all_documents: isMac ? "opt+command+s" : "ctrl+shift+s", 13 | save_and_close: isMac ? "opt+command+q" : "ctrl+shift+q", 14 | stop_playback: isMac ? "opt+command+p" : "ctrl+shift+p", 15 | // editor 16 | doc_at_point: isMac ? "ctrl+." : "alt+.", 17 | // find_simple: isMac ? "command+f" : "ctrl+f", 18 | undo: isMac ? "command+z" : "ctrl+z", 19 | redo: isMac ? "shift+command+z" : "shift+ctrl+z", 20 | eval: isMac ? "command+e" : "ctrl+e", 21 | eval_block: isMac ? "command+enter" : "ctrl+enter", 22 | toggle_comment: isMac 23 | ? ["command+;", "opt+command+;"] 24 | : ["ctrl+;", "ctrl+shift+;"] 25 | }; 26 | 27 | export default defaultBindings; 28 | -------------------------------------------------------------------------------- /src/svgs/fad-logo-lv2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import viteTsconfigPaths from "vite-tsconfig-paths"; 4 | import svgr from "vite-plugin-svgr"; 5 | import checker from "vite-plugin-checker"; 6 | import viteRawPlugin from "vite-raw-plugin"; 7 | 8 | export default defineConfig({ 9 | define: { 10 | "process.env.REACT_APP_DATABASE": JSON.stringify( 11 | process.env.REACT_APP_DATABASE 12 | ) 13 | }, 14 | // depending on your application, base can also be "/" 15 | base: "/", 16 | plugins: [ 17 | checker({ 18 | // e.g. use TypeScript check 19 | typescript: true 20 | }), 21 | react({ 22 | jsxImportSource: "@emotion/react", 23 | babel: { 24 | plugins: ["@emotion/babel-plugin"] 25 | } 26 | }), 27 | viteTsconfigPaths(), 28 | svgr() 29 | // viteRawPlugin({ 30 | // fileRegex: /\.csd|\.orc\.sco\.udo$/ 31 | // }) 32 | ], 33 | server: { 34 | // this ensures that the browser opens upon server start 35 | open: true, 36 | // this sets a default port to 3000 37 | port: 3000 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /src/svgs/fad-digital5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-bluetooth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svgs/fad-waveform.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-digital3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-softclip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/project-editor/types.ts: -------------------------------------------------------------------------------- 1 | const PREFIX = "PROJECT_EDITOR."; 2 | 3 | // ACTION TYPES 4 | export const MANUAL_LOOKUP_STRING = PREFIX + "MANUAL_LOOKUP_STRING"; 5 | export const SET_MANUAL_PANEL_OPEN = PREFIX + "SET_MANUAL_PANEL_OPEN"; 6 | export const SET_FILE_TREE_PANEL_OPEN = PREFIX + "SET_FILE_TREE_PANEL_OPEN"; 7 | export const TAB_DOCK_INIT = PREFIX + "TAB_DOCK_INIT"; 8 | export const TAB_DOCK_SWITCH_TAB = PREFIX + "TAB_DOCK_SWITCH_TAB"; 9 | export const TAB_DOCK_REARRANGE_TABS = PREFIX + "TAB_DOCK_REARRANGE_TABS"; 10 | export const TAB_DOCK_OPEN_NON_CLOUD_FILE = 11 | PREFIX + "TAB_DOCK_OPEN_NON_CLOUD_FILE"; 12 | export const TAB_DOCK_OPEN_TAB_BY_DOCUMENT_UID = 13 | PREFIX + "TAB_DOCK_OPEN_TAB_BY_DOCUMENT_UID"; 14 | export const TAB_DOCK_CLOSE = PREFIX + "TAB_DOCK_CLOSE"; 15 | export const TAB_CLOSE = PREFIX + "TAB_CLOSE"; 16 | export const TOGGLE_MANUAL_PANEL = PREFIX + "TOGGLE_MANUAL_PANEL"; 17 | 18 | // DATA TYPES 19 | export interface IOpenDocument { 20 | uid: string; 21 | isNonCloudDocument?: boolean; 22 | nonCloudFileAudioUrl?: string | undefined; 23 | nonCloudFileData?: string | undefined; 24 | editorInstance?: any; 25 | } 26 | 27 | export interface ITabDock { 28 | tabIndex: number; 29 | openDocuments: IOpenDocument[]; 30 | } 31 | -------------------------------------------------------------------------------- /src/svgs/fad-digital2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/router/router.tsx: -------------------------------------------------------------------------------- 1 | import Home from "../home/home"; 2 | import CsoundManual from "@comp/manual/manual"; 3 | import { Profile } from "../profile/profile"; 4 | import { Page404 } from "../page-404/page-404"; 5 | import { ProjectContext } from "../projects/project-context"; 6 | import { BrowserRouter, Route, Routes } from "react-router"; 7 | import { SiteDocuments } from "../site-documents/site-documents"; 8 | 9 | export const WebIdeRouter = () => { 10 | return ( 11 | 12 | 13 | } /> 14 | } /> 15 | }> 16 | } /> 17 | 18 | }> 19 | } /> 20 | 21 | 22 | } /> 23 | } /> 24 | } /> 25 | 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "esnext"], 5 | "jsxImportSource": "@emotion/react", 6 | "jsx": "react-jsx", 7 | "allowJs": true, 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": true, 12 | "strictNullChecks": true, 13 | "noImplicitAny": true, 14 | "noUnusedParameters": false, 15 | "forceConsistentCasingInFileNames": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "noEmit": true, 20 | "baseUrl": "src", 21 | "paths": { 22 | "@root/*": ["./*"], 23 | "@comp/*": ["./components/*"], 24 | "@elem/*": ["./elements/*"], 25 | "@config/*": ["./config/*"], 26 | "@db/*": ["./db/*"], 27 | "@styles": ["./styles/index.ts"], 28 | "@styles/*": ["./styles/*"] 29 | }, 30 | "noFallthroughCasesInSwitch": true 31 | }, 32 | "include": ["./src/**/*"], 33 | "exclude": [ 34 | "node_modules", 35 | "./functions", 36 | "src/**/*.test.ts", 37 | "src/**/*.test.tsx" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/svgs/fad-paste.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // jest.config.js 2 | const { jsWithBabel: tsjPreset } = require("ts-jest/presets"); 3 | 4 | module.exports = { 5 | automock: false, 6 | setupFiles: ["/scripts/setupJest.js"], 7 | transform: { 8 | ...tsjPreset.transform, 9 | "^.+\\.svg$": "babel-jest" 10 | }, 11 | roots: ["/src"], 12 | testMatch: [ 13 | "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", 14 | "/src/**/*.{spec,test}.{js,jsx,ts,tsx}" 15 | ], 16 | transformIgnorePatterns: [ 17 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$", 18 | "^.+\\.module\\.(css|sass|scss)$" 19 | ], 20 | moduleNameMapper: { 21 | "^react-native$": "react-native-web", 22 | "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy", 23 | "^@root(.*)$": "/src$1", 24 | "^@styles(.*)$": "/src/styles$1", 25 | "^@comp(.*)$": "/src/components$1", 26 | "^@elem(.*)$": "/src/elements$1", 27 | "^@config(.*)$": "/src/config$1", 28 | "^.*\\.svg$": "/__mocks__/svgMock.js", 29 | "\\.(jpg|jpeg|png|gif|orc|sco|csd|udo)$": 30 | "/__mocks__/fileMock.js", 31 | "\\.(css|less)$": "/__mocks__/styleMock.js" 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/svgs/fad-logo-live.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /functions/src/followers_counter.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | import { onDocumentWritten } from "firebase-functions/v2/firestore"; 3 | 4 | export const followersCounter = onDocumentWritten( 5 | "followers/{userUid}", 6 | async (event) => { 7 | const before = event.data.before; 8 | const after = event.data.after; 9 | const userUid = event.params.userUid; 10 | 11 | const followersCountRef = admin 12 | .firestore() 13 | .collection("followersCount") 14 | .doc(userUid); 15 | 16 | // Document created 17 | if (!before.exists && after.exists) { 18 | // New followers doc created = 1 new follower 19 | await followersCountRef.set({ followersCount: 1 }); 20 | } 21 | // Document updated 22 | else if (before.exists && after.exists) { 23 | const dataAfter = after.data(); 24 | await followersCountRef.set({ 25 | followersCount: Object.keys(dataAfter || {}).length 26 | }); 27 | } 28 | // Document deleted 29 | else if (!after.exists) { 30 | // No followers left, do nothing (or you could remove the doc) 31 | // await followersCountRef.delete(); 32 | } 33 | 34 | return; 35 | } 36 | ); 37 | -------------------------------------------------------------------------------- /src/components/modal/reducer.tsx: -------------------------------------------------------------------------------- 1 | import { assoc, pipe } from "ramda"; 2 | 3 | export interface IModalReducer { 4 | isOpen: boolean; 5 | properties: Record | undefined; 6 | title?: string; 7 | modalComponentName?: string; 8 | } 9 | 10 | const initialModalState: IModalReducer = { 11 | isOpen: false, 12 | properties: undefined 13 | }; 14 | 15 | const ModalReducer = ( 16 | state: IModalReducer | undefined, 17 | action: Record 18 | ): IModalReducer => { 19 | if (!state) { 20 | return initialModalState; 21 | } 22 | switch (action.type) { 23 | case "MODAL_CLOSE": { 24 | return { 25 | isOpen: false, 26 | properties: undefined 27 | }; 28 | } 29 | case "MODAL_SET_ON_CLOSE": { 30 | return assoc("onClose", action.onClose, state); 31 | } 32 | case "MODAL_OPEN_SIMPLE": { 33 | return pipe( 34 | assoc("isOpen", true), 35 | assoc("properties", action.properties), 36 | assoc("modalComponentName", action.modalComponentName) 37 | )(state); 38 | } 39 | default: { 40 | return state || initialModalState; 41 | } 42 | } 43 | }; 44 | 45 | export default ModalReducer; 46 | -------------------------------------------------------------------------------- /functions/src/following_counter.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | import { onDocumentWritten } from "firebase-functions/v2/firestore"; 3 | import { log } from "firebase-functions/logger"; 4 | 5 | export const followingCounter = onDocumentWritten( 6 | "following/{userUid}", 7 | async (event) => { 8 | const before = event.data.before; 9 | const after = event.data.after; 10 | const userUid = event.params.userUid; 11 | 12 | const followingCountRef = admin 13 | .firestore() 14 | .collection("followingCount") 15 | .doc(userUid); 16 | 17 | if (!before.exists && after.exists) { 18 | // First time following = 1 following 19 | await followingCountRef.set({ followingCount: 1 }); 20 | } else if (before.exists && after.exists) { 21 | // Following count changed 22 | const dataAfter = after.data(); 23 | await followingCountRef.set({ 24 | followingCount: Object.keys(dataAfter || {}).length 25 | }); 26 | } else if (!after.exists) { 27 | // No longer following anyone 28 | // You can leave it as is, or optionally remove the doc 29 | // await followingCountRef.delete(); 30 | } 31 | 32 | return; 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /functions/src/project_file_storage_delete.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | import { onDocumentDeleted } from "firebase-functions/v2/firestore"; 3 | import { log } from "firebase-functions/logger"; 4 | import "dotenv/config"; 5 | 6 | async function projectFileStorageDelete(binaryUrl: string): Promise { 7 | log( 8 | `project_file_storage_delete: Deleting associated binary file from storage: ${binaryUrl}` 9 | ); 10 | 11 | await admin 12 | .storage() 13 | .bucket(process.env.STORAGE_BUCKET_URL!) 14 | .file(binaryUrl) 15 | .delete(); 16 | } 17 | 18 | export const projectFileStorageDeleteCallback = onDocumentDeleted( 19 | "projects/{projectId}/files/{fileId}", 20 | async ({ params: { projectId, fileId }, data }) => { 21 | const uid = data.get("userUid"); 22 | const filetype = data.get("type"); 23 | const binaryUrl = `${uid}/${projectId}/${fileId}`; 24 | 25 | log(`project_file_storage_delete_callback: ${filetype} ${binaryUrl}`); 26 | 27 | if (uid && filetype === "bin") { 28 | log( 29 | `project_file_storage_delete_callback: Deleting binary file: ${binaryUrl}` 30 | ); 31 | await projectFileStorageDelete(binaryUrl); 32 | } 33 | 34 | return true; 35 | } 36 | ); 37 | -------------------------------------------------------------------------------- /src/components/audio-editor/audio-editor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { getDownloadURL } from "firebase/storage"; 3 | import { storageReference } from "../../config/firestore"; 4 | import { rootStyle } from "./styles"; 5 | 6 | export const AudioEditor = ({ audioFileUrl }: { audioFileUrl: string }) => { 7 | const [data, setData] = useState(""); 8 | 9 | useEffect(() => { 10 | if ( 11 | !data && 12 | typeof audioFileUrl === "string" && 13 | !audioFileUrl.startsWith("blob") 14 | ) { 15 | storageReference(audioFileUrl).then((storage) => { 16 | getDownloadURL(storage).then((fileUrl) => { 17 | setData(fileUrl); 18 | }); 19 | }); 20 | } else if ( 21 | typeof audioFileUrl === "string" && 22 | audioFileUrl.startsWith("blob") 23 | ) { 24 | setData(audioFileUrl); 25 | } 26 | }, [data, setData, audioFileUrl]); 27 | 28 | return data ? ( 29 |
30 | 33 |
34 | ) : ( 35 |
36 |

Looking up audio file URL...

37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /src/components/target-controls/config-dialog/styles.ts: -------------------------------------------------------------------------------- 1 | import { css, SerializedStyles, Theme } from "@emotion/react"; 2 | import { shadow } from "@styles/_common"; 3 | 4 | export const closeIcon = (theme: Theme): SerializedStyles => css` 5 | position: absolute; 6 | background-color: ${theme.highlightBackground}!important; 7 | right: 50px; 8 | width: 24px; 9 | height: 24px; 10 | min-height: 24px; 11 | & span, 12 | svg { 13 | width: 15px; 14 | } 15 | `; 16 | 17 | export const targetsDialog = css` 18 | min-width: 400px; 19 | `; 20 | 21 | export const targetsDialogMain = (theme: Theme): SerializedStyles => css` 22 | ${shadow} 23 | box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.8); 24 | margin-bottom: 12px; 25 | border: 2px solid ${theme.line}; 26 | border-radius: 6px; 27 | padding: 12px; 28 | `; 29 | 30 | export const targetsDialogFooter = css` 31 | padding-top: 12px; 32 | & button { 33 | padding: 0 18px !important; 34 | } 35 | & svg { 36 | margin-left: 6px; 37 | } 38 | `; 39 | 40 | export const targetLabel = (theme: Theme): SerializedStyles => css` 41 | color: ${theme.altTextColor}; 42 | position: absolute; 43 | font-size: 12px; 44 | font-weight: 400; 45 | line-height: 1; 46 | margin: 0; 47 | margin-top: -4px; 48 | `; 49 | -------------------------------------------------------------------------------- /src/svgs/fad-headphones.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/profile/delete-project-modal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "@root/store"; 3 | import { closeModal } from "@comp/modal/actions"; 4 | import { TextField, Button } from "@mui/material"; 5 | import { deleteUserProject } from "./actions"; 6 | 7 | export function DeleteProjectModal({ 8 | projectUid, 9 | projectName 10 | }: { 11 | projectUid: string; 12 | projectName: string; 13 | }) { 14 | const [name, setName] = useState(""); 15 | const dispatch = useDispatch(); 16 | return ( 17 |
18 |

Confirm Project Delete

19 | { 23 | setName(event.target.value); 24 | }} 25 | /> 26 | 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/snackbar/reducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OPEN_SNACKBAR, 3 | CLOSE_SNACKBAR, 4 | OpenSnackbar, 5 | SnackbarType, 6 | SnackbarActionTypes 7 | } from "./types"; 8 | 9 | export interface ISnackbarReducer { 10 | readonly text: string; 11 | readonly type: SnackbarType; 12 | readonly open: boolean; 13 | readonly timeout: number | typeof Number.POSITIVE_INFINITY; 14 | } 15 | 16 | const INITIAL_STATE: ISnackbarReducer = { 17 | text: "", 18 | type: SnackbarType.Info, 19 | open: false, 20 | timeout: 6000 21 | }; 22 | 23 | const SnackbarReducer = ( 24 | state: ISnackbarReducer | undefined, 25 | unknownAction: SnackbarActionTypes 26 | ): ISnackbarReducer => { 27 | if (!state) { 28 | return INITIAL_STATE; 29 | } 30 | 31 | switch (unknownAction.type) { 32 | case OPEN_SNACKBAR: { 33 | const action = unknownAction as OpenSnackbar; 34 | return { 35 | ...state, 36 | ...action.payload, 37 | open: true 38 | }; 39 | } 40 | case CLOSE_SNACKBAR: { 41 | return { 42 | ...state, 43 | open: false 44 | }; 45 | } 46 | default: { 47 | return state; 48 | } 49 | } 50 | }; 51 | 52 | export default SnackbarReducer; 53 | -------------------------------------------------------------------------------- /src/svgs/fad-preset-b.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "type": "module", 5 | "main": "dist/main.js", 6 | "scripts": { 7 | "lint": "eslint .", 8 | "serve": "firebase serve --only functions", 9 | "shell": "firebase functions:shell", 10 | "build": "npx tsc -p ./tsconfig.json && cp ../dist/index.html dist", 11 | "start": "npm run shell", 12 | "deploy": "firebase deploy --only functions", 13 | "deploy:dev": "firebase deploy -P develop --only functions", 14 | "logs": "firebase functions:log" 15 | }, 16 | "dependencies": { 17 | "@google-cloud/logging": "^11.2.1", 18 | "@types/ramda": "^0.31.0", 19 | "dotenv": "^16.4.5", 20 | "firebase": "^12.2.1", 21 | "firebase-admin": "^13.5.0", 22 | "firebase-functions": "^6.4.0", 23 | "firebase-tools": "^14.15.2", 24 | "fuse.js": "^7.1.0", 25 | "isbot": "^5.1.30", 26 | "lodash-es": "^4.17.21", 27 | "ramda": "^0.31.3", 28 | "typescript": "^5.9.2" 29 | }, 30 | "devDependencies": { 31 | "@types/lodash-es": "^4.17.12", 32 | "eslint": "^9.34.0", 33 | "eslint-plugin-promise": "^7.2.1", 34 | "firebase-functions-test": "^3.4.1", 35 | "tsx": "^4.20.5" 36 | }, 37 | "private": true 38 | } 39 | -------------------------------------------------------------------------------- /src/components/project-last-modified/actions.tsx: -------------------------------------------------------------------------------- 1 | import { doc, getDoc, setDoc } from "firebase/firestore"; 2 | import { getFirebaseTimestamp, projectLastModified } from "@config/firestore"; 3 | import { UPDATE_PROJECT_LAST_MODIFIED_LOCALLY } from "./types"; 4 | 5 | export const updateProjectLastModified = async ( 6 | projectUid: string 7 | ): Promise => { 8 | setDoc( 9 | doc(projectLastModified, projectUid), 10 | { timestamp: getFirebaseTimestamp() }, 11 | { merge: true } 12 | ); 13 | }; 14 | 15 | export const updateProjectLastModifiedLocally = ( 16 | projectUid: string, 17 | timestamp: number 18 | ): { type: string; projectUid: string; timestamp: number } => ({ 19 | type: UPDATE_PROJECT_LAST_MODIFIED_LOCALLY, 20 | projectUid, 21 | timestamp 22 | }); 23 | 24 | export const getProjectLastModifiedOnce = ( 25 | projectUid: string 26 | ): ((dispatch: any) => Promise) => { 27 | return async (dispatch) => { 28 | const timestampReference = await getDoc( 29 | doc(projectLastModified, projectUid) 30 | ); 31 | const timestampData = timestampReference.data() as any; 32 | const timestamp = timestampData && timestampData.timestamp.toMillis(); 33 | return await dispatch( 34 | updateProjectLastModifiedLocally(projectUid, timestamp) 35 | ); 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/file-tree/actions.ts: -------------------------------------------------------------------------------- 1 | import { store } from "@root/store"; 2 | import { 3 | ADD_NON_CLOUD_FILE, 4 | CLEANUP_NON_CLOUD_FILES, 5 | DELETE_NON_CLOUD_FILE, 6 | NonCloudFile, 7 | NonCloudFileTreeEntry, 8 | CleanupNonCloudFileAction 9 | } from "./types"; 10 | import { TAB_CLOSE } from "@comp/project-editor/types"; 11 | 12 | export const nonCloudFiles: Map = new Map(); 13 | 14 | export const addNonCloudFile = ( 15 | file: NonCloudFileTreeEntry 16 | ): { type: typeof ADD_NON_CLOUD_FILE; file: NonCloudFileTreeEntry } => { 17 | return { 18 | type: ADD_NON_CLOUD_FILE, 19 | file: { 20 | name: file.name, 21 | createdAt: Number(file.createdAt) 22 | } 23 | }; 24 | }; 25 | export const deleteNonCloudFiles = (filename: string) => { 26 | return { 27 | type: DELETE_NON_CLOUD_FILE, 28 | filename 29 | }; 30 | }; 31 | 32 | export const cleanupNonCloudFiles = ({ 33 | projectUid 34 | }: { 35 | projectUid: string; 36 | }): CleanupNonCloudFileAction => { 37 | for (const openNcf of nonCloudFiles.keys()) { 38 | store.dispatch({ 39 | type: TAB_CLOSE, 40 | projectUid, 41 | documentUid: openNcf 42 | }); 43 | } 44 | 45 | nonCloudFiles.clear(); 46 | 47 | return { 48 | type: CLEANUP_NON_CLOUD_FILES 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/login/subscribers.tsx: -------------------------------------------------------------------------------- 1 | import { doc, onSnapshot } from "firebase/firestore"; 2 | import { UPDATE_USER_PROFILE } from "./types"; 3 | import { profiles } from "@config/firestore"; 4 | import { AppThunkDispatch } from "@root/store"; 5 | 6 | export const subscribeToLoggedInUserProfile = ( 7 | userUid: string, 8 | dispatch: AppThunkDispatch 9 | ): (() => void) => { 10 | const unsubscribe: () => void = onSnapshot( 11 | doc(profiles, userUid), 12 | (profile) => { 13 | const profileData = profile.data(); 14 | if (!profileData) { 15 | console.error("No profile data found for user", { 16 | userUid, 17 | profile 18 | }); 19 | dispatch({ 20 | type: UPDATE_USER_PROFILE, 21 | profile: undefined, 22 | userUid 23 | }); 24 | return; 25 | } 26 | if (typeof profileData.userJoinDate === "object") { 27 | profileData.userJoinDate = profileData.userJoinDate.toMillis(); 28 | } 29 | dispatch({ 30 | type: UPDATE_USER_PROFILE, 31 | profile: profileData, 32 | userUid 33 | }); 34 | }, 35 | (error: any) => console.error(error) 36 | ); 37 | return unsubscribe; 38 | }; 39 | -------------------------------------------------------------------------------- /src/svgs/fad-slider-round-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/store/root-reducer.tsx: -------------------------------------------------------------------------------- 1 | import { Reducer } from "redux"; 2 | import BottomTabsReducer from "@comp/bottom-tabs/reducer"; 3 | import ConsoleReducer from "@comp/console/reducer"; 4 | import CsoundReducer from "@comp/csound/reducer"; 5 | import FileTreeReducer from "@comp/file-tree/reducer"; 6 | import HomeReducer from "@comp/home/reducer"; 7 | import HotKeysReducer from "@comp/hot-keys/reducer"; 8 | import IDReducer from "../db/id-reducer"; 9 | import LoginReducer from "@comp/login/reducer"; 10 | import ModalReducer from "@comp/modal/reducer"; 11 | import ProfileReducer from "@comp/profile/reducer"; 12 | import ProjectEditorReducer from "@comp/project-editor/reducer"; 13 | import ProjectLastModifiedReducer from "@comp/project-last-modified/reducer"; 14 | import ProjectsReducer from "@comp/projects/reducer"; 15 | import SnackbarReducer from "@comp/snackbar/reducer"; 16 | import TargetControlsReducer from "@comp/target-controls/reducer"; 17 | import ThemeReducer from "@comp/themes/reducer"; 18 | 19 | export const reducer = { 20 | ProjectsReducer, 21 | LoginReducer, 22 | ProjectEditorReducer, 23 | userProfile: IDReducer, 24 | csound: CsoundReducer, 25 | FileTreeReducer, 26 | ThemeReducer, 27 | ModalReducer, 28 | ConsoleReducer, 29 | ProfileReducer, 30 | SnackbarReducer, 31 | HotKeysReducer, 32 | TargetControlsReducer, 33 | ProjectLastModifiedReducer, 34 | BottomTabsReducer, 35 | HomeReducer 36 | }; 37 | -------------------------------------------------------------------------------- /src/svgs/fad-digital0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svgs/fad-copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/svgs/fad-save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | --------------------------------------------------------------------------------