Add full shell, replaceable Omics adapter and bilingual interaction
Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { Check, Copy, TriangleAlert } from "lucide-react";
|
||||
import { highlightSql } from "./highlight";
|
||||
import { statusBadgeClass } from "./statusBadge";
|
||||
import { useI18n } from "../i18n";
|
||||
|
||||
export interface SqlBlock {
|
||||
name: string;
|
||||
@@ -20,6 +21,7 @@ const STATUS_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
function Block({ block }: { block: SqlBlock }) {
|
||||
const { t } = useI18n();
|
||||
const [open, setOpen] = useState(true);
|
||||
const [html, setHtml] = useState<string>("");
|
||||
const [copyState, setCopyState] = useState<"idle" | "copied" | "failed">("idle");
|
||||
@@ -61,21 +63,21 @@ function Block({ block }: { block: SqlBlock }) {
|
||||
<span className="truncate font-semibold">{block.name}</span>
|
||||
{block.fields !== undefined && (
|
||||
<span className="shrink-0 text-xs text-muted-foreground">
|
||||
{block.fields} campi
|
||||
{t(block.fields === 1 ? "{count} field" : "{count} fields", { count: block.fields })}
|
||||
</span>
|
||||
)}
|
||||
{badge && (
|
||||
<span
|
||||
className={`ml-auto shrink-0 rounded-md px-1.5 py-0.5 text-xs ${badgeClass}`}
|
||||
>
|
||||
{badge}
|
||||
{t(badge)}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`Copy SQL for ${block.name}`}
|
||||
title="Copy SQL"
|
||||
aria-label={t("Copy SQL for {name}", { name: block.name })}
|
||||
title={t("Copy SQL")}
|
||||
onClick={copySql}
|
||||
className="flex shrink-0 items-center gap-1.5 border-l border-border/70 px-2.5 text-xs text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground"
|
||||
>
|
||||
@@ -87,7 +89,7 @@ function Block({ block }: { block: SqlBlock }) {
|
||||
<Copy className="size-3.5" aria-hidden="true" />
|
||||
)}
|
||||
<span role="status" aria-live="polite">
|
||||
{copyState === "copied" ? "Copied" : copyState === "failed" ? "Copy failed" : ""}
|
||||
{copyState === "copied" ? t("Copied") : copyState === "failed" ? t("Copy failed") : ""}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -117,6 +119,7 @@ function Block({ block }: { block: SqlBlock }) {
|
||||
}
|
||||
|
||||
export function SqlViewer({ blocks }: { blocks: SqlBlock[] }) {
|
||||
const { t } = useI18n();
|
||||
const [layout, setLayout] = useState<Layout>("vertical");
|
||||
|
||||
return (
|
||||
@@ -129,7 +132,7 @@ export function SqlViewer({ blocks }: { blocks: SqlBlock[] }) {
|
||||
setLayout((l) => (l === "vertical" ? "horizontal" : "vertical"))
|
||||
}
|
||||
>
|
||||
{layout === "vertical" ? "Horizontal" : "Vertical"}
|
||||
{t(layout === "vertical" ? "Horizontal" : "Vertical")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user