Ribbon Phase 4: modulare Custom-Bar als eigener Tab (Eigene) mit +-Picker, localStorage-persistiert
This commit is contained in:
@@ -17,10 +17,19 @@ import { ToolIcon } from "../../panels/ToolsPanel";
|
||||
import { getTool } from "../../tools/tools";
|
||||
import type { ToolId } from "../../tools/types";
|
||||
import { getCommand } from "../../commands/registry";
|
||||
import { RIBBON_TABS } from "./ribbonItems";
|
||||
import { Dropdown } from "../Dropdown";
|
||||
import type { DropdownItem } from "../Dropdown";
|
||||
import { RIBBON_TABS, ALL_RIBBON_ITEMS, itemKey, itemFromKey } from "./ribbonItems";
|
||||
import type { RibbonItem, RibbonTabId } from "./ribbonItems";
|
||||
import { CommandIcon } from "./CommandIcon";
|
||||
|
||||
/** Aufgelöste Beschriftung eines Items (Werkzeug bzw. Befehl). */
|
||||
function itemLabel(item: RibbonItem): string {
|
||||
if (item.kind === "tool") return t(getTool(item.id).labelKey);
|
||||
const cmd = getCommand(item.name);
|
||||
return cmd ? t(cmd.labelKey) : item.name;
|
||||
}
|
||||
|
||||
export interface RibbonBarProps {
|
||||
/** Aktiver Tab (State liegt in App, geteilt mit den Reitern in der TopBar). */
|
||||
tab: RibbonTabId;
|
||||
@@ -41,6 +50,10 @@ export interface RibbonBarProps {
|
||||
* Gruppen zeigt den Leer-Hinweis.
|
||||
*/
|
||||
tabContent?: Partial<Record<RibbonTabId, ReactNode>>;
|
||||
/** Persistierte Item-Schlüssel der modularen Custom-Bar (Tab „Eigene"). */
|
||||
customItems: string[];
|
||||
/** Ein Item in der Custom-Bar an-/abwählen (Picker im „Eigene"-Tab). */
|
||||
onToggleCustomItem: (key: string) => void;
|
||||
}
|
||||
|
||||
/** Aktivierungs-/Highlight-Kontext, den jeder Button-Typ braucht. */
|
||||
@@ -120,6 +133,53 @@ export function RibbonTabs({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modulare Custom-Bar (Tab „Eigene") — rendert die vom Nutzer gewählten Items
|
||||
* plus einen „+"-Picker, der ALLE verfügbaren Werkzeuge/Befehle mit Häkchen für
|
||||
* die bereits enthaltenen listet (Klick = an-/abwählen). Gleiche Item-Typen +
|
||||
* gleiche Aktivierung wie die normalen Tabs — kein Sonderweg.
|
||||
*/
|
||||
function CustomBar({
|
||||
customItems,
|
||||
onToggle,
|
||||
ctx,
|
||||
}: {
|
||||
customItems: string[];
|
||||
onToggle: (key: string) => void;
|
||||
ctx: ButtonCtx;
|
||||
}) {
|
||||
const pickerItems: DropdownItem[] = ALL_RIBBON_ITEMS.map((it) => {
|
||||
const key = itemKey(it);
|
||||
return {
|
||||
label: itemLabel(it),
|
||||
active: customItems.includes(key),
|
||||
onSelect: () => onToggle(key),
|
||||
};
|
||||
});
|
||||
const items = customItems
|
||||
.map(itemFromKey)
|
||||
.filter((it): it is RibbonItem => it !== null);
|
||||
return (
|
||||
<div className="ribbon-group">
|
||||
<div className="ribbon-items">
|
||||
{items.length === 0 && (
|
||||
<div className="ribbon-empty">{t("ribbon.custom.empty")}</div>
|
||||
)}
|
||||
{items.map((item, i) => (
|
||||
<RibbonButton key={`${itemKey(item)}-${i}`} item={item} ctx={ctx} />
|
||||
))}
|
||||
<Dropdown
|
||||
items={pickerItems}
|
||||
placeholder={t("ribbon.custom.add")}
|
||||
title={t("ribbon.custom.add")}
|
||||
triggerClassName="ribbon-add"
|
||||
/>
|
||||
</div>
|
||||
<div className="ribbon-group-title">{t("ribbon.group.custom")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RibbonBar(props: RibbonBarProps) {
|
||||
const current = RIBBON_TABS.find((tt) => tt.id === props.tab) ?? RIBBON_TABS[0];
|
||||
const custom = props.tabContent?.[current.id];
|
||||
@@ -134,9 +194,16 @@ export function RibbonBar(props: RibbonBarProps) {
|
||||
return (
|
||||
<div className="ribbon">
|
||||
{/* Inhalt des aktiven Tabs. Tabs mit reichem Custom-Inhalt („Ansichten",
|
||||
„Text") rendern diesen statt der datengetriebenen Item-Gruppen. */}
|
||||
„Text") rendern diesen; der „Eigene"-Tab die modulare Custom-Bar; sonst
|
||||
die datengetriebenen Item-Gruppen. */}
|
||||
<div className="ribbon-body">
|
||||
{custom ? (
|
||||
{current.id === "custom" ? (
|
||||
<CustomBar
|
||||
customItems={props.customItems}
|
||||
onToggle={props.onToggleCustomItem}
|
||||
ctx={ctx}
|
||||
/>
|
||||
) : custom ? (
|
||||
custom
|
||||
) : current.groups.length === 0 ? (
|
||||
<div className="ribbon-empty">{t("ribbon.empty")}</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface RibbonGroup {
|
||||
}
|
||||
|
||||
/** Kennung eines Ribbon-Tabs (fest, für Tab-State + i18n). */
|
||||
export type RibbonTabId = "views" | "2d" | "3d" | "bim";
|
||||
export type RibbonTabId = "views" | "2d" | "3d" | "bim" | "custom";
|
||||
|
||||
/** Ein Ribbon-Tab: Beschriftung + gruppierte Elemente. */
|
||||
export interface RibbonTab {
|
||||
@@ -105,4 +105,35 @@ export const RIBBON_TABS: RibbonTab[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// Modulare Custom-Bar (Phase 4): der Nutzer stellt sich die Elemente selbst
|
||||
// zusammen (persistiert). Gerendert aus `customItems`, nicht aus `groups`.
|
||||
id: "custom",
|
||||
labelKey: "ribbon.tab.custom",
|
||||
groups: [],
|
||||
},
|
||||
];
|
||||
|
||||
/** Stabiler Schlüssel eines Items für die Persistenz der Custom-Bar. */
|
||||
export function itemKey(item: RibbonItem): string {
|
||||
return item.kind === "tool" ? `tool:${item.id}` : `command:${item.name}`;
|
||||
}
|
||||
|
||||
/** Parst einen Item-Schlüssel zurück (oder null bei unbekanntem Präfix). */
|
||||
export function itemFromKey(key: string): RibbonItem | null {
|
||||
const i = key.indexOf(":");
|
||||
if (i < 0) return null;
|
||||
const kind = key.slice(0, i);
|
||||
const rest = key.slice(i + 1);
|
||||
if (kind === "tool") return { kind: "tool", id: rest as ToolId };
|
||||
if (kind === "command") return { kind: "command", name: rest };
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle datengetriebenen Items (aus den Werkzeug-/Befehls-Tabs) — die wählbare
|
||||
* Menge für den Custom-Bar-Picker. „views"/„custom" tragen keine `groups`.
|
||||
*/
|
||||
export const ALL_RIBBON_ITEMS: RibbonItem[] = RIBBON_TABS.flatMap((tab) =>
|
||||
tab.groups.flatMap((g) => g.items),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user