Ship the Signature UX as standalone, SSR-safe pieces: bilingual copy, a dual SVG/list systems map, a Map-backed command palette in the shell, opt-in reveal and metric primitives, and a leak-free decorative canvas. Co-authored-by: Cursor <cursoragent@cursor.com>
168 lines
3.9 KiB
TypeScript
168 lines
3.9 KiB
TypeScript
import { APP_LOCALES, type AppLocale } from '../../core/i18n/locale';
|
|
import { type RouteId } from '../../core/routing/route-ids';
|
|
|
|
export type CommandId =
|
|
| 'help'
|
|
| 'projects'
|
|
| 'servicesAi'
|
|
| 'cv'
|
|
| 'contact'
|
|
| 'brew'
|
|
| 'ignite'
|
|
| 'rev'
|
|
| 'clear'
|
|
| 'close';
|
|
|
|
export const COMMAND_IDS: readonly CommandId[] = [
|
|
'help',
|
|
'projects',
|
|
'servicesAi',
|
|
'cv',
|
|
'contact',
|
|
'brew',
|
|
'ignite',
|
|
'rev',
|
|
'clear',
|
|
'close',
|
|
];
|
|
|
|
export type CommandAction =
|
|
| { readonly kind: 'navigate'; readonly routeId: RouteId }
|
|
| { readonly kind: 'openCv' }
|
|
| { readonly kind: 'message'; readonly messageKey: 'help' | 'brew' | 'ignite' | 'rev' }
|
|
| { readonly kind: 'clear' }
|
|
| { readonly kind: 'close' };
|
|
|
|
export interface CommandDefinition {
|
|
readonly id: CommandId;
|
|
readonly input: string;
|
|
readonly aliases: Record<AppLocale, readonly string[]>;
|
|
readonly action: CommandAction;
|
|
}
|
|
|
|
export type CommandMatch =
|
|
| { readonly kind: 'command'; readonly definition: CommandDefinition }
|
|
| { readonly kind: 'empty' }
|
|
| { readonly kind: 'unknown'; readonly input: string };
|
|
|
|
export const COMMANDS: readonly CommandDefinition[] = [
|
|
{
|
|
id: 'help',
|
|
input: 'help',
|
|
aliases: { de: ['hilfe'], en: [] },
|
|
action: { kind: 'message', messageKey: 'help' },
|
|
},
|
|
{
|
|
id: 'projects',
|
|
input: 'projects',
|
|
aliases: { de: ['projekte'], en: [] },
|
|
action: { kind: 'navigate', routeId: 'projects' },
|
|
},
|
|
{
|
|
id: 'servicesAi',
|
|
input: 'services ai',
|
|
aliases: { de: ['leistungen ai'], en: [] },
|
|
action: { kind: 'navigate', routeId: 'servicesAi' },
|
|
},
|
|
{
|
|
id: 'cv',
|
|
input: 'cv',
|
|
aliases: { de: ['lebenslauf'], en: ['resume'] },
|
|
action: { kind: 'openCv' },
|
|
},
|
|
{
|
|
id: 'contact',
|
|
input: 'contact',
|
|
aliases: { de: ['kontakt'], en: [] },
|
|
action: { kind: 'navigate', routeId: 'contact' },
|
|
},
|
|
{
|
|
id: 'brew',
|
|
input: 'brew',
|
|
aliases: { de: ['brauen'], en: [] },
|
|
action: { kind: 'message', messageKey: 'brew' },
|
|
},
|
|
{
|
|
id: 'ignite',
|
|
input: 'ignite',
|
|
aliases: { de: ['zuenden', 'zünden'], en: [] },
|
|
action: { kind: 'message', messageKey: 'ignite' },
|
|
},
|
|
{
|
|
id: 'rev',
|
|
input: 'rev',
|
|
aliases: { de: ['drehzahl'], en: [] },
|
|
action: { kind: 'message', messageKey: 'rev' },
|
|
},
|
|
{
|
|
id: 'clear',
|
|
input: 'clear',
|
|
aliases: { de: ['leeren'], en: [] },
|
|
action: { kind: 'clear' },
|
|
},
|
|
{
|
|
id: 'close',
|
|
input: 'close',
|
|
aliases: { de: ['schliessen', 'schließen'], en: [] },
|
|
action: { kind: 'close' },
|
|
},
|
|
];
|
|
|
|
const COMMAND_LOOKUP = buildCommandLookup(COMMANDS);
|
|
|
|
function buildCommandLookup(
|
|
commands: readonly CommandDefinition[],
|
|
): ReadonlyMap<string, CommandDefinition> {
|
|
const lookup = new Map<string, CommandDefinition>();
|
|
|
|
for (const command of commands) {
|
|
lookup.set(normalizeCommandInput(command.input), command);
|
|
|
|
for (const locale of APP_LOCALES) {
|
|
for (const alias of command.aliases[locale]) {
|
|
lookup.set(normalizeCommandInput(alias), command);
|
|
}
|
|
}
|
|
}
|
|
|
|
return lookup;
|
|
}
|
|
|
|
export function normalizeCommandInput(raw: string): string {
|
|
return raw.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
}
|
|
|
|
export function parseCommand(raw: string): CommandMatch {
|
|
const normalized = normalizeCommandInput(raw);
|
|
|
|
if (normalized.length === 0) {
|
|
return { kind: 'empty' };
|
|
}
|
|
|
|
const definition = COMMAND_LOOKUP.get(normalized);
|
|
|
|
if (!definition) {
|
|
return { kind: 'unknown', input: raw };
|
|
}
|
|
|
|
return { kind: 'command', definition };
|
|
}
|
|
|
|
export function suggestCommands(raw: string, locale: AppLocale): readonly CommandDefinition[] {
|
|
const normalized = normalizeCommandInput(raw);
|
|
|
|
return COMMANDS.filter((command) => {
|
|
if (normalized.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
if (command.input.startsWith(normalized)) {
|
|
return true;
|
|
}
|
|
|
|
return command.aliases[locale].some((alias) =>
|
|
normalizeCommandInput(alias).startsWith(normalized),
|
|
);
|
|
});
|
|
}
|