feat: show project and session translation tables
This commit is contained in:
@@ -89,6 +89,12 @@
|
||||
"description": "Zwischenablage übernehmen, Projekt oder Sitzung wählen und wiederherstellen.",
|
||||
"mode": "view"
|
||||
},
|
||||
{
|
||||
"name": "show-translation-tables",
|
||||
"title": "Übersetzungstabellen anzeigen",
|
||||
"description": "Gespeicherte Zuordnungen je Projekt oder Sitzung durchsuchen.",
|
||||
"mode": "view"
|
||||
},
|
||||
{
|
||||
"name": "manage-sessions",
|
||||
"title": "Sitzungen verwalten",
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "./sessions";
|
||||
import type { VelumSession } from "./types";
|
||||
import { mappingSummary, sessionSubtitle } from "./ui";
|
||||
import { TranslationTable } from "./show-translation-tables";
|
||||
|
||||
export default function Command() {
|
||||
const [sessions, setSessions] = useState<VelumSession[]>([]);
|
||||
@@ -145,6 +146,11 @@ export default function Command() {
|
||||
accessories={[{ text: `${countMappingEntries(session.mapping)}` }]}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action.Push
|
||||
title="Übersetzungstabelle anzeigen"
|
||||
icon={Icon.List}
|
||||
target={<TranslationTable title={session.name} mapping={session.mapping} />}
|
||||
/>
|
||||
<Action
|
||||
title="Als Aktive Sitzung Setzen"
|
||||
icon={Icon.CheckCircle}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { canonicalProjectPath, listAllRuns, resolveWithin, restorePath, restoreT
|
||||
import type { ProjectFile, ProjectRun } from "./lib/velum-project";
|
||||
import { canonicalPerson, mergePeople } from "./lib/project-review";
|
||||
import { planProjectRevision, reviseProjectRun } from "./lib/project-revision";
|
||||
import { TranslationTable } from "./show-translation-tables";
|
||||
|
||||
type SavedRun = { run: ProjectRun; directory: string };
|
||||
|
||||
@@ -229,6 +230,9 @@ function ProjectHistory(props: { projectName: string; entries: SavedRun[] }) {
|
||||
title={new Date(run.createdAt).toLocaleString("de-AT")}
|
||||
subtitle={`${run.files.length} Dateien`}
|
||||
actions={<ActionPanel>
|
||||
<Action.Push title="Übersetzungstabelle anzeigen" icon={Icon.List}
|
||||
target={<TranslationTable title={`${props.projectName} · ${new Date(run.createdAt).toLocaleString("de-AT")}`}
|
||||
mapping={run.mapping} />} />
|
||||
<Action title="Pseudonymisierte Kopien im Finder anzeigen" icon={Icon.Folder}
|
||||
onAction={() => showInFinder(runCopiesPath(run, directory))} />
|
||||
<Action.CopyToClipboard title="Speicherpfad kopieren" content={runCopiesPath(run, directory)} />
|
||||
@@ -265,6 +269,8 @@ export default function Command() {
|
||||
accessories={[{ text: `${run.files.length} Dateien` }, { text: new Date(run.createdAt).toLocaleString("de-AT") },
|
||||
...(entries.length > 1 ? [{ text: `${entries.length} Stände` }] : [])]}
|
||||
actions={<ActionPanel>
|
||||
<Action.Push title="Übersetzungstabelle anzeigen" icon={Icon.List}
|
||||
target={<TranslationTable title={run.projectName} mapping={run.mapping} />} />
|
||||
<Action title="Projektdateien als Klartext wiederherstellen" icon={Icon.Download}
|
||||
onAction={() => push(<FilePicker run={run} directory={directory} />)} />
|
||||
<Action title="Pseudonymisierte Kopien im Finder anzeigen" icon={Icon.Folder}
|
||||
|
||||
224
src/show-translation-tables.tsx
Normal file
224
src/show-translation-tables.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
import {
|
||||
Action,
|
||||
ActionPanel,
|
||||
Detail,
|
||||
Icon,
|
||||
List,
|
||||
showToast,
|
||||
Toast,
|
||||
} from "@raycast/api";
|
||||
import { useEffect, useState } from "react";
|
||||
import { canonicalProjectPath, listAllRuns } from "./lib/velum-project";
|
||||
import { listSessions } from "./sessions";
|
||||
import type { PlaceholderMapping } from "./types";
|
||||
import { markdownCodeBlock } from "./ui";
|
||||
|
||||
type TableSource = {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
mapping: PlaceholderMapping;
|
||||
};
|
||||
|
||||
export function TranslationTable({
|
||||
title,
|
||||
mapping,
|
||||
}: {
|
||||
title: string;
|
||||
mapping: PlaceholderMapping;
|
||||
}) {
|
||||
const groups = new Map<string, Array<[string, PlaceholderMapping[string]]>>();
|
||||
for (const entry of Object.entries(mapping).sort(([a], [b]) =>
|
||||
a.localeCompare(b),
|
||||
)) {
|
||||
const type = entry[1].type || "Sonstige";
|
||||
const entries = groups.get(type) ?? [];
|
||||
entries.push(entry);
|
||||
groups.set(type, entries);
|
||||
}
|
||||
|
||||
return (
|
||||
<List
|
||||
navigationTitle={`Übersetzungstabelle · ${title}`}
|
||||
searchBarPlaceholder="Platzhalter oder Klartext suchen"
|
||||
>
|
||||
<List.EmptyView
|
||||
title="Keine Zuordnungen"
|
||||
description="Für diesen Stand sind noch keine Übersetzungen gespeichert."
|
||||
/>
|
||||
{[...groups]
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([type, entries]) => (
|
||||
<List.Section key={type} title={`${type} · ${entries.length}`}>
|
||||
{entries.map(([placeholder, entry]) => (
|
||||
<List.Item
|
||||
key={placeholder}
|
||||
icon={Icon.ArrowRight}
|
||||
title={placeholder}
|
||||
subtitle={entry.original}
|
||||
keywords={[entry.original, entry.type, entry.merged_into ?? ""]}
|
||||
accessories={
|
||||
entry.merged_into ? [{ text: `→ ${entry.merged_into}` }] : []
|
||||
}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action.Push
|
||||
title="Eintrag vollständig anzeigen"
|
||||
icon={Icon.Eye}
|
||||
target={
|
||||
<Detail
|
||||
navigationTitle={placeholder}
|
||||
markdown={[
|
||||
`# ${entry.type}`,
|
||||
"",
|
||||
"Platzhalter",
|
||||
markdownCodeBlock(placeholder),
|
||||
"",
|
||||
"Klartext",
|
||||
markdownCodeBlock(entry.original),
|
||||
...(entry.merged_into
|
||||
? [
|
||||
"",
|
||||
"Zusammengeführt mit",
|
||||
markdownCodeBlock(entry.merged_into),
|
||||
]
|
||||
: []),
|
||||
].join("\n")}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action.CopyToClipboard
|
||||
title="Platzhalter kopieren"
|
||||
content={placeholder}
|
||||
/>
|
||||
<Action.CopyToClipboard
|
||||
title="Klartext kopieren"
|
||||
content={entry.original}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Action.CopyToClipboard
|
||||
title="Platzhalter kopieren"
|
||||
content={placeholder}
|
||||
/>
|
||||
<Action.CopyToClipboard
|
||||
title="Klartext kopieren"
|
||||
content={entry.original}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</List.Section>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Command() {
|
||||
const [projects, setProjects] = useState<TableSource[]>([]);
|
||||
const [sessions, setSessions] = useState<TableSource[]>([]);
|
||||
const [busy, setBusy] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([listAllRuns(), listSessions()])
|
||||
.then(([runs, savedSessions]) => {
|
||||
const latest = new Map<string, TableSource>();
|
||||
for (const { run } of runs) {
|
||||
const path = canonicalProjectPath(run.projectPath);
|
||||
if (!latest.has(path))
|
||||
latest.set(path, {
|
||||
id: path,
|
||||
title: run.projectName,
|
||||
subtitle: `${path} · ${new Date(run.createdAt).toLocaleString("de-AT")}`,
|
||||
mapping: run.mapping,
|
||||
});
|
||||
}
|
||||
setProjects(
|
||||
[...latest.values()].sort((a, b) => a.title.localeCompare(b.title)),
|
||||
);
|
||||
setSessions(
|
||||
savedSessions.map((session) => ({
|
||||
id: session.id,
|
||||
title: session.name,
|
||||
subtitle: `${Object.keys(session.mapping).length} Zuordnungen · ${new Date(session.updatedAt).toLocaleString("de-AT")}`,
|
||||
mapping: session.mapping,
|
||||
})),
|
||||
);
|
||||
})
|
||||
.catch((error: Error) =>
|
||||
showToast({
|
||||
style: Toast.Style.Failure,
|
||||
title: "Übersetzungstabellen nicht lesbar",
|
||||
message: error.message,
|
||||
}),
|
||||
)
|
||||
.finally(() => setBusy(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<List
|
||||
isLoading={busy}
|
||||
navigationTitle="Übersetzungstabellen"
|
||||
searchBarPlaceholder="Projekt oder Sitzung suchen"
|
||||
>
|
||||
<List.EmptyView
|
||||
title="Keine Tabellen"
|
||||
description="Pseudonymisiere zuerst Text oder Projektdateien."
|
||||
/>
|
||||
<List.Section title="Projekte">
|
||||
{projects.map((source) => (
|
||||
<List.Item
|
||||
key={source.id}
|
||||
icon={Icon.Folder}
|
||||
title={source.title}
|
||||
subtitle={source.subtitle}
|
||||
accessories={[
|
||||
{ text: `${Object.keys(source.mapping).length} Zuordnungen` },
|
||||
]}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action.Push
|
||||
title="Übersetzungstabelle anzeigen"
|
||||
icon={Icon.List}
|
||||
target={
|
||||
<TranslationTable
|
||||
title={source.title}
|
||||
mapping={source.mapping}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</List.Section>
|
||||
<List.Section title="Sitzungen">
|
||||
{sessions.map((source) => (
|
||||
<List.Item
|
||||
key={source.id}
|
||||
icon={Icon.Person}
|
||||
title={source.title}
|
||||
subtitle={source.subtitle}
|
||||
actions={
|
||||
<ActionPanel>
|
||||
<Action.Push
|
||||
title="Übersetzungstabelle anzeigen"
|
||||
icon={Icon.List}
|
||||
target={
|
||||
<TranslationTable
|
||||
title={source.title}
|
||||
mapping={source.mapping}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</ActionPanel>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</List.Section>
|
||||
</List>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user