diff --git a/bun.lockb b/bun.lockb index 4fababa..f4c7ad9 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index a526565..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,33 +0,0 @@ -import prettier from 'eslint-config-prettier'; -import js from '@eslint/js'; -import svelte from 'eslint-plugin-svelte'; -import globals from 'globals'; -import ts from 'typescript-eslint'; - -export default ts.config( - js.configs.recommended, - ...ts.configs.recommended, - ...svelte.configs['flat/recommended'], - prettier, - ...svelte.configs['flat/prettier'], - { - languageOptions: { - globals: { - ...globals.browser, - ...globals.node - } - } - }, - { - files: ['**/*.svelte'], - - languageOptions: { - parserOptions: { - parser: ts.parser - } - } - }, - { - ignores: ['build/', '.svelte-kit/', 'dist/'] - } -); diff --git a/package.json b/package.json index c10bc5f..f97d1d4 100644 --- a/package.json +++ b/package.json @@ -8,19 +8,17 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "format": "prettier --write .", - "lint": "prettier --check . && eslint ." + "format": "prettier --write ." }, "devDependencies": { "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0", "autoprefixer": "^10.4.20", + "bits-ui": "^1.0.0-next.64", "clsx": "^2.1.1", - "eslint": "^9.7.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.36.0", "globals": "^15.0.0", + "lucide-svelte": "^0.460.1", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.6", "prettier-plugin-tailwindcss": "^0.6.5", @@ -31,7 +29,12 @@ "tailwindcss": "^3.4.9", "tailwindcss-animate": "^1.0.7", "typescript": "^5.0.0", - "typescript-eslint": "^8.0.0", "vite": "^5.0.3" + }, + "dependencies": { + "@protobuf-ts/grpcweb-transport": "^2.9.4", + "@protobuf-ts/plugin": "^2.9.4", + "dayjs": "^1.11.13", + "mode-watcher": "^0.5.0" } } diff --git a/protos/library.proto b/protos/library.proto new file mode 100644 index 0000000..3787455 --- /dev/null +++ b/protos/library.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +import 'google/protobuf/empty.proto'; + +package library; + +service Library { + rpc ListTracks(google.protobuf.Empty) returns (TrackList); +} + +message TrackList { + repeated Track tracks = 1; +} + +message Track { + string hash = 1; + string name = 2; + string artist_name = 3; + uint64 artist_id = 4; +} diff --git a/protos/player.proto b/protos/player.proto new file mode 100644 index 0000000..837622d --- /dev/null +++ b/protos/player.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +import 'google/protobuf/empty.proto'; + +package player; + +service Player { + rpc PlayTrack(PlayTrackRequest) returns (PlayTrackResponse); + rpc ResumeTrack(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc PauseTrack(google.protobuf.Empty) returns (google.protobuf.Empty); +} + +message PlayTrackRequest { + string hash = 1; +} + +message PlayTrackResponse { +} diff --git a/protos/settings.proto b/protos/settings.proto new file mode 100644 index 0000000..727a20a --- /dev/null +++ b/protos/settings.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +import 'google/protobuf/empty.proto'; + +package settings; + +service Settings { + rpc ListPaths(google.protobuf.Empty) returns (SettingsData); + rpc AddPath(AddPathRequest) returns (AddPathResponse); + rpc DeletePath(DeletePathRequest) returns (DeletePathResponse); + rpc RefreshPath(RefreshPathRequest) returns (RefreshPathResponse); +} + +message SettingsData { + repeated LibraryPath library_paths = 1; +} + +message LibraryPath { + uint64 id = 1; + string path = 2; +} + +message AddPathRequest { + string path = 1; +} + +message AddPathResponse { + uint64 id = 1; +} + +message DeletePathRequest { + uint64 id = 1; +} + +message DeletePathResponse { +} + +message RefreshPathRequest { + uint64 id = 1; +} + +message RefreshPathResponse { +} diff --git a/src/app.css b/src/app.css index cd4053f..1f1ad5f 100644 --- a/src/app.css +++ b/src/app.css @@ -6,24 +6,24 @@ :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --primary: 222.2 47.4% 11.2%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; - --destructive: 0 72.2% 50.6%; - --destructive-foreground: 210 40% 98%; - --ring: 222.2 84% 4.9%; - --radius: 0.5rem; + --destructive: 0 100% 67%; + --destructive-foreground: 0 100% 5%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --sidebar-background: 0 0% 98%; --sidebar-foreground: 240 5.3% 26.1%; --sidebar-primary: 240 5.9% 10%; @@ -32,28 +32,30 @@ --sidebar-accent-foreground: 240 5.9% 10%; --sidebar-border: 220 13% 91%; --sidebar-ring: 217.2 91.2% 59.8%; - } + --radius: 0.5rem; + } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --primary: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; --primary-foreground: 222.2 47.4% 11.2%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - --ring: 212.7 26.8% 83.9%; + --destructive: 0 100% 67%; + --destructive-foreground: 0 100% 5%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 224.3 76.3% 48%; + --sidebar-background: 240 5.9% 10%; --sidebar-foreground: 240 4.8% 95.9%; --sidebar-primary: 224.3 76.3% 48%; @@ -73,3 +75,7 @@ @apply bg-background text-foreground; } } + +* { + -webkit-user-drag: none !important; +} diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..ebfd068 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,5 @@ +import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport'; + +export const protoTransport = new GrpcWebFetchTransport({ + baseUrl: 'http://[::1]:39993' +}); diff --git a/src/lib/components/groove/AppSidebar.svelte b/src/lib/components/groove/AppSidebar.svelte new file mode 100644 index 0000000..09c5f7f --- /dev/null +++ b/src/lib/components/groove/AppSidebar.svelte @@ -0,0 +1,73 @@ + + + + + + + + {#snippet child({ props })} + + + Home + + {/snippet} + + + + + + + Library + + + + {#snippet child({ props })} + + + Songs + + {/snippet} + + + + + {#snippet child({ props })} + + + Albums + + {/snippet} + + + + + {#snippet child({ props })} + + + Playlists + + {/snippet} + + + + + + + + + + {#snippet child({ props })} + + + Settings + + {/snippet} + + + + + diff --git a/src/lib/components/groove/Footer.svelte b/src/lib/components/groove/Footer.svelte new file mode 100644 index 0000000..718cacf --- /dev/null +++ b/src/lib/components/groove/Footer.svelte @@ -0,0 +1,160 @@ + + + diff --git a/src/lib/components/groove/SongListing.svelte b/src/lib/components/groove/SongListing.svelte new file mode 100644 index 0000000..de31017 --- /dev/null +++ b/src/lib/components/groove/SongListing.svelte @@ -0,0 +1,41 @@ + + +
+
+ + + +
+
+

{song.name}

+

{song.artistName}

+
+
diff --git a/src/lib/components/groove/ThemeSwitcher.svelte b/src/lib/components/groove/ThemeSwitcher.svelte new file mode 100644 index 0000000..baafb13 --- /dev/null +++ b/src/lib/components/groove/ThemeSwitcher.svelte @@ -0,0 +1,13 @@ + + + diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..9f526ea --- /dev/null +++ b/src/lib/components/ui/button/button.svelte @@ -0,0 +1,69 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/src/lib/components/ui/button/index.ts b/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..5414d9d --- /dev/null +++ b/src/lib/components/ui/button/index.ts @@ -0,0 +1,17 @@ +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants +} from './button.svelte'; + +export { + Root, + type ButtonProps as Props, + // + Root as Button, + buttonVariants, + type ButtonProps, + type ButtonSize, + type ButtonVariant +}; diff --git a/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte b/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte new file mode 100644 index 0000000..2244617 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte @@ -0,0 +1,40 @@ + + + + {#snippet children({ checked, indeterminate })} + + {#if indeterminate} + + {:else} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/src/lib/components/ui/context-menu/context-menu-content.svelte b/src/lib/components/ui/context-menu/context-menu-content.svelte new file mode 100644 index 0000000..7063285 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-content.svelte @@ -0,0 +1,24 @@ + + + + + diff --git a/src/lib/components/ui/context-menu/context-menu-group-heading.svelte b/src/lib/components/ui/context-menu/context-menu-group-heading.svelte new file mode 100644 index 0000000..2ef5f35 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-group-heading.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-item.svelte b/src/lib/components/ui/context-menu/context-menu-item.svelte new file mode 100644 index 0000000..0746b32 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-item.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-radio-item.svelte b/src/lib/components/ui/context-menu/context-menu-radio-item.svelte new file mode 100644 index 0000000..4260177 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-radio-item.svelte @@ -0,0 +1,30 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/src/lib/components/ui/context-menu/context-menu-separator.svelte b/src/lib/components/ui/context-menu/context-menu-separator.svelte new file mode 100644 index 0000000..023ee02 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-separator.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-shortcut.svelte b/src/lib/components/ui/context-menu/context-menu-shortcut.svelte new file mode 100644 index 0000000..ace2d5f --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/context-menu/context-menu-sub-content.svelte b/src/lib/components/ui/context-menu/context-menu-sub-content.svelte new file mode 100644 index 0000000..8d20ab5 --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-sub-content.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte b/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte new file mode 100644 index 0000000..8d9af3c --- /dev/null +++ b/src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte @@ -0,0 +1,28 @@ + + + + {@render children?.()} + + diff --git a/src/lib/components/ui/context-menu/index.ts b/src/lib/components/ui/context-menu/index.ts new file mode 100644 index 0000000..3b3ed7d --- /dev/null +++ b/src/lib/components/ui/context-menu/index.ts @@ -0,0 +1,49 @@ +import { ContextMenu as ContextMenuPrimitive } from 'bits-ui'; + +import Item from './context-menu-item.svelte'; +import GroupHeading from './context-menu-group-heading.svelte'; +import Content from './context-menu-content.svelte'; +import Shortcut from './context-menu-shortcut.svelte'; +import RadioItem from './context-menu-radio-item.svelte'; +import Separator from './context-menu-separator.svelte'; +import SubContent from './context-menu-sub-content.svelte'; +import SubTrigger from './context-menu-sub-trigger.svelte'; +import CheckboxItem from './context-menu-checkbox-item.svelte'; + +const Sub: typeof ContextMenuPrimitive.Sub = ContextMenuPrimitive.Sub; +const Root: typeof ContextMenuPrimitive.Root = ContextMenuPrimitive.Root; +const Trigger: typeof ContextMenuPrimitive.Trigger = ContextMenuPrimitive.Trigger; +const Group: typeof ContextMenuPrimitive.Group = ContextMenuPrimitive.Group; +const RadioGroup: typeof ContextMenuPrimitive.RadioGroup = ContextMenuPrimitive.RadioGroup; + +export { + Sub, + Root, + Item, + Group, + Trigger, + Content, + Shortcut, + Separator, + RadioItem, + GroupHeading, + SubContent, + SubTrigger, + RadioGroup, + CheckboxItem, + // + Root as ContextMenu, + Sub as ContextMenuSub, + Item as ContextMenuItem, + Group as ContextMenuGroup, + Content as ContextMenuContent, + Trigger as ContextMenuTrigger, + Shortcut as ContextMenuShortcut, + RadioItem as ContextMenuRadioItem, + Separator as ContextMenuSeparator, + GroupHeading as ContextMenuGroupHeading, + RadioGroup as ContextMenuRadioGroup, + SubContent as ContextMenuSubContent, + SubTrigger as ContextMenuSubTrigger, + CheckboxItem as ContextMenuCheckboxItem +}; diff --git a/src/lib/components/ui/input/index.ts b/src/lib/components/ui/input/index.ts new file mode 100644 index 0000000..15c0933 --- /dev/null +++ b/src/lib/components/ui/input/index.ts @@ -0,0 +1,7 @@ +import Root from './input.svelte'; + +export { + Root, + // + Root as Input +}; diff --git a/src/lib/components/ui/input/input.svelte b/src/lib/components/ui/input/input.svelte new file mode 100644 index 0000000..50012de --- /dev/null +++ b/src/lib/components/ui/input/input.svelte @@ -0,0 +1,22 @@ + + + diff --git a/src/lib/components/ui/popover/index.ts b/src/lib/components/ui/popover/index.ts new file mode 100644 index 0000000..5db432e --- /dev/null +++ b/src/lib/components/ui/popover/index.ts @@ -0,0 +1,17 @@ +import { Popover as PopoverPrimitive } from 'bits-ui'; +import Content from './popover-content.svelte'; +const Root = PopoverPrimitive.Root; +const Trigger = PopoverPrimitive.Trigger; +const Close = PopoverPrimitive.Close; + +export { + Root, + Content, + Trigger, + Close, + // + Root as Popover, + Content as PopoverContent, + Trigger as PopoverTrigger, + Close as PopoverClose +}; diff --git a/src/lib/components/ui/popover/popover-content.svelte b/src/lib/components/ui/popover/popover-content.svelte new file mode 100644 index 0000000..583ed09 --- /dev/null +++ b/src/lib/components/ui/popover/popover-content.svelte @@ -0,0 +1,28 @@ + + + + + diff --git a/src/lib/components/ui/progress/index.ts b/src/lib/components/ui/progress/index.ts new file mode 100644 index 0000000..97f57fc --- /dev/null +++ b/src/lib/components/ui/progress/index.ts @@ -0,0 +1,7 @@ +import Root from './progress.svelte'; + +export { + Root, + // + Root as Progress +}; diff --git a/src/lib/components/ui/progress/progress.svelte b/src/lib/components/ui/progress/progress.svelte new file mode 100644 index 0000000..0bed8e3 --- /dev/null +++ b/src/lib/components/ui/progress/progress.svelte @@ -0,0 +1,24 @@ + + + +
+
diff --git a/src/lib/components/ui/scroll-area/index.ts b/src/lib/components/ui/scroll-area/index.ts new file mode 100644 index 0000000..d546806 --- /dev/null +++ b/src/lib/components/ui/scroll-area/index.ts @@ -0,0 +1,10 @@ +import Scrollbar from './scroll-area-scrollbar.svelte'; +import Root from './scroll-area.svelte'; + +export { + Root, + Scrollbar, + //, + Root as ScrollArea, + Scrollbar as ScrollAreaScrollbar +}; diff --git a/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte b/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte new file mode 100644 index 0000000..9bc3d13 --- /dev/null +++ b/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/src/lib/components/ui/scroll-area/scroll-area.svelte b/src/lib/components/ui/scroll-area/scroll-area.svelte new file mode 100644 index 0000000..614fbdc --- /dev/null +++ b/src/lib/components/ui/scroll-area/scroll-area.svelte @@ -0,0 +1,32 @@ + + + + + {@render children?.()} + + {#if orientation === 'vertical' || orientation === 'both'} + + {/if} + {#if orientation === 'horizontal' || orientation === 'both'} + + {/if} + + diff --git a/src/lib/components/ui/separator/index.ts b/src/lib/components/ui/separator/index.ts new file mode 100644 index 0000000..768efac --- /dev/null +++ b/src/lib/components/ui/separator/index.ts @@ -0,0 +1,7 @@ +import Root from './separator.svelte'; + +export { + Root, + // + Root as Separator +}; diff --git a/src/lib/components/ui/separator/separator.svelte b/src/lib/components/ui/separator/separator.svelte new file mode 100644 index 0000000..76b5bad --- /dev/null +++ b/src/lib/components/ui/separator/separator.svelte @@ -0,0 +1,22 @@ + + + diff --git a/src/lib/components/ui/sheet/index.ts b/src/lib/components/ui/sheet/index.ts new file mode 100644 index 0000000..d315263 --- /dev/null +++ b/src/lib/components/ui/sheet/index.ts @@ -0,0 +1,37 @@ +import { Dialog as SheetPrimitive } from 'bits-ui'; + +import Overlay from './sheet-overlay.svelte'; +import Content from './sheet-content.svelte'; +import Header from './sheet-header.svelte'; +import Footer from './sheet-footer.svelte'; +import Title from './sheet-title.svelte'; +import Description from './sheet-description.svelte'; + +const Root = SheetPrimitive.Root; +const Close = SheetPrimitive.Close; +const Trigger = SheetPrimitive.Trigger; +const Portal = SheetPrimitive.Portal; + +export { + Root, + Close, + Trigger, + Portal, + Overlay, + Content, + Header, + Footer, + Title, + Description, + // + Root as Sheet, + Close as SheetClose, + Trigger as SheetTrigger, + Portal as SheetPortal, + Overlay as SheetOverlay, + Content as SheetContent, + Header as SheetHeader, + Footer as SheetFooter, + Title as SheetTitle, + Description as SheetDescription +}; diff --git a/src/lib/components/ui/sheet/sheet-content.svelte b/src/lib/components/ui/sheet/sheet-content.svelte new file mode 100644 index 0000000..c2ad5df --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-content.svelte @@ -0,0 +1,56 @@ + + + + + + + + {@render children?.()} + + + Close + + + diff --git a/src/lib/components/ui/sheet/sheet-description.svelte b/src/lib/components/ui/sheet/sheet-description.svelte new file mode 100644 index 0000000..83574ad --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-description.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/sheet/sheet-footer.svelte b/src/lib/components/ui/sheet/sheet-footer.svelte new file mode 100644 index 0000000..6c0e68d --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/sheet/sheet-header.svelte b/src/lib/components/ui/sheet/sheet-header.svelte new file mode 100644 index 0000000..691cbee --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/sheet/sheet-overlay.svelte b/src/lib/components/ui/sheet/sheet-overlay.svelte new file mode 100644 index 0000000..3cfbef4 --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-overlay.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/sheet/sheet-title.svelte b/src/lib/components/ui/sheet/sheet-title.svelte new file mode 100644 index 0000000..7696932 --- /dev/null +++ b/src/lib/components/ui/sheet/sheet-title.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/lib/components/ui/sidebar/constants.ts b/src/lib/components/ui/sidebar/constants.ts new file mode 100644 index 0000000..2d3bbfb --- /dev/null +++ b/src/lib/components/ui/sidebar/constants.ts @@ -0,0 +1,6 @@ +export const SIDEBAR_COOKIE_NAME = 'sidebar:state'; +export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +export const SIDEBAR_WIDTH = '16rem'; +export const SIDEBAR_WIDTH_MOBILE = '18rem'; +export const SIDEBAR_WIDTH_ICON = '3rem'; +export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; diff --git a/src/lib/components/ui/sidebar/context.svelte.ts b/src/lib/components/ui/sidebar/context.svelte.ts new file mode 100644 index 0000000..6fa2aa3 --- /dev/null +++ b/src/lib/components/ui/sidebar/context.svelte.ts @@ -0,0 +1,79 @@ +import { IsMobile } from '$lib/hooks/is-mobile.svelte.js'; +import { getContext, setContext } from 'svelte'; +import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js'; + +type Getter = () => T; + +export type SidebarStateProps = { + /** + * A getter function that returns the current open state of the sidebar. + * We use a getter function here to support `bind:open` on the `Sidebar.Provider` + * component. + */ + open: Getter; + + /** + * A function that sets the open state of the sidebar. To support `bind:open`, we need + * a source of truth for changing the open state to ensure it will be synced throughout + * the sub-components and any `bind:` references. + */ + setOpen: (open: boolean) => void; +}; + +class SidebarState { + readonly props: SidebarStateProps; + open = $derived.by(() => this.props.open()); + openMobile = $state(false); + setOpen: SidebarStateProps['setOpen']; + #isMobile: IsMobile; + state = $derived.by(() => (this.open ? 'expanded' : 'collapsed')); + + constructor(props: SidebarStateProps) { + this.setOpen = props.setOpen; + this.#isMobile = new IsMobile(); + this.props = props; + } + + // Convenience getter for checking if the sidebar is mobile + // without this, we would need to use `sidebar.isMobile.current` everywhere + get isMobile() { + return this.#isMobile.current; + } + + // Event handler to apply to the `` + handleShortcutKeydown = (e: KeyboardEvent) => { + if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + this.toggle(); + } + }; + + setOpenMobile = (value: boolean) => { + this.openMobile = value; + }; + + toggle = () => { + return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open); + }; +} + +const SYMBOL_KEY = 'scn-sidebar'; + +/** + * Instantiates a new `SidebarState` instance and sets it in the context. + * + * @param props The constructor props for the `SidebarState` class. + * @returns The `SidebarState` instance. + */ +export function setSidebar(props: SidebarStateProps): SidebarState { + return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props)); +} + +/** + * Retrieves the `SidebarState` instance from the context. This is a class instance, + * so you cannot destructure it. + * @returns The `SidebarState` instance. + */ +export function useSidebar(): SidebarState { + return getContext(Symbol.for(SYMBOL_KEY)); +} diff --git a/src/lib/components/ui/sidebar/index.ts b/src/lib/components/ui/sidebar/index.ts new file mode 100644 index 0000000..280e640 --- /dev/null +++ b/src/lib/components/ui/sidebar/index.ts @@ -0,0 +1,75 @@ +import { useSidebar } from './context.svelte.js'; +import Content from './sidebar-content.svelte'; +import Footer from './sidebar-footer.svelte'; +import GroupAction from './sidebar-group-action.svelte'; +import GroupContent from './sidebar-group-content.svelte'; +import GroupLabel from './sidebar-group-label.svelte'; +import Group from './sidebar-group.svelte'; +import Header from './sidebar-header.svelte'; +import Input from './sidebar-input.svelte'; +import Inset from './sidebar-inset.svelte'; +import MenuAction from './sidebar-menu-action.svelte'; +import MenuBadge from './sidebar-menu-badge.svelte'; +import MenuButton from './sidebar-menu-button.svelte'; +import MenuItem from './sidebar-menu-item.svelte'; +import MenuSkeleton from './sidebar-menu-skeleton.svelte'; +import MenuSubButton from './sidebar-menu-sub-button.svelte'; +import MenuSubItem from './sidebar-menu-sub-item.svelte'; +import MenuSub from './sidebar-menu-sub.svelte'; +import Menu from './sidebar-menu.svelte'; +import Provider from './sidebar-provider.svelte'; +import Rail from './sidebar-rail.svelte'; +import Separator from './sidebar-separator.svelte'; +import Trigger from './sidebar-trigger.svelte'; +import Root from './sidebar.svelte'; + +export { + Content, + Footer, + Group, + GroupAction, + GroupContent, + GroupLabel, + Header, + Input, + Inset, + Menu, + MenuAction, + MenuBadge, + MenuButton, + MenuItem, + MenuSkeleton, + MenuSub, + MenuSubButton, + MenuSubItem, + Provider, + Rail, + Root, + Separator, + // + Root as Sidebar, + Content as SidebarContent, + Footer as SidebarFooter, + Group as SidebarGroup, + GroupAction as SidebarGroupAction, + GroupContent as SidebarGroupContent, + GroupLabel as SidebarGroupLabel, + Header as SidebarHeader, + Input as SidebarInput, + Inset as SidebarInset, + Menu as SidebarMenu, + MenuAction as SidebarMenuAction, + MenuBadge as SidebarMenuBadge, + MenuButton as SidebarMenuButton, + MenuItem as SidebarMenuItem, + MenuSkeleton as SidebarMenuSkeleton, + MenuSub as SidebarMenuSub, + MenuSubButton as SidebarMenuSubButton, + MenuSubItem as SidebarMenuSubItem, + Provider as SidebarProvider, + Rail as SidebarRail, + Separator as SidebarSeparator, + Trigger as SidebarTrigger, + Trigger, + useSidebar +}; diff --git a/src/lib/components/ui/sidebar/sidebar-content.svelte b/src/lib/components/ui/sidebar/sidebar-content.svelte new file mode 100644 index 0000000..d3c9be2 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-content.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/sidebar/sidebar-footer.svelte b/src/lib/components/ui/sidebar/sidebar-footer.svelte new file mode 100644 index 0000000..9bad817 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-footer.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/sidebar/sidebar-group-action.svelte b/src/lib/components/ui/sidebar/sidebar-group-action.svelte new file mode 100644 index 0000000..21f103b --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-group-action.svelte @@ -0,0 +1,36 @@ + + +{#if child} + {@render child({ props: propObj })} +{:else} + +{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-group-content.svelte b/src/lib/components/ui/sidebar/sidebar-group-content.svelte new file mode 100644 index 0000000..38eb796 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-group-content.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/sidebar/sidebar-group-label.svelte b/src/lib/components/ui/sidebar/sidebar-group-label.svelte new file mode 100644 index 0000000..2abc7d0 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-group-label.svelte @@ -0,0 +1,34 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} +
+ {@render children?.()} +
+{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-group.svelte b/src/lib/components/ui/sidebar/sidebar-group.svelte new file mode 100644 index 0000000..fc5c9e1 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-group.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/sidebar/sidebar-header.svelte b/src/lib/components/ui/sidebar/sidebar-header.svelte new file mode 100644 index 0000000..b95409c --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-header.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/sidebar/sidebar-input.svelte b/src/lib/components/ui/sidebar/sidebar-input.svelte new file mode 100644 index 0000000..5a899fc --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-input.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/lib/components/ui/sidebar/sidebar-inset.svelte b/src/lib/components/ui/sidebar/sidebar-inset.svelte new file mode 100644 index 0000000..220c64b --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-inset.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/sidebar/sidebar-menu-action.svelte b/src/lib/components/ui/sidebar/sidebar-menu-action.svelte new file mode 100644 index 0000000..33f029f --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-action.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + +{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte b/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte new file mode 100644 index 0000000..9dcd223 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte @@ -0,0 +1,29 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/sidebar/sidebar-menu-button.svelte b/src/lib/components/ui/sidebar/sidebar-menu-button.svelte new file mode 100644 index 0000000..d3e5fbc --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-button.svelte @@ -0,0 +1,95 @@ + + + + +{#snippet Button({ props }: { props?: Record })} + {@const mergedProps = mergeProps(buttonProps, props)} + {#if child} + {@render child({ props: mergedProps })} + {:else} + + {/if} +{/snippet} + +{#if !tooltipContent} + {@render Button({})} +{:else} + + + {#snippet child({ props })} + {@render Button({ props })} + {/snippet} + + +{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-menu-item.svelte b/src/lib/components/ui/sidebar/sidebar-menu-item.svelte new file mode 100644 index 0000000..9d5dab6 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-item.svelte @@ -0,0 +1,21 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte b/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte new file mode 100644 index 0000000..50ac650 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte @@ -0,0 +1,36 @@ + + +
    + {#if showIcon} + + {/if} + + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte b/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte new file mode 100644 index 0000000..3fd6b40 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + + {@render children?.()} + +{/if} diff --git a/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte b/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte new file mode 100644 index 0000000..a163119 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte @@ -0,0 +1,14 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte b/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte new file mode 100644 index 0000000..39c9de3 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte @@ -0,0 +1,25 @@ + + +
      + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-menu.svelte b/src/lib/components/ui/sidebar/sidebar-menu.svelte new file mode 100644 index 0000000..c0d1d7b --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-menu.svelte @@ -0,0 +1,21 @@ + + +
      + {@render children?.()} +
    diff --git a/src/lib/components/ui/sidebar/sidebar-provider.svelte b/src/lib/components/ui/sidebar/sidebar-provider.svelte new file mode 100644 index 0000000..5c9059c --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-provider.svelte @@ -0,0 +1,59 @@ + + + + + +
    + {@render children?.()} +
    +
    diff --git a/src/lib/components/ui/sidebar/sidebar-rail.svelte b/src/lib/components/ui/sidebar/sidebar-rail.svelte new file mode 100644 index 0000000..f747a35 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-rail.svelte @@ -0,0 +1,36 @@ + + + diff --git a/src/lib/components/ui/sidebar/sidebar-separator.svelte b/src/lib/components/ui/sidebar/sidebar-separator.svelte new file mode 100644 index 0000000..94ed358 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-separator.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/lib/components/ui/sidebar/sidebar-trigger.svelte b/src/lib/components/ui/sidebar/sidebar-trigger.svelte new file mode 100644 index 0000000..e7a2ba9 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar-trigger.svelte @@ -0,0 +1,34 @@ + + + diff --git a/src/lib/components/ui/sidebar/sidebar.svelte b/src/lib/components/ui/sidebar/sidebar.svelte new file mode 100644 index 0000000..a19a657 --- /dev/null +++ b/src/lib/components/ui/sidebar/sidebar.svelte @@ -0,0 +1,98 @@ + + +{#if collapsible === 'none'} +
    + {@render children?.()} +
    +{:else if sidebar.isMobile} + + +
    + {@render children?.()} +
    +
    +
    +{:else} + +{/if} diff --git a/src/lib/components/ui/skeleton/index.ts b/src/lib/components/ui/skeleton/index.ts new file mode 100644 index 0000000..3120ce1 --- /dev/null +++ b/src/lib/components/ui/skeleton/index.ts @@ -0,0 +1,7 @@ +import Root from './skeleton.svelte'; + +export { + Root, + // + Root as Skeleton +}; diff --git a/src/lib/components/ui/skeleton/skeleton.svelte b/src/lib/components/ui/skeleton/skeleton.svelte new file mode 100644 index 0000000..0b62163 --- /dev/null +++ b/src/lib/components/ui/skeleton/skeleton.svelte @@ -0,0 +1,17 @@ + + +
    diff --git a/src/lib/components/ui/slider/index.ts b/src/lib/components/ui/slider/index.ts new file mode 100644 index 0000000..f1524fc --- /dev/null +++ b/src/lib/components/ui/slider/index.ts @@ -0,0 +1,7 @@ +import Root from './slider.svelte'; + +export { + Root, + // + Root as Slider +}; diff --git a/src/lib/components/ui/slider/slider.svelte b/src/lib/components/ui/slider/slider.svelte new file mode 100644 index 0000000..4c16135 --- /dev/null +++ b/src/lib/components/ui/slider/slider.svelte @@ -0,0 +1,32 @@ + + + + {#snippet children({ thumbs })} + + + + {#each thumbs as thumb} + + {/each} + {/snippet} + diff --git a/src/lib/components/ui/tooltip/index.ts b/src/lib/components/ui/tooltip/index.ts new file mode 100644 index 0000000..034ab20 --- /dev/null +++ b/src/lib/components/ui/tooltip/index.ts @@ -0,0 +1,18 @@ +import { Tooltip as TooltipPrimitive } from 'bits-ui'; +import Content from './tooltip-content.svelte'; + +const Root = TooltipPrimitive.Root; +const Trigger = TooltipPrimitive.Trigger; +const Provider = TooltipPrimitive.Provider; + +export { + Root, + Trigger, + Content, + Provider, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider +}; diff --git a/src/lib/components/ui/tooltip/tooltip-content.svelte b/src/lib/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000..103e43c --- /dev/null +++ b/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,21 @@ + + + diff --git a/src/lib/covers.ts b/src/lib/covers.ts new file mode 100644 index 0000000..fc87d3a --- /dev/null +++ b/src/lib/covers.ts @@ -0,0 +1,3 @@ +export function getCoverUrl(songHash: string) { + return `http://localhost:39994/${songHash}.webp`; +} diff --git a/src/lib/hooks/is-mobile.svelte.ts b/src/lib/hooks/is-mobile.svelte.ts new file mode 100644 index 0000000..a957a64 --- /dev/null +++ b/src/lib/hooks/is-mobile.svelte.ts @@ -0,0 +1,27 @@ +import { untrack } from 'svelte'; + +const MOBILE_BREAKPOINT = 768; + +export class IsMobile { + #current = $state(false); + + constructor() { + $effect(() => { + return untrack(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); + const onChange = () => { + this.#current = window.innerWidth < MOBILE_BREAKPOINT; + }; + mql.addEventListener('change', onChange); + onChange(); + return () => { + mql.removeEventListener('change', onChange); + }; + }); + }); + } + + get current() { + return this.#current; + } +} diff --git a/src/lib/proto/google/protobuf/empty.ts b/src/lib/proto/google/protobuf/empty.ts new file mode 100644 index 0000000..77093fe --- /dev/null +++ b/src/lib/proto/google/protobuf/empty.ts @@ -0,0 +1,87 @@ +// @generated by protobuf-ts 2.9.4 +// @generated from protobuf file "google/protobuf/empty.proto" (package "google.protobuf", syntax proto3) +// tslint:disable +// +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +import type { BinaryWriteOptions } from '@protobuf-ts/runtime'; +import type { IBinaryWriter } from '@protobuf-ts/runtime'; +import { UnknownFieldHandler } from '@protobuf-ts/runtime'; +import type { BinaryReadOptions } from '@protobuf-ts/runtime'; +import type { IBinaryReader } from '@protobuf-ts/runtime'; +import type { PartialMessage } from '@protobuf-ts/runtime'; +import { reflectionMergePartial } from '@protobuf-ts/runtime'; +import { MessageType } from '@protobuf-ts/runtime'; +/** + * A generic empty message that you can re-use to avoid defining duplicated + * empty messages in your APIs. A typical example is to use it as the request + * or the response type of an API method. For instance: + * + * service Foo { + * rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); + * } + * + * + * @generated from protobuf message google.protobuf.Empty + */ +export interface Empty {} +// @generated message type with reflection information, may provide speed optimized methods +class Empty$Type extends MessageType { + constructor() { + super('google.protobuf.Empty', []); + } + create(value?: PartialMessage): Empty { + const message = globalThis.Object.create(this.messagePrototype!); + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: Empty + ): Empty { + return target ?? this.create(); + } + internalBinaryWrite( + message: Empty, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message google.protobuf.Empty + */ +export const Empty = new Empty$Type(); diff --git a/src/lib/proto/library.client.ts b/src/lib/proto/library.client.ts new file mode 100644 index 0000000..d5177dd --- /dev/null +++ b/src/lib/proto/library.client.ts @@ -0,0 +1,37 @@ +// @generated by protobuf-ts 2.9.4 +// @generated from protobuf file "library.proto" (package "library", syntax proto3) +// tslint:disable +import type { RpcTransport } from '@protobuf-ts/runtime-rpc'; +import type { ServiceInfo } from '@protobuf-ts/runtime-rpc'; +import { Library } from './library'; +import { stackIntercept } from '@protobuf-ts/runtime-rpc'; +import type { TrackList } from './library'; +import type { Empty } from './google/protobuf/empty'; +import type { UnaryCall } from '@protobuf-ts/runtime-rpc'; +import type { RpcOptions } from '@protobuf-ts/runtime-rpc'; +/** + * @generated from protobuf service library.Library + */ +export interface ILibraryClient { + /** + * @generated from protobuf rpc: ListTracks(google.protobuf.Empty) returns (library.TrackList); + */ + listTracks(input: Empty, options?: RpcOptions): UnaryCall; +} +/** + * @generated from protobuf service library.Library + */ +export class LibraryClient implements ILibraryClient, ServiceInfo { + typeName = Library.typeName; + methods = Library.methods; + options = Library.options; + constructor(private readonly _transport: RpcTransport) {} + /** + * @generated from protobuf rpc: ListTracks(google.protobuf.Empty) returns (library.TrackList); + */ + listTracks(input: Empty, options?: RpcOptions): UnaryCall { + const method = this.methods[0], + opt = this._transport.mergeOptions(options); + return stackIntercept('unary', this._transport, method, opt, input); + } +} diff --git a/src/lib/proto/library.ts b/src/lib/proto/library.ts new file mode 100644 index 0000000..022586f --- /dev/null +++ b/src/lib/proto/library.ts @@ -0,0 +1,207 @@ +// @generated by protobuf-ts 2.9.4 +// @generated from protobuf file "library.proto" (package "library", syntax proto3) +// tslint:disable +import { Empty } from './google/protobuf/empty'; +import { ServiceType } from '@protobuf-ts/runtime-rpc'; +import type { BinaryWriteOptions } from '@protobuf-ts/runtime'; +import type { IBinaryWriter } from '@protobuf-ts/runtime'; +import { WireType } from '@protobuf-ts/runtime'; +import type { BinaryReadOptions } from '@protobuf-ts/runtime'; +import type { IBinaryReader } from '@protobuf-ts/runtime'; +import { UnknownFieldHandler } from '@protobuf-ts/runtime'; +import type { PartialMessage } from '@protobuf-ts/runtime'; +import { reflectionMergePartial } from '@protobuf-ts/runtime'; +import { MessageType } from '@protobuf-ts/runtime'; +/** + * @generated from protobuf message library.TrackList + */ +export interface TrackList { + /** + * @generated from protobuf field: repeated library.Track tracks = 1; + */ + tracks: Track[]; +} +/** + * @generated from protobuf message library.Track + */ +export interface Track { + /** + * @generated from protobuf field: string hash = 1; + */ + hash: string; + /** + * @generated from protobuf field: string name = 2; + */ + name: string; + /** + * @generated from protobuf field: string artist_name = 3; + */ + artistName: string; + /** + * @generated from protobuf field: uint64 artist_id = 4; + */ + artistId: bigint; +} +// @generated message type with reflection information, may provide speed optimized methods +class TrackList$Type extends MessageType { + constructor() { + super('library.TrackList', [ + { no: 1, name: 'tracks', kind: 'message', repeat: 1 /*RepeatType.PACKED*/, T: () => Track } + ]); + } + create(value?: PartialMessage): TrackList { + const message = globalThis.Object.create(this.messagePrototype!); + message.tracks = []; + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: TrackList + ): TrackList { + let message = target ?? this.create(), + end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated library.Track tracks */ 1: + message.tracks.push(Track.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === 'throw') + throw new globalThis.Error( + `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` + ); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)( + this.typeName, + message, + fieldNo, + wireType, + d + ); + } + } + return message; + } + internalBinaryWrite( + message: TrackList, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + /* repeated library.Track tracks = 1; */ + for (let i = 0; i < message.tracks.length; i++) + Track.internalBinaryWrite( + message.tracks[i], + writer.tag(1, WireType.LengthDelimited).fork(), + options + ).join(); + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message library.TrackList + */ +export const TrackList = new TrackList$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Track$Type extends MessageType { + constructor() { + super('library.Track', [ + { no: 1, name: 'hash', kind: 'scalar', T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: 'name', kind: 'scalar', T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: 'artist_name', kind: 'scalar', T: 9 /*ScalarType.STRING*/ }, + { + no: 4, + name: 'artist_id', + kind: 'scalar', + T: 4 /*ScalarType.UINT64*/, + L: 0 /*LongType.BIGINT*/ + } + ]); + } + create(value?: PartialMessage): Track { + const message = globalThis.Object.create(this.messagePrototype!); + message.hash = ''; + message.name = ''; + message.artistName = ''; + message.artistId = 0n; + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: Track + ): Track { + let message = target ?? this.create(), + end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string hash */ 1: + message.hash = reader.string(); + break; + case /* string name */ 2: + message.name = reader.string(); + break; + case /* string artist_name */ 3: + message.artistName = reader.string(); + break; + case /* uint64 artist_id */ 4: + message.artistId = reader.uint64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === 'throw') + throw new globalThis.Error( + `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` + ); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)( + this.typeName, + message, + fieldNo, + wireType, + d + ); + } + } + return message; + } + internalBinaryWrite( + message: Track, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + /* string hash = 1; */ + if (message.hash !== '') writer.tag(1, WireType.LengthDelimited).string(message.hash); + /* string name = 2; */ + if (message.name !== '') writer.tag(2, WireType.LengthDelimited).string(message.name); + /* string artist_name = 3; */ + if (message.artistName !== '') + writer.tag(3, WireType.LengthDelimited).string(message.artistName); + /* uint64 artist_id = 4; */ + if (message.artistId !== 0n) writer.tag(4, WireType.Varint).uint64(message.artistId); + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message library.Track + */ +export const Track = new Track$Type(); +/** + * @generated ServiceType for protobuf service library.Library + */ +export const Library = new ServiceType('library.Library', [ + { name: 'ListTracks', options: {}, I: Empty, O: TrackList } +]); diff --git a/src/lib/proto/player.client.ts b/src/lib/proto/player.client.ts new file mode 100644 index 0000000..5360cae --- /dev/null +++ b/src/lib/proto/player.client.ts @@ -0,0 +1,74 @@ +// @generated by protobuf-ts 2.9.4 +// @generated from protobuf file "player.proto" (package "player", syntax proto3) +// tslint:disable +import type { RpcTransport } from '@protobuf-ts/runtime-rpc'; +import type { ServiceInfo } from '@protobuf-ts/runtime-rpc'; +import { Player } from './player'; +import type { Empty } from './google/protobuf/empty'; +import { stackIntercept } from '@protobuf-ts/runtime-rpc'; +import type { PlayTrackResponse } from './player'; +import type { PlayTrackRequest } from './player'; +import type { UnaryCall } from '@protobuf-ts/runtime-rpc'; +import type { RpcOptions } from '@protobuf-ts/runtime-rpc'; +/** + * @generated from protobuf service player.Player + */ +export interface IPlayerClient { + /** + * @generated from protobuf rpc: PlayTrack(player.PlayTrackRequest) returns (player.PlayTrackResponse); + */ + playTrack( + input: PlayTrackRequest, + options?: RpcOptions + ): UnaryCall; + /** + * @generated from protobuf rpc: ResumeTrack(google.protobuf.Empty) returns (google.protobuf.Empty); + */ + resumeTrack(input: Empty, options?: RpcOptions): UnaryCall; + /** + * @generated from protobuf rpc: PauseTrack(google.protobuf.Empty) returns (google.protobuf.Empty); + */ + pauseTrack(input: Empty, options?: RpcOptions): UnaryCall; +} +/** + * @generated from protobuf service player.Player + */ +export class PlayerClient implements IPlayerClient, ServiceInfo { + typeName = Player.typeName; + methods = Player.methods; + options = Player.options; + constructor(private readonly _transport: RpcTransport) {} + /** + * @generated from protobuf rpc: PlayTrack(player.PlayTrackRequest) returns (player.PlayTrackResponse); + */ + playTrack( + input: PlayTrackRequest, + options?: RpcOptions + ): UnaryCall { + const method = this.methods[0], + opt = this._transport.mergeOptions(options); + return stackIntercept( + 'unary', + this._transport, + method, + opt, + input + ); + } + /** + * @generated from protobuf rpc: ResumeTrack(google.protobuf.Empty) returns (google.protobuf.Empty); + */ + resumeTrack(input: Empty, options?: RpcOptions): UnaryCall { + const method = this.methods[1], + opt = this._transport.mergeOptions(options); + return stackIntercept('unary', this._transport, method, opt, input); + } + /** + * @generated from protobuf rpc: PauseTrack(google.protobuf.Empty) returns (google.protobuf.Empty); + */ + pauseTrack(input: Empty, options?: RpcOptions): UnaryCall { + const method = this.methods[2], + opt = this._transport.mergeOptions(options); + return stackIntercept('unary', this._transport, method, opt, input); + } +} diff --git a/src/lib/proto/player.ts b/src/lib/proto/player.ts new file mode 100644 index 0000000..9748c76 --- /dev/null +++ b/src/lib/proto/player.ts @@ -0,0 +1,129 @@ +// @generated by protobuf-ts 2.9.4 +// @generated from protobuf file "player.proto" (package "player", syntax proto3) +// tslint:disable +import { Empty } from './google/protobuf/empty'; +import { ServiceType } from '@protobuf-ts/runtime-rpc'; +import type { BinaryWriteOptions } from '@protobuf-ts/runtime'; +import type { IBinaryWriter } from '@protobuf-ts/runtime'; +import { WireType } from '@protobuf-ts/runtime'; +import type { BinaryReadOptions } from '@protobuf-ts/runtime'; +import type { IBinaryReader } from '@protobuf-ts/runtime'; +import { UnknownFieldHandler } from '@protobuf-ts/runtime'; +import type { PartialMessage } from '@protobuf-ts/runtime'; +import { reflectionMergePartial } from '@protobuf-ts/runtime'; +import { MessageType } from '@protobuf-ts/runtime'; +/** + * @generated from protobuf message player.PlayTrackRequest + */ +export interface PlayTrackRequest { + /** + * @generated from protobuf field: string hash = 1; + */ + hash: string; +} +/** + * @generated from protobuf message player.PlayTrackResponse + */ +export interface PlayTrackResponse {} +// @generated message type with reflection information, may provide speed optimized methods +class PlayTrackRequest$Type extends MessageType { + constructor() { + super('player.PlayTrackRequest', [ + { no: 1, name: 'hash', kind: 'scalar', T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): PlayTrackRequest { + const message = globalThis.Object.create(this.messagePrototype!); + message.hash = ''; + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: PlayTrackRequest + ): PlayTrackRequest { + let message = target ?? this.create(), + end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string hash */ 1: + message.hash = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === 'throw') + throw new globalThis.Error( + `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` + ); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)( + this.typeName, + message, + fieldNo, + wireType, + d + ); + } + } + return message; + } + internalBinaryWrite( + message: PlayTrackRequest, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + /* string hash = 1; */ + if (message.hash !== '') writer.tag(1, WireType.LengthDelimited).string(message.hash); + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message player.PlayTrackRequest + */ +export const PlayTrackRequest = new PlayTrackRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class PlayTrackResponse$Type extends MessageType { + constructor() { + super('player.PlayTrackResponse', []); + } + create(value?: PartialMessage): PlayTrackResponse { + const message = globalThis.Object.create(this.messagePrototype!); + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: PlayTrackResponse + ): PlayTrackResponse { + return target ?? this.create(); + } + internalBinaryWrite( + message: PlayTrackResponse, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message player.PlayTrackResponse + */ +export const PlayTrackResponse = new PlayTrackResponse$Type(); +/** + * @generated ServiceType for protobuf service player.Player + */ +export const Player = new ServiceType('player.Player', [ + { name: 'PlayTrack', options: {}, I: PlayTrackRequest, O: PlayTrackResponse }, + { name: 'ResumeTrack', options: {}, I: Empty, O: Empty }, + { name: 'PauseTrack', options: {}, I: Empty, O: Empty } +]); diff --git a/src/lib/proto/settings.client.ts b/src/lib/proto/settings.client.ts new file mode 100644 index 0000000..d3e775e --- /dev/null +++ b/src/lib/proto/settings.client.ts @@ -0,0 +1,109 @@ +// @generated by protobuf-ts 2.9.4 +// @generated from protobuf file "settings.proto" (package "settings", syntax proto3) +// tslint:disable +import type { RpcTransport } from '@protobuf-ts/runtime-rpc'; +import type { ServiceInfo } from '@protobuf-ts/runtime-rpc'; +import { Settings } from './settings'; +import type { RefreshPathResponse } from './settings'; +import type { RefreshPathRequest } from './settings'; +import type { DeletePathResponse } from './settings'; +import type { DeletePathRequest } from './settings'; +import type { AddPathResponse } from './settings'; +import type { AddPathRequest } from './settings'; +import { stackIntercept } from '@protobuf-ts/runtime-rpc'; +import type { SettingsData } from './settings'; +import type { Empty } from './google/protobuf/empty'; +import type { UnaryCall } from '@protobuf-ts/runtime-rpc'; +import type { RpcOptions } from '@protobuf-ts/runtime-rpc'; +/** + * @generated from protobuf service settings.Settings + */ +export interface ISettingsClient { + /** + * @generated from protobuf rpc: ListPaths(google.protobuf.Empty) returns (settings.SettingsData); + */ + listPaths(input: Empty, options?: RpcOptions): UnaryCall; + /** + * @generated from protobuf rpc: AddPath(settings.AddPathRequest) returns (settings.AddPathResponse); + */ + addPath(input: AddPathRequest, options?: RpcOptions): UnaryCall; + /** + * @generated from protobuf rpc: DeletePath(settings.DeletePathRequest) returns (settings.DeletePathResponse); + */ + deletePath( + input: DeletePathRequest, + options?: RpcOptions + ): UnaryCall; + /** + * @generated from protobuf rpc: RefreshPath(settings.RefreshPathRequest) returns (settings.RefreshPathResponse); + */ + refreshPath( + input: RefreshPathRequest, + options?: RpcOptions + ): UnaryCall; +} +/** + * @generated from protobuf service settings.Settings + */ +export class SettingsClient implements ISettingsClient, ServiceInfo { + typeName = Settings.typeName; + methods = Settings.methods; + options = Settings.options; + constructor(private readonly _transport: RpcTransport) {} + /** + * @generated from protobuf rpc: ListPaths(google.protobuf.Empty) returns (settings.SettingsData); + */ + listPaths(input: Empty, options?: RpcOptions): UnaryCall { + const method = this.methods[0], + opt = this._transport.mergeOptions(options); + return stackIntercept('unary', this._transport, method, opt, input); + } + /** + * @generated from protobuf rpc: AddPath(settings.AddPathRequest) returns (settings.AddPathResponse); + */ + addPath(input: AddPathRequest, options?: RpcOptions): UnaryCall { + const method = this.methods[1], + opt = this._transport.mergeOptions(options); + return stackIntercept( + 'unary', + this._transport, + method, + opt, + input + ); + } + /** + * @generated from protobuf rpc: DeletePath(settings.DeletePathRequest) returns (settings.DeletePathResponse); + */ + deletePath( + input: DeletePathRequest, + options?: RpcOptions + ): UnaryCall { + const method = this.methods[2], + opt = this._transport.mergeOptions(options); + return stackIntercept( + 'unary', + this._transport, + method, + opt, + input + ); + } + /** + * @generated from protobuf rpc: RefreshPath(settings.RefreshPathRequest) returns (settings.RefreshPathResponse); + */ + refreshPath( + input: RefreshPathRequest, + options?: RpcOptions + ): UnaryCall { + const method = this.methods[3], + opt = this._transport.mergeOptions(options); + return stackIntercept( + 'unary', + this._transport, + method, + opt, + input + ); + } +} diff --git a/src/lib/proto/settings.ts b/src/lib/proto/settings.ts new file mode 100644 index 0000000..38af3b8 --- /dev/null +++ b/src/lib/proto/settings.ts @@ -0,0 +1,545 @@ +// @generated by protobuf-ts 2.9.4 +// @generated from protobuf file "settings.proto" (package "settings", syntax proto3) +// tslint:disable +import { Empty } from './google/protobuf/empty'; +import { ServiceType } from '@protobuf-ts/runtime-rpc'; +import type { BinaryWriteOptions } from '@protobuf-ts/runtime'; +import type { IBinaryWriter } from '@protobuf-ts/runtime'; +import { WireType } from '@protobuf-ts/runtime'; +import type { BinaryReadOptions } from '@protobuf-ts/runtime'; +import type { IBinaryReader } from '@protobuf-ts/runtime'; +import { UnknownFieldHandler } from '@protobuf-ts/runtime'; +import type { PartialMessage } from '@protobuf-ts/runtime'; +import { reflectionMergePartial } from '@protobuf-ts/runtime'; +import { MessageType } from '@protobuf-ts/runtime'; +/** + * @generated from protobuf message settings.SettingsData + */ +export interface SettingsData { + /** + * @generated from protobuf field: repeated settings.LibraryPath library_paths = 1; + */ + libraryPaths: LibraryPath[]; +} +/** + * @generated from protobuf message settings.LibraryPath + */ +export interface LibraryPath { + /** + * @generated from protobuf field: uint64 id = 1; + */ + id: bigint; + /** + * @generated from protobuf field: string path = 2; + */ + path: string; +} +/** + * @generated from protobuf message settings.AddPathRequest + */ +export interface AddPathRequest { + /** + * @generated from protobuf field: string path = 1; + */ + path: string; +} +/** + * @generated from protobuf message settings.AddPathResponse + */ +export interface AddPathResponse { + /** + * @generated from protobuf field: uint64 id = 1; + */ + id: bigint; +} +/** + * @generated from protobuf message settings.DeletePathRequest + */ +export interface DeletePathRequest { + /** + * @generated from protobuf field: uint64 id = 1; + */ + id: bigint; +} +/** + * @generated from protobuf message settings.DeletePathResponse + */ +export interface DeletePathResponse {} +/** + * @generated from protobuf message settings.RefreshPathRequest + */ +export interface RefreshPathRequest { + /** + * @generated from protobuf field: uint64 id = 1; + */ + id: bigint; +} +/** + * @generated from protobuf message settings.RefreshPathResponse + */ +export interface RefreshPathResponse {} +// @generated message type with reflection information, may provide speed optimized methods +class SettingsData$Type extends MessageType { + constructor() { + super('settings.SettingsData', [ + { + no: 1, + name: 'library_paths', + kind: 'message', + repeat: 1 /*RepeatType.PACKED*/, + T: () => LibraryPath + } + ]); + } + create(value?: PartialMessage): SettingsData { + const message = globalThis.Object.create(this.messagePrototype!); + message.libraryPaths = []; + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: SettingsData + ): SettingsData { + let message = target ?? this.create(), + end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated settings.LibraryPath library_paths */ 1: + message.libraryPaths.push( + LibraryPath.internalBinaryRead(reader, reader.uint32(), options) + ); + break; + default: + let u = options.readUnknownField; + if (u === 'throw') + throw new globalThis.Error( + `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` + ); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)( + this.typeName, + message, + fieldNo, + wireType, + d + ); + } + } + return message; + } + internalBinaryWrite( + message: SettingsData, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + /* repeated settings.LibraryPath library_paths = 1; */ + for (let i = 0; i < message.libraryPaths.length; i++) + LibraryPath.internalBinaryWrite( + message.libraryPaths[i], + writer.tag(1, WireType.LengthDelimited).fork(), + options + ).join(); + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message settings.SettingsData + */ +export const SettingsData = new SettingsData$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class LibraryPath$Type extends MessageType { + constructor() { + super('settings.LibraryPath', [ + { no: 1, name: 'id', kind: 'scalar', T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: 'path', kind: 'scalar', T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): LibraryPath { + const message = globalThis.Object.create(this.messagePrototype!); + message.id = 0n; + message.path = ''; + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: LibraryPath + ): LibraryPath { + let message = target ?? this.create(), + end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 id */ 1: + message.id = reader.uint64().toBigInt(); + break; + case /* string path */ 2: + message.path = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === 'throw') + throw new globalThis.Error( + `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` + ); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)( + this.typeName, + message, + fieldNo, + wireType, + d + ); + } + } + return message; + } + internalBinaryWrite( + message: LibraryPath, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + /* uint64 id = 1; */ + if (message.id !== 0n) writer.tag(1, WireType.Varint).uint64(message.id); + /* string path = 2; */ + if (message.path !== '') writer.tag(2, WireType.LengthDelimited).string(message.path); + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message settings.LibraryPath + */ +export const LibraryPath = new LibraryPath$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AddPathRequest$Type extends MessageType { + constructor() { + super('settings.AddPathRequest', [ + { no: 1, name: 'path', kind: 'scalar', T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): AddPathRequest { + const message = globalThis.Object.create(this.messagePrototype!); + message.path = ''; + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: AddPathRequest + ): AddPathRequest { + let message = target ?? this.create(), + end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string path */ 1: + message.path = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === 'throw') + throw new globalThis.Error( + `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` + ); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)( + this.typeName, + message, + fieldNo, + wireType, + d + ); + } + } + return message; + } + internalBinaryWrite( + message: AddPathRequest, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + /* string path = 1; */ + if (message.path !== '') writer.tag(1, WireType.LengthDelimited).string(message.path); + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message settings.AddPathRequest + */ +export const AddPathRequest = new AddPathRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AddPathResponse$Type extends MessageType { + constructor() { + super('settings.AddPathResponse', [ + { no: 1, name: 'id', kind: 'scalar', T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): AddPathResponse { + const message = globalThis.Object.create(this.messagePrototype!); + message.id = 0n; + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: AddPathResponse + ): AddPathResponse { + let message = target ?? this.create(), + end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 id */ 1: + message.id = reader.uint64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === 'throw') + throw new globalThis.Error( + `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` + ); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)( + this.typeName, + message, + fieldNo, + wireType, + d + ); + } + } + return message; + } + internalBinaryWrite( + message: AddPathResponse, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + /* uint64 id = 1; */ + if (message.id !== 0n) writer.tag(1, WireType.Varint).uint64(message.id); + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message settings.AddPathResponse + */ +export const AddPathResponse = new AddPathResponse$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class DeletePathRequest$Type extends MessageType { + constructor() { + super('settings.DeletePathRequest', [ + { no: 1, name: 'id', kind: 'scalar', T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): DeletePathRequest { + const message = globalThis.Object.create(this.messagePrototype!); + message.id = 0n; + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: DeletePathRequest + ): DeletePathRequest { + let message = target ?? this.create(), + end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 id */ 1: + message.id = reader.uint64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === 'throw') + throw new globalThis.Error( + `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` + ); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)( + this.typeName, + message, + fieldNo, + wireType, + d + ); + } + } + return message; + } + internalBinaryWrite( + message: DeletePathRequest, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + /* uint64 id = 1; */ + if (message.id !== 0n) writer.tag(1, WireType.Varint).uint64(message.id); + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message settings.DeletePathRequest + */ +export const DeletePathRequest = new DeletePathRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class DeletePathResponse$Type extends MessageType { + constructor() { + super('settings.DeletePathResponse', []); + } + create(value?: PartialMessage): DeletePathResponse { + const message = globalThis.Object.create(this.messagePrototype!); + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: DeletePathResponse + ): DeletePathResponse { + return target ?? this.create(); + } + internalBinaryWrite( + message: DeletePathResponse, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message settings.DeletePathResponse + */ +export const DeletePathResponse = new DeletePathResponse$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class RefreshPathRequest$Type extends MessageType { + constructor() { + super('settings.RefreshPathRequest', [ + { no: 1, name: 'id', kind: 'scalar', T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): RefreshPathRequest { + const message = globalThis.Object.create(this.messagePrototype!); + message.id = 0n; + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: RefreshPathRequest + ): RefreshPathRequest { + let message = target ?? this.create(), + end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 id */ 1: + message.id = reader.uint64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === 'throw') + throw new globalThis.Error( + `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}` + ); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)( + this.typeName, + message, + fieldNo, + wireType, + d + ); + } + } + return message; + } + internalBinaryWrite( + message: RefreshPathRequest, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + /* uint64 id = 1; */ + if (message.id !== 0n) writer.tag(1, WireType.Varint).uint64(message.id); + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message settings.RefreshPathRequest + */ +export const RefreshPathRequest = new RefreshPathRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class RefreshPathResponse$Type extends MessageType { + constructor() { + super('settings.RefreshPathResponse', []); + } + create(value?: PartialMessage): RefreshPathResponse { + const message = globalThis.Object.create(this.messagePrototype!); + if (value !== undefined) reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead( + reader: IBinaryReader, + length: number, + options: BinaryReadOptions, + target?: RefreshPathResponse + ): RefreshPathResponse { + return target ?? this.create(); + } + internalBinaryWrite( + message: RefreshPathResponse, + writer: IBinaryWriter, + options: BinaryWriteOptions + ): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message settings.RefreshPathResponse + */ +export const RefreshPathResponse = new RefreshPathResponse$Type(); +/** + * @generated ServiceType for protobuf service settings.Settings + */ +export const Settings = new ServiceType('settings.Settings', [ + { name: 'ListPaths', options: {}, I: Empty, O: SettingsData }, + { name: 'AddPath', options: {}, I: AddPathRequest, O: AddPathResponse }, + { name: 'DeletePath', options: {}, I: DeletePathRequest, O: DeletePathResponse }, + { name: 'RefreshPath', options: {}, I: RefreshPathRequest, O: RefreshPathResponse } +]); diff --git a/src/lib/song.ts b/src/lib/song.ts new file mode 100644 index 0000000..6c30da4 --- /dev/null +++ b/src/lib/song.ts @@ -0,0 +1,6 @@ +export type Song = { + hash: string; + name: string; + artistName: string; + artistId: bigint; +}; diff --git a/src/lib/stores/player.ts b/src/lib/stores/player.ts new file mode 100644 index 0000000..ed409c1 --- /dev/null +++ b/src/lib/stores/player.ts @@ -0,0 +1,4 @@ +import { writable } from 'svelte/store'; + +export const currentlyPlaying = writable(null); +export const volume = writable(1.0); diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..7e6d447 --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,8 @@ +import { PlayerClient } from '$lib/proto/player.client'; +import { protoTransport } from '../hooks.server'; +import type { LayoutServerLoad } from './$types'; + +export const load: LayoutServerLoad = async () => { + // const client = new PlayerClient(protoTransport); + // TODO: Get current song +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 9b776b7..18fef6e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,6 +1,31 @@ -{@render children()} + + + + + +
    +
    + + +
    + + {@render children()} +
    +
    +
    +
    +
    +
    + +Groove diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index cc88df0..f95bef3 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1 @@ -

    Welcome to SvelteKit

    -

    Visit svelte.dev/docs/kit to read the documentation

    +

    Home

    diff --git a/src/routes/albums/+page.server.ts b/src/routes/albums/+page.server.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/albums/+page.svelte b/src/routes/albums/+page.svelte new file mode 100644 index 0000000..b8f0f1a --- /dev/null +++ b/src/routes/albums/+page.svelte @@ -0,0 +1 @@ +Albums diff --git a/src/routes/player/+page.server.ts b/src/routes/player/+page.server.ts new file mode 100644 index 0000000..dd52c95 --- /dev/null +++ b/src/routes/player/+page.server.ts @@ -0,0 +1,16 @@ +import { PlayerClient } from '$lib/proto/player.client'; +import { protoTransport } from '../../hooks.server'; +import type { Actions } from './$types'; + +export const actions = { + resume: async () => { + const client = new PlayerClient(protoTransport); + + await client.resumeTrack({}); + }, + pause: async () => { + const client = new PlayerClient(protoTransport); + + await client.pauseTrack({}); + } +} satisfies Actions; diff --git a/src/routes/playlists/+page.server.ts b/src/routes/playlists/+page.server.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/playlists/+page.svelte b/src/routes/playlists/+page.svelte new file mode 100644 index 0000000..aeef039 --- /dev/null +++ b/src/routes/playlists/+page.svelte @@ -0,0 +1 @@ +Playlists diff --git a/src/routes/settings/+page.server.ts b/src/routes/settings/+page.server.ts new file mode 100644 index 0000000..780c789 --- /dev/null +++ b/src/routes/settings/+page.server.ts @@ -0,0 +1,43 @@ +import { SettingsClient } from '$lib/proto/settings.client'; +import { fail } from '@sveltejs/kit'; +import { protoTransport } from '../../hooks.server'; +import type { Actions, PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ depends }) => { + depends('settings:library'); + + const client = new SettingsClient(protoTransport); + + const response = await client.listPaths({}); + + return { + libraryPaths: response.response.libraryPaths.map((p) => ({ + id: p.id, + path: p.path + })) + }; +}; + +export const actions = { + 'add-path': async ({ request }) => { + const formData = await request.formData(); + + const path = formData.get('path')?.toString(); + + if (!path) { + return fail(400, { + errors: 'Invalid path' + }); + } + + const client = new SettingsClient(protoTransport); + + const response = await client.addPath({ + path + }); + + return { + id: response.response.id + }; + } +} satisfies Actions; diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte new file mode 100644 index 0000000..2ea00b6 --- /dev/null +++ b/src/routes/settings/+page.svelte @@ -0,0 +1,73 @@ + + +

    + Settings +

    + + + +
    +
    +
    +

    Appearance

    +

    + Change the way Groove looks +

    +
    + +
    + +
    +
    +

    Library

    +

    + Add the directories you want to include in your library +

    +
    + +
    + + +
    + +
    + {#each settings.libraryPaths as path} +
    + {path.path} +
    + + +
    +
    + {:else} +

    You have not added a directory yet

    + {/each} +
    +
    +
    diff --git a/src/routes/settings/path/[id]/+page.server.ts b/src/routes/settings/path/[id]/+page.server.ts new file mode 100644 index 0000000..7a18b85 --- /dev/null +++ b/src/routes/settings/path/[id]/+page.server.ts @@ -0,0 +1,20 @@ +import type { Actions } from './$types'; +import { SettingsClient } from '$lib/proto/settings.client'; +import { protoTransport } from '../../../../hooks.server'; + +export const actions = { + refresh: async ({ params }) => { + const client = new SettingsClient(protoTransport); + + await client.refreshPath({ + id: BigInt(params.id) + }); + }, + delete: async ({ params }) => { + const client = new SettingsClient(protoTransport); + + await client.deletePath({ + id: BigInt(params.id) + }); + } +} satisfies Actions; diff --git a/src/routes/songs/+page.server.ts b/src/routes/songs/+page.server.ts new file mode 100644 index 0000000..cfa03cc --- /dev/null +++ b/src/routes/songs/+page.server.ts @@ -0,0 +1,20 @@ +import { LibraryClient } from '$lib/proto/library.client'; +import { protoTransport } from '../../hooks.server'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async () => { + const client = new LibraryClient(protoTransport); + + const response = await client.listTracks({}); + + const tracks = response.response.tracks.map((t) => ({ + hash: t.hash, + name: t.name, + artistId: t.artistId, + artistName: t.artistName + })); + + return { + songs: tracks + }; +}; diff --git a/src/routes/songs/+page.svelte b/src/routes/songs/+page.svelte new file mode 100644 index 0000000..d1d2ca9 --- /dev/null +++ b/src/routes/songs/+page.svelte @@ -0,0 +1,18 @@ + + +
    + {#each data.songs as song} + + {/each} +
    diff --git a/src/routes/songs/[hash]/+page.server.ts b/src/routes/songs/[hash]/+page.server.ts new file mode 100644 index 0000000..ca4abb1 --- /dev/null +++ b/src/routes/songs/[hash]/+page.server.ts @@ -0,0 +1,13 @@ +import { PlayerClient } from '$lib/proto/player.client'; +import { protoTransport } from '../../../hooks.server'; +import type { Actions } from './$types'; + +export const actions = { + play: async ({ params }) => { + const client = new PlayerClient(protoTransport); + + await client.playTrack({ + hash: params.hash + }); + } +} satisfies Actions;