-
Notifications
You must be signed in to change notification settings - Fork 253
Command center and migrations #479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 78 commits
fc2932b
a7c53ae
afcbb5d
05d9cb7
41ce56e
bf77043
adbd2c7
46c7a26
ea8a924
6874ab2
d01c078
7de0a21
142e89a
dce2d82
7badf3a
318d72d
de42801
411fa32
ca7c9c0
ba27b42
5069ee2
0a74fd9
920c65f
47d51eb
9cb611b
6ac6c6a
29d5826
7a18b76
1ca164b
9382eff
92032f5
5256a13
09df201
1ee4daf
bd3c086
7a6a44d
f32e1d5
2bfd693
21de81d
59ccdf8
1daa098
e7c9eab
60591cc
0bbd82e
1c0b050
f977fab
e91ef41
1a9207d
b4c1516
cf21609
e6363ec
e983413
dd0bc31
89255a1
79feb7d
edea04c
7d4009f
311d5c8
c8e9da2
aef914d
1957154
4238efe
cb20d7a
11dd905
13e972d
d02fd1c
09a9be3
23cb171
cd193f2
40396d2
08f0202
a4fddd6
575dcf2
248fbb6
5535d43
9de3158
e11b177
297974f
597ae77
cfd815d
b9c4393
7c03f05
ece145d
791f55f
2e13a56
9a31ce8
10acb77
1fb3a01
14051cc
c76a61e
fe90912
f0961b4
8c4825f
41102e6
e45823f
92d3baf
0b21fe4
1fdd2fc
263e328
92af191
cee7726
d0693ad
382b473
5d2aace
f922cc1
274e110
8d8d690
d32bfc5
ea2ae81
57d5541
ccc9a72
42c8e91
836a3b0
58f652f
2664b54
545a256
6cb932b
d437f64
deb173a
29924a2
1c8d520
e8f73d5
1a9a61d
f9c9b19
8b6ae78
1cfe0e3
68f8fe7
d5e26ca
c8b6048
87a4ef3
bf9617e
1c38e70
5d4d2ff
3164453
98143ef
6d9a528
dd44289
35a74b2
6cb9333
5d5f6a0
d64b9cd
af6480a
e674fe3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,3 +13,5 @@ node_modules | |
| node_modules/ | ||
| dist/ | ||
| .vercel | ||
|
|
||
| *.swp | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {} | ||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import { tick } from 'svelte'; | ||
| import type { Action } from 'svelte/action'; | ||
|
|
||
| export type PortalConfig = string | HTMLElement | undefined; | ||
|
|
||
| export const portal: Action<HTMLElement, PortalConfig> = (el, target = 'body') => { | ||
| let targetEl; | ||
|
|
||
| async function update(newTarget: HTMLElement | string | undefined) { | ||
|
TGlide marked this conversation as resolved.
Outdated
|
||
| target = newTarget; | ||
| if (typeof target === 'string') { | ||
| targetEl = document.querySelector(target); | ||
| if (targetEl === null) { | ||
| await tick(); | ||
| targetEl = document.querySelector(target); | ||
| } | ||
| if (targetEl === null) { | ||
| throw new Error(`No element found matching css selector: "${target}"`); | ||
| } | ||
| } else if (target instanceof HTMLElement) { | ||
| targetEl = target; | ||
| } else { | ||
| throw new TypeError( | ||
| `Unknown portal target type: ${ | ||
| target === null ? 'null' : typeof target | ||
| }. Allowed types: string (CSS selector) or HTMLElement.` | ||
| ); | ||
| } | ||
| targetEl.appendChild(el); | ||
| el.hidden = false; | ||
| } | ||
|
|
||
| function destroy() { | ||
| el.remove(); | ||
| } | ||
|
|
||
| update(target); | ||
| return { | ||
| update, | ||
| destroy | ||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| <script lang="ts" context="module"> | ||
| type Context = Readable<{ | ||
| isInitialPanel: boolean; | ||
| open: boolean; | ||
| }>; | ||
| type ReadableValue<T> = T extends Readable<infer U> ? U : never; | ||
|
|
||
| const contextKey = 'command-center'; | ||
| export const getCommandCenterCtx = () => getContext<Context>(contextKey); | ||
| const setCommandCenterCtx = (value: ReadableValue<Context>) => { | ||
| const store = writable(value); | ||
| setContext(contextKey, store); | ||
| return store; | ||
| }; | ||
|
|
||
| export const toggleCommandCenter = () => { | ||
| if (get(subPanels).length > 0) { | ||
| clearSubPanels(); | ||
| } else { | ||
| addSubPanel(RootPanel); | ||
| } | ||
| }; | ||
| </script> | ||
|
|
||
| <script lang="ts"> | ||
| import { dev } from '$app/environment'; | ||
| import { afterNavigate } from '$app/navigation'; | ||
| import { portal } from '$lib/actions/portal'; | ||
| import { last } from '$lib/helpers/array'; | ||
| import { debounce } from '$lib/helpers/debounce'; | ||
| import { getContext, setContext } from 'svelte'; | ||
| import { get, writable, type Readable } from 'svelte/store'; | ||
| import { fade } from 'svelte/transition'; | ||
| import { commandCenterKeyDownHandler, disableCommands, registerCommands } from './commands'; | ||
| import { RootPanel } from './panels'; | ||
| import { addSubPanel, clearSubPanels, subPanels } from './subPanels'; | ||
|
|
||
| $: $registerCommands([ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we typically put all these
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not necessarily, as the order of where they are matters (Svelte uses static code analysis to determine how to execute the code, so we can't be super liberal with this: https://svelte.dev/docs/svelte-components#script-3-$-marks-a-statement-as-reactive) |
||
| { | ||
| callback: toggleCommandCenter, | ||
| keys: ['k'], | ||
| ctrl: true, | ||
| forceEnable: true | ||
| } | ||
| ]); | ||
|
|
||
| $: openSubPanel = last($subPanels) ?? null; | ||
| $: $disableCommands(!!openSubPanel); | ||
|
|
||
| $: if (openSubPanel) { | ||
| document.documentElement.classList.add('u-overflow-hidden'); | ||
| } else { | ||
| document.documentElement.classList.remove('u-overflow-hidden'); | ||
| } | ||
|
|
||
| let dialog: HTMLDivElement; | ||
|
|
||
| function handleBlur(event: MouseEvent) { | ||
| if (event.target === dialog) { | ||
| clearSubPanels(); | ||
| } | ||
| } | ||
|
|
||
| afterNavigate(() => { | ||
| clearSubPanels(); | ||
| }); | ||
|
|
||
| const ctx = setCommandCenterCtx({ | ||
| isInitialPanel: true, | ||
| open: false | ||
| }); | ||
|
|
||
| $: if (!openSubPanel) { | ||
| $ctx.isInitialPanel = true; | ||
| } | ||
| $: $subPanels.length > 1 && ($ctx.isInitialPanel = false); | ||
|
|
||
| $: $ctx.open = !!openSubPanel; | ||
|
|
||
| let keys: string[] = []; | ||
|
|
||
| const resetKeys = debounce(() => { | ||
| keys = []; | ||
| }, 1000); | ||
|
|
||
| function isInputEvent(event: KeyboardEvent) { | ||
| return ['INPUT', 'TEXTAREA', 'SELECT'].includes((event.target as HTMLElement).tagName); | ||
| } | ||
|
|
||
| const handleKeydown = (e) => { | ||
| if (isInputEvent(e)) return; | ||
| if (!$subPanels.length) { | ||
| keys = [...keys, e.key].slice(-10); | ||
| resetKeys(); | ||
| } | ||
| $commandCenterKeyDownHandler(e); | ||
| }; | ||
| </script> | ||
|
|
||
| <svelte:window on:mousedown={handleBlur} on:keydown={handleKeydown} /> | ||
|
|
||
| {#if openSubPanel} | ||
| <div class="dialog" bind:this={dialog} transition:fade={{ duration: 100 }}> | ||
| <svelte:component this={openSubPanel.component} /> | ||
| </div> | ||
| {/if} | ||
|
|
||
| {#if dev} | ||
| <div class="debug-keys" use:portal> | ||
| {#each keys as key, i (i)} | ||
| <kbd class="kbd" transition:fade|local={{ duration: 150 }}> | ||
| {key.length === 1 ? key.toUpperCase() : key} | ||
| </kbd> | ||
| {/each} | ||
| </div> | ||
| {/if} | ||
|
|
||
| <style lang="scss"> | ||
| .dialog { | ||
| padding: 0.5rem; | ||
| position: fixed; | ||
| inset: 0; | ||
|
|
||
| background-color: hsl(var(--color-neutral-500) / 0.5); | ||
| z-index: 9999; | ||
| } | ||
|
|
||
| .debug-keys { | ||
| position: fixed; | ||
| bottom: 10%; | ||
| left: 50%; | ||
| transform: translateX(-50%); | ||
| padding: 0.5rem; | ||
| // background-color: hsl(var(--color-neutral-500) / 0.5); | ||
| z-index: 9999; | ||
|
|
||
| display: flex; | ||
| gap: 1rem; | ||
|
|
||
| font-size: 2rem; | ||
|
|
||
| .kbd { | ||
| padding-inline: 0.5rem; | ||
| padding-block: 1.5rem; | ||
| } | ||
| } | ||
| </style> | ||
Uh oh!
There was an error while loading. Please reload this page.