mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
feat(spaces): Phase 2d.5b — tag-source picker in SpaceCreateDialog
Wires the Phase 2d.5a helpers (applyPresetToSpace, copyTagsBetweenSpaces) into the new-Space UX so users get a familiar tag taxonomy in every Space they create, without manual re-entry. The dialog gains a "Tag-Set" dropdown: - "Leer starten" — new Space starts without any tags - "Aus <current> kopieren" — clones the user's active Space's globalTags + tagGroups as a one-shot snapshot (fresh ids, no live link back to the source) - <named-preset> — applies a userTagPreset snapshot, creating tagGroups for each distinct groupName so the user's familiar grouping carries over Default pick (when the dropdown first renders): - If the user has a default preset → that preset - Else if currently in Personal → "copy-current" - Else "empty" (safer inside shared Spaces — don't leak Team/Family taxonomy into a new one by default) Seeding runs BEFORE the Space activation switches context, so copyTagsBetweenSpaces still sees the source-Space's tags as read-scope. Seeding failures are caught and logged but deliberately non-fatal — the Space is already created, the user can seed later from inside it. `<select>` styling piggy-backs on the existing .field input/textarea rules (extends the shared selector list instead of duplicating). Type-check + Svelte a11y check clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4d5a96e21b
commit
81a426af28
1 changed files with 75 additions and 2 deletions
|
|
@ -12,6 +12,9 @@
|
|||
import { SPACE_TYPES, SPACE_TYPE_LABELS, SPACE_TYPE_DESCRIPTIONS } from '@mana/shared-branding';
|
||||
import type { SpaceType } from '@mana/shared-types';
|
||||
import { loadActiveSpace, authFetch, writeActiveSpaceHint } from '$lib/data/scope';
|
||||
import { getActiveSpace } from '$lib/data/scope/active-space.svelte';
|
||||
import { useUserTagPresets } from '$lib/data/tag-presets/queries';
|
||||
import { tagPresetsStore } from '$lib/data/tag-presets/store.svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
|
@ -31,6 +34,29 @@
|
|||
let submitting = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// ── Tag-set seeding ──────────────────────────────────────────
|
||||
// 'empty' = the new Space starts without any tags.
|
||||
// 'copy-current' = clones the user's current Space tags as a one-shot.
|
||||
// <presetId> = applies the named preset's frozen snapshot.
|
||||
//
|
||||
// Default depends on the active Space: if the user is currently in
|
||||
// Personal, "copy-current" is a sensible default (most users manage
|
||||
// their tag taxonomy there). Inside a shared Space, "empty" is safer
|
||||
// so the user doesn't unintentionally leak Team/Family tag taxonomy
|
||||
// into the new Space.
|
||||
const activeSpace = $derived(getActiveSpace());
|
||||
const presets = $derived(useUserTagPresets());
|
||||
let tagSource = $state<'empty' | 'copy-current' | string>('empty');
|
||||
|
||||
$effect(() => {
|
||||
// Pick a sensible default for tagSource whenever the active Space
|
||||
// (re)loads. User can still flip it manually.
|
||||
if (tagSource !== 'empty') return;
|
||||
const defaultPreset = presets.value.find((p) => p.isDefault);
|
||||
if (defaultPreset) tagSource = defaultPreset.id;
|
||||
else if (activeSpace?.type === 'personal') tagSource = 'copy-current';
|
||||
});
|
||||
|
||||
/**
|
||||
* Keep `slug` in sync with `name` until the user edits the slug
|
||||
* directly. A minimal slugifier — lowercase alphanumerics + hyphens,
|
||||
|
|
@ -87,6 +113,26 @@
|
|||
throw new Error(text || `create failed: ${res.status}`);
|
||||
}
|
||||
const created = (await res.json()) as { id: string };
|
||||
|
||||
// Seed tags BEFORE activating the Space so copyTagsBetweenSpaces
|
||||
// can still read from the current active Space as the source.
|
||||
// applyPresetToSpace reads the preset from the user-level
|
||||
// userTagPresets table (active-space-agnostic) so it works
|
||||
// either way. Seeding failures are caught and surfaced but
|
||||
// don't undo the Space creation — the user can seed later from
|
||||
// inside the new Space.
|
||||
try {
|
||||
const sourceSpaceId = activeSpace?.id;
|
||||
if (tagSource === 'copy-current' && sourceSpaceId) {
|
||||
await tagPresetsStore.copyTagsBetweenSpaces(sourceSpaceId, created.id);
|
||||
} else if (tagSource !== 'empty' && tagSource !== 'copy-current') {
|
||||
await tagPresetsStore.applyPresetToSpace(tagSource, created.id);
|
||||
}
|
||||
} catch (seedErr) {
|
||||
console.error('[SpaceCreateDialog] tag-seeding failed:', seedErr);
|
||||
// Deliberately non-fatal — proceed with activation.
|
||||
}
|
||||
|
||||
// Activate the new space so the user lands inside it on reload.
|
||||
await authFetch('/api/auth/organization/set-active', {
|
||||
method: 'POST',
|
||||
|
|
@ -195,6 +241,31 @@
|
|||
</label>
|
||||
{/if}
|
||||
|
||||
<label class="field">
|
||||
<span>{locale === 'de' ? 'Tag-Set' : 'Tag set'}</span>
|
||||
<select bind:value={tagSource}>
|
||||
<option value="empty">{locale === 'de' ? 'Leer starten' : 'Start empty'}</option>
|
||||
{#if activeSpace}
|
||||
<option value="copy-current">
|
||||
{locale === 'de'
|
||||
? `Aus „${activeSpace.name}" kopieren`
|
||||
: `Copy from "${activeSpace.name}"`}
|
||||
</option>
|
||||
{/if}
|
||||
{#each presets.value as preset (preset.id)}
|
||||
<option value={preset.id}>
|
||||
{preset.name} ({preset.tags.length}
|
||||
{locale === 'de' ? 'Tags' : 'tags'})
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<small class="hint">
|
||||
{locale === 'de'
|
||||
? 'Tags werden einmalig kopiert — der neue Space entwickelt sich unabhängig weiter.'
|
||||
: 'Tags get one-shot-copied — the new Space then evolves independently.'}
|
||||
</small>
|
||||
</label>
|
||||
|
||||
{#if error}
|
||||
<div class="error">{error}</div>
|
||||
{/if}
|
||||
|
|
@ -369,7 +440,8 @@
|
|||
}
|
||||
|
||||
.field input,
|
||||
.field textarea {
|
||||
.field textarea,
|
||||
.field select {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 8px;
|
||||
|
|
@ -391,7 +463,8 @@
|
|||
}
|
||||
|
||||
.field input:focus,
|
||||
.field textarea:focus {
|
||||
.field textarea:focus,
|
||||
.field select:focus {
|
||||
outline: none;
|
||||
border-color: var(--pill-primary-color, hsl(var(--color-primary, 230 80% 55%)));
|
||||
box-shadow: 0 0 0 3px
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue