mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-09-24 17:11:00 +00:00
feat(VersionsView): initial redesign
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 297 B |
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 216 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 255 B |
@@ -10,7 +10,11 @@ interface DownloadOverlayProps {
|
|||||||
editions: Edition[];
|
editions: Edition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DownloadOverlay = memo(function DownloadOverlay({ downloadProgress, downloadingIds, editions }: DownloadOverlayProps) {
|
export const DownloadOverlay = memo(function DownloadOverlay({
|
||||||
|
downloadProgress,
|
||||||
|
downloadingIds,
|
||||||
|
editions,
|
||||||
|
}: DownloadOverlayProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
if (downloadingIds.length === 0) return null;
|
if (downloadingIds.length === 0) return null;
|
||||||
|
|
||||||
@@ -24,14 +28,14 @@ export const DownloadOverlay = memo(function DownloadOverlay({ downloadProgress,
|
|||||||
style={{ imageRendering: "pixelated" }}
|
style={{ imageRendering: "pixelated" }}
|
||||||
>
|
>
|
||||||
<div className="px-3 pt-2.5 pb-2">
|
<div className="px-3 pt-2.5 pb-2">
|
||||||
<span className="text-xl text-[#333333] mc-text-shadow">
|
<span className="text-xl text-[#333333]">{t("download.title")}</span>
|
||||||
{t("download.title")}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1.5 py-2 max-h-[260px] overflow-y-auto custom-scrollbar mc-options-fg">
|
<div className="flex flex-col gap-1.5 py-2 max-h-[260px] overflow-y-auto custom-scrollbar mc-options-fg">
|
||||||
{downloadingIds.map((id) => {
|
{downloadingIds.map((id) => {
|
||||||
const pct = downloadProgress[id] ?? 0;
|
const pct = downloadProgress[id] ?? 0;
|
||||||
const edition = editions.find((e) => e.instanceId === id || e.id === id);
|
const edition = editions.find(
|
||||||
|
(e) => e.instanceId === id || e.id === id,
|
||||||
|
);
|
||||||
const name =
|
const name =
|
||||||
edition?.name ||
|
edition?.name ||
|
||||||
(id.startsWith("runner_")
|
(id.startsWith("runner_")
|
||||||
@@ -43,53 +47,61 @@ export const DownloadOverlay = memo(function DownloadOverlay({ downloadProgress,
|
|||||||
<div className="flex-1 flex flex-row justify-between">
|
<div className="flex-1 flex flex-row justify-between">
|
||||||
<div className="flex-1 flex flex-row">
|
<div className="flex-1 flex flex-row">
|
||||||
{edition?.logo ? (
|
{edition?.logo ? (
|
||||||
edition.logo.startsWith("http") || edition.logo.startsWith("/images") ? (
|
edition.logo.startsWith("http") ||
|
||||||
<img
|
edition.logo.startsWith("/images") ? (
|
||||||
src={edition.logo}
|
<img
|
||||||
alt=""
|
src={edition.logo}
|
||||||
className="w-6 h-6 object-contain shrink-0"
|
alt=""
|
||||||
style={{ imageRendering: "pixelated" }}
|
className="w-6 h-6 object-contain shrink-0"
|
||||||
/>
|
style={{ imageRendering: "pixelated" }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ScreenshotImage
|
||||||
|
path={edition.logo}
|
||||||
|
alt=""
|
||||||
|
className="w-6 h-6 object-contain shrink-0"
|
||||||
|
style={{ imageRendering: "pixelated" }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : edition?.titleImage ? (
|
||||||
|
edition.titleImage.startsWith("http") ||
|
||||||
|
edition.titleImage.startsWith("/images") ? (
|
||||||
|
<img
|
||||||
|
src={edition.titleImage}
|
||||||
|
alt=""
|
||||||
|
className="w-6 h-6 object-contain shrink-0"
|
||||||
|
style={{ imageRendering: "pixelated" }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ScreenshotImage
|
||||||
|
path={edition.titleImage}
|
||||||
|
alt=""
|
||||||
|
className="w-6 h-6 object-contain shrink-0"
|
||||||
|
style={{ imageRendering: "pixelated" }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<ScreenshotImage
|
<div className="w-6 h-6 flex items-center justify-center border border-[#555] bg-black/40 shrink-0">
|
||||||
path={edition.logo}
|
<svg
|
||||||
alt=""
|
className="w-3 h-3 text-[#FFFF55]"
|
||||||
className="w-6 h-6 object-contain shrink-0"
|
viewBox="0 0 24 24"
|
||||||
style={{ imageRendering: "pixelated" }}
|
fill="none"
|
||||||
/>
|
stroke="currentColor"
|
||||||
)
|
strokeWidth="3"
|
||||||
) : edition?.titleImage ? (
|
>
|
||||||
edition.titleImage.startsWith("http") || edition.titleImage.startsWith("/images") ? (
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||||
<img
|
<polyline points="7 10 12 15 17 10" />
|
||||||
src={edition.titleImage}
|
<line x1="12" y1="15" x2="12" y2="3" />
|
||||||
alt=""
|
</svg>
|
||||||
className="w-6 h-6 object-contain shrink-0"
|
</div>
|
||||||
style={{ imageRendering: "pixelated" }}
|
)}
|
||||||
/>
|
<span className="text-sm text-[#333333] truncate leading-tight shrink-0 self-center pl-1">
|
||||||
) : (
|
{name}
|
||||||
<ScreenshotImage
|
</span>
|
||||||
path={edition.titleImage}
|
</div>
|
||||||
alt=""
|
<span className="text-sm text-[#333333] w-7 text-right shrink-0 pr-1">
|
||||||
className="w-6 h-6 object-contain shrink-0"
|
{Math.floor(pct)}%
|
||||||
style={{ imageRendering: "pixelated" }}
|
</span>
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<div className="w-6 h-6 flex items-center justify-center border border-[#555] bg-black/40 shrink-0">
|
|
||||||
<svg className="w-3 h-3 text-[#FFFF55]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
|
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
||||||
<polyline points="7 10 12 15 17 10" />
|
|
||||||
<line x1="12" y1="15" x2="12" y2="3" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<span className="text-sm text-[#333333] truncate leading-tight shrink-0 self-center pl-1">
|
|
||||||
{name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-sm text-[#333333] w-7 text-right shrink-0 pr-1">
|
|
||||||
{Math.floor(pct)}%
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<div className="flex-1 h-3 border border-[#666666] bg-[#666666]">
|
<div className="flex-1 h-3 border border-[#666666] bg-[#666666]">
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { useState, useEffect, useRef, memo } from "react";
|
import {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
memo,
|
||||||
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { TauriService, PlaytimeResponse } from "../../services/TauriService";
|
import { TauriService, PlaytimeResponse } from "../../services/TauriService";
|
||||||
@@ -54,6 +61,9 @@ const DeleteConfirmButton = memo(function DeleteConfirmButton({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ROW_ESTIMATE = 52;
|
||||||
|
const ROW_GAP = 4;
|
||||||
|
|
||||||
function formatPlaytime(seconds: number): string {
|
function formatPlaytime(seconds: number): string {
|
||||||
const h = Math.floor(seconds / 3600);
|
const h = Math.floor(seconds / 3600);
|
||||||
const m = Math.floor((seconds % 3600) / 60);
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
@@ -66,7 +76,6 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setActiveView } = useUI();
|
const { setActiveView } = useUI();
|
||||||
const {
|
const {
|
||||||
profile: selectedProfile,
|
|
||||||
setProfile: setSelectedProfile,
|
setProfile: setSelectedProfile,
|
||||||
animationsEnabled,
|
animationsEnabled,
|
||||||
instanceLaunchArgs,
|
instanceLaunchArgs,
|
||||||
@@ -83,7 +92,6 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
addCustomEdition: onAddEdition,
|
addCustomEdition: onAddEdition,
|
||||||
updateCustomEdition: onUpdateEdition,
|
updateCustomEdition: onUpdateEdition,
|
||||||
downloadingIds,
|
downloadingIds,
|
||||||
downloadProgress,
|
|
||||||
updatesAvailable,
|
updatesAvailable,
|
||||||
addToSteam,
|
addToSteam,
|
||||||
cycleBranch,
|
cycleBranch,
|
||||||
@@ -138,7 +146,106 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
const [argsSchemas, setArgsSchemas] = useState<Record<string, boolean>>({});
|
const [argsSchemas, setArgsSchemas] = useState<Record<string, boolean>>({});
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const listRef = useRef<HTMLDivElement>(null);
|
const listRef = useRef<HTMLDivElement>(null);
|
||||||
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [menuDir, setMenuDir] = useState<"down" | "up">("down");
|
||||||
const ITEM_COUNT = visibleEditions.length + 3;
|
const ITEM_COUNT = visibleEditions.length + 3;
|
||||||
|
const [visibleRange, setVisibleRange] = useState<{
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}>({
|
||||||
|
start: 0,
|
||||||
|
end: visibleEditions.length ? visibleEditions.length - 1 : 0,
|
||||||
|
});
|
||||||
|
const itemHeightsRef = useRef<number[]>([]);
|
||||||
|
const observersRef = useRef<Map<number, ResizeObserver>>(new Map());
|
||||||
|
const visibleEditionsRef = useRef(visibleEditions);
|
||||||
|
visibleEditionsRef.current = visibleEditions;
|
||||||
|
const focusIndexRef = useRef(focusIndex);
|
||||||
|
const openMenuIdRef = useRef(openMenuId);
|
||||||
|
|
||||||
|
const updateVisibleRange = useCallback(() => {
|
||||||
|
const container = listRef.current;
|
||||||
|
const editions = visibleEditionsRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
const scrollTop = container.scrollTop;
|
||||||
|
const viewportHeight = container.clientHeight;
|
||||||
|
const heights = itemHeightsRef.current;
|
||||||
|
const n = editions.length;
|
||||||
|
let acc = 0;
|
||||||
|
let start = n;
|
||||||
|
let end = -1;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const itemTop = acc;
|
||||||
|
const itemBottom = acc + (heights[i] ?? ROW_ESTIMATE);
|
||||||
|
if (
|
||||||
|
itemTop >= scrollTop &&
|
||||||
|
itemBottom <= scrollTop + viewportHeight
|
||||||
|
) {
|
||||||
|
if (start === n) start = i;
|
||||||
|
end = i;
|
||||||
|
}
|
||||||
|
acc += (heights[i] ?? ROW_ESTIMATE) + ROW_GAP;
|
||||||
|
}
|
||||||
|
if (start === n) start = 0;
|
||||||
|
if (end < 0) end = n - 1;
|
||||||
|
const fi = focusIndexRef.current;
|
||||||
|
if (fi >= 0 && fi < n) {
|
||||||
|
start = Math.min(start, fi);
|
||||||
|
end = Math.max(end, fi);
|
||||||
|
}
|
||||||
|
const menuId = openMenuIdRef.current;
|
||||||
|
if (menuId) {
|
||||||
|
const mi = editions.findIndex((e) => e.id === menuId);
|
||||||
|
if (mi >= 0) {
|
||||||
|
start = Math.min(start, mi);
|
||||||
|
end = Math.max(end, mi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setVisibleRange((prev) =>
|
||||||
|
prev.start === start && prev.end === end ? prev : { start, end },
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setRowRef = (index: number) => (el: HTMLDivElement | null) => {
|
||||||
|
if (el) {
|
||||||
|
let ro = observersRef.current.get(index);
|
||||||
|
if (!ro) {
|
||||||
|
ro = new ResizeObserver(() => {
|
||||||
|
itemHeightsRef.current[index] = el.offsetHeight;
|
||||||
|
updateVisibleRange();
|
||||||
|
});
|
||||||
|
observersRef.current.set(index, ro);
|
||||||
|
}
|
||||||
|
ro.observe(el);
|
||||||
|
} else {
|
||||||
|
const ro = observersRef.current.get(index);
|
||||||
|
if (ro) {
|
||||||
|
ro.disconnect();
|
||||||
|
observersRef.current.delete(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
focusIndexRef.current = focusIndex;
|
||||||
|
}, [focusIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
openMenuIdRef.current = openMenuId;
|
||||||
|
}, [openMenuId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const container = listRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
const ro = new ResizeObserver(() => updateVisibleRange());
|
||||||
|
ro.observe(container);
|
||||||
|
updateVisibleRange();
|
||||||
|
return () => {
|
||||||
|
ro.disconnect();
|
||||||
|
observersRef.current.forEach((obs) => obs.disconnect());
|
||||||
|
observersRef.current.clear();
|
||||||
|
};
|
||||||
|
}, [updateVisibleRange]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (document.activeElement?.tagName === "INPUT") return;
|
if (document.activeElement?.tagName === "INPUT") return;
|
||||||
@@ -263,6 +370,23 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
}
|
}
|
||||||
}, [focusIndex]);
|
}, [focusIndex]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!openMenuId) return;
|
||||||
|
const container = listRef.current;
|
||||||
|
const menu = menuRef.current;
|
||||||
|
if (!container || !menu) return;
|
||||||
|
const row = menu.closest("[data-index]") as HTMLElement | null;
|
||||||
|
if (!row) return;
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
const rowRect = row.getBoundingClientRect();
|
||||||
|
const menuHeight = menu.offsetHeight;
|
||||||
|
const spaceBelow = containerRect.bottom - rowRect.bottom;
|
||||||
|
const spaceAbove = rowRect.top - containerRect.top;
|
||||||
|
setMenuDir(
|
||||||
|
spaceBelow < menuHeight && spaceAbove > spaceBelow ? "up" : "down",
|
||||||
|
);
|
||||||
|
}, [openMenuId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchPlaytimes = async () => {
|
const fetchPlaytimes = async () => {
|
||||||
const map: Record<string, PlaytimeResponse> = {};
|
const map: Record<string, PlaytimeResponse> = {};
|
||||||
@@ -341,51 +465,49 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
transition={{ duration: animationsEnabled ? 0.25 : 0 }}
|
transition={{ duration: animationsEnabled ? 0.25 : 0 }}
|
||||||
className="flex flex-col items-center w-full max-w-5xl outline-none"
|
className="flex flex-col items-center w-full max-w-5xl outline-none"
|
||||||
>
|
>
|
||||||
<h2 className="text-2xl text-white mc-text-shadow mt-2 mb-4 pb-2 w-[40%] max-w-[200px] text-center tracking-widest uppercase font-bold">
|
|
||||||
{t("versions.title")}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="w-full min-w-[480px] p-6 mb-4 mc-options-bg">
|
<div className="w-full min-w-[480px] p-6 mb-4 mc-options-bg">
|
||||||
<div
|
<div
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
className="w-full max-h-[45vh] overflow-y-auto py-2 custom-scrollbar"
|
onScroll={updateVisibleRange}
|
||||||
>
|
className="w-full max-h-[48vh] overflow-y-auto snap-y snap-mandatory mc-versionrecess hidden-scrollbar"
|
||||||
<div className="flex flex-col gap-1">
|
>
|
||||||
|
<div className="flex flex-col gap-1 hidden-scrollbar">
|
||||||
{visibleEditions.map((edition: Edition, i: number) => {
|
{visibleEditions.map((edition: Edition, i: number) => {
|
||||||
|
if (i < visibleRange.start || i > visibleRange.end) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={edition.id}
|
||||||
|
data-index={i}
|
||||||
|
style={{
|
||||||
|
height: itemHeightsRef.current[i] ?? ROW_ESTIMATE,
|
||||||
|
}}
|
||||||
|
className="w-[calc(100%-20px)] shrink-0 snap-start"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
const isInstalled = installedVersions.includes(
|
const isInstalled = installedVersions.includes(
|
||||||
edition.instanceId,
|
edition.instanceId,
|
||||||
);
|
);
|
||||||
const hasAnyInstall = installedVersions.length > 0;
|
|
||||||
const isSelected =
|
|
||||||
hasAnyInstall && selectedProfile === edition.instanceId;
|
|
||||||
const isFocused = focusIndex === i;
|
const isFocused = focusIndex === i;
|
||||||
const isCustom = edition.id.startsWith("custom_");
|
const isCustom = edition.id.startsWith("custom_");
|
||||||
const isDownloading = downloadingIds.includes(edition.instanceId);
|
const isDownloading = downloadingIds.includes(edition.instanceId);
|
||||||
const isComingSoon = edition.comingSoon;
|
const isComingSoon = edition.comingSoon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={edition.id}
|
key={edition.id}
|
||||||
data-index={i}
|
data-index={i}
|
||||||
className={`w-[calc(100%-16px)] mx-2 flex items-center gap-3 p-2 rounded-sm ${
|
ref={setRowRef(i)}
|
||||||
isSelected && !isComingSoon ? "bg-[#404040]/50" : ""
|
className={`w-[calc(100%-20px)] snap-start flex items-center gap-3 ${!isFocused ? "mc-button-ninesliced" : "mc-button-ninesliced-selected"} ${isComingSoon ? "opacity-50 cursor-not-allowed" : ""} relative ${openMenuId === edition.id ? "z-50" : "z-0"}`}
|
||||||
} ${isFocused && !isComingSoon ? "ring-2 ring-white" : ""} ${
|
|
||||||
isComingSoon ? "opacity-50 cursor-not-allowed" : ""
|
|
||||||
} relative ${openMenuId === edition.id ? "z-50" : "z-0"}`}
|
|
||||||
onMouseEnter={() => !isComingSoon && setFocusIndex(i)}
|
onMouseEnter={() => !isComingSoon && setFocusIndex(i)}
|
||||||
>
|
>
|
||||||
<div className="w-6 flex items-center justify-center flex-shrink-0">
|
<div className="w-12 h-12 flex items-center justify-center shrink-0 bg-[url(/images/empty.png)] bg-cover bg-center bg-no-repeat">
|
||||||
{isDownloading ? (
|
{edition.logo ? (
|
||||||
<span className="text-xs text-gray-400 font-bold">
|
|
||||||
{Math.floor(downloadProgress[edition.instanceId] || 0)}%
|
|
||||||
</span>
|
|
||||||
) : edition.logo ? (
|
|
||||||
edition.logo.startsWith("http") ||
|
edition.logo.startsWith("http") ||
|
||||||
edition.logo.startsWith("/images") ? (
|
edition.logo.startsWith("/images") ? (
|
||||||
<img
|
<img
|
||||||
src={edition.logo}
|
src={edition.logo}
|
||||||
alt=""
|
alt=""
|
||||||
className={`w-6 h-6 object-contain ${isComingSoon ? "opacity-40 grayscale" : ""}`}
|
className={`w-10 h-10 object-contain ${isComingSoon ? "opacity-40 grayscale" : ""}`}
|
||||||
style={{ imageRendering: "pixelated" }}
|
style={{ imageRendering: "pixelated" }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -405,17 +527,11 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
!isComingSoon && handleEditionClick(edition, i)
|
!isComingSoon && handleEditionClick(edition, i)
|
||||||
}
|
}
|
||||||
className={`flex-1 text-left min-w-0 outline-none rounded cursor-pointer ${
|
className={`flex-1 text-left min-w-0 outline-none rounded cursor-pointer ${isComingSoon ? "cursor-not-allowed" : ""}`}
|
||||||
focusIndex === i && focusBtn === 0 && !isComingSoon
|
|
||||||
? "ring-2 ring-white"
|
|
||||||
: ""
|
|
||||||
} ${isComingSoon ? "cursor-not-allowed" : ""}`}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
className={`text-xl tracking-wide truncate ${
|
className={"text-xl tracking-wide truncate text-white"}
|
||||||
isSelected ? "text-white" : "text-black"
|
|
||||||
}`}
|
|
||||||
style={{ textShadow: "none" }}
|
style={{ textShadow: "none" }}
|
||||||
>
|
>
|
||||||
{edition.name}
|
{edition.name}
|
||||||
@@ -472,13 +588,6 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p
|
|
||||||
className={`text-base font-medium leading-tight ${
|
|
||||||
isSelected ? "text-[#DDDDDD]" : "text-[#666666]"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{edition.desc}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 flex-shrink-0 relative">
|
<div className="flex items-center gap-2 flex-shrink-0 relative">
|
||||||
@@ -568,7 +677,8 @@ const VersionsView = memo(function VersionsView() {
|
|||||||
|
|
||||||
{openMenuId === edition.id && (
|
{openMenuId === edition.id && (
|
||||||
<div
|
<div
|
||||||
className="absolute right-0 top-11 w-48 bg-[#1a1a1a] border-2 border-[#555] z-[100] shadow-2xl p-0.5 animate-in fade-in zoom-in duration-75"
|
ref={menuRef}
|
||||||
|
className={`absolute right-0 ${menuDir === "up" ? "bottom-full mb-2" : "top-11"} w-48 bg-[#1a1a1a] border-2 border-[#555] z-[100] shadow-2xl p-0.5 animate-in fade-in zoom-in duration-75`}
|
||||||
style={{
|
style={{
|
||||||
imageRendering: "pixelated",
|
imageRendering: "pixelated",
|
||||||
}}
|
}}
|
||||||
|
|||||||
+42
-2
@@ -72,8 +72,48 @@
|
|||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mc-options-fg { /*neo: tysm https://leanrada.com/9-slicer/ */
|
.mc-options-fg {
|
||||||
border-image: url("/images/empty.png") 30 30 30 30 fill / 30px 30px 30px 30px;
|
/*neo: tysm https://leanrada.com/9-slicer/ */
|
||||||
|
border-image: url("/images/empty.png") 30 30 30 30 fill / 30px 30px 30px
|
||||||
|
30px;
|
||||||
|
border-width: 12px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-button-ninesliced {
|
||||||
|
/*neo: tysm https://leanrada.com/9-slicer/ */
|
||||||
|
border-image: url("/images/Button_Background.png") 13 13 13 13 fill / 13px
|
||||||
|
13px 13px 13px;
|
||||||
|
border-width: 12px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-button-ninesliced-selected {
|
||||||
|
/*neo: tysm https://leanrada.com/9-slicer/ */
|
||||||
|
border-image: url("/images/button_highlighted.png") 13 13 13 13 fill / 13px
|
||||||
|
13px 13px 13px;
|
||||||
|
border-width: 12px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-versionrecess {
|
||||||
|
/*neo: tysm https://leanrada.com/9-slicer/ */
|
||||||
|
border-image: url("/images/versionrecess.png") 12 30 30 12 fill / 12px 30px 30px 12px;
|
||||||
|
border-width: 12px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-help {
|
||||||
|
/*neo: tysm https://leanrada.com/9-slicer/ */
|
||||||
|
border-image: url("/images/helpbg.png") 5 15 17 6 fill / 5px 15px 17px 6px;
|
||||||
border-width: 12px;
|
border-width: 12px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
|
|||||||
Reference in New Issue
Block a user