├── packages └── core │ ├── .eslintignore │ ├── .gitignore │ ├── src │ ├── index.ts │ ├── types.ts │ ├── timing-parsers.test.ts │ ├── cue-generator.test.ts │ ├── cue-generator.ts │ ├── formatter.ts │ ├── formatter.test.ts │ ├── timing-parsers.ts │ ├── parser.ts │ └── parser.test.ts │ ├── .prettierrc.js │ ├── tsconfig.build.json │ ├── jest.config.js │ ├── tsconfig.json │ ├── .eslintrc.js │ └── package.json ├── apps └── svelte │ ├── .npmrc │ ├── static │ ├── favicon.ico │ └── images │ │ ├── help │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── nero_1.jpg │ │ ├── audacity_1.jpg │ │ ├── audacity_2.jpg │ │ ├── audacity_3.jpg │ │ ├── adobe_audition_1.png │ │ ├── adobe_audition_2.png │ │ ├── adobe_audition_3.png │ │ ├── adobe_audition_4.png │ │ └── adobe_audition_5.png │ │ ├── read-only.gif │ │ ├── CUEgenerator.png │ │ └── README │ │ └── global-performer.png │ ├── .env.development │ ├── .gitignore │ ├── .eslintignore │ ├── .prettierignore │ ├── tests │ ├── help.test.ts │ └── index.test.ts │ ├── vite.config.ts │ ├── src │ ├── app.d.ts │ ├── routes │ │ ├── +layout.ts │ │ ├── +page.svelte │ │ ├── header.svelte │ │ ├── +layout.svelte │ │ ├── cue-buttons.svelte │ │ ├── form.svelte │ │ └── help │ │ │ └── +page.svelte │ ├── utils.ts │ ├── app.html │ ├── config.ts │ ├── types.ts │ ├── utils.test.ts │ ├── stores.ts │ └── services.ts │ ├── .prettierrc │ ├── playwright.config.ts │ ├── .env.production │ ├── tsconfig.json │ ├── svelte.config.js │ ├── .eslintrc.cjs │ ├── README.md │ └── package.json ├── .gitignore ├── .simple-git-hooks.json ├── .firebaserc ├── firebase.json ├── turbo.json ├── package.json ├── .github └── workflows │ ├── firebase-hosting-merge.yml │ └── firebase-hosting-pull-request.yml └── README.md /packages/core/.eslintignore: -------------------------------------------------------------------------------- 1 | .turbo/ 2 | build/ 3 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | build 3 | node_modules -------------------------------------------------------------------------------- /apps/svelte/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | node_modules/* 3 | public/* 4 | .vscode/* 5 | .firebase/* -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cue-generator'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /.simple-git-hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "pre-commit": "npm run format", 3 | "pre-push": "npm run pre-deploy" 4 | } 5 | -------------------------------------------------------------------------------- /packages/core/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | tabWidth: 2, 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /apps/svelte/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/favicon.ico -------------------------------------------------------------------------------- /apps/svelte/.env.development: -------------------------------------------------------------------------------- 1 | VITE_APP_TITLE="DEV -- CUEgenerator" 2 | VITE_API_BASE_URL=https://us-central1-cuegenerator.cloudfunctions.net 3 | -------------------------------------------------------------------------------- /packages/core/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /apps/svelte/static/images/help/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/1.jpg -------------------------------------------------------------------------------- /apps/svelte/static/images/help/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/2.jpg -------------------------------------------------------------------------------- /apps/svelte/static/images/help/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/3.jpg -------------------------------------------------------------------------------- /apps/svelte/static/images/read-only.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/read-only.gif -------------------------------------------------------------------------------- /apps/svelte/static/images/CUEgenerator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/CUEgenerator.png -------------------------------------------------------------------------------- /apps/svelte/static/images/help/nero_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/nero_1.jpg -------------------------------------------------------------------------------- /apps/svelte/static/images/help/audacity_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/audacity_1.jpg -------------------------------------------------------------------------------- /apps/svelte/static/images/help/audacity_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/audacity_2.jpg -------------------------------------------------------------------------------- /apps/svelte/static/images/help/audacity_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/audacity_3.jpg -------------------------------------------------------------------------------- /apps/svelte/static/images/README/global-performer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/README/global-performer.png -------------------------------------------------------------------------------- /apps/svelte/static/images/help/adobe_audition_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/adobe_audition_1.png -------------------------------------------------------------------------------- /apps/svelte/static/images/help/adobe_audition_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/adobe_audition_2.png -------------------------------------------------------------------------------- /apps/svelte/static/images/help/adobe_audition_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/adobe_audition_3.png -------------------------------------------------------------------------------- /apps/svelte/static/images/help/adobe_audition_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/adobe_audition_4.png -------------------------------------------------------------------------------- /apps/svelte/static/images/help/adobe_audition_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmitryVarennikov/cuegenerator-svelte/main/apps/svelte/static/images/help/adobe_audition_5.png -------------------------------------------------------------------------------- /packages/core/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | roots: ['/src'], 6 | }; 7 | -------------------------------------------------------------------------------- /apps/svelte/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | vite.config.js.timestamp-* 7 | vite.config.ts.timestamp-* 8 | .turbo 9 | test-results 10 | *.local 11 | public/* -------------------------------------------------------------------------------- /packages/core/src/types.ts: -------------------------------------------------------------------------------- 1 | export type TrackList = Array<{ track: number; performer: string; title: string; time: string }>; 2 | export type TimeEntry = { mn: number; sc: number; fr: number }; 3 | export type Timings = Array; 4 | -------------------------------------------------------------------------------- /apps/svelte/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /apps/svelte/.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /public 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /apps/svelte/tests/help.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test('help page expects a table of contents', async ({ page }) => { 4 | await page.goto('/help'); 5 | await expect(page.getByRole('list')).toBeVisible(); 6 | }); 7 | -------------------------------------------------------------------------------- /apps/svelte/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | test: { 7 | include: ['src/**/*.{test,spec}.{js,ts}'] 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "cuegenerator" 4 | }, 5 | "targets": { 6 | "cuegenerator": { 7 | "hosting": { 8 | "cuegenerator-v3": [ 9 | "cuegenerator-v3" 10 | ] 11 | } 12 | } 13 | }, 14 | "etags": {} 15 | } 16 | -------------------------------------------------------------------------------- /apps/svelte/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /apps/svelte/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 120, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "arrowParens": "avoid", 9 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 10 | } 11 | -------------------------------------------------------------------------------- /apps/svelte/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | }, 8 | testDir: 'tests', 9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/ 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": [ 3 | { 4 | "site": "cuegenerator-v3", 5 | "public": "apps/svelte/public", 6 | "ignore": [ 7 | "firebase.json", 8 | "**/.*", 9 | "**/node_modules/**" 10 | ], 11 | "rewrites": [ 12 | { 13 | "source": "**", 14 | "destination": "/index.html" 15 | } 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp } from 'firebase/app'; 2 | import { firebaseConfig } from '../config'; 3 | 4 | export const ssr = false; 5 | // This can be false if you're using a fallback (i.e. SPA mode) 6 | export const prerender = true; 7 | 8 | console.log('App started', { MODE: import.meta.env.MODE }); 9 | initializeApp(firebaseConfig); 10 | console.log('Firebase initiated', { firebaseConfig }); 11 | -------------------------------------------------------------------------------- /apps/svelte/src/utils.ts: -------------------------------------------------------------------------------- 1 | export const replaceFileExt = (fileName: string, ext: string) => { 2 | const dotIndex = fileName.lastIndexOf('.'); 3 | if (dotIndex === -1) return fileName + ext; 4 | 5 | const baseName = fileName.substring(0, fileName.lastIndexOf('.')); 6 | return baseName + ext; 7 | }; 8 | 9 | export const makeCueFileName = (soundFileName: string) => { 10 | if (soundFileName) return replaceFileExt(soundFileName, '.cue'); 11 | return 'Untitled.cue'; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/core/src/timing-parsers.test.ts: -------------------------------------------------------------------------------- 1 | import { standard } from './timing-parsers'; 2 | 3 | describe('TimingParsers', () => { 4 | describe('standard', () => { 5 | it('DD:DD:DD', () => { 6 | const actual = standard(' 01:23:45 '); 7 | expect(actual).toEqual({ fr: 45, mn: 1, sc: 23 }); 8 | }); 9 | it('D:DD:DD', () => { 10 | const actual = standard(' 0:05:47 '); 11 | expect(actual).toEqual({ fr: 47, mn: 0, sc: 5 }); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /apps/svelte/.env.production: -------------------------------------------------------------------------------- 1 | VITE_APP_TITLE=CUEgenerator 2 | VITE_API_BASE_URL=https://us-central1-cuegenerator.cloudfunctions.net 3 | 4 | VITE_APP_API_KEY=AIzaSyCChgj0Ybm5wtCaftXHAYgpXbTyXL5NLws 5 | VITE_APP_AUTH_DOMAIN="cuegenerator.firebaseapp.com" 6 | VITE_APP_PROJECT_ID=cuegenerator 7 | VITE_APP_STORAGE_BUCKET="cuegenerator.appspot.com" 8 | VITE_APP_MESSAGING_SENDER_ID=747548828434 9 | VITE_APP_APP_ID="1:747548828434:web:19096ad9bd290cb48f09cb" 10 | VITE_APP_MEASUREMENT_ID="G-HFZF5M8PG4" -------------------------------------------------------------------------------- /apps/svelte/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | {#if cueState} 12 |
13 | {:else} 14 | 15 | {/if} 16 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "src", 4 | "outDir": "build", 5 | "module": "es6", 6 | "lib": ["es5", "dom"], 7 | "esModuleInterop": true, 8 | "sourceMap": true, 9 | "noUnusedLocals": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "moduleResolution": "node", 13 | "declaration": true 14 | }, 15 | "include": ["src/**/*.ts"], 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": [".svelte-kit/**", ".vercel/**", "public/**"] 7 | }, 8 | "lint": {}, 9 | "format": {}, 10 | "test": { 11 | "dependsOn": ["build"] 12 | }, 13 | "pre-deploy": { 14 | "dependsOn": ["lint", "test"] 15 | }, 16 | "dev": { 17 | "cache": false, 18 | "persistent": true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/svelte/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | %sveltekit.head% 12 | 13 | 14 |
%sveltekit.body%
15 | 16 | 17 | -------------------------------------------------------------------------------- /apps/svelte/src/config.ts: -------------------------------------------------------------------------------- 1 | const { 2 | VITE_APP_API_KEY, 3 | VITE_APP_AUTH_DOMAIN, 4 | VITE_APP_PROJECT_ID, 5 | VITE_APP_STORAGE_BUCKET, 6 | VITE_APP_MESSAGING_SENDER_ID, 7 | VITE_APP_APP_ID, 8 | VITE_APP_MEASUREMENT_ID 9 | } = import.meta.env; 10 | 11 | export const firebaseConfig = { 12 | apiKey: VITE_APP_API_KEY, 13 | authDomain: VITE_APP_AUTH_DOMAIN, 14 | projectId: VITE_APP_PROJECT_ID, 15 | storageBucket: VITE_APP_STORAGE_BUCKET, 16 | messagingSenderId: VITE_APP_MESSAGING_SENDER_ID, 17 | appId: VITE_APP_APP_ID, 18 | measurementId: VITE_APP_MEASUREMENT_ID 19 | }; 20 | -------------------------------------------------------------------------------- /apps/svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /apps/svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/kit/vite'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | adapter: adapter({ 12 | pages: 'public', 13 | assets: 'public', 14 | fallback: null, 15 | precompress: false, 16 | prerender: { 17 | default: true 18 | } 19 | }) 20 | } 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /apps/svelte/src/types.ts: -------------------------------------------------------------------------------- 1 | export type CueInputState = { 2 | performer: string; 3 | title: string; 4 | fileName: string; 5 | fileType: string; 6 | trackList: string; 7 | regionsList: string; 8 | }; 9 | export type CueOutputState = { cue: string }; 10 | export type CueState = { input: CueInputState; output: CueOutputState }; 11 | 12 | export type ApiResponse = { 13 | getToken: { code: 200; body: { token: string } }; 14 | getCounter: { code: 200; body: { counter: number } }; 15 | postCounter: { code: 200; body: { counter: number } }; 16 | }; 17 | export type ApiRequest = { 18 | postCounter: { 19 | payload: { performer: string; title: string; fileName: string; cue: string }; 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /apps/svelte/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:svelte/recommended', 'prettier'], 4 | parser: '@typescript-eslint/parser', 5 | plugins: ['@typescript-eslint'], 6 | parserOptions: { 7 | sourceType: 'module', 8 | ecmaVersion: 2020, 9 | extraFileExtensions: ['.svelte'] 10 | }, 11 | env: { 12 | browser: true, 13 | es2017: true, 14 | node: true 15 | }, 16 | overrides: [ 17 | { 18 | files: ['*.svelte'], 19 | parser: 'svelte-eslint-parser', 20 | parserOptions: { 21 | parser: '@typescript-eslint/parser' 22 | }, 23 | rules: { 24 | '@typescript-eslint/no-empty-function': 'off' 25 | } 26 | } 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /packages/core/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:import/errors', 10 | 'plugin:import/warnings', 11 | 'plugin:import/typescript', 12 | 'plugin:@typescript-eslint/eslint-recommended', 13 | ], 14 | parser: '@typescript-eslint/parser', 15 | parserOptions: { 16 | project: ['tsconfig.json'], 17 | sourceType: 'module', 18 | }, 19 | ignorePatterns: ['/build/**/*', '.eslintrc.js', 'jest.config.js'], 20 | plugins: ['@typescript-eslint', 'import'], 21 | rules: { 22 | quotes: ['warn', 'single'], 23 | 'no-unused-vars': 1, 24 | }, 25 | overrides: [ 26 | { 27 | files: ['**/*.test.ts'], 28 | env: { jest: true }, 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/header.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 |
15 | Cuegenerator logo 16 |
{$apiCueCounterStore}
17 | 22 |
23 | 24 | 30 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cuegenerator-v2/core", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Core functionality for the Cuegenerator app", 6 | "author": "Dmitry Varennikov", 7 | "license": "ISC", 8 | "main": "build/index.js", 9 | "scripts": { 10 | "test": "jest", 11 | "lint": "eslint --ext .js,.ts .", 12 | "lint:fix": "eslint --fix --ext .js,.ts .", 13 | "build": "tsc --project tsconfig.build.json", 14 | "format": "prettier --write .", 15 | "dev": "tsc -w" 16 | }, 17 | "devDependencies": { 18 | "@jest/globals": "^29.5.0", 19 | "@types/jest": "^29.5.1", 20 | "@types/node": "^14.11.2", 21 | "@typescript-eslint/eslint-plugin": "^5.59.7", 22 | "@typescript-eslint/parser": "^5.59.7", 23 | "eslint": "^8.41.0", 24 | "eslint-plugin-import": "^2.27.5", 25 | "jest": "^29.5.0", 26 | "ts-jest": "^29.1.0", 27 | "typescript": "^5.0.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cuegenerator-v2", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Cuegenerator monorepo", 6 | "workspaces": [ 7 | "apps/*", 8 | "packages/*" 9 | ], 10 | "scripts": { 11 | "test": "turbo run test", 12 | "build": "turbo run build", 13 | "lint": "turbo run lint", 14 | "format": "turbo run format", 15 | "pre-deploy": "turbo run pre-deploy", 16 | "deploy": "npm run pre-deploy && npm run firebase deploy -- --only hosting:cuegenerator-v3", 17 | "preview": "npm run firebase hosting:channel:deploy tmp", 18 | "dev": "turbo run dev", 19 | "simple-hooks:install": "npx simple-git-hooks", 20 | "postinstall": "npm run simple-hooks:install && npx playwright install --with-deps", 21 | "firebase": "firebase" 22 | }, 23 | "author": "Dmitry Varennikov", 24 | "license": "ISC", 25 | "devDependencies": { 26 | "firebase-tools": "^12.3.0", 27 | "simple-git-hooks": "^2.8.1", 28 | "turbo": "^1.10.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/firebase-hosting-merge.yml: -------------------------------------------------------------------------------- 1 | # This file was auto-generated by the Firebase CLI 2 | # https://github.com/firebase/firebase-tools 3 | 4 | name: Deploy to Firebase Hosting on merge to the main branch 5 | 'on': 6 | push: 7 | branches: 8 | - main 9 | jobs: 10 | build_and_deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 20 17 | cache: "npm" 18 | cache-dependency-path: "**/package-lock.json" 19 | - run: npm ci 20 | - run: npm run pre-deploy 21 | - uses: FirebaseExtended/action-hosting-deploy@v0 22 | with: 23 | repoToken: '${{ secrets.GITHUB_TOKEN }}' 24 | firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_CUEGENERATOR }}' 25 | channelId: live 26 | projectId: cuegenerator 27 | target: cuegenerator-v3 28 | env: 29 | FIREBASE_CLI_EXPERIMENTS: webframeworks -------------------------------------------------------------------------------- /apps/svelte/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | {import.meta.env.VITE_APP_TITLE} 7 | 8 | 9 |
10 |
11 | 12 |
13 | 14 | 41 | -------------------------------------------------------------------------------- /.github/workflows/firebase-hosting-pull-request.yml: -------------------------------------------------------------------------------- 1 | # This file was auto-generated by the Firebase CLI 2 | # https://github.com/firebase/firebase-tools 3 | 4 | name: Deploy to Firebase Preview Hosting on PR 5 | "on": pull_request 6 | jobs: 7 | build_and_preview: 8 | if: "${{ github.event.pull_request.head.repo.full_name == github.repository }}" 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: 20 15 | cache: "npm" 16 | cache-dependency-path: "**/package-lock.json" 17 | - run: npm ci 18 | - run: npm run pre-deploy 19 | - uses: FirebaseExtended/action-hosting-deploy@v0 20 | with: 21 | repoToken: "${{ secrets.GITHUB_TOKEN }}" 22 | firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_CUEGENERATOR }}" 23 | projectId: cuegenerator 24 | target: cuegenerator-v3 25 | env: 26 | FIREBASE_CLI_EXPERIMENTS: webframeworks -------------------------------------------------------------------------------- /apps/svelte/src/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { replaceFileExt, makeCueFileName } from './utils'; 3 | 4 | describe('utils', () => { 5 | describe('replaceFileExt', () => { 6 | it('replace existing extension', () => { 7 | const actual = replaceFileExt('my sound file.mp3', '.cue'); 8 | const expected = 'my sound file.cue'; 9 | expect(actual).toBe(expected); 10 | }); 11 | it('no extension', () => { 12 | const actual = replaceFileExt('my sound file', '.cue'); 13 | const expected = 'my sound file.cue'; 14 | expect(actual).toBe(expected); 15 | }); 16 | }); 17 | 18 | describe('makeCueFileName', () => { 19 | it('non-empty sound file name', () => { 20 | const actual = makeCueFileName('sound file name'); 21 | const expected = 'sound file name.cue'; 22 | expect(actual).toBe(expected); 23 | }); 24 | it('empty sound file name', () => { 25 | const actual = makeCueFileName(''); 26 | const expected = 'Untitled.cue'; 27 | expect(actual).toBe(expected); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /apps/svelte/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /packages/core/src/cue-generator.test.ts: -------------------------------------------------------------------------------- 1 | import { cueGeneratorFactory } from './cue-generator'; 2 | import { Formatter } from './formatter'; 3 | import { Parser } from './parser'; 4 | 5 | describe('cueFactory', () => { 6 | it('factory instantiates a cue generator with the default parser and formatter', () => { 7 | const gnerator = cueGeneratorFactory(); 8 | expect(gnerator.parser).toBeInstanceOf(Parser); 9 | expect(gnerator.formatter).toBeInstanceOf(Formatter); 10 | }); 11 | it('cue generator should create a cue', () => { 12 | const generator = cueGeneratorFactory(); 13 | const actual = generator.create({ 14 | performer: 'Marilyn Manson', 15 | title: 'We Are Chaos', 16 | fileName: 'we-are-chaos.mp3', 17 | fileType: 'mp3', 18 | trackList: '01 Red Black and Blue', 19 | regionsList: '', 20 | }); 21 | 22 | const expected = `PERFORMER "Marilyn Manson" 23 | TITLE "We Are Chaos" 24 | FILE "we-are-chaos.mp3" mp3 25 | TRACK 01 AUDIO 26 | PERFORMER "Marilyn Manson" 27 | TITLE "Red Black and Blue" 28 | INDEX 01 00:00:00 29 | `; 30 | expect(actual).toEqual(expected); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /apps/svelte/src/stores.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import type { CueState } from './types'; 3 | 4 | const browser = typeof window !== 'undefined'; 5 | export const cueStore = writable(undefined); 6 | // NOTE: exported only for the test purpose, use `cueHistoryStore` instead 7 | export const cueHistoryKey = 'cueHistory'; 8 | export function createCueHistoryStore(stackLimit = 10) { 9 | let initCueState = []; 10 | if (browser && window.localStorage.getItem(cueHistoryKey)) 11 | initCueState = JSON.parse(localStorage.getItem(cueHistoryKey)!); 12 | 13 | const { subscribe, update } = writable>(initCueState); 14 | return { 15 | subscribe: (cb: (cueState: CueState | undefined) => void) => 16 | subscribe((stack: Array) => { 17 | cb(stack[0]); 18 | // store updated value in the local storage 19 | if (browser) window.localStorage.setItem(cueHistoryKey, JSON.stringify(stack)); 20 | }), 21 | set: (cue: CueState) => update(stack => [cue, ...(stack.length > stackLimit ? stack.slice(0, stackLimit) : stack)]) 22 | }; 23 | } 24 | export const cueHistoryStore = createCueHistoryStore(); 25 | 26 | export const apiCueCounterStore = writable(0); 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CUEgenerator 2 | 3 | A small utility which facilitates creating cue files from track lists. 4 | 5 | # How to report bugs 6 | 7 | Please create [a new issue](https://github.com/dVaffection/cuegenerator-svelte/issues) in this repository and leave as many details as possible. 8 | 9 | # Documentation 10 | 11 | ## Track list highlights 12 | 13 | - In case if a local performer is absent the global one is used, e.g. 14 | ![](https://user-images.githubusercontent.com/457097/111331254-b81dff00-863e-11eb-9b80-cc7504f4d6fd.png) 15 | 16 | - Performer and title track separators: 17 | - `' - '` — 45 (hyphen-minus) 18 | - `' – '` — 8211 (en dash) 19 | - `' ‒ '` — 8210 (figure dash) 20 | - `' — '` — 8212 (em dash) 21 | - `' ― '` — 8213 (horizontal bar) 22 | - Timings recognition: 23 | - `[08:45] 03. 8 Ball` → `08:45` 24 | - `01.[18:02] Giuseppe` → `18:02` 25 | - `10:57 02. Space Manoeuvres` → `10:57` 26 | - `56:53 T.O.M'` → `56:53` 27 | - `1:02:28 Mossy` → `62:28` 28 | 29 | ## Regions list recognition 30 | 31 | - Sony Sound Forge format `dd:dd:dd[.,]dd` 32 | - Adobe Audition format `dd:dd:dd:dd` 33 | - Audacity format `ddddd.dddddd` 34 | 35 | # For developers 36 | 37 | This is a [BFF](https://github.com/DmitryVarennikov/cuegenerator-server). 38 | 39 | ## How to release 40 | 41 | A new release happens when a new commit is merged to the `main` branch. 42 | -------------------------------------------------------------------------------- /packages/core/src/cue-generator.ts: -------------------------------------------------------------------------------- 1 | import { Formatter } from './formatter'; 2 | import { Parser, ParserHelper } from './parser'; 3 | 4 | const defaultParser = new Parser(new ParserHelper()); 5 | const defaultFormatter = new Formatter(); 6 | 7 | type CueProps = { 8 | performer: string; 9 | title: string; 10 | fileName: string; 11 | fileType: string; 12 | trackList: string; 13 | regionsList: string; 14 | }; 15 | 16 | export class CueGenerator { 17 | constructor(readonly parser: Parser, readonly formatter: Formatter) {} 18 | 19 | create({ performer, title, fileName, fileType, trackList, regionsList }: CueProps) { 20 | if (!performer && !title && !fileName && !trackList) return ''; 21 | 22 | const formattedPerformer = this.formatter.formatPerformer(this.parser.parsePerformer(performer)); 23 | const formattedTitle = this.formatter.formatTitle(this.parser.parseTitle(title)); 24 | const formattedFileName = this.formatter.formatFilename(this.parser.parseFileName(fileName), fileType); 25 | const formattedTracklist = this.formatter.formatTracklist( 26 | this.parser.parseTrackList(trackList), 27 | this.parser.parseTimings(regionsList), 28 | performer 29 | ); 30 | 31 | return formattedPerformer + formattedTitle + formattedFileName + formattedTracklist; 32 | } 33 | } 34 | 35 | export const cueGeneratorFactory = () => new CueGenerator(defaultParser, defaultFormatter); 36 | -------------------------------------------------------------------------------- /packages/core/src/formatter.ts: -------------------------------------------------------------------------------- 1 | import { Timings, TimeEntry, TrackList } from './types'; 2 | 3 | export class Formatter { 4 | formatPerformer(value: string) { 5 | return 'PERFORMER "' + value + '"\n'; 6 | } 7 | 8 | formatTitle(value: string) { 9 | return 'TITLE "' + value + '"\n'; 10 | } 11 | 12 | formatFilename(name: string, type: string) { 13 | return `FILE "${name}" ${type}\n`; 14 | } 15 | 16 | formatTracklist(tracklist: TrackList, regionsList: Timings, globalPerformer: string) { 17 | let output = ''; 18 | for (let i = 0; i < tracklist.length; i++) { 19 | const row = tracklist[i]; 20 | const performer = row.performer || globalPerformer; 21 | const time = regionsList[i] ? timeEntryToString(regionsList[i]) : row.time; 22 | 23 | output += ' TRACK ' + (row.track < 10 ? '0' + row.track : row.track) + ' AUDIO\n'; 24 | output += ' PERFORMER "' + performer + '"\n'; 25 | output += ' TITLE "' + row.title + '"\n'; 26 | // when first track does not start at 00:00:00 27 | // "INDEX 00 00:00:00" line has to be the first index 28 | if (i === 0 && time !== '00:00:00') { 29 | output += ' INDEX 00 00:00:00\n'; 30 | } 31 | output += ' INDEX 01 ' + time + '\n'; 32 | } 33 | 34 | return output; 35 | } 36 | } 37 | 38 | export const timeEntryToString = (timeEntry: TimeEntry): string => { 39 | const mn = timeEntry.mn.toString(10).padStart(2, '0'); 40 | const sc = timeEntry.sc.toString(10).padStart(2, '0'); 41 | const fr = timeEntry.fr.toString(10).padStart(2, '0'); 42 | return `${mn}:${sc}:${fr}`; 43 | }; 44 | -------------------------------------------------------------------------------- /apps/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cuegenerator-v2/svelte", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "Cuegenerator is a small utility which facilitates creating cue files from track lists", 6 | "author": "Dmitry Varennikov", 7 | "license": "ISC", 8 | "type": "module", 9 | "scripts": { 10 | "dev": "vite dev --open", 11 | "build": "vite build", 12 | "preview": "vite preview", 13 | "test": "vitest run && playwright test", 14 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 15 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 16 | "test:unit": "vitest", 17 | "test:e2e": "playwright test", 18 | "lint": "prettier --plugin-search-dir . --check . && eslint src/**/*", 19 | "format": "prettier --plugin-search-dir . --write ." 20 | }, 21 | "devDependencies": { 22 | "@playwright/test": "^1.28.1", 23 | "@sveltejs/adapter-auto": "^2.0.0", 24 | "@sveltejs/adapter-node": "^1.2.4", 25 | "@sveltejs/kit": "^1.5.0", 26 | "@typescript-eslint/eslint-plugin": "^5.45.0", 27 | "@typescript-eslint/parser": "^5.45.0", 28 | "eslint": "^8.28.0", 29 | "eslint-config-prettier": "^8.5.0", 30 | "eslint-plugin-svelte": "^2.26.0", 31 | "prettier": "^2.8.0", 32 | "prettier-plugin-svelte": "^2.8.1", 33 | "svelte": "^3.54.0", 34 | "svelte-check": "^3.0.1", 35 | "tslib": "^2.4.1", 36 | "typescript": "^5.0.0", 37 | "vite": "^4.3.0", 38 | "vitest": "^0.25.3" 39 | }, 40 | "dependencies": { 41 | "@cuegenerator-v2/core": "^1.0.0", 42 | "@sveltejs/adapter-static": "^2.0.2", 43 | "dotenv": "^16.1.3", 44 | "firebase": "^10.4.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/core/src/formatter.test.ts: -------------------------------------------------------------------------------- 1 | import { Timings, TrackList } from './types'; 2 | import { Formatter } from './formatter'; 3 | 4 | const formatter = new Formatter(); 5 | describe('Formatter', () => { 6 | // let formatter: Formatter | undefined; 7 | // beforeAll(() => { 8 | 9 | // }); 10 | test('formatPerformer', () => { 11 | const actual = formatter.formatPerformer('Bobina'); 12 | const expected = 'PERFORMER "Bobina"\n'; 13 | expect(actual).toBe(expected); 14 | }); 15 | test('formatTitle', () => { 16 | const actual = formatter.formatTitle('Russia Goes Clubbing'); 17 | const expected = 'TITLE "Russia Goes Clubbing"\n'; 18 | expect(actual).toBe(expected); 19 | }); 20 | test('formatFilename', () => { 21 | const actual = formatter.formatFilename('Bobina - Russia Goes Clubbing #272.cue', 'WAVE'); 22 | const expected = 'FILE "Bobina - Russia Goes Clubbing #272.cue" WAVE\n'; 23 | expect(actual).toBe(expected); 24 | }); 25 | test('formatTracklist_1', () => { 26 | const tracklist: TrackList = [{ track: 1, performer: '', title: 'Miami Echoes', time: '02:41:00' }]; 27 | const regionsList: Timings = []; 28 | const globalPerformer = 'Bobina'; 29 | 30 | const actual = formatter.formatTracklist(tracklist, regionsList, globalPerformer); 31 | let expected = ''; 32 | expected += ' TRACK 01 AUDIO\n'; 33 | expected += ' PERFORMER "Bobina"\n'; 34 | expected += ' TITLE "Miami Echoes"\n'; 35 | expected += ' INDEX 00 00:00:00\n'; 36 | expected += ' INDEX 01 02:41:00\n'; 37 | expect(actual).toBe(expected); 38 | }); 39 | test('formatTracklist_2', () => { 40 | const tracklist: TrackList = [{ track: 1, performer: '', title: 'Miami Echoes', time: '' }]; 41 | const regionsList: Timings = [{ mn: 2, sc: 41, fr: 0 }]; //['02:41:00']; 42 | const globalPerformer = 'Bobina'; 43 | 44 | const actual = formatter.formatTracklist(tracklist, regionsList, globalPerformer); 45 | let expected = ''; 46 | expected += ' TRACK 01 AUDIO\n'; 47 | expected += ' PERFORMER "Bobina"\n'; 48 | expected += ' TITLE "Miami Echoes"\n'; 49 | expected += ' INDEX 00 00:00:00\n'; 50 | expected += ' INDEX 01 02:41:00\n'; 51 | expect(actual).toBe(expected); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /apps/svelte/tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | // import { cueHistoryKey } from '../src/stores'; 3 | 4 | test('index page expects a counter', async ({ page }) => { 5 | await page.goto('/'); 6 | await expect(page.getByTitle('cues-counter')).toBeVisible(); 7 | }); 8 | test('save cue to file button is disabled', async ({ page }) => { 9 | await page.goto('/'); 10 | await expect(page.locator('input[type=button]', { hasText: 'Save Cue to file' })).toBeDisabled(); 11 | }); 12 | test('save cue to file button is enabled and disabled again', async ({ page }) => { 13 | await page.goto('/'); 14 | const saveCueToFileBtn = page.locator('input[type=button]', { hasText: 'Save Cue to file' }); 15 | 16 | await page.getByLabel('Performer:').type('performer'); 17 | await expect(saveCueToFileBtn).toBeEnabled(); 18 | 19 | // ??? 20 | // await page.getByLabel('Performer:').type(' '); 21 | // await expect(saveCueToFileBtn).toBeDisabled(); 22 | }); 23 | test('Load my prev Cue button is disabled when there is no previous cue in the local storage', async ({ page }) => { 24 | await page.goto('/'); 25 | const btnLocator = page.locator('input[type=button]', { hasText: 'Load my prev Cue' }); 26 | await expect(btnLocator).toBeDisabled(); 27 | 28 | // ??? 29 | // const localStorageCueState = [ 30 | // { 31 | // input: { 32 | // performer: 'Kreator', 33 | // title: 'Outcast', 34 | // fileName: 'outcast.aac', 35 | // fileType: 'AAC', 36 | // trackList: 'Forever\nPhobia\nWhatever It May Take', 37 | // regionsList: '00:00:00\n03:23:00\n05:47:00' 38 | // }, 39 | // output: { 40 | // cue: 'PERFORMER "Kreator"\nTITLE "Outcast"\nFILE "outcast.aac" AAC\n TRACK 01 AUDIO\n PERFORMER "Kreator"\n TITLE "Forever"\n INDEX 01 00:00:00\n TRACK 02 AUDIO\n PERFORMER "Kreator"\n TITLE "Phobia"\n INDEX 01 03:23:00\n TRACK 03 AUDIO\n PERFORMER "Kreator"\n TITLE "Whatever It May Take"\n INDEX 01 05:47:00\n' 41 | // } 42 | // } 43 | // ]; 44 | 45 | // await page.addInitScript((value) => { 46 | // window.localStorage.setItem(cueHistoryKey, JSON.stringify(value)); 47 | // }, localStorageCueState); 48 | // await page.goto('/'); 49 | // await expect(btnLocator).toBeEnabled(); 50 | }); 51 | -------------------------------------------------------------------------------- /apps/svelte/src/services.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp, type FirebaseOptions } from 'firebase/app'; 2 | import { initializeAnalytics, logEvent } from 'firebase/analytics'; 3 | import type { ApiRequest, ApiResponse } from './types'; 4 | import { firebaseConfig } from './config'; 5 | 6 | class ApiService { 7 | private baseUrl = import.meta.env.VITE_API_BASE_URL; 8 | private token?: string; 9 | 10 | private async fetchToken(): Promise { 11 | if (!this.token) { 12 | const res = await fetch(this.baseUrl + '/api'); 13 | this.token = ((await res.json()) as ApiResponse['getToken']['body']).token; 14 | } 15 | return this.token; 16 | } 17 | async getCounter(): Promise { 18 | try { 19 | const token = await this.fetchToken(); 20 | const res = await fetch(this.baseUrl + '/api/counter', { headers: { Authorization: `Bearer ${token}` } }); 21 | return ((await res.json()) as ApiResponse['getCounter']['body']).counter; 22 | } catch (e) { 23 | console.error(e); 24 | return 0; 25 | } 26 | } 27 | async postCounter(body: ApiRequest['postCounter']['payload']): Promise { 28 | try { 29 | const token = await this.fetchToken(); 30 | const res = await fetch(this.baseUrl + '/api/counter', { 31 | headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, 32 | method: 'POST', 33 | body: JSON.stringify(body) 34 | }); 35 | return ((await res.json()) as ApiResponse['postCounter']['body']).counter; 36 | } catch (e) { 37 | console.error(e); 38 | return undefined; 39 | } 40 | } 41 | } 42 | 43 | export enum AnalyticsEvent { 44 | CUE_FILE_SAVED = 'cue_file_saved', 45 | PREV_CUE_LOADED = 'prev_cue_loaded' 46 | } 47 | 48 | class AnalyticService { 49 | private analytics; 50 | constructor(options: FirebaseOptions) { 51 | if (AnalyticService.areOptions(options)) { 52 | const app = initializeApp(options); 53 | this.analytics = initializeAnalytics(app); 54 | } 55 | } 56 | 57 | private static areOptions = (options: {}) => !Object.values(options).every(v => !v); 58 | private logEvent(event: AnalyticsEvent) { 59 | if (this.analytics) logEvent(this.analytics, event); 60 | } 61 | logCueFileSaved() { 62 | this.logEvent(AnalyticsEvent.CUE_FILE_SAVED); 63 | } 64 | logPrevCueLoaded() { 65 | this.logEvent(AnalyticsEvent.PREV_CUE_LOADED); 66 | } 67 | } 68 | 69 | export const apiService = new ApiService(); 70 | export const analyticService = new AnalyticService(firebaseConfig); 71 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/cue-buttons.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 50 | 51 | 58 | 65 |
66 | 67 | 86 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/form.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 42 | 43 |