initial commit after split
This commit is contained in:
127
webui/src/features/campaigns/components/VersionLine.tsx
Normal file
127
webui/src/features/campaigns/components/VersionLine.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { ArrowBigLeft, ArrowBigLeftDash, ArrowBigRight, ArrowBigRightDash } from "lucide-react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import type { CampaignVersionDetail, CampaignVersionListItem } from "../../../api/campaigns";
|
||||
import { useCampaignUnsavedChanges } from "../context/UnsavedChangesContext";
|
||||
import { formatDateTime } from "../utils/campaignView";
|
||||
|
||||
type VersionLineProps = {
|
||||
version: CampaignVersionDetail | CampaignVersionListItem | null;
|
||||
versions?: CampaignVersionListItem[];
|
||||
status?: string;
|
||||
loadedAt?: string | null;
|
||||
};
|
||||
|
||||
export default function VersionLine({ version, versions = [], status, loadedAt }: VersionLineProps) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { requestNavigation } = useCampaignUnsavedChanges();
|
||||
const sorted = versions.slice().sort((a, b) => (a.version_number ?? 0) - (b.version_number ?? 0));
|
||||
const currentIndex = version ? sorted.findIndex((item) => item.id === version.id) : -1;
|
||||
const first = currentIndex > 0 ? sorted[0] : null;
|
||||
const previous = currentIndex > 0 ? sorted[currentIndex - 1] : null;
|
||||
const next = currentIndex >= 0 && currentIndex < sorted.length - 1 ? sorted[currentIndex + 1] : null;
|
||||
const latest = currentIndex >= 0 && currentIndex < sorted.length - 1 ? sorted[sorted.length - 1] : null;
|
||||
const suffix = status ?? `Loaded ${formatDateTime(loadedAt ?? version?.updated_at)}`;
|
||||
|
||||
function openVersion(target: CampaignVersionListItem) {
|
||||
const targetUrl = versionTarget(location.pathname, location.search, target.id);
|
||||
requestNavigation(() => navigate(targetUrl));
|
||||
}
|
||||
|
||||
return (
|
||||
<p className="mono-small version-line">
|
||||
<VersionArrow
|
||||
icon="first"
|
||||
target={first}
|
||||
fallbackLabel="Already at first version"
|
||||
onOpen={openVersion}
|
||||
/>
|
||||
<VersionArrow
|
||||
icon="previous"
|
||||
target={previous}
|
||||
fallbackLabel="No older version"
|
||||
onOpen={openVersion}
|
||||
/>
|
||||
<span>Version {version ? `#${version.version_number}` : "—"}</span>
|
||||
<VersionArrow
|
||||
icon="next"
|
||||
target={next}
|
||||
fallbackLabel="No newer version"
|
||||
onOpen={openVersion}
|
||||
/>
|
||||
<VersionArrow
|
||||
icon="latest"
|
||||
target={latest}
|
||||
fallbackLabel="Already at latest version"
|
||||
onOpen={openVersion}
|
||||
/>
|
||||
<span className="version-line-separator">·</span>
|
||||
<span>{suffix}</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
type VersionArrowProps = {
|
||||
icon: "first" | "previous" | "next" | "latest";
|
||||
target: CampaignVersionListItem | null;
|
||||
fallbackLabel: string;
|
||||
onOpen: (target: CampaignVersionListItem) => void;
|
||||
};
|
||||
|
||||
function VersionArrow({ icon, target, fallbackLabel, onOpen }: VersionArrowProps) {
|
||||
const Icon = getVersionIcon(icon);
|
||||
const actionLabel = getVersionActionLabel(icon, target);
|
||||
|
||||
if (!target) {
|
||||
return (
|
||||
<span className="version-arrow disabled" title={fallbackLabel} aria-label={fallbackLabel}>
|
||||
<Icon aria-hidden="true" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="version-arrow"
|
||||
onClick={() => onOpen(target)}
|
||||
title={actionLabel}
|
||||
aria-label={actionLabel}
|
||||
>
|
||||
<Icon aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function getVersionIcon(icon: VersionArrowProps["icon"]) {
|
||||
switch (icon) {
|
||||
case "first":
|
||||
return ArrowBigLeftDash;
|
||||
case "previous":
|
||||
return ArrowBigLeft;
|
||||
case "next":
|
||||
return ArrowBigRight;
|
||||
case "latest":
|
||||
return ArrowBigRightDash;
|
||||
}
|
||||
}
|
||||
|
||||
function getVersionActionLabel(icon: VersionArrowProps["icon"], target: CampaignVersionListItem | null): string {
|
||||
const versionLabel = target ? `version #${target.version_number}` : "version";
|
||||
switch (icon) {
|
||||
case "first":
|
||||
return `Open first ${versionLabel}`;
|
||||
case "previous":
|
||||
return `Open previous ${versionLabel}`;
|
||||
case "next":
|
||||
return `Open next ${versionLabel}`;
|
||||
case "latest":
|
||||
return `Open latest ${versionLabel}`;
|
||||
}
|
||||
}
|
||||
|
||||
function versionTarget(pathname: string, search: string, versionId: string): string {
|
||||
const params = new URLSearchParams(search);
|
||||
params.set("version", versionId);
|
||||
return `${pathname}?${params.toString()}`;
|
||||
}
|
||||
Reference in New Issue
Block a user