Thomas G. Lopes commited on
Commit
19d1d46
·
1 Parent(s): ddebbc2

improve toast logic

Browse files
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.17.8",
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.17.8
86
- version: 0.17.8(@floating-ui/[email protected])([email protected])
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.17.8:
1549
- resolution: {integrity: sha512-k2yRx3B4CTHylSOkXelYh6ds/0/NEhy/Q5pfpF4ETlZH6JZMlzhuO7bWKEuLTxqZ4X2tmAnrQTqWLtSzUB5uGA==}
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.17.8(@floating-ui/[email protected])([email protected]):
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
- <div
313
- class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
314
- style="animation-delay: 0.25s;"
315
- ></div>
316
- <div
317
- class="h-1 w-1 flex-none animate-bounce rounded-full bg-gray-500 dark:bg-gray-100"
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-white sm:text-3xl">
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 {...toaster.root} class="fixed !right-4 !bottom-4 flex w-[300px] flex-col" style:--toasts={toaster.toasts.length}>
 
 
 
 
 
9
  {#each toaster.toasts as toast, i (toast.id)}
10
  <div
11
- class="relative flex h-(--toast-height) w-full flex-col justify-center rounded-xl bg-white px-4 text-left transition dark:bg-gray-800"
12
  {...toast.content}
13
  style:--n={toaster.toasts.length - i}
14
- in:fly={{ y: 60, opacity: 0.9 }}
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
- <div {...toast.description} class="max-w-[200px] text-xs text-gray-700 dark:text-gray-300">
23
  {toast.data.description}
24
- </div>
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
- grid-template-rows: var(--hidden-offset) var(--toast-height) repeat(
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
- --toast-gap: calc(calc(var(--gap) * var(--n)) + var(--hover-offset));
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 />