fix: unify analysis workspace controls
This commit is contained in:
@@ -8,16 +8,23 @@ import {
|
|||||||
Database,
|
Database,
|
||||||
FileSearch,
|
FileSearch,
|
||||||
Focus,
|
Focus,
|
||||||
Map as MapIcon,
|
|
||||||
Minimize2,
|
Minimize2,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Trash2,
|
Trash2,
|
||||||
X
|
X
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { motion, useReducedMotion } from "motion/react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { showMapNotice } from "@/features/map/core";
|
import { showMapNotice } from "@/features/map/core";
|
||||||
import { cn } from "@/shared/ui/cn";
|
import { cn } from "@/shared/ui/cn";
|
||||||
|
import {
|
||||||
|
ARTIFACT_DANGER_ICON_BUTTON_CLASS_NAME,
|
||||||
|
ARTIFACT_ICON_BUTTON_CLASS_NAME,
|
||||||
|
ARTIFACT_PRIMARY_ACTION_BUTTON_CLASS_NAME,
|
||||||
|
ARTIFACT_VIEW_CONTROL_BUTTON_CLASS_NAME,
|
||||||
|
ARTIFACT_VIEW_CONTROL_SHELL_CLASS_NAME
|
||||||
|
} from "./artifact-view-control-styles";
|
||||||
import type {
|
import type {
|
||||||
AnalysisArtifact,
|
AnalysisArtifact,
|
||||||
AnalysisArtifactAction,
|
AnalysisArtifactAction,
|
||||||
@@ -30,7 +37,6 @@ import type {
|
|||||||
type AnalysisArtifactWorkspaceProps = {
|
type AnalysisArtifactWorkspaceProps = {
|
||||||
artifact: AnalysisArtifact;
|
artifact: AnalysisArtifact;
|
||||||
surfaceMode: WorkbenchSurfaceMode;
|
surfaceMode: WorkbenchSurfaceMode;
|
||||||
mapSplitAvailable: boolean;
|
|
||||||
onSetView: (view: ArtifactRequestedView) => void;
|
onSetView: (view: ArtifactRequestedView) => void;
|
||||||
onCollapse: () => void;
|
onCollapse: () => void;
|
||||||
onDestroy: () => void;
|
onDestroy: () => void;
|
||||||
@@ -41,7 +47,6 @@ type EvidenceBlock = Exclude<AnalysisBlock, { kind: "metric-grid" }>;
|
|||||||
export function AnalysisArtifactWorkspace({
|
export function AnalysisArtifactWorkspace({
|
||||||
artifact,
|
artifact,
|
||||||
surfaceMode,
|
surfaceMode,
|
||||||
mapSplitAvailable,
|
|
||||||
onSetView,
|
onSetView,
|
||||||
onCollapse,
|
onCollapse,
|
||||||
onDestroy
|
onDestroy
|
||||||
@@ -72,7 +77,10 @@ export function AnalysisArtifactWorkspace({
|
|||||||
className="acrylic-panel relative m-3 grid h-[calc(100%-1.5rem)] min-h-0 grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden rounded-2xl border text-slate-900"
|
className="acrylic-panel relative m-3 grid h-[calc(100%-1.5rem)] min-h-0 grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden rounded-2xl border text-slate-900"
|
||||||
lang="zh-CN"
|
lang="zh-CN"
|
||||||
>
|
>
|
||||||
<ArtifactHeader artifact={artifact} />
|
<ArtifactHeader
|
||||||
|
artifact={artifact}
|
||||||
|
reserveViewControlSpace={surfaceMode === "focus" && artifact.mapRelation !== "none"}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="min-h-0 overflow-y-auto overscroll-contain px-4 py-4 xl:px-5">
|
<div className="min-h-0 overflow-y-auto overscroll-contain px-4 py-4 xl:px-5">
|
||||||
<section aria-labelledby="artifact-summary-heading">
|
<section aria-labelledby="artifact-summary-heading">
|
||||||
@@ -116,7 +124,6 @@ export function AnalysisArtifactWorkspace({
|
|||||||
<ArtifactActionBar
|
<ArtifactActionBar
|
||||||
artifact={artifact}
|
artifact={artifact}
|
||||||
surfaceMode={surfaceMode}
|
surfaceMode={surfaceMode}
|
||||||
mapSplitAvailable={mapSplitAvailable}
|
|
||||||
onSetView={onSetView}
|
onSetView={onSetView}
|
||||||
onCollapse={onCollapse}
|
onCollapse={onCollapse}
|
||||||
onDestroy={onDestroy}
|
onDestroy={onDestroy}
|
||||||
@@ -133,9 +140,20 @@ export function AnalysisArtifactWorkspace({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ArtifactHeader({ artifact }: { artifact: AnalysisArtifact }) {
|
function ArtifactHeader({
|
||||||
|
artifact,
|
||||||
|
reserveViewControlSpace
|
||||||
|
}: {
|
||||||
|
artifact: AnalysisArtifact;
|
||||||
|
reserveViewControlSpace: boolean;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<header className="surface-control relative z-10 border-b px-4 py-4 xl:px-5">
|
<header
|
||||||
|
className={cn(
|
||||||
|
"surface-control relative z-10 border-b px-4 pb-4 xl:px-5",
|
||||||
|
reserveViewControlSpace ? "pt-14" : "pt-4"
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex items-start justify-between gap-5">
|
<div className="flex items-start justify-between gap-5">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="flex flex-wrap items-center gap-2 text-xs font-medium text-slate-500">
|
<div className="flex flex-wrap items-center gap-2 text-xs font-medium text-slate-500">
|
||||||
@@ -195,11 +213,11 @@ function AnalysisBlockView({
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex min-h-14 w-full items-start gap-3 px-4 py-3 text-left active:scale-[0.99]"
|
className="flex min-h-14 w-full items-start gap-3 px-4 py-3 text-left transition-colors hover:bg-blue-50/40 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500/60"
|
||||||
aria-label={`查看${block.title}详情`}
|
aria-label={`查看${block.title}详情`}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
>
|
>
|
||||||
<span className="mt-0.5 grid h-8 w-8 shrink-0 place-items-center rounded-md bg-blue-50 text-blue-700">
|
<span className="mt-0.5 grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-blue-50 text-blue-700">
|
||||||
<FileSearch size={16} aria-hidden="true" />
|
<FileSearch size={16} aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
<span className="min-w-0 flex-1">
|
<span className="min-w-0 flex-1">
|
||||||
@@ -336,9 +354,9 @@ function ArtifactDetailDrawer({
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label="关闭证据详情"
|
aria-label="关闭证据详情"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="grid h-9 w-9 shrink-0 place-items-center rounded-lg text-slate-500 hover:bg-slate-200/70 hover:text-slate-900 active:scale-95"
|
className={ARTIFACT_ICON_BUTTON_CLASS_NAME}
|
||||||
>
|
>
|
||||||
<X size={17} aria-hidden="true" />
|
<X size={15} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-5 px-5 py-5">
|
<div className="space-y-5 px-5 py-5">
|
||||||
@@ -431,32 +449,39 @@ function formatAnalyticalTimeRange(range: AnalysisArtifact["analyticalTimeRange"
|
|||||||
function ArtifactActionBar({
|
function ArtifactActionBar({
|
||||||
artifact,
|
artifact,
|
||||||
surfaceMode,
|
surfaceMode,
|
||||||
mapSplitAvailable,
|
|
||||||
onSetView,
|
onSetView,
|
||||||
onCollapse,
|
onCollapse,
|
||||||
onDestroy
|
onDestroy
|
||||||
}: {
|
}: {
|
||||||
artifact: AnalysisArtifact;
|
artifact: AnalysisArtifact;
|
||||||
surfaceMode: WorkbenchSurfaceMode;
|
surfaceMode: WorkbenchSurfaceMode;
|
||||||
mapSplitAvailable: boolean;
|
|
||||||
onSetView: (view: ArtifactRequestedView) => void;
|
onSetView: (view: ArtifactRequestedView) => void;
|
||||||
onCollapse: () => void;
|
onCollapse: () => void;
|
||||||
onDestroy: () => void;
|
onDestroy: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const prefersReducedMotion = useReducedMotion();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="surface-control relative z-20 mb-[calc(var(--workbench-timeline-height)+0.25rem)] flex min-h-14 items-center justify-between gap-3 border-t px-3">
|
<footer className="surface-control relative z-20 mb-[calc(var(--workbench-timeline-height)+0.25rem)] flex min-h-14 items-center justify-between gap-3 border-t px-3">
|
||||||
<div className="flex min-w-0 items-center gap-1">
|
<div className="flex min-w-0 items-center gap-1">
|
||||||
{artifact.mapRelation !== "none" ? (
|
{artifact.mapRelation !== "none" && surfaceMode === "map_split" ? (
|
||||||
<button type="button" onClick={() => onSetView(surfaceMode === "map_split" ? "focus" : mapSplitAvailable ? "map_split" : "map_only")} className="inline-flex h-10 items-center gap-2 rounded-md px-3 text-xs font-semibold text-slate-700 hover:bg-slate-100 active:scale-95">
|
<motion.div
|
||||||
{surfaceMode === "map_split" ? <Focus size={15} aria-hidden="true" /> : <MapIcon size={15} aria-hidden="true" />}
|
className={ARTIFACT_VIEW_CONTROL_SHELL_CLASS_NAME}
|
||||||
{surfaceMode === "map_split" ? "专注分析" : "显示地图"}
|
initial={prefersReducedMotion ? false : { opacity: 0, y: 4, scale: 0.98 }}
|
||||||
</button>
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
|
transition={prefersReducedMotion ? { duration: 0 } : { duration: 0.18, ease: [0.22, 1, 0.36, 1] }}
|
||||||
|
>
|
||||||
|
<button type="button" onClick={() => onSetView("focus")} className={ARTIFACT_VIEW_CONTROL_BUTTON_CLASS_NAME}>
|
||||||
|
<Focus size={13} aria-hidden="true" />
|
||||||
|
专注分析
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex shrink-0 items-center gap-1">
|
<div className="flex shrink-0 items-center gap-1">
|
||||||
{artifact.actions.slice(0, 1).map((action) => <ArtifactPrimaryAction key={action.id} action={action} artifact={artifact} />)}
|
{artifact.actions.slice(0, 1).map((action) => <ArtifactPrimaryAction key={action.id} action={action} artifact={artifact} />)}
|
||||||
<button type="button" aria-label="将成果收起为预览" title="收起为预览" onClick={onCollapse} className="grid h-10 w-10 place-items-center rounded-md text-slate-600 hover:bg-slate-100 active:scale-95"><Minimize2 size={16} aria-hidden="true" /></button>
|
<button type="button" aria-label="将成果收起为预览" title="收起为预览" onClick={onCollapse} className={ARTIFACT_ICON_BUTTON_CLASS_NAME}><Minimize2 size={15} aria-hidden="true" /></button>
|
||||||
<button type="button" aria-label="销毁当前成果" title="销毁当前成果" onClick={onDestroy} className="grid h-10 w-10 place-items-center rounded-md text-slate-500 hover:bg-rose-50 hover:text-rose-700 active:scale-95"><Trash2 size={16} aria-hidden="true" /></button>
|
<button type="button" aria-label="销毁当前成果" title="销毁当前成果" onClick={onDestroy} className={ARTIFACT_DANGER_ICON_BUTTON_CLASS_NAME}><Trash2 size={15} aria-hidden="true" /></button>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
@@ -467,9 +492,9 @@ function ArtifactPrimaryAction({ action, artifact }: { action: AnalysisArtifactA
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => showMapNotice({ tone: "info", title: action.label, message: `${artifact.title}:${action.description}` })}
|
onClick={() => showMapNotice({ tone: "info", title: action.label, message: `${artifact.title}:${action.description}` })}
|
||||||
className="hidden h-10 items-center gap-2 rounded-md bg-blue-600 px-3 text-xs font-semibold text-white shadow-[0_2px_6px_rgba(37,99,235,0.24)] hover:bg-blue-700 active:scale-95 sm:inline-flex"
|
className={cn("hidden sm:inline-flex", ARTIFACT_PRIMARY_ACTION_BUTTON_CLASS_NAME)}
|
||||||
>
|
>
|
||||||
<ShieldCheck size={15} aria-hidden="true" />
|
<ShieldCheck size={14} aria-hidden="true" />
|
||||||
{action.label}
|
{action.label}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { ChevronUp, FileChartColumnIncreasing, Sparkles, Trash2 } from "lucide-react";
|
import { ChevronUp, FileChartColumnIncreasing, Sparkles, Trash2 } from "lucide-react";
|
||||||
import { cn } from "@/shared/ui/cn";
|
import { cn } from "@/shared/ui/cn";
|
||||||
|
import {
|
||||||
|
ARTIFACT_DANGER_ICON_BUTTON_CLASS_NAME,
|
||||||
|
ARTIFACT_PRIMARY_ACTION_BUTTON_CLASS_NAME
|
||||||
|
} from "./artifact-view-control-styles";
|
||||||
import type { AnalysisArtifact, WorkbenchSurfaceMode } from "./workspace-model";
|
import type { AnalysisArtifact, WorkbenchSurfaceMode } from "./workspace-model";
|
||||||
|
|
||||||
export function ArtifactPeekBar({
|
export function ArtifactPeekBar({
|
||||||
@@ -36,9 +40,9 @@ export function ArtifactPeekBar({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
className="inline-flex h-10 shrink-0 items-center gap-1.5 rounded-md bg-blue-500 px-3 text-xs font-semibold text-white hover:bg-blue-400 active:scale-95"
|
className={ARTIFACT_PRIMARY_ACTION_BUTTON_CLASS_NAME}
|
||||||
>
|
>
|
||||||
<ChevronUp size={15} aria-hidden="true" />
|
<ChevronUp size={14} aria-hidden="true" />
|
||||||
打开成果
|
打开成果
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -46,9 +50,9 @@ export function ArtifactPeekBar({
|
|||||||
aria-label="销毁预览成果"
|
aria-label="销毁预览成果"
|
||||||
title="销毁预览成果"
|
title="销毁预览成果"
|
||||||
onClick={onDestroy}
|
onClick={onDestroy}
|
||||||
className="surface-control grid h-10 w-10 shrink-0 place-items-center rounded-xl border text-slate-500 hover:text-rose-700 active:scale-95"
|
className={cn("surface-control border", ARTIFACT_DANGER_ICON_BUTTON_CLASS_NAME)}
|
||||||
>
|
>
|
||||||
<Trash2 size={16} aria-hidden="true" />
|
<Trash2 size={15} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export const ARTIFACT_VIEW_CONTROL_SHELL_CLASS_NAME =
|
||||||
|
"surface-control inline-flex shrink-0 items-center rounded-xl border border-white/70 p-0.5 shadow-[0_4px_14px_rgba(15,23,42,0.08)]";
|
||||||
|
|
||||||
|
export const ARTIFACT_VIEW_CONTROL_BUTTON_CLASS_NAME =
|
||||||
|
"inline-flex h-7 items-center gap-1.5 rounded-lg px-2 text-[10px] font-medium text-slate-500 transition-colors hover:bg-white/80 hover:text-blue-700 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500/60";
|
||||||
|
|
||||||
|
export const ARTIFACT_PRIMARY_ACTION_BUTTON_CLASS_NAME =
|
||||||
|
"inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg bg-blue-600 px-2.5 text-[11px] font-semibold text-white shadow-[0_2px_6px_rgba(37,99,235,0.2)] transition-colors hover:bg-blue-700 active:bg-blue-800 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500/60 focus-visible:ring-offset-2";
|
||||||
|
|
||||||
|
export const ARTIFACT_ICON_BUTTON_CLASS_NAME =
|
||||||
|
"grid h-8 w-8 shrink-0 place-items-center rounded-lg text-slate-500 transition-colors hover:bg-white/80 hover:text-blue-700 active:bg-slate-200/70 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-500/60";
|
||||||
|
|
||||||
|
export const ARTIFACT_DANGER_ICON_BUTTON_CLASS_NAME =
|
||||||
|
"grid h-8 w-8 shrink-0 place-items-center rounded-lg text-slate-500 transition-colors hover:bg-rose-50 hover:text-rose-700 active:bg-rose-100 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-rose-500/60";
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft, Map as MapIcon } from "lucide-react";
|
||||||
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import {
|
|
||||||
MAP_CONTROL_HOVER_CLASS_NAME,
|
|
||||||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
|
||||||
MAP_CONTROL_SURFACE_CLASS_NAME
|
|
||||||
} from "@/features/map/core";
|
|
||||||
import { cn } from "@/shared/ui/cn";
|
import { cn } from "@/shared/ui/cn";
|
||||||
import { OPERATIONAL_TIMELINE_LAYOUT } from "../operational-timeline/operational-timeline-model";
|
import { OPERATIONAL_TIMELINE_LAYOUT } from "../operational-timeline/operational-timeline-model";
|
||||||
import { AnalysisArtifactWorkspace } from "./analysis-document-view";
|
import { AnalysisArtifactWorkspace } from "./analysis-document-view";
|
||||||
import { ArtifactPeekBar } from "./artifact-peek-bar";
|
import { ArtifactPeekBar } from "./artifact-peek-bar";
|
||||||
|
import {
|
||||||
|
ARTIFACT_VIEW_CONTROL_BUTTON_CLASS_NAME,
|
||||||
|
ARTIFACT_VIEW_CONTROL_SHELL_CLASS_NAME
|
||||||
|
} from "./artifact-view-control-styles";
|
||||||
import type {
|
import type {
|
||||||
AnalysisArtifact,
|
AnalysisArtifact,
|
||||||
ArtifactRequestedView,
|
ArtifactRequestedView,
|
||||||
@@ -52,9 +51,18 @@ export function WorkbenchMainFrame({
|
|||||||
const prefersReducedMotion = useReducedMotion();
|
const prefersReducedMotion = useReducedMotion();
|
||||||
const showMap = surfaceMode !== "focus";
|
const showMap = surfaceMode !== "focus";
|
||||||
const showArtifact = activeArtifact && surfaceMode !== "map_only";
|
const showArtifact = activeArtifact && surfaceMode !== "map_only";
|
||||||
|
const showArtifactViewControl = Boolean(
|
||||||
|
activeArtifact &&
|
||||||
|
activeArtifact.mapRelation !== "none" &&
|
||||||
|
(surfaceMode === "focus" || surfaceMode === "map_only")
|
||||||
|
);
|
||||||
|
const mapOnly = surfaceMode === "map_only";
|
||||||
const timelineTransition = prefersReducedMotion
|
const timelineTransition = prefersReducedMotion
|
||||||
? { duration: 0 }
|
? { duration: 0 }
|
||||||
: { duration: 0.28, ease: [0.22, 1, 0.36, 1] as const };
|
: { duration: 0.28, ease: [0.22, 1, 0.36, 1] as const };
|
||||||
|
const viewControlTransition = prefersReducedMotion
|
||||||
|
? { duration: 0 }
|
||||||
|
: { duration: 0.18, ease: [0.22, 1, 0.36, 1] as const };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -82,21 +90,6 @@ export function WorkbenchMainFrame({
|
|||||||
>
|
>
|
||||||
{mapContent}
|
{mapContent}
|
||||||
{mapOverlay}
|
{mapOverlay}
|
||||||
{activeArtifact && surfaceMode === "map_only" ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => onSetArtifactView("focus")}
|
|
||||||
className={cn(
|
|
||||||
"pointer-events-auto absolute left-3 top-3 z-20 inline-flex h-10 items-center gap-2 px-3 text-xs font-semibold lg:left-[max(1rem,calc(6rem-var(--workbench-agent-current-width)))] lg:top-4",
|
|
||||||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
|
||||||
MAP_CONTROL_SURFACE_CLASS_NAME,
|
|
||||||
MAP_CONTROL_HOVER_CLASS_NAME
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ArrowLeft size={15} aria-hidden="true" />
|
|
||||||
返回分析成果
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{activeArtifact ? (
|
{activeArtifact ? (
|
||||||
@@ -112,7 +105,6 @@ export function WorkbenchMainFrame({
|
|||||||
<AnalysisArtifactWorkspace
|
<AnalysisArtifactWorkspace
|
||||||
artifact={activeArtifact}
|
artifact={activeArtifact}
|
||||||
surfaceMode={surfaceMode}
|
surfaceMode={surfaceMode}
|
||||||
mapSplitAvailable={mapSplitAvailable}
|
|
||||||
onSetView={onSetArtifactView}
|
onSetView={onSetArtifactView}
|
||||||
onCollapse={onCollapseArtifact}
|
onCollapse={onCollapseArtifact}
|
||||||
onDestroy={onDestroyArtifact}
|
onDestroy={onDestroyArtifact}
|
||||||
@@ -128,6 +120,42 @@ export function WorkbenchMainFrame({
|
|||||||
onDestroy={onDestroyPendingArtifact}
|
onDestroy={onDestroyPendingArtifact}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
{showArtifactViewControl ? (
|
||||||
|
<motion.div
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-auto absolute left-6 top-4 z-40 lg:left-[max(1.5rem,calc(6rem-var(--workbench-agent-current-width)))]",
|
||||||
|
ARTIFACT_VIEW_CONTROL_SHELL_CLASS_NAME
|
||||||
|
)}
|
||||||
|
initial={prefersReducedMotion ? false : { opacity: 0, y: 6, scale: 0.98 }}
|
||||||
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
|
exit={prefersReducedMotion ? { opacity: 0 } : { opacity: 0, y: 5, scale: 0.98 }}
|
||||||
|
transition={viewControlTransition}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={mapOnly ? "返回分析成果" : "显示地图"}
|
||||||
|
onClick={() => onSetArtifactView(mapOnly ? "focus" : mapSplitAvailable ? "map_split" : "map_only")}
|
||||||
|
className={ARTIFACT_VIEW_CONTROL_BUTTON_CLASS_NAME}
|
||||||
|
>
|
||||||
|
<AnimatePresence initial={false} mode="wait">
|
||||||
|
<motion.span
|
||||||
|
key={mapOnly ? "return-analysis" : "show-map"}
|
||||||
|
className="inline-flex items-center gap-1.5"
|
||||||
|
initial={prefersReducedMotion ? false : { opacity: 0, x: mapOnly ? 5 : -5 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={prefersReducedMotion ? { opacity: 0 } : { opacity: 0, x: mapOnly ? -5 : 5 }}
|
||||||
|
transition={viewControlTransition}
|
||||||
|
>
|
||||||
|
{mapOnly ? <ArrowLeft size={13} aria-hidden="true" /> : <MapIcon size={13} aria-hidden="true" />}
|
||||||
|
{mapOnly ? "返回分析成果" : "显示地图"}
|
||||||
|
</motion.span>
|
||||||
|
</AnimatePresence>
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
) : null}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
Reference in New Issue
Block a user