Thomas G. Lopes
commited on
Commit
·
19d1d46
1
Parent(s):
ddebbc2
improve toast logic
Browse files- package.json +1 -1
- pnpm-lock.yaml +5 -5
- src/lib/components/debug-menu.svelte +7 -0
- src/lib/components/inference-playground/playground.svelte +13 -12
- src/lib/components/quota-modal.svelte +1 -1
- src/lib/components/toaster.svelte +59 -20
- src/lib/components/toaster.svelte.ts +19 -0
- src/lib/types.ts +2 -0
- src/lib/utils/object.ts +20 -0
- src/routes/+layout.svelte +0 -1
package.json
CHANGED
@@ -36,7 +36,7 @@
|
|
36 |
"globals": "^16.0.0",
|
37 |
"highlight.js": "^11.10.0",
|
38 |
"jiti": "^2.4.2",
|
39 |
-
"melt": "^0.
|
40 |
"postcss": "^8.4.38",
|
41 |
"prettier": "^3.1.1",
|
42 |
"prettier-plugin-svelte": "^3.2.6",
|
|
|
36 |
"globals": "^16.0.0",
|
37 |
"highlight.js": "^11.10.0",
|
38 |
"jiti": "^2.4.2",
|
39 |
+
"melt": "^0.18.4",
|
40 |
"postcss": "^8.4.38",
|
41 |
"prettier": "^3.1.1",
|
42 |
"prettier-plugin-svelte": "^3.2.6",
|
pnpm-lock.yaml
CHANGED
@@ -82,8 +82,8 @@ importers:
|
|
82 |
specifier: ^2.4.2
|
83 |
version: 2.4.2
|
84 |
melt:
|
85 |
-
specifier: ^0.
|
86 |
-
version: 0.
|
87 |
postcss:
|
88 |
specifier: ^8.4.38
|
89 |
version: 8.5.3
|
@@ -1545,8 +1545,8 @@ packages:
|
|
1545 | |
1546 |
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
1547 |
|
1548 |
-
melt@0.
|
1549 |
-
resolution: {integrity: sha512-
|
1550 |
peerDependencies:
|
1551 |
'@floating-ui/dom': ^1.6.0
|
1552 |
svelte: ^5.0.0
|
@@ -3471,7 +3471,7 @@ snapshots:
|
|
3471 |
dependencies:
|
3472 |
'@jridgewell/sourcemap-codec': 1.5.0
|
3473 |
|
3474 |
-
melt@0.
|
3475 |
dependencies:
|
3476 |
'@floating-ui/dom': 1.6.13
|
3477 |
jest-axe: 9.0.0
|
|
|
82 |
specifier: ^2.4.2
|
83 |
version: 2.4.2
|
84 |
melt:
|
85 |
+
specifier: ^0.18.4
|
86 |
+
version: 0.18.4(@floating-ui/[email protected])([email protected])
|
87 |
postcss:
|
88 |
specifier: ^8.4.38
|
89 |
version: 8.5.3
|
|
|
1545 | |
1546 |
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
1547 |
|
1548 |
+
melt@0.18.4:
|
1549 |
+
resolution: {integrity: sha512-AH7im4MEmHS/8MFhGDnfiuF4ibdm0guFWiBvBaF93YIzA0hhuWMcGxD8HdEsQ8q1Q6xIIRm9FdGd0WICc7Nk2A==}
|
1550 |
peerDependencies:
|
1551 |
'@floating-ui/dom': ^1.6.0
|
1552 |
svelte: ^5.0.0
|
|
|
3471 |
dependencies:
|
3472 |
'@jridgewell/sourcemap-codec': 1.5.0
|
3473 |
|
3474 |
+
melt@0.18.4(@floating-ui/[email protected])([email protected]):
|
3475 |
dependencies:
|
3476 |
'@floating-ui/dom': 1.6.13
|
3477 |
jest-axe: 9.0.0
|
src/lib/components/debug-menu.svelte
CHANGED
@@ -68,6 +68,13 @@
|
|
68 |
description: "Something did not work!",
|
69 |
variant: "error",
|
70 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
];
|
72 |
|
73 |
addToast(toastData[Math.floor(Math.random() * toastData.length)]!);
|
|
|
68 |
description: "Something did not work!",
|
69 |
variant: "error",
|
70 |
},
|
71 |
+
|
72 |
+
{
|
73 |
+
title: "Big one",
|
74 |
+
description:
|
75 |
+
"This one has a lot of text. like seriously. its a lot. so this toast should be really big! and we see how that affects the other ones. ",
|
76 |
+
variant: "success",
|
77 |
+
},
|
78 |
];
|
79 |
|
80 |
addToast(toastData[Math.floor(Math.random() * toastData.length)]!);
|
src/lib/components/inference-playground/playground.svelte
CHANGED
@@ -23,6 +23,7 @@
|
|
23 |
import ModelSelector from "./model-selector.svelte";
|
24 |
import ProjectSelect from "./project-select.svelte";
|
25 |
import { showQuotaModal } from "../quota-modal.svelte";
|
|
|
26 |
|
27 |
const startMessageUser: ConversationMessage = { role: "user", content: "" };
|
28 |
|
@@ -207,6 +208,7 @@
|
|
207 |
? 'md:grid-cols-[clamp(220px,20%,350px)_minmax(0,1fr)]'
|
208 |
: 'md:grid-cols-[clamp(220px,20%,350px)_minmax(0,1fr)_clamp(270px,25%,300px)]'}"
|
209 |
>
|
|
|
210 |
<div class="flex flex-col gap-2 overflow-y-auto py-3 pr-3 max-md:pl-3">
|
211 |
<div class="pl-2">
|
212 |
<ProjectSelect />
|
@@ -234,7 +236,10 @@
|
|
234 |
></textarea>
|
235 |
</div>
|
236 |
</div>
|
|
|
|
|
237 |
<div class="relative divide-y divide-gray-200 dark:divide-gray-800" onkeydown={onKeydown}>
|
|
|
238 |
<div
|
239 |
class="flex h-[calc(100dvh-5rem-120px)] divide-x divide-gray-200 overflow-x-auto overflow-y-hidden *:w-full max-sm:w-dvw md:h-[calc(100dvh-5rem)] md:pt-3 dark:divide-gray-800"
|
240 |
>
|
@@ -309,18 +314,12 @@
|
|
309 |
Cancel
|
310 |
{/if}
|
311 |
</span>
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
style="animation-delay: 0.5s;"
|
319 |
-
></div>
|
320 |
-
<div
|
321 |
-
class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
|
322 |
-
style="animation-delay: 0.75s;"
|
323 |
-
></div>
|
324 |
</div>
|
325 |
{:else}
|
326 |
Run <span
|
@@ -332,6 +331,8 @@
|
|
332 |
</div>
|
333 |
</div>
|
334 |
</div>
|
|
|
|
|
335 |
{#if !compareActive}
|
336 |
<div class="flex flex-col p-3 {viewSettings ? 'max-md:fixed' : 'max-md:hidden'} max-md:inset-x-0 max-md:bottom-20">
|
337 |
<div
|
|
|
23 |
import ModelSelector from "./model-selector.svelte";
|
24 |
import ProjectSelect from "./project-select.svelte";
|
25 |
import { showQuotaModal } from "../quota-modal.svelte";
|
26 |
+
import Toaster from "../toaster.svelte";
|
27 |
|
28 |
const startMessageUser: ConversationMessage = { role: "user", content: "" };
|
29 |
|
|
|
208 |
? 'md:grid-cols-[clamp(220px,20%,350px)_minmax(0,1fr)]'
|
209 |
: 'md:grid-cols-[clamp(220px,20%,350px)_minmax(0,1fr)_clamp(270px,25%,300px)]'}"
|
210 |
>
|
211 |
+
<!-- First column -->
|
212 |
<div class="flex flex-col gap-2 overflow-y-auto py-3 pr-3 max-md:pl-3">
|
213 |
<div class="pl-2">
|
214 |
<ProjectSelect />
|
|
|
236 |
></textarea>
|
237 |
</div>
|
238 |
</div>
|
239 |
+
|
240 |
+
<!-- Center column -->
|
241 |
<div class="relative divide-y divide-gray-200 dark:divide-gray-800" onkeydown={onKeydown}>
|
242 |
+
<Toaster />
|
243 |
<div
|
244 |
class="flex h-[calc(100dvh-5rem-120px)] divide-x divide-gray-200 overflow-x-auto overflow-y-hidden *:w-full max-sm:w-dvw md:h-[calc(100dvh-5rem)] md:pt-3 dark:divide-gray-800"
|
245 |
>
|
|
|
314 |
Cancel
|
315 |
{/if}
|
316 |
</span>
|
317 |
+
{#each { length: 3 } as _, i}
|
318 |
+
<div
|
319 |
+
class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
|
320 |
+
style="animation-delay: {(i + 1) * 0.25}s;"
|
321 |
+
></div>
|
322 |
+
{/each}
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
</div>
|
324 |
{:else}
|
325 |
Run <span
|
|
|
331 |
</div>
|
332 |
</div>
|
333 |
</div>
|
334 |
+
|
335 |
+
<!-- Last column -->
|
336 |
{#if !compareActive}
|
337 |
<div class="flex flex-col p-3 {viewSettings ? 'max-md:fixed' : 'max-md:hidden'} max-md:inset-x-0 max-md:bottom-20">
|
338 |
<div
|
src/lib/components/quota-modal.svelte
CHANGED
@@ -44,7 +44,7 @@
|
|
44 |
use:clickOutside={() => (open = false)}
|
45 |
transition:scale={{ start: 0.975, duration: 250 }}
|
46 |
>
|
47 |
-
<h2 class="mt-8 text-center text-2xl font-semibold text-balance text-
|
48 |
Upgrade Your AI Experience with a <LabelPro
|
49 |
class="mx-0.5 -translate-y-px align-middle leading-6 max-sm:py-0! sm:text-xl"
|
50 |
/> Account
|
|
|
44 |
use:clickOutside={() => (open = false)}
|
45 |
transition:scale={{ start: 0.975, duration: 250 }}
|
46 |
>
|
47 |
+
<h2 class="mt-8 text-center text-2xl font-semibold text-balance sm:text-3xl dark:text-white">
|
48 |
Upgrade Your AI Experience with a <LabelPro
|
49 |
class="mx-0.5 -translate-y-px align-middle leading-6 max-sm:py-0! sm:text-xl"
|
50 |
/> Account
|
src/lib/components/toaster.svelte
CHANGED
@@ -3,25 +3,71 @@
|
|
3 |
import { toaster } from "./toaster.svelte.js";
|
4 |
import { Progress } from "melt/components";
|
5 |
import Close from "~icons/carbon/close";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
</script>
|
7 |
|
8 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
9 |
{#each toaster.toasts as toast, i (toast.id)}
|
10 |
<div
|
11 |
-
class="
|
12 |
{...toast.content}
|
13 |
style:--n={toaster.toasts.length - i}
|
14 |
-
in:fly={{ y:
|
15 |
out:fly={{ y: 20 }}
|
|
|
16 |
>
|
17 |
<h3 {...toast.title} class="text-sm font-medium whitespace-nowrap text-gray-700 dark:text-gray-300">
|
18 |
{toast.data.title}
|
19 |
</h3>
|
20 |
|
21 |
{#if toast.data.description}
|
22 |
-
<
|
23 |
{toast.data.description}
|
24 |
-
</
|
25 |
{/if}
|
26 |
|
27 |
<button
|
@@ -61,25 +107,22 @@
|
|
61 |
[data-melt-toaster-root] {
|
62 |
--gap: 0.75rem;
|
63 |
--hover-offset: 0rem;
|
64 |
-
--toast-height: 4.5rem;
|
65 |
--hidden-offset: 0.75rem;
|
66 |
|
67 |
--hidden-toasts: calc(var(--toasts) - 1);
|
68 |
|
69 |
overflow: visible;
|
70 |
-
display: grid;
|
71 |
-
grid-template-rows: var(--toast-height) repeat(var(--hidden-toasts), var(--hidden-offset));
|
72 |
-
grid-template-columns: 1fr;
|
73 |
gap: 0;
|
74 |
background: unset;
|
75 |
padding: 0;
|
|
|
|
|
|
|
76 |
}
|
77 |
|
78 |
[data-melt-toaster-root]:hover {
|
79 |
-
|
80 |
-
var(--hidden-toasts),
|
81 |
-
calc(var(--toast-height) + var(--gap))
|
82 |
-
);
|
83 |
}
|
84 |
|
85 |
[data-melt-toaster-toast-content] {
|
@@ -91,6 +134,8 @@
|
|
91 |
|
92 |
transform-origin: 50% 0%;
|
93 |
transition: all 350ms ease;
|
|
|
|
|
94 |
}
|
95 |
|
96 |
:global(.dark [data-melt-toaster-toast-content]) {
|
@@ -101,32 +146,26 @@
|
|
101 |
z-index: 1;
|
102 |
scale: 0.925;
|
103 |
opacity: 0;
|
104 |
-
translate: 0 calc(-3 * var(--hidden-offset));
|
105 |
}
|
106 |
|
107 |
[data-melt-toaster-toast-content]:nth-last-child(-n + 3) {
|
108 |
z-index: 2;
|
109 |
scale: 0.95;
|
110 |
-
translate: 0 calc(-2 * var(--hidden-offset));
|
111 |
}
|
112 |
|
113 |
[data-melt-toaster-toast-content]:nth-last-child(-n + 2) {
|
114 |
z-index: 3;
|
115 |
scale: 0.975;
|
116 |
-
translate: 0 calc(-1 * var(--hidden-offset));
|
117 |
}
|
118 |
|
119 |
[data-melt-toaster-toast-content]:nth-last-child(-n + 1) {
|
120 |
z-index: 4;
|
121 |
scale: 1;
|
122 |
-
translate: 0;
|
123 |
}
|
124 |
|
125 |
[data-melt-toaster-root]:hover [data-melt-toaster-toast-content] {
|
126 |
scale: 1;
|
127 |
opacity: 1;
|
128 |
-
|
129 |
-
--percentage: calc(-100% * calc(var(--n) - 1));
|
130 |
-
translate: 0 calc(var(--percentage) - var(--toast-gap));
|
131 |
}
|
132 |
</style>
|
|
|
3 |
import { toaster } from "./toaster.svelte.js";
|
4 |
import { Progress } from "melt/components";
|
5 |
import Close from "~icons/carbon/close";
|
6 |
+
import { omit } from "$lib/utils/object.js";
|
7 |
+
import { session } from "$lib/state/session.svelte.js";
|
8 |
+
import { AnimationFrames } from "runed";
|
9 |
+
|
10 |
+
let toastHeights = $state<number[]>([]);
|
11 |
+
new AnimationFrames(() => {
|
12 |
+
const rootEl = document.getElementById(toaster.root.id);
|
13 |
+
if (!rootEl) return;
|
14 |
+
|
15 |
+
const toastEls = Array.from(rootEl.querySelectorAll("[data-melt-toaster-toast-content]"));
|
16 |
+
toastHeights = toastEls.map(el => el.clientHeight);
|
17 |
+
// console.log(toastHeights);
|
18 |
+
});
|
19 |
+
|
20 |
+
const isComparing = $derived(session.project.conversations.length > 1);
|
21 |
+
|
22 |
+
const GAP = 8;
|
23 |
+
|
24 |
+
function getToastStyle(i: number) {
|
25 |
+
// Remember, the order is reversed! Meaning i=0 was the first toast, so its the last
|
26 |
+
// we want to show.
|
27 |
+
const n = toaster.toasts.length - i - 1;
|
28 |
+
if (n === 0) return "";
|
29 |
+
const reversedHeights = toastHeights.toReversed();
|
30 |
+
const yHover = -1 * reversedHeights.slice(0, n).reduce((a, b) => a + b + GAP, 0);
|
31 |
+
|
32 |
+
const y = -n * 10;
|
33 |
+
|
34 |
+
return `
|
35 |
+
--y-hover: ${yHover}px;
|
36 |
+
--y: ${y}px;
|
37 |
+
`;
|
38 |
+
}
|
39 |
+
|
40 |
+
function getRootStyle() {
|
41 |
+
const heightHover = toastHeights.reduce((a, b) => a + b + GAP, 0);
|
42 |
+
return `
|
43 |
+
--h-hover: ${heightHover}px;
|
44 |
+
`;
|
45 |
+
}
|
46 |
</script>
|
47 |
|
48 |
+
<div
|
49 |
+
{...omit(toaster.root, "popover")}
|
50 |
+
class={["absolute right-2 bottom-23 flex w-[300px] flex-col ", !isComparing && "md:right-0"]}
|
51 |
+
style:--toasts={toaster.toasts.length}
|
52 |
+
style={getRootStyle()}
|
53 |
+
>
|
54 |
{#each toaster.toasts as toast, i (toast.id)}
|
55 |
<div
|
56 |
+
class="flex w-full flex-col justify-center rounded-xl bg-white px-4 py-4 text-left transition dark:bg-gray-800"
|
57 |
{...toast.content}
|
58 |
style:--n={toaster.toasts.length - i}
|
59 |
+
in:fly={{ y: 20, opacity: 0 }}
|
60 |
out:fly={{ y: 20 }}
|
61 |
+
style={getToastStyle(i)}
|
62 |
>
|
63 |
<h3 {...toast.title} class="text-sm font-medium whitespace-nowrap text-gray-700 dark:text-gray-300">
|
64 |
{toast.data.title}
|
65 |
</h3>
|
66 |
|
67 |
{#if toast.data.description}
|
68 |
+
<p {...toast.description} class="max-w-[200px] text-xs text-gray-700 dark:text-gray-300">
|
69 |
{toast.data.description}
|
70 |
+
</p>
|
71 |
{/if}
|
72 |
|
73 |
<button
|
|
|
107 |
[data-melt-toaster-root] {
|
108 |
--gap: 0.75rem;
|
109 |
--hover-offset: 0rem;
|
110 |
+
/* --toast-height: 4.5rem; */
|
111 |
--hidden-offset: 0.75rem;
|
112 |
|
113 |
--hidden-toasts: calc(var(--toasts) - 1);
|
114 |
|
115 |
overflow: visible;
|
|
|
|
|
|
|
116 |
gap: 0;
|
117 |
background: unset;
|
118 |
padding: 0;
|
119 |
+
|
120 |
+
border: 1px solid var(--color-emerald-500);
|
121 |
+
height: var(--h);
|
122 |
}
|
123 |
|
124 |
[data-melt-toaster-root]:hover {
|
125 |
+
height: var(--h-hover);
|
|
|
|
|
|
|
126 |
}
|
127 |
|
128 |
[data-melt-toaster-toast-content] {
|
|
|
134 |
|
135 |
transform-origin: 50% 0%;
|
136 |
transition: all 350ms ease;
|
137 |
+
|
138 |
+
translate: 0 var(--y);
|
139 |
}
|
140 |
|
141 |
:global(.dark [data-melt-toaster-toast-content]) {
|
|
|
146 |
z-index: 1;
|
147 |
scale: 0.925;
|
148 |
opacity: 0;
|
|
|
149 |
}
|
150 |
|
151 |
[data-melt-toaster-toast-content]:nth-last-child(-n + 3) {
|
152 |
z-index: 2;
|
153 |
scale: 0.95;
|
|
|
154 |
}
|
155 |
|
156 |
[data-melt-toaster-toast-content]:nth-last-child(-n + 2) {
|
157 |
z-index: 3;
|
158 |
scale: 0.975;
|
|
|
159 |
}
|
160 |
|
161 |
[data-melt-toaster-toast-content]:nth-last-child(-n + 1) {
|
162 |
z-index: 4;
|
163 |
scale: 1;
|
|
|
164 |
}
|
165 |
|
166 |
[data-melt-toaster-root]:hover [data-melt-toaster-toast-content] {
|
167 |
scale: 1;
|
168 |
opacity: 1;
|
169 |
+
translate: 0 var(--y-hover);
|
|
|
|
|
170 |
}
|
171 |
</style>
|
src/lib/components/toaster.svelte.ts
CHANGED
@@ -8,6 +8,7 @@ export type ToastData = {
|
|
8 |
|
9 |
export const toaster = new Toaster<ToastData>({
|
10 |
hover: "pause-all",
|
|
|
11 |
});
|
12 |
|
13 |
export function addToast(data: ToastData) {
|
@@ -17,3 +18,21 @@ export function addToast(data: ToastData) {
|
|
17 |
export function removeToast(id: string) {
|
18 |
toaster.removeToast(id);
|
19 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
export const toaster = new Toaster<ToastData>({
|
10 |
hover: "pause-all",
|
11 |
+
closeDelay: 0,
|
12 |
});
|
13 |
|
14 |
export function addToast(data: ToastData) {
|
|
|
18 |
export function removeToast(id: string) {
|
19 |
toaster.removeToast(id);
|
20 |
}
|
21 |
+
|
22 |
+
addToast({
|
23 |
+
title: "Hello World 1",
|
24 |
+
description: "hey",
|
25 |
+
variant: "success",
|
26 |
+
});
|
27 |
+
|
28 |
+
addToast({
|
29 |
+
title: "Hello World 2",
|
30 |
+
description: "hey",
|
31 |
+
variant: "success",
|
32 |
+
});
|
33 |
+
|
34 |
+
addToast({
|
35 |
+
title: "Hello World 3",
|
36 |
+
description: "hi",
|
37 |
+
variant: "success",
|
38 |
+
});
|
src/lib/types.ts
CHANGED
@@ -172,3 +172,5 @@ export enum PipelineTag {
|
|
172 |
}
|
173 |
|
174 |
export type MaybeGetter<T> = T | (() => T);
|
|
|
|
|
|
172 |
}
|
173 |
|
174 |
export type MaybeGetter<T> = T | (() => T);
|
175 |
+
|
176 |
+
export type ValueOf<T> = T[keyof T];
|
src/lib/utils/object.ts
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
// typed Object.keys
|
2 |
export function keys<T extends object>(o: T): (keyof T)[] {
|
3 |
return Object.keys(o) as (keyof T)[];
|
@@ -12,3 +14,21 @@ export function entries<T extends object>(o: T): [keyof T, T[keyof T]][] {
|
|
12 |
export function fromEntries<T extends object>(entries: [keyof T, T[keyof T]][]): T {
|
13 |
return Object.fromEntries(entries) as T;
|
14 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { ValueOf } from "$lib/types.js";
|
2 |
+
|
3 |
// typed Object.keys
|
4 |
export function keys<T extends object>(o: T): (keyof T)[] {
|
5 |
return Object.keys(o) as (keyof T)[];
|
|
|
14 |
export function fromEntries<T extends object>(entries: [keyof T, T[keyof T]][]): T {
|
15 |
return Object.fromEntries(entries) as T;
|
16 |
}
|
17 |
+
|
18 |
+
export function omit<T extends Record<string, unknown>, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> {
|
19 |
+
const result = {} as Omit<T, K>;
|
20 |
+
for (const key of Object.keys(obj)) {
|
21 |
+
if (!keys.includes(key as unknown as K)) {
|
22 |
+
result[key as keyof Omit<T, K>] = obj[key] as ValueOf<Omit<T, K>>;
|
23 |
+
}
|
24 |
+
}
|
25 |
+
return result;
|
26 |
+
}
|
27 |
+
|
28 |
+
export function pick<T extends Record<string, unknown>, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
|
29 |
+
const result = {} as Pick<T, K>;
|
30 |
+
for (const key of keys) {
|
31 |
+
result[key] = obj[key] as ValueOf<Pick<T, K>>;
|
32 |
+
}
|
33 |
+
return result;
|
34 |
+
}
|
src/routes/+layout.svelte
CHANGED
@@ -14,5 +14,4 @@
|
|
14 |
{@render children?.()}
|
15 |
<DebugMenu />
|
16 |
<Prompts />
|
17 |
-
<Toaster />
|
18 |
<QuotaModal />
|
|
|
14 |
{@render children?.()}
|
15 |
<DebugMenu />
|
16 |
<Prompts />
|
|
|
17 |
<QuotaModal />
|