diff --git a/src/routes/practice/(practice)/+layout.svelte b/src/routes/practice/(practice)/+layout.svelte new file mode 100644 index 0000000..677a8c2 --- /dev/null +++ b/src/routes/practice/(practice)/+layout.svelte @@ -0,0 +1,90 @@ + + +
+
+ JazzyDalpeng / Jazz Guitar Practice +
+
+ +
+ +
diff --git a/src/routes/practice/(practice)/+layout.ts b/src/routes/practice/(practice)/+layout.ts new file mode 100644 index 0000000..521d52e --- /dev/null +++ b/src/routes/practice/(practice)/+layout.ts @@ -0,0 +1,33 @@ +import { base } from '$app/paths'; +import { redirect } from '@sveltejs/kit'; +import { routes, type PracticeRoute, type PracticeRouteCategory } from '../data'; +import type { LayoutLoad } from './$types'; + +export const load: LayoutLoad = (data) => { + const { category, slug } = data.params; + + if (category !== 'core' && category !== 'custom') { + redirect(303, `${base}/practice`); + } + + const currentCategoryRoutes = routes[category]; + const currentPageIndex = currentCategoryRoutes.findIndex((route) => route.slug === slug); + if (currentPageIndex === -1) { + redirect(303, `${base}/practice/${category}/${routes[category][0].slug}`); + } + + const pages: { + previous?: PracticeRoute; + current: PracticeRoute; + next?: PracticeRoute; + } = { + previous: currentPageIndex - 1 >= 0 ? currentCategoryRoutes[currentPageIndex - 1] : undefined, + current: currentCategoryRoutes[currentPageIndex], + next: + currentPageIndex < currentCategoryRoutes.length - 1 + ? currentCategoryRoutes[currentPageIndex + 1] + : undefined + }; + + return { routes, category: category as PracticeRouteCategory, pages }; +}; diff --git a/src/routes/practice/(practice)/[category]/+page.svelte b/src/routes/practice/(practice)/[category]/+page.svelte new file mode 100644 index 0000000..8c76075 --- /dev/null +++ b/src/routes/practice/(practice)/[category]/+page.svelte @@ -0,0 +1 @@ +category \ No newline at end of file diff --git a/src/routes/practice/(practice)/[category]/[slug]/+page.svelte b/src/routes/practice/(practice)/[category]/[slug]/+page.svelte new file mode 100644 index 0000000..9e8d233 --- /dev/null +++ b/src/routes/practice/(practice)/[category]/[slug]/+page.svelte @@ -0,0 +1 @@ +slug diff --git a/src/routes/practice/+page.svelte b/src/routes/practice/+page.svelte index 45b983b..d6adf83 100644 --- a/src/routes/practice/+page.svelte +++ b/src/routes/practice/+page.svelte @@ -1 +1,5 @@ -hi + + +???? diff --git a/src/routes/practice/data.ts b/src/routes/practice/data.ts new file mode 100644 index 0000000..ed9b723 --- /dev/null +++ b/src/routes/practice/data.ts @@ -0,0 +1,212 @@ +import type { Practice } from '$/lib/practice/types'; +import { TUNE } from '$/utils/music/pitch'; + +export interface PracticeRoute { + title: string; + slug: string; + practice: Practice; +} + +export type PracticeRouteCategory = 'core' | 'custom'; +export interface PracticeRoutes extends Record {} + +export const routes: PracticeRoutes = { + core: [ + { + title: 'Major Scale', + slug: 'major-scale', + practice: { + tempo: { + bpm: 120, + beatPerBar: 4, + signatureUnit: 4 + }, + guitar: { + tuning: TUNE.standard + }, + scores: [ + { + positions: [ + { line: 6, fret: 8 }, + { line: 6, fret: 10 }, + { line: 5, fret: 7 }, + { line: 5, fret: 8 }, + { line: 5, fret: 10 }, + { line: 4, fret: 7 }, + { line: 4, fret: 9 }, + { line: 4, fret: 10 }, + { line: 3, fret: 7 }, + { line: 3, fret: 9 }, + { line: 3, fret: 10 }, + { line: 2, fret: 8 }, + { line: 2, fret: 10 }, + { line: 1, fret: 7 }, + { line: 1, fret: 8 }, + { line: 1, fret: 10 }, + { line: 6, fret: 7 } + ], + notes: [ + { position: 0, time: { start: 0, duration: 0.0625 } }, + { position: 1, time: { start: 0.0625, duration: 0.0625 } }, + { position: 2, time: { start: 0.125, duration: 0.0625 } }, + { position: 3, time: { start: 0.1875, duration: 0.0625 } }, + { position: 4, time: { start: 0.25, duration: 0.0625 } }, + { position: 5, time: { start: 0.3125, duration: 0.0625 } }, + { position: 6, time: { start: 0.375, duration: 0.0625 } }, + { position: 7, time: { start: 0.4375, duration: 0.0625 } }, + { position: 8, time: { start: 0.5, duration: 0.0625 } }, + { position: 9, time: { start: 0.5625, duration: 0.0625 } }, + { position: 10, time: { start: 0.625, duration: 0.0625 } }, + { position: 11, time: { start: 0.6875, duration: 0.0625 } }, + { position: 12, time: { start: 0.75, duration: 0.0625 } }, + { position: 13, time: { start: 0.8125, duration: 0.0625 } }, + { position: 14, time: { start: 0.875, duration: 0.0625 } }, + { position: 15, time: { start: 0.9375, duration: 0.0625 } }, + { position: 14, time: { start: 1, duration: 0.0625 } }, + { position: 13, time: { start: 1.0625, duration: 0.0625 } }, + { position: 12, time: { start: 1.125, duration: 0.0625 } }, + { position: 11, time: { start: 1.1875, duration: 0.0625 } }, + { position: 10, time: { start: 1.25, duration: 0.0625 } }, + { position: 9, time: { start: 1.3125, duration: 0.0625 } }, + { position: 8, time: { start: 1.375, duration: 0.0625 } }, + { position: 7, time: { start: 1.4375, duration: 0.0625 } }, + { position: 6, time: { start: 1.5, duration: 0.0625 } }, + { position: 5, time: { start: 1.5625, duration: 0.0625 } }, + { position: 4, time: { start: 1.625, duration: 0.0625 } }, + { position: 3, time: { start: 1.6875, duration: 0.0625 } }, + { position: 2, time: { start: 1.75, duration: 0.0625 } }, + { position: 1, time: { start: 1.8125, duration: 0.0625 } }, + { position: 0, time: { start: 1.875, duration: 0.0625 } }, + { position: 16, time: { start: 1.9375, duration: 0.0625 } }, + { position: 0, time: { start: 2, duration: 0.0625 } } + ], + boards: [ + { + title: 'C line 1', + fingers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + time: { start: 0 } + }, + { + title: 'C line 2', + fingers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + time: { start: 15 / 16 } + } + ], + fretRange: { + start: 5, + end: 11, + visibility: 'all' + } + } + ] + } + }, + { + title: 'Rhythm Test', + slug: 'rhythm-test', + practice: { + tempo: { + bpm: 120, + beatPerBar: 4, + signatureUnit: 4 + }, + guitar: { + tuning: TUNE.standard + }, + scores: [ + { + positions: [ + { line: 5, fret: 3 }, + { line: 6, fret: 3 }, + { line: 1, fret: 'open' }, + { line: 2, fret: 1 }, + { line: 3, fret: 'open' } + ], + notes: [ + { position: 0, time: { start: 0, duration: 1 / 4 } }, + { position: 2, time: { start: 1 / 4, duration: 1 / 4 } }, + { position: 3, time: { start: 1 / 4, duration: 1 / 4 } }, + { position: 4, time: { start: 1 / 4, duration: 1 / 4 } }, + { position: 1, time: { start: 2 / 4, duration: 1 / 4 } }, + { position: 2, time: { start: 3 / 4, duration: 1 / 4 } }, + { position: 3, time: { start: 3 / 4, duration: 1 / 4 } }, + { position: 4, time: { start: 3 / 4, duration: 1 / 4 } }, + { position: 0, time: { start: 4 / 4, duration: 1 / 4 } }, + { position: 2, time: { start: 5 / 4, duration: 1 / 4 } }, + { position: 3, time: { start: 5 / 4, duration: 1 / 4 } }, + { position: 4, time: { start: 5 / 4, duration: 1 / 4 } }, + { position: 1, time: { start: 6 / 4, duration: 1 / 4 } }, + { position: 2, time: { start: 7 / 4, duration: 1 / 4 } }, + { position: 3, time: { start: 7 / 4, duration: 1 / 4 } }, + { position: 4, time: { start: 7 / 4, duration: 1 / 4 } }, + { position: 0, time: { start: 8 / 4, duration: 1 / 4 } }, + { position: 2, time: { start: 9 / 4, duration: 1 / 4 } }, + { position: 3, time: { start: 9 / 4, duration: 1 / 4 } }, + { position: 4, time: { start: 9 / 4, duration: 1 / 4 } }, + { position: 1, time: { start: 10 / 4, duration: 1 / 4 } }, + { position: 2, time: { start: 11 / 4, duration: 1 / 4 } }, + { position: 3, time: { start: 11 / 4, duration: 1 / 4 } }, + { position: 4, time: { start: 11 / 4, duration: 1 / 4 } }, + { position: 0, time: { start: 12 / 4, duration: 1 / 4 } }, + { position: 2, time: { start: 13 / 4, duration: 1 / 4 } }, + { position: 3, time: { start: 13 / 4, duration: 1 / 4 } }, + { position: 4, time: { start: 13 / 4, duration: 1 / 4 } }, + { position: 1, time: { start: 14 / 4, duration: 1 / 4 } }, + { position: 2, time: { start: 15 / 4, duration: 1 / 4 } }, + { position: 3, time: { start: 15 / 4, duration: 1 / 4 } }, + { position: 4, time: { start: 15 / 4, duration: 1 / 4 } } + ], + boards: [ + { + title: 'C line 1', + fingers: [0, 2, 3, 4], + time: { start: 0 } + }, + { + title: 'C line 1', + fingers: [1, 2, 3, 4], + time: { start: 1 / 2 } + }, + { + title: 'C line 1', + fingers: [0, 2, 3, 4], + time: { start: 2 / 2 } + }, + { + title: 'C line 1', + fingers: [1, 2, 3, 4], + time: { start: 3 / 2 } + }, + { + title: 'C line 1', + fingers: [0, 2, 3, 4], + time: { start: 4 / 2 } + }, + { + title: 'C line 1', + fingers: [1, 2, 3, 4], + time: { start: 5 / 2 } + }, + { + title: 'C line 1', + fingers: [0, 2, 3, 4], + time: { start: 6 / 2 } + }, + { + title: 'C line 1', + fingers: [1, 2, 3, 4], + time: { start: 7 / 2 } + } + ], + fretRange: { + start: 0, + end: 12, + visibility: 'all' + } + } + ] + } + } + ], + custom: [] +}; diff --git a/src/utils/hooks/click-outside.ts b/src/utils/hooks/click-outside.ts new file mode 100644 index 0000000..dea3527 --- /dev/null +++ b/src/utils/hooks/click-outside.ts @@ -0,0 +1,25 @@ +import type { Action, ActionReturn } from 'svelte/action'; + +export const clickoutside: Action< + Element, + unknown, + { 'on:clickoutside'?: (event: CustomEvent) => any } +> = (node) => { + const handleClick = (event: MouseEvent) => { + const target = event.target as HTMLElement; + if (!event.target) { + return; + } + if (node && !node.contains(target) && !event.defaultPrevented) { + node.dispatchEvent(new CustomEvent('clickoutside', { detail: CustomEvent })); + } + }; + + document.addEventListener('click', handleClick, true); + + return { + destroy() { + document.removeEventListener('click', handleClick, true); + } + }; +};