m/cup
1
0
mirror of https://github.com/sergi0g/cup.git synced 2025-11-11 14:43:49 -05:00

Various frontend improvements

This commit is contained in:
Sergio
2024-12-20 17:35:39 +02:00
parent 4f1075b2b2
commit 09b6880295
3 changed files with 63 additions and 47 deletions

View File

@@ -7,11 +7,8 @@ import {
} from "@headlessui/react"; } from "@headlessui/react";
import { import {
IconAlertTriangleFilled, IconAlertTriangleFilled,
IconArrowUpRight,
IconCheck,
IconCircleArrowUpFilled, IconCircleArrowUpFilled,
IconCircleCheckFilled, IconCircleCheckFilled,
IconCopy,
IconCube, IconCube,
IconHelpCircleFilled, IconHelpCircleFilled,
IconStopwatch, IconStopwatch,
@@ -20,6 +17,7 @@ import {
import { WithTooltip } from "./Tooltip"; import { WithTooltip } from "./Tooltip";
import type { Image } from "../types"; import type { Image } from "../types";
import { theme } from "../theme"; import { theme } from "../theme";
import { PullCommand } from "./PullCommand";
const clickable_registries = [ const clickable_registries = [
"registry-1.docker.io", "registry-1.docker.io",
@@ -30,23 +28,16 @@ const clickable_registries = [
export default function Image({ data }: { data: Image }) { export default function Image({ data }: { data: Image }) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [copySuccess, setCopySuccess] = useState(false);
const handleOpen = () => { const handleOpen = () => {
setOpen(true); setOpen(true);
}; };
const handleClose = () => { const handleClose = () => {
setOpen(false); setOpen(false);
}; };
const handleCopy = (text: string) => { const new_reference =
return () => { data.result.info?.type == "version"
navigator.clipboard.writeText(text).then(() => { ? data.reference.replace(data.parts.tag, data.result.info.new_version)
setCopySuccess(true); : data.reference;
setTimeout(() => {
setCopySuccess(false);
}, 3000);
});
};
};
var url: string | null = null; var url: string | null = null;
if (clickable_registries.includes(data.parts.registry)) { if (clickable_registries.includes(data.parts.registry)) {
switch (data.parts.registry) { switch (data.parts.registry) {
@@ -93,12 +84,21 @@ export default function Image({ data }: { data: Image }) {
href={url} href={url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="after:relative after:bottom-[1px] after:left-0 after:block after:h-[2px] after:w-full after:scale-x-0 after:bg-black after:transition-transform after:duration-300 hover:after:scale-x-100 after:dark:bg-white" className={`group flex w-fit items-center justify-center gap-1 text-black hover:underline dark:text-white`}
> >
{data.reference} <span>{data.reference}</span>
<span className="inline-flex align-text-top"> <svg
<IconArrowUpRight className="size-3 shrink-0" /> viewBox="0 0 12 12"
</span> fill="none"
width="10px"
xmlns="http://www.w3.org/2000/svg"
className="transition-all duration-100 group-hover:rotate-45"
>
<path
d="M11 9.283V1H2.727v1.44h5.83L1 9.99 2.01 11l7.556-7.55v5.833H11Z"
fill="currentColor"
></path>
</svg>
</a> </a>
</> </>
) : ( ) : (
@@ -119,35 +119,13 @@ export default function Image({ data }: { data: Image }) {
</span> </span>
</div> </div>
{data.result.error && ( {data.result.error && (
<div className="mb-4 flex items-center gap-3 overflow-hidden break-all rounded-md bg-yellow-400/10 px-3 py-2"> <div className="break-before mb-4 flex items-center gap-3 overflow-hidden rounded-md bg-yellow-400/10 px-3 py-2">
<IconAlertTriangleFilled className="size-5 shrink-0 text-yellow-500" /> <IconAlertTriangleFilled className="size-6 shrink-0 text-yellow-500" />
{data.result.error} {data.result.error}
</div> </div>
)} )}
{data.result.has_update && ( {data.result.has_update && (
<div className="flex flex-col gap-1"> <PullCommand reference={new_reference} />
Pull command
<div
className={`bg-${theme}-100 dark:bg-${theme}-950 group relative mb-4 flex items-center rounded-md px-3 py-2 font-mono text-gray-500`}
>
<p className="overflow-scroll">
docker pull {data.result.info?.type == "version" ? data.reference.replace(data.parts.tag, data.result.info.new_version) : data.reference}
</p>
{navigator.clipboard &&
(copySuccess ? (
<IconCheck className="absolute right-3" />
) : (
<button
className="duration-50 absolute right-3 opacity-0 transition-opacity group-hover:opacity-100"
onClick={handleCopy(
`docker pull ${data.result.info?.type == "version" ? data.reference.replace(data.parts.tag, data.result.info.new_version) : data.reference}`,
)}
>
<IconCopy />
</button>
))}
</div>
</div>
)} )}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
{data.result.info?.type == "digest" && ( {data.result.info?.type == "digest" && (
@@ -156,7 +134,7 @@ export default function Image({ data }: { data: Image }) {
? "Local digests" ? "Local digests"
: "Local digest"} : "Local digest"}
<div <div
className={`bg-${theme}-100 dark:bg-${theme}-950 scrollable rounded-md px-3 py-2 font-mono text-gray-500`} className={`bg-${theme}-100 dark:bg-${theme}-950 scrollable rounded-md px-3 py-2 font-mono text-${theme}-500`}
> >
<p className="overflow-x-scroll"> <p className="overflow-x-scroll">
{data.result.info.local_digests.join("\n")} {data.result.info.local_digests.join("\n")}
@@ -166,7 +144,7 @@ export default function Image({ data }: { data: Image }) {
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
Remote digest Remote digest
<div <div
className={`bg-${theme}-100 dark:bg-${theme}-950 rounded-md px-3 py-2 font-mono text-gray-500`} className={`bg-${theme}-100 dark:bg-${theme}-950 rounded-md px-3 py-2 font-mono text-${theme}-500`}
> >
<p className="overflow-x-scroll"> <p className="overflow-x-scroll">
{data.result.info.remote_digest} {data.result.info.remote_digest}

View File

@@ -0,0 +1,38 @@
import { useState } from "react";
import { theme } from "../theme";
import { IconCheck, IconClipboard } from "@tabler/icons-react";
export function PullCommand({ reference }: { reference: string }) {
const [copySuccess, setCopySuccess] = useState(false);
const handleCopy = (text: string) => {
return () => {
navigator.clipboard.writeText(text).then(() => {
setCopySuccess(true);
setTimeout(() => {
setCopySuccess(false);
}, 3000);
});
};
};
return (
<div className="flex flex-col gap-1">
Pull command
<div
className={`w-full group relative flex items-center rounded-lg bg-${theme}-100 px-3 py-2 font-mono text-${theme}-700 dark:bg-${theme}-950 dark:text-${theme}-300`}
>
<p className="overflow-scroll">docker pull {reference}</p>
{navigator.clipboard &&
(copySuccess ? (
<IconCheck className="absolute right-3 size-7 bg-neutral-100 pl-2 dark:bg-neutral-950" />
) : (
<button
className="duration-50 absolute right-3 opacity-0 transition-opacity group-hover:opacity-100"
onClick={handleCopy(`docker pull ${reference}`)}
>
<IconClipboard className="size-5"/>
</button>
))}
</div>
</div>
);
}

View File

@@ -33,7 +33,7 @@ export default function RefreshButton() {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
className="group-disabled:animate-spin" className="shrink-0 group-disabled:animate-spin"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M4,11A8.1,8.1 0 0 1 19.5,9M20,5v4h-4" /> <path d="M4,11A8.1,8.1 0 0 1 19.5,9M20,5v4h-4" />