-
Notifications
You must be signed in to change notification settings - Fork 187
feat: dashboard favourites #9631
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 all commits
88ffa19
e1baab5
cc69880
1fd640f
3f17d37
9381af7
1488ea8
98626eb
001fa6d
bf47792
ab4b206
8a51dde
30acdfa
3955a43
88bc52d
e4cc524
1a2b3f1
4546946
0b99483
2c6c70f
fc452bd
13e6679
6df43e7
c4ea4e1
de8e265
fec19ee
f00e922
2ea6b8a
5932a3f
e538a64
c8fe799
92749de
ccea7e5
888aa46
78591dc
4243d43
f6d30fb
4ea001a
9c29d05
89e360f
d232c4f
658d59e
10fe792
2cafbbe
d32b1fb
b48ce06
f4df65e
1cd0b3c
12a13df
dc6f085
03f480b
b3e4239
9374bb3
21f30d5
f4368ad
57ead88
632edb5
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 |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| --- | ||
| description: Find hardcoded user-facing strings in the branch's changes and migrate them to i18n (paraglide messages), following the repo's i18n conventions | ||
| allowed-tools: Bash(git:*), Bash(npm:*), Glob, Grep, Read, Edit, AskUserQuestion | ||
| argument-hint: "[optional: files/dirs to narrow focus]" | ||
| --- | ||
|
|
||
| Audit the changes on the current branch for hardcoded user-facing strings that should be moved to i18n (Paraglide/inlang messages), following the conventions in `web-common/src/lib/i18n/README.md`. | ||
|
|
||
| Optional focus: $ARGUMENTS | ||
|
|
||
| ## Instructions | ||
|
|
||
| ### 1. Determine the diff to audit | ||
|
|
||
| - If `$ARGUMENTS` lists files or directories, scope the audit to those paths only. | ||
| - Otherwise, audit the PR diff: `git diff --merge-base main` to get changed lines across the branch. | ||
| - Only consider **added or modified** lines. Do not flag pre-existing strings unless the file was explicitly passed in `$ARGUMENTS`. | ||
| - Only frontend files are in scope: `web-common/`, `web-admin/`, `web-local/`, with extensions `.svelte` and `.ts`/`.svelte.ts`. | ||
|
|
||
| ### 2. Find hardcoded user-facing strings | ||
|
|
||
| Mirror the heuristics in `scripts/i18n-guard.js`: | ||
|
|
||
| - **Svelte visible text**: content between `>` and `<` that contains real words. | ||
| - **Human-facing attributes**: `placeholder`, `title`, `aria-label`, `alt`, `label`. | ||
| - **TypeScript**: string literals used as user-facing copy — e.g. `label:`, `header:`, `body:`, toast/notification text, thrown error messages shown to users, option arrays. | ||
|
|
||
| **Skip** (not user-facing): identifiers, URLs, paths, `CONSTANT_CASE`, class/style names, object keys, `console.*`/log messages, code comments, test files (`*.spec.ts`, `*.test.ts`, e2e), and lines with an `i18n-ignore` comment on or above them. | ||
|
|
||
| ### 3. Propose keys, reusing what exists | ||
|
|
||
| For each finding, before proposing a new key, grep `web-common/src/lib/i18n/messages/en.json` for the exact copy — **reuse an existing key** if one matches (especially `common_*`). | ||
|
|
||
| When a new key is needed, follow the README conventions: | ||
|
|
||
| - Naming: `feature_component_purpose`, lower snake_case, grouped by prefix. | ||
| - `common_*` for copy reused across features; otherwise prefix with the feature directory name. | ||
| - **Generic shared components** (in `web-common/src/components/`) should use `common_*` keys, not feature-specific ones. | ||
| - **Interpolation**: named placeholders, never string concatenation — `"Sort by {label}"`, not `"Sort by " + label`. | ||
| - **Pluralization**: use Paraglide variants, not `count === 1 ? ... : ...`. | ||
|
|
||
| ### 4. Watch for these patterns | ||
|
|
||
| - **Module-level `const` in `.ts`** (e.g. an options array with `label`): calling `m.key()` at module load freezes the locale. Use a getter — `get label() { return m.key(); }` — so it resolves lazily and stays locale-reactive. | ||
| - **Composed labels**: parameterize the whole string as one interpolated message rather than concatenating a translated prefix with a value. | ||
|
|
||
| ### 5. Report | ||
|
|
||
| Present a table ordered by significance: `file:line`, the current string, the suggested key (or existing key to reuse), and whether interpolation/variants are needed. Note any strings that are borderline or intentionally left as-is. | ||
|
|
||
| ### 6. Offer to migrate | ||
|
|
||
| Ask whether to apply the migration. If yes, for each string: | ||
|
|
||
| 1. Add the key to **both** `messages/en.json` and `messages/es.json`, placed alphabetically within its prefix group. Provide a real Spanish translation for `es.json`. | ||
| 2. Replace the literal in code with `m.key()` / `m.key({ var })`. | ||
| 3. Run `npm run build:i18n` to compile and confirm the message functions generate cleanly. | ||
| 4. If the change fully migrates a new area, append its directory to `MIGRATED_GLOBS` in `scripts/i18n-guard.js`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,10 @@ | |
| import ResourceTypeBadge from "@rilldata/web-common/features/entity-management/ResourceTypeBadge.svelte"; | ||
| import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; | ||
| import { timeAgo } from "@rilldata/web-common/lib/time/relative-time"; | ||
| import { Star } from "lucide-svelte"; | ||
| import { ArrayRuneStore } from "web-common/src/lib/store-utils/types.svelte.ts"; | ||
| import { m } from "@rilldata/web-common/lib/i18n/gen/messages"; | ||
| import type { RecentlyUsedDashboards } from "./dashboard-favourites.ts"; | ||
|
|
||
| export let name: string; | ||
| export let title: string; | ||
|
|
@@ -17,6 +20,10 @@ | |
| export let organization: string; | ||
| export let project: string; | ||
| export let tags: string[] = []; | ||
| export let dashboardFavourites: ArrayRuneStore<string> | undefined = | ||
| undefined; | ||
| export let recentlyUsedDashboards: RecentlyUsedDashboards | undefined = | ||
| undefined; | ||
|
|
||
| $: lastRefreshedDate = lastRefreshed ? new Date(lastRefreshed) : null; | ||
|
|
||
|
|
@@ -28,9 +35,41 @@ | |
| $: resourceKind = isMetricsExplorer | ||
| ? ResourceKind.Explore | ||
| : ResourceKind.Canvas; | ||
|
|
||
| $: favourites = dashboardFavourites?.value ?? []; | ||
| $: isFavourite = favourites.includes(name?.toLowerCase()); | ||
|
|
||
| $: lastUsed = | ||
| recentlyUsedDashboards?.recentlyUsed?.value?.[name.toLowerCase()]; | ||
| $: lastUsedDate = lastUsed ? new Date(lastUsed) : null; | ||
|
|
||
| let hovered = false; | ||
|
|
||
| function toggleFavourite() { | ||
| dashboardFavourites?.toggle(name?.toLowerCase()); | ||
| } | ||
|
|
||
| function onDashboardFavouriteToggle(e: MouseEvent) { | ||
| e.stopPropagation(); | ||
| e.preventDefault(); | ||
| toggleFavourite(); | ||
| } | ||
|
|
||
| function onDashboardFavouriteKeydown(e: KeyboardEvent) { | ||
| if (e.key !== "Enter" && e.key !== " ") return; | ||
| e.stopPropagation(); | ||
| e.preventDefault(); | ||
| toggleFavourite(); | ||
| } | ||
| </script> | ||
|
|
||
| <a class="flex flex-col gap-y-1 group px-4 py-2.5 w-full h-full" {href}> | ||
| <!-- TODO: Support non-hover actions like using keyboard for showing favourite icon --> | ||
| <a | ||
| class="flex flex-col gap-y-1 group px-4 py-2.5 w-full h-full" | ||
| {href} | ||
| onmouseenter={() => (hovered = true)} | ||
| onmouseleave={() => (hovered = false)} | ||
| > | ||
| <div class="flex gap-x-2 items-center min-h-[20px]"> | ||
| <ResourceTypeBadge kind={resourceKind} /> | ||
| <span | ||
|
|
@@ -44,6 +83,25 @@ | |
| {#each tags as tag (tag)} | ||
| <Tag color="gray">{tag}</Tag> | ||
| {/each} | ||
| <div class="grow"></div> | ||
| {#if dashboardFavourites && (hovered || isFavourite)} | ||
|
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.
For dashboards that are not already favourited, the star is only rendered while Useful? React with 👍 / 👎.
Collaborator
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. This is a problem across the app (eg: bookmarks), will do a follow up to fix. |
||
| <span | ||
| role="button" | ||
| tabindex="0" | ||
| aria-label={isFavourite | ||
| ? m.dashboard_remove_favourite() | ||
| : m.dashboard_add_favourite()} | ||
| aria-pressed={isFavourite} | ||
| class="shrink-0 flex cursor-pointer" | ||
| onclick={onDashboardFavouriteToggle} | ||
| onkeydown={onDashboardFavouriteKeydown} | ||
| > | ||
| <Star | ||
| size="16px" | ||
| class={isFavourite ? "fill-accent-primary-action" : "none"} | ||
| /> | ||
| </span> | ||
| {/if} | ||
| </div> | ||
| <div | ||
| class="flex gap-x-1 text-fg-tertiary text-xs font-normal min-h-[16px] overflow-hidden" | ||
|
|
@@ -62,6 +120,19 @@ | |
| </TooltipContent> | ||
| </Tooltip> | ||
| {/if} | ||
| {#if lastUsedDate} | ||
| <span class="shrink-0">•</span> | ||
| <Tooltip distance={8}> | ||
| <span class="shrink-0" | ||
| >{m.dashboard_last_used_ago({ | ||
| time: timeAgo(lastUsedDate), | ||
| })}</span | ||
| > | ||
| <TooltipContent slot="tooltip-content"> | ||
| {lastUsedDate.toLocaleString()} | ||
| </TooltipContent> | ||
| </Tooltip> | ||
| {/if} | ||
| {#if description} | ||
| <span class="shrink-0">•</span> | ||
| <span class="truncate">{description}</span> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.