Merge pull request #397 from Zagrios/feature/maps-tooltips/392

Feature/maps tooltips/392
This commit is contained in:
Shidorien
2024-01-07 19:10:29 +01:00
committed by GitHub
11 changed files with 173 additions and 101 deletions
+8 -5
View File
@@ -25,9 +25,6 @@
}
},
"pages": {
"shared-maps": {
"title": "Geteilte Maps"
},
"version-viewer": {
"launch-mods": {
"oculus": "Oculus Modus",
@@ -42,7 +39,6 @@
}
},
"maps": {
"title": "Geteilte Maps",
"search-bar": {
"search-placeholder": "Suche eine Map",
"filters-btn": "Filter",
@@ -728,6 +724,7 @@
"maps": {
"map-filter-panel": {
"duration": "Länge",
"nps" : "Noten Pro Sekunde",
"tags": "Stichworte",
"specificities": "Allgemein",
"requirements": "Anforderungen"
@@ -795,7 +792,13 @@
},
"map-item": {
"by": "Von {songAutor}",
"mapped-by": "Gemapped von"
"mapped-by": "Gemapped von",
"delete": "Löschen",
"preview" : "Kartenvorschau",
"bsr-code" : "BSR Code",
"download" : "Karte herunterladen",
"downloading" :"Karte wird heruntergeladen",
"cancel-download" : "Download abbrechen"
}
},
"models": {
+8 -5
View File
@@ -25,9 +25,6 @@
}
},
"pages": {
"shared-maps": {
"title": "Shared maps"
},
"version-viewer": {
"launch-mods": {
"oculus": "Oculus Mode",
@@ -42,7 +39,6 @@
}
},
"maps": {
"title": "Shared maps",
"search-bar": {
"search-placeholder": "Search a map",
"filters-btn": "Filters",
@@ -728,6 +724,7 @@
"maps": {
"map-filter-panel": {
"duration": "Duration",
"nps" : "Notes Per Second",
"tags": "tags",
"specificities": "general",
"requirements": "requirements"
@@ -795,7 +792,13 @@
},
"map-item": {
"by": "By {songAutor}",
"mapped-by": "mapped by"
"mapped-by": "mapped by",
"delete": "Delete",
"preview" : "Preview map",
"bsr-code" : "BSR code",
"download" : "Download map",
"downloading" :"Downloading map",
"cancel-download" : "Cancel download"
}
},
"models": {
+8 -4
View File
@@ -25,9 +25,6 @@
}
},
"pages": {
"shared-maps": {
"title": "Mapas comunes"
},
"version-viewer": {
"launch-mods": {
"oculus": "Modo Oculus",
@@ -727,6 +724,7 @@
"maps": {
"map-filter-panel": {
"duration": "Duración",
"nps" : "Notas Por Segundo",
"tags": "tags",
"specificities": "general",
"requirements": "requisitos"
@@ -794,7 +792,13 @@
},
"map-item": {
"by": "Por {songAutor}",
"mapped-by": "mapeado por"
"mapped-by": "mapeado por",
"delete": "Borrar",
"preview" : "Vista previa del mapa",
"bsr-code" : "Código BSR",
"download" : "Descargar mapa",
"downloading" :"Descargando mapa",
"cancel-download" : "Cancelar descarga"
}
},
"models": {
+12 -5
View File
@@ -25,9 +25,6 @@
}
},
"pages": {
"shared-maps": {
"title": "Maps partagées"
},
"version-viewer": {
"launch-mods": {
"oculus": "Mode Oculus",
@@ -230,8 +227,11 @@
"steam-download": {
"warnings": {
"msg": {
"ManifestChecksum": "Le manifeste précédemment téléchargé ne correspond pas au nouveau 🤔",
"ConnectionTimeout": "Ta connexion internet semble instable 🥶",
"Unknown": "Quelque chose d'étrange s'est produit 🤔 Votre connexion est surement instable."
"ConnectionLost": "La connexion a été perdue, essayez à nouveau...",
"ConnectionError": "Impossible de se connecter à Steam, essayez à nouveau...",
"Unknown": "Quelque chose d'étrange s'est produit 🤔 Votre connexion est probablement instable."
}
},
"errors": {
@@ -724,6 +724,7 @@
"maps": {
"map-filter-panel": {
"duration": "Durée",
"nps" : "Notes Par Seconde",
"tags": "tags",
"specificities": "général",
"requirements": "requis"
@@ -791,7 +792,13 @@
},
"map-item": {
"by": "Par {songAutor}",
"mapped-by": "mappée par"
"mapped-by": "mappée par",
"delete": "Supprimer",
"preview" : "Aperçu de la carte",
"bsr-code" : "Code BSR",
"download" : "Télécharger la carte",
"downloading" :"Téléchargement de la carte",
"cancel-download" : "Annuler le téléchargement"
}
},
"models": {
+8 -5
View File
@@ -25,9 +25,6 @@
}
},
"pages": {
"shared-maps": {
"title": "共有マップ"
},
"version-viewer": {
"launch-mods": {
"oculus": "Oculusモード",
@@ -42,7 +39,6 @@
}
},
"maps": {
"title": "共有マップ",
"search-bar": {
"search-placeholder": "マップを検索",
"filters-btn": "絞り込み",
@@ -728,6 +724,7 @@
"maps": {
"map-filter-panel": {
"duration": "尺",
"nps" : "秒間ノート数",
"tags": "タグ",
"specificities": "一般",
"requirements": "要Mod"
@@ -795,7 +792,13 @@
},
"map-item": {
"by": "By {songAutor}",
"mapped-by": "作成者 "
"mapped-by": "作成者 ",
"delete": "削除",
"preview" : "マッププレビュー",
"bsr-code" : "BSRコード",
"download" : "マップをダウンロード",
"downloading" :"マップをダウンロード中",
"cancel-download" : "ダウンロードをキャンセル"
}
},
"models": {
+8 -5
View File
@@ -25,9 +25,6 @@
}
},
"pages": {
"shared-maps": {
"title": "Общие карты"
},
"version-viewer": {
"launch-mods": {
"oculus": "Режим Oculus",
@@ -42,7 +39,6 @@
}
},
"maps": {
"title": "Общие карты",
"search-bar": {
"search-placeholder": "Поиск среди карт",
"filters-btn": "Фильтр",
@@ -728,6 +724,7 @@
"maps": {
"map-filter-panel": {
"duration": "Длительность",
"nps" : "Нот в Секунду",
"tags": "тэги",
"specificities": "основное",
"requirements": "требуемые моды"
@@ -795,7 +792,13 @@
},
"map-item": {
"by": "От {songAutor}",
"mapped-by": "автор карты "
"mapped-by": "автор карты ",
"delete": "Удалить",
"preview" : "Предпросмотр карты",
"bsr-code" : "Код BSR",
"download" : "Скачать карту",
"downloading" :"Загрузка карты",
"cancel-download" : "Отменить загрузку"
}
},
"models": {
+8 -6
View File
@@ -25,9 +25,6 @@
}
},
"pages": {
"shared-maps": {
"title": "共享譜面 "
},
"version-viewer": {
"launch-mods": {
"oculus": "Oculus 模式",
@@ -42,7 +39,6 @@
}
},
"maps": {
"title": "Shared maps",
"search-bar": {
"search-placeholder": "譜面搜尋",
"filters-btn": "篩選",
@@ -653,7 +649,6 @@
"valid-btn": "取消關聯譜面"
}
},
"download-maps": {
"search-btn": "搜尋",
"loading-maps": "譜面載入中...",
@@ -729,6 +724,7 @@
"maps": {
"map-filter-panel": {
"duration": "時長",
"nps" : "每秒音符數",
"tags": "標籤",
"specificities": "general",
"requirements": "要求"
@@ -796,7 +792,13 @@
},
"map-item": {
"by": "By {songAutor}",
"mapped-by": "mapped by"
"mapped-by": "mapped by",
"delete": "刪除",
"preview" : "預覽地圖",
"bsr-code" : "BSR代碼",
"download" : "下載地圖",
"downloading" :"正在下載地圖",
"cancel-download" : "取消下載"
}
},
"models": {
+8 -6
View File
@@ -25,9 +25,6 @@
}
},
"pages": {
"shared-maps": {
"title": "共享谱面"
},
"version-viewer": {
"launch-mods": {
"oculus": "Oculus 模式",
@@ -42,7 +39,6 @@
}
},
"maps": {
"title": "Shared maps",
"search-bar": {
"search-placeholder": "谱面搜索",
"filters-btn": "筛选",
@@ -653,7 +649,6 @@
"valid-btn": "取消关联谱面"
}
},
"download-maps": {
"search-btn": "搜索",
"loading-maps": "谱面加载中...",
@@ -729,6 +724,7 @@
"maps": {
"map-filter-panel": {
"duration": "时长",
"nps" : "每秒音符数",
"tags": "标签",
"specificities": "general",
"requirements": "要求"
@@ -796,7 +792,13 @@
},
"map-item": {
"by": "By {songAutor}",
"mapped-by": "mapped by"
"mapped-by": "mapped by",
"delete": "删除",
"preview" : "预览地图",
"bsr-code" : "BSR代码",
"download" : "下载地图",
"downloading" :"正在下载地图",
"cancel-download" : "取消下载"
}
},
"models": {
@@ -56,7 +56,7 @@ export function FilterPanel({ className, ref, playlist = false, filter, onChange
const renderDurationLabel = (sec: number): JSX.Element => {
const textValue = (() => {
if (sec === MIN_DURATION) {
return t("maps.map-filter-panel.duration");
return MIN_DURATION;
}
if (sec === MAX_DURATION) {
return "∞";
@@ -72,7 +72,7 @@ export function FilterPanel({ className, ref, playlist = false, filter, onChange
const renderNpsLabel = (nps: number): JSX.Element => {
const textValue = (() => {
if (nps === MIN_NPS) {
return "NPS";
return MIN_NPS;
}
if (nps === MAX_NPS) {
return "∞";
@@ -167,9 +167,11 @@ export function FilterPanel({ className, ref, playlist = false, filter, onChange
return !playlist ? (
<motion.div ref={ref} className={`${className} bg-light-main-color-2 dark:bg-main-color-3`} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
<div className="w-full h-6 grid grid-cols-2 gap-x-12 px-4 mb-6 pt-2">
<BsmRange min={MIN_NPS} max={MAX_NPS} values={npss} onChange={onNpssChange} renderLabel={renderNpsLabel} step={0.1} />
<BsmRange min={MIN_DURATION} max={MAX_DURATION} values={durations} onChange={onDurationsChange} renderLabel={renderDurationLabel} step={5} />
<div className="w-full h-6 grid grid-cols-2 gap-x-12 px-4 mb-6 pt-1">
<BsmRange min={MIN_NPS} max={MAX_NPS} values={npss} onChange={onNpssChange} renderLabel={renderNpsLabel} step={0.1} />
<BsmRange min={MIN_DURATION} max={MAX_DURATION} values={durations} onChange={onDurationsChange} renderLabel={renderDurationLabel} step={5} />
<span className=" text-sm font-bold text-center mt-2.5">{t("maps.map-filter-panel.nps")}</span>
<span className=" text-sm font-bold text-center mt-2.5">{t("maps.map-filter-panel.duration")}</span>
</div>
<div className="w-full h-full flex gap-x-2">
<section className="shrink-0">
@@ -180,7 +182,6 @@ export function FilterPanel({ className, ref, playlist = false, filter, onChange
<span className="grow capitalize">{translateMapSpecificity(specificity)}</span>
</div>
))}
<h2 className="my-1 uppercase text-sm">{t("maps.map-filter-panel.requirements")}</h2>
{MAP_REQUIREMENTS.map(requirement => (
<div key={requirement} className="flex justify-start items-center h-[22px] z-20 relative py-0.5 cursor-pointer" onClick={() => handleCheckbox(requirement)}>
@@ -21,6 +21,7 @@ import useDoubleClick from "use-double-click";
import { GlowEffect } from "../shared/glow-effect.component";
import { useDelayedState } from "renderer/hooks/use-delayed-state.hook";
import { useService } from "renderer/hooks/use-service.hook";
import Tippy from "@tippyjs/react";
export type ParsedMapDiff = { type: BsvMapDifficultyType; name: string; stars: number };
@@ -51,7 +52,6 @@ export type MapItemProps<T = unknown> = {
};
export const MapItem = memo(({ hash, title, autor, songAutor, coverUrl, songUrl, autorId, mapId, diffs, ranked, bpm, duration, likes, createdAt, selected, downloading, showOwned, callBackParam, onDelete, onDownload, onSelected, onCancelDownload, onDoubleClick }: MapItemProps) => {
const linkOpener = useService(LinkOpenerService);
const audioPlayer = useService(AudioPlayerService);
@@ -156,25 +156,25 @@ export const MapItem = memo(({ hash, title, autor, songAutor, coverUrl, songUrl,
return (
<motion.li className="relative h-[100px] w-[400px] min-w-[370px] shrink-0 grow basis-0 text-white group cursor-pointer" onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)} style={{ zIndex: hovered && 5, transform: "translateZ(0) scale(1.0, 1.0)", backfaceVisibility: "hidden" }}>
<GlowEffect visible={hovered || (selected && !!onSelected)} />
<AnimatePresence>
{(diffsPanelHovered || bottomBarHovered) && (
<motion.ul key={hash} className="absolute top-[calc(100%-10px)] w-full h-fit max-h-[200%] pt-4 pb-2 px-2 overflow-y-scroll bg-light-main-color-3 dark:bg-main-color-3 text-main-color-1 dark:text-current brightness-125 rounded-md flex flex-col gap-3 scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900 shadow-sm shadow-black" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }} onHoverStart={diffsPanelHoverStart} onHoverEnd={diffsPanelHoverEnd}>
{Array.from(diffs.entries()).map(([charac, diffSet]) => (
<ol key={crypto.randomUUID()} className="flex flex-col w-full gap-1">
{diffSet.map(({ type, name, stars }) => (
<li key={`${type}${name}${stars}`} className="w-full h-4 flex items-center gap-1">
<BsmIcon className="h-full w-fit p-px" icon={charac} />
<span className="h-full px-2 flex items-center text-xs font-bold bg-current rounded-full" style={{ color: MAP_DIFFICULTIES_COLORS[type] }}>
{stars ? <span className="h-full block brightness-[.25]"> {stars}</span> : <span className="h-full brightness-[.25] leading-4 pb-[2px] capitalize">{parseDiffLabel(type)}</span>}
</span>
<span className="text-sm leading-4 pb-[2px] capitalize">{parseDiffLabel(name)}</span>
</li>
))}
</ol>
))}
</motion.ul>
)}
</AnimatePresence>
<AnimatePresence>
{(diffsPanelHovered || bottomBarHovered) && (
<motion.ul key={hash} className="absolute top-[calc(100%-10px)] w-full h-fit max-h-[200%] pt-4 pb-2 px-2 overflow-y-scroll bg-light-main-color-3 dark:bg-main-color-3 text-main-color-1 dark:text-current brightness-125 rounded-md flex flex-col gap-3 scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900 shadow-sm shadow-black" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }} onHoverStart={diffsPanelHoverStart} onHoverEnd={diffsPanelHoverEnd}>
{Array.from(diffs.entries()).map(([charac, diffSet]) => (
<ol key={crypto.randomUUID()} className="flex flex-col w-full gap-1">
{diffSet.map(({ type, name, stars }) => (
<li key={`${type}${name}${stars}`} className="w-full h-4 flex items-center gap-1">
<BsmIcon className="h-full w-fit p-px" icon={charac} />
<span className="h-full px-2 flex items-center text-xs font-bold bg-current rounded-full" style={{ color: MAP_DIFFICULTIES_COLORS[type] }}>
{stars ? <span className="h-full block brightness-[.25]"> {stars}</span> : <span className="h-full brightness-[.25] leading-4 pb-[2px] capitalize">{parseDiffLabel(type)}</span>}
</span>
<span className="text-sm leading-4 pb-[2px] capitalize">{parseDiffLabel(name)}</span>
</li>
))}
</ol>
))}
</motion.ul>
)}
</AnimatePresence>
<div className="h-full w-full relative pl-[100px] rounded-md overflow-hidden flex flex-row justify-end">
<div className={`absolute top-0 left-0 h-full aspect-square cursor-pointer ${showOwned && "border-l-[5px]"}`} style={{ borderColor: showOwned && color }}>
<BsmImage className="w-full h-full object-cover" image={coverUrl} placeholder={defaultImage} errorImage={defaultImage} loading="lazy" />
@@ -246,46 +246,66 @@ export const MapItem = memo(({ hash, title, autor, songAutor, coverUrl, songUrl,
<div className="flex flex-col justify-center items-center gap-1 w-full h-full overflow-hidden opacity-0 group-hover:opacity-100">
{onDelete && !downloading && (
<BsmButton
className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2"
iconClassName="w-full h-full brightness-75 dark:brightness-200"
iconColor={color}
icon="trash"
withBar={false}
onClick={e => {
e.stopPropagation();
onDelete(callBackParam);
}}
/>
<Tippy content={t("maps.map-item.delete")} placement="left" theme="default">
<div>
<BsmButton
className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2"
iconClassName="w-full h-full brightness-75 dark:brightness-200"
iconColor={color}
icon="trash"
withBar={false}
onClick={e => {
e.stopPropagation();
onDelete(callBackParam);
}}
/>
</div>
</Tippy>
)}
{onDownload && !downloading && (
<BsmButton
className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2"
iconClassName="w-full h-full brightness-75 dark:brightness-200"
iconColor={color}
icon="download"
withBar={false}
onClick={e => {
e.stopPropagation();
onDownload(callBackParam);
}}
/>
<Tippy content={t("maps.map-item.download")} placement="left" theme="default">
<div>
<BsmButton
className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2"
iconClassName="w-full h-full brightness-75 dark:brightness-200"
iconColor={color}
icon="download"
withBar={false}
onClick={e => {
e.stopPropagation();
onDownload(callBackParam);
}}
/>
</div>
</Tippy>
)}
{onCancelDownload && !downloading && (
<BsmButton
className="w-6 h-6 p-1 rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2"
iconClassName="w-full h-full brightness-75 dark:brightness-200"
iconColor="red"
icon="cross"
withBar={false}
onClick={e => {
e.stopPropagation();
onCancelDownload(callBackParam);
}}
/>
<Tippy content={t("maps.map-item.cancel-download")} placement="left" theme="default">
<div>
<BsmButton
className="w-6 h-6 p-1 rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2"
iconClassName="w-full h-full brightness-75 dark:brightness-200"
iconColor="red"
icon="cross"
withBar={false}
onClick={e => {
e.stopPropagation();
onCancelDownload(callBackParam);
}}
/>
</div>
</Tippy>
)}
{downloading && <BsmBasicSpinner className="w-6 h-6 p-1 rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2 flex items-center justify-center" spinnerClassName="brightness-75 dark:brightness-200" style={{ color }} thikness="3px" />}
{downloading &&
<Tippy content={t("maps.map-item.downloading")} placement="left" theme="default">
<div>
<BsmBasicSpinner className="w-6 h-6 p-1 rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2 flex items-center justify-center" spinnerClassName="brightness-75 dark:brightness-200" style={{ color }} thikness="3px" />
</div>
</Tippy>
}
{previewUrl && (
<Tippy content={t("maps.map-item.preview")} placement="left" theme="default">
<div>
<BsmButton
className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2"
iconClassName="w-full h-full brightness-75 dark:brightness-200"
@@ -297,8 +317,12 @@ export const MapItem = memo(({ hash, title, autor, songAutor, coverUrl, songUrl,
openPreview();
}}
/>
</div>
</Tippy>
)}
{mapId && (
<Tippy content={t("maps.map-item.bsr-code")} placement="left" theme="default">
<div>
<BsmButton
className="w-6 h-6 p-1 rounded-md !bg-inherit hover:!bg-light-main-color-2 hover:dark:!bg-main-color-2"
iconClassName="w-full h-full brightness-75 dark:brightness-200"
@@ -310,6 +334,8 @@ export const MapItem = memo(({ hash, title, autor, songAutor, coverUrl, songUrl,
copyBsr();
}}
/>
</div>
</Tippy>
)}
</div>
</div>
+18
View File
@@ -78,6 +78,24 @@
background: rgba(0, 0, 0, 0.25);
}
.tippy-box[data-theme~='default'] {
@apply bg-neutral-900;
@apply text-white;
}
.tippy-box[data-theme~='default'][data-placement^='top'] > .tippy-arrow::before {
@apply border-t-neutral-900
}
.tippy-box[data-theme~='default'][data-placement^='bottom'] > .tippy-arrow::before {
@apply border-b-neutral-900
}
.tippy-box[data-theme~='default'][data-placement^='left'] > .tippy-arrow::before {
@apply border-l-neutral-900
}
.tippy-box[data-theme~='default'][data-placement^='right'] > .tippy-arrow::before {
@apply border-r-neutral-900
}
@keyframes glowing {
0% {
background-position: 0 0;