mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:41:09 +02:00
feat(workbench): improve fullscreen, scene bar styling, and inline scene creation
- PageShell maximized: fill viewport edge-to-edge (max-width none, z-index 95 above bottom bar, border none), constrain body/header to 48rem centered, Esc exits fullscreen - SceneAppBar: pill-shaped bar and items, match TagStrip font size (0.9375rem), visual group bracket for active scene with separator, tab count on all scenes, inline name input for new scenes instead of modal dialog - SceneRenameDialog: remove icon field, simplify to name-only Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
971ad37b4a
commit
65160024f7
4 changed files with 242 additions and 138 deletions
|
|
@ -5,6 +5,7 @@
|
|||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import { X, CornersOut, CornersIn, CaretLeft, CaretRight } from '@mana/shared-icons';
|
||||
import type { Snippet, Component } from 'svelte';
|
||||
|
||||
|
|
@ -51,6 +52,19 @@
|
|||
children,
|
||||
}: Props = $props();
|
||||
|
||||
// Escape exits maximized mode
|
||||
onMount(() => {
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && maximized && onMaximize) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onMaximize();
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKeydown);
|
||||
return () => window.removeEventListener('keydown', onKeydown);
|
||||
});
|
||||
|
||||
const MIN_WIDTH = 280;
|
||||
const MAX_WIDTH = 1200;
|
||||
const MIN_HEIGHT = 200;
|
||||
|
|
@ -298,10 +312,13 @@
|
|||
.page-shell.maximized {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
z-index: 95;
|
||||
width: 100% !important;
|
||||
min-height: 100vh;
|
||||
max-width: none;
|
||||
height: 100dvh !important;
|
||||
min-height: 100dvh;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
animation: fadeInScale 0.2s ease-out;
|
||||
}
|
||||
|
|
@ -400,6 +417,16 @@
|
|||
overflow-y: auto;
|
||||
min-height: 200px;
|
||||
}
|
||||
.maximized .page-header {
|
||||
max-width: 48rem;
|
||||
margin-inline: auto;
|
||||
width: 100%;
|
||||
}
|
||||
.maximized .page-body {
|
||||
max-width: 48rem;
|
||||
margin-inline: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Resize handle */
|
||||
.resize-handle {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
Rendered by the layout's bottom-stack via bottomBarStore.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Plus } from '@mana/shared-icons';
|
||||
import { Plus, Check, X } from '@mana/shared-icons';
|
||||
import { tick } from 'svelte';
|
||||
import type { CarouselPage } from '$lib/components/page-carousel/types';
|
||||
import type { WorkbenchScene } from '$lib/types/workbench-scenes';
|
||||
|
||||
|
|
@ -12,7 +13,7 @@
|
|||
activeSceneId: string | null;
|
||||
pages: CarouselPage[];
|
||||
onSceneSelect: (id: string) => void;
|
||||
onSceneCreate: () => void;
|
||||
onSceneCreate: (name: string) => void;
|
||||
onSceneContextMenu: (e: MouseEvent, scene: WorkbenchScene) => void;
|
||||
onAppClick: (id: string) => void;
|
||||
onAppContextMenu: (e: MouseEvent, id: string) => void;
|
||||
|
|
@ -30,54 +31,119 @@
|
|||
onAppContextMenu,
|
||||
onAddApp,
|
||||
}: Props = $props();
|
||||
|
||||
let creating = $state(false);
|
||||
let newName = $state('');
|
||||
let inputEl = $state<HTMLInputElement | null>(null);
|
||||
|
||||
async function startCreate() {
|
||||
creating = true;
|
||||
newName = '';
|
||||
await tick();
|
||||
inputEl?.focus();
|
||||
}
|
||||
|
||||
function submitCreate() {
|
||||
const trimmed = newName.trim();
|
||||
if (trimmed) {
|
||||
onSceneCreate(trimmed);
|
||||
}
|
||||
creating = false;
|
||||
newName = '';
|
||||
}
|
||||
|
||||
function cancelCreate() {
|
||||
creating = false;
|
||||
newName = '';
|
||||
}
|
||||
|
||||
function handleInputKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
submitCreate();
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
cancelCreate();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="scene-app-bar">
|
||||
{#each scenes as scene (scene.id)}
|
||||
{@const isActive = scene.id === activeSceneId}
|
||||
<button
|
||||
type="button"
|
||||
class="scene-pill"
|
||||
class:active={isActive}
|
||||
onclick={() => onSceneSelect(scene.id)}
|
||||
oncontextmenu={(e) => onSceneContextMenu(e, scene)}
|
||||
>
|
||||
{#if scene.icon}
|
||||
<span class="scene-icon">{scene.icon}</span>
|
||||
{/if}
|
||||
<span class="scene-name">{scene.name}</span>
|
||||
</button>
|
||||
|
||||
<!-- App tabs appear inline right after the active scene pill -->
|
||||
{#if isActive && pages.length > 0}
|
||||
{#each pages as p (p.id)}
|
||||
{@const AppIcon = p.icon}
|
||||
<!-- Active scene + its app tabs wrapped in a visual group -->
|
||||
<div class="scene-group">
|
||||
<button
|
||||
class="app-tab"
|
||||
onclick={() => onAppClick(p.id)}
|
||||
oncontextmenu={(e) => onAppContextMenu(e, p.id)}
|
||||
type="button"
|
||||
class="scene-pill active"
|
||||
onclick={() => onSceneSelect(scene.id)}
|
||||
oncontextmenu={(e) => onSceneContextMenu(e, scene)}
|
||||
>
|
||||
{#if AppIcon}
|
||||
<span class="app-icon" style="color: {p.color}">
|
||||
<AppIcon size={12} weight="fill" />
|
||||
</span>
|
||||
{:else}
|
||||
<span class="app-dot" style="background-color: {p.color}"></span>
|
||||
{/if}
|
||||
<span class="app-title">{p.title}</span>
|
||||
<span class="scene-name">{scene.name}</span>
|
||||
<span class="scene-count">{scene.openApps.length}</span>
|
||||
</button>
|
||||
{/each}
|
||||
<button class="app-add" onclick={onAddApp} title="App hinzufügen">
|
||||
<Plus size={12} />
|
||||
<span class="group-sep"></span>
|
||||
{#each pages as p (p.id)}
|
||||
{@const AppIcon = p.icon}
|
||||
<button
|
||||
class="app-tab"
|
||||
onclick={() => onAppClick(p.id)}
|
||||
oncontextmenu={(e) => onAppContextMenu(e, p.id)}
|
||||
>
|
||||
{#if AppIcon}
|
||||
<span class="app-icon" style="color: {p.color}">
|
||||
<AppIcon size={12} weight="fill" />
|
||||
</span>
|
||||
{:else}
|
||||
<span class="app-dot" style="background-color: {p.color}"></span>
|
||||
{/if}
|
||||
<span class="app-title">{p.title}</span>
|
||||
</button>
|
||||
{/each}
|
||||
<button class="app-add" onclick={onAddApp} title="App hinzufügen">
|
||||
<Plus size={12} />
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
class="scene-pill"
|
||||
class:active={isActive}
|
||||
onclick={() => onSceneSelect(scene.id)}
|
||||
oncontextmenu={(e) => onSceneContextMenu(e, scene)}
|
||||
>
|
||||
<span class="scene-name">{scene.name}</span>
|
||||
<span class="scene-count">{scene.openApps.length}</span>
|
||||
</button>
|
||||
{#if scenes.length > 1}
|
||||
<span class="bar-sep"></span>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
<button type="button" class="scene-add" onclick={onSceneCreate} title="Neue Szene">
|
||||
<Plus size={12} />
|
||||
</button>
|
||||
|
||||
{#if creating}
|
||||
<div class="inline-create">
|
||||
<input
|
||||
bind:this={inputEl}
|
||||
class="inline-create-input"
|
||||
type="text"
|
||||
maxlength="40"
|
||||
placeholder="Name…"
|
||||
bind:value={newName}
|
||||
onkeydown={handleInputKeydown}
|
||||
onblur={submitCreate}
|
||||
/>
|
||||
<button class="inline-create-btn confirm" onclick={submitCreate} title="Erstellen">
|
||||
<Check size={14} weight="bold" />
|
||||
</button>
|
||||
<button class="inline-create-btn cancel" onclick={cancelCreate} title="Abbrechen">
|
||||
<X size={14} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<button type="button" class="scene-add" onclick={startCreate} title="Neue Szene">
|
||||
<Plus size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
@ -85,15 +151,15 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.125rem;
|
||||
padding: 0.3125rem 0.625rem;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0 auto;
|
||||
width: fit-content;
|
||||
max-width: calc(100vw - 2rem);
|
||||
pointer-events: auto;
|
||||
background: hsl(var(--color-card));
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 0.75rem;
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
|
|
@ -101,15 +167,17 @@
|
|||
.scene-app-bar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.bar-sep {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: hsl(var(--color-border));
|
||||
flex-shrink: 0;
|
||||
margin: 0 0.25rem;
|
||||
.scene-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.125rem;
|
||||
background: hsl(var(--color-primary) / 0.1);
|
||||
border: 1.5px solid hsl(var(--color-primary) / 0.25);
|
||||
border-radius: 9999px;
|
||||
padding: 0.1875rem;
|
||||
}
|
||||
|
||||
/* Scene pills — bold group headers */
|
||||
/* Scene pills */
|
||||
.scene-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -118,12 +186,10 @@
|
|||
border: none;
|
||||
background: hsl(var(--color-muted) / 0.3);
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
padding: 0.3125rem 0.625rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
max-width: 140px;
|
||||
|
|
@ -133,19 +199,26 @@
|
|||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
.scene-pill.active {
|
||||
background: hsl(var(--color-primary) / 0.15);
|
||||
color: hsl(var(--color-primary));
|
||||
box-shadow: inset 0 0 0 1px hsl(var(--color-primary) / 0.25);
|
||||
}
|
||||
.scene-icon {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1;
|
||||
background: transparent;
|
||||
color: hsl(var(--color-foreground));
|
||||
box-shadow: none;
|
||||
}
|
||||
.scene-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.scene-count {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
.group-sep {
|
||||
width: 1px;
|
||||
height: 1.25rem;
|
||||
background: hsl(var(--color-border));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.scene-add {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -155,7 +228,7 @@
|
|||
background: transparent;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.375rem;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
|
@ -164,13 +237,63 @@
|
|||
color: hsl(var(--color-primary));
|
||||
}
|
||||
|
||||
/* Inline create */
|
||||
.inline-create {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.inline-create-input {
|
||||
width: 120px;
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
border: 1.5px solid hsl(var(--color-primary) / 0.4);
|
||||
background: hsl(var(--color-card));
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-foreground));
|
||||
outline: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.inline-create-input:focus {
|
||||
border-color: hsl(var(--color-primary));
|
||||
}
|
||||
.inline-create-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
padding: 0;
|
||||
}
|
||||
.inline-create-btn.confirm {
|
||||
background: hsl(var(--color-primary) / 0.15);
|
||||
color: hsl(var(--color-primary));
|
||||
}
|
||||
.inline-create-btn.confirm:hover {
|
||||
background: hsl(var(--color-primary) / 0.25);
|
||||
}
|
||||
.inline-create-btn.cancel {
|
||||
background: transparent;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
.inline-create-btn.cancel:hover {
|
||||
background: hsl(var(--color-surface-hover));
|
||||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
|
||||
/* App tabs — lighter, inline after active scene */
|
||||
.app-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.1875rem 0.375rem;
|
||||
border-radius: 0.3125rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 9999px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
|
|
@ -194,8 +317,8 @@
|
|||
flex-shrink: 0;
|
||||
}
|
||||
.app-title {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 400;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
max-width: 90px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
@ -207,7 +330,7 @@
|
|||
color: hsl(var(--color-muted-foreground));
|
||||
cursor: pointer;
|
||||
padding: 0.1875rem;
|
||||
border-radius: 0.25rem;
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.15s;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
<!--
|
||||
SceneRenameDialog — modal for creating or renaming a workbench scene.
|
||||
|
||||
Single dialog handles both flows; the parent decides which by passing an
|
||||
initial name (empty for create, current name for rename) and a title.
|
||||
SceneRenameDialog — modal for renaming a workbench scene.
|
||||
-->
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
show: boolean;
|
||||
title: string;
|
||||
initialName?: string;
|
||||
initialIcon?: string;
|
||||
confirmLabel?: string;
|
||||
onSubmit: (name: string, icon: string | undefined) => void | Promise<void>;
|
||||
onSubmit: (name: string) => void | Promise<void>;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -19,23 +15,18 @@
|
|||
show,
|
||||
title,
|
||||
initialName = '',
|
||||
initialIcon = '',
|
||||
confirmLabel = 'Speichern',
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: Props = $props();
|
||||
|
||||
let name = $state('');
|
||||
let icon = $state('');
|
||||
let pending = $state(false);
|
||||
let inputEl = $state<HTMLInputElement | null>(null);
|
||||
|
||||
// Reset local fields each time the dialog opens with new props.
|
||||
$effect(() => {
|
||||
if (show) {
|
||||
name = initialName;
|
||||
icon = initialIcon;
|
||||
// Focus on next tick once the input is mounted.
|
||||
queueMicrotask(() => inputEl?.focus());
|
||||
}
|
||||
});
|
||||
|
|
@ -45,7 +36,7 @@
|
|||
if (pending || !name.trim()) return;
|
||||
pending = true;
|
||||
try {
|
||||
await onSubmit(name.trim(), icon.trim() || undefined);
|
||||
await onSubmit(name.trim());
|
||||
} finally {
|
||||
pending = false;
|
||||
}
|
||||
|
|
@ -72,30 +63,18 @@
|
|||
>
|
||||
<h3 id="srd-title" class="srd-title">{title}</h3>
|
||||
<form onsubmit={handleSubmit}>
|
||||
<div class="srd-fields">
|
||||
<label class="srd-field srd-field-icon">
|
||||
<span class="srd-label">Icon</span>
|
||||
<input
|
||||
class="srd-input srd-icon-input"
|
||||
type="text"
|
||||
maxlength="2"
|
||||
placeholder="🏠"
|
||||
bind:value={icon}
|
||||
/>
|
||||
</label>
|
||||
<label class="srd-field srd-field-name">
|
||||
<span class="srd-label">Name</span>
|
||||
<input
|
||||
class="srd-input"
|
||||
type="text"
|
||||
maxlength="40"
|
||||
placeholder="z.B. Deep Work"
|
||||
bind:this={inputEl}
|
||||
bind:value={name}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<label class="srd-field">
|
||||
<span class="srd-label">Name</span>
|
||||
<input
|
||||
class="srd-input"
|
||||
type="text"
|
||||
maxlength="40"
|
||||
placeholder="z.B. Deep Work"
|
||||
bind:this={inputEl}
|
||||
bind:value={name}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<div class="srd-actions">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -150,22 +129,11 @@
|
|||
color: hsl(var(--color-foreground));
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
.srd-fields {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.srd-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.srd-field-icon {
|
||||
flex: 0 0 auto;
|
||||
width: 64px;
|
||||
}
|
||||
.srd-field-name {
|
||||
flex: 1;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.srd-label {
|
||||
font-size: 0.75rem;
|
||||
|
|
@ -185,10 +153,6 @@
|
|||
.srd-input:focus {
|
||||
border-color: hsl(var(--color-primary));
|
||||
}
|
||||
.srd-icon-input {
|
||||
text-align: center;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.srd-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@
|
|||
activeSceneId,
|
||||
pages: carouselPages,
|
||||
onSceneSelect: (id: string) => workbenchScenesStore.setActiveScene(id),
|
||||
onSceneCreate: handleCreateScene,
|
||||
onSceneCreate: (name: string) => workbenchScenesStore.createScene({ name }),
|
||||
onSceneContextMenu: handleSceneContextMenu,
|
||||
onAppClick: scrollToPage,
|
||||
onAppContextMenu: (e: MouseEvent, id: string) => handleTabContextMenu(e, id),
|
||||
|
|
@ -199,28 +199,19 @@
|
|||
}
|
||||
|
||||
// ── Scene CRUD dialogs ──────────────────────────────────
|
||||
type SceneDialogMode =
|
||||
| { kind: 'create' }
|
||||
| { kind: 'rename'; id: string; name: string; icon?: string };
|
||||
type SceneDialogMode = { kind: 'rename'; id: string; name: string };
|
||||
let sceneDialog = $state<SceneDialogMode | null>(null);
|
||||
let sceneToDelete = $state<{ id: string; name: string } | null>(null);
|
||||
|
||||
function handleCreateScene() {
|
||||
sceneDialog = { kind: 'create' };
|
||||
}
|
||||
function handleRequestRename(id: string) {
|
||||
const scene = scenes.find((s) => s.id === id);
|
||||
if (!scene) return;
|
||||
sceneDialog = { kind: 'rename', id, name: scene.name, icon: scene.icon };
|
||||
sceneDialog = { kind: 'rename', id, name: scene.name };
|
||||
}
|
||||
async function handleSubmitSceneDialog(name: string, icon: string | undefined) {
|
||||
async function handleSubmitSceneDialog(name: string) {
|
||||
const mode = sceneDialog;
|
||||
if (!mode) return;
|
||||
if (mode.kind === 'create') {
|
||||
await workbenchScenesStore.createScene({ name, icon });
|
||||
} else {
|
||||
await workbenchScenesStore.renameScene(mode.id, name, icon);
|
||||
}
|
||||
await workbenchScenesStore.renameScene(mode.id, name);
|
||||
sceneDialog = null;
|
||||
}
|
||||
function handleDuplicateScene(id: string) {
|
||||
|
|
@ -287,10 +278,9 @@
|
|||
|
||||
<SceneRenameDialog
|
||||
show={sceneDialog !== null}
|
||||
title={sceneDialog?.kind === 'rename' ? 'Szene umbenennen' : 'Neue Szene'}
|
||||
initialName={sceneDialog?.kind === 'rename' ? sceneDialog.name : ''}
|
||||
initialIcon={sceneDialog?.kind === 'rename' ? (sceneDialog.icon ?? '') : ''}
|
||||
confirmLabel={sceneDialog?.kind === 'rename' ? 'Speichern' : 'Erstellen'}
|
||||
title="Szene umbenennen"
|
||||
initialName={sceneDialog?.name ?? ''}
|
||||
confirmLabel="Speichern"
|
||||
onSubmit={handleSubmitSceneDialog}
|
||||
onCancel={() => (sceneDialog = null)}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue