[feature-136] Add default image + fix not get local platforms + Add temp icon in nav bar + some style adjustements

This commit is contained in:
MathieuG-P
2023-05-04 00:08:52 +02:00
parent d7d3c980cb
commit d1dc7bbca0
8 changed files with 75 additions and 45 deletions
-6
View File
@@ -127,12 +127,6 @@ export async function copyDirectoryWithJunctions(src: string, dest: string, opti
}
}
export function extname(filePath: string, withDot = true){
const ext = path.extname(filePath);
if(withDot){ return ext; }
return ext.split('.').at(-1);
}
export interface Progression<T = unknown>{
total: number;
current: number;
@@ -11,8 +11,8 @@ import path from "path";
import { RequestService } from "../request.service";
import { copyFileSync } from "fs-extra";
import sanitize from "sanitize-filename";
import { Progression, ensureFolderExist, extname } from "../../helpers/fs.helpers";
import { MODEL_TYPE_FOLDERS } from "../../../shared/models/models/constants";
import { Progression, ensureFolderExist } from "../../helpers/fs.helpers";
import { MODEL_FILE_EXTENSIONS, MODEL_TYPE_FOLDERS } from "../../../shared/models/models/constants";
import { InstallationLocationService } from "../installation-location.service";
import { Observable } from "rxjs";
import { readdir } from "fs/promises";
@@ -118,7 +118,12 @@ export class LocalModelsManagerService {
const modelsPath = await this.getModelFolderPath(type, version);
const files = await readdir(modelsPath, {withFileTypes: true});
return files.filter(file => file.isFile() && extname(file.name, false) === type).map(file => path.join(modelsPath, file.name));
//return files.filter(file => file.isFile() && path.extname(file.name) === MODEL_FILE_EXTENSIONS[type]).map(file => path.join(modelsPath, file.name));
return files.reduce((acc, file) => {
if(!file.isFile() || path.extname(file.name) !== MODEL_FILE_EXTENSIONS[type]){ return acc; }
acc.push(path.join(modelsPath, file.name));
return acc;
}, []);
}
public getModels(type: MSModelType, version?: BSVersion): Observable<Progression<BsmLocalModel[]>>{
@@ -1,6 +1,6 @@
import equal from "fast-deep-equal";
import { memo, useState } from "react";
import { MSModel, MSModelType } from "shared/models/models/model-saber.model";
import { MSModel } from "shared/models/models/model-saber.model";
import { BsmImage } from "../shared/bsm-image.component";
import { motion } from "framer-motion";
import { GlowEffect } from "../shared/glow-effect.component";
@@ -9,11 +9,13 @@ import { BsmLink } from "../shared/bsm-link.component";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import Tippy from "@tippyjs/react";
import { followCursor } from "tippy.js"
import defaultImage from '../../../../assets/images/default-version-img.jpg'
type Props = {
className?: string,
selected?: boolean,
hash: string,
onClick?: React.ComponentProps<"li">["onClick"],
} & Partial<MSModel>
export const ModelItem = memo(({
@@ -25,13 +27,14 @@ export const ModelItem = memo(({
name,
thumbnail,
author,
discord,
discordid,
tags
tags,
onClick
}: Props) => {
const color = useThemeColor("first-color");
const [hovered, setHovered] = useState(false);
const [idContentCopied, setIdContentCopied] = useState<string>(null);
const modelPageUrl = (() => {
if(!id){ return null; }
@@ -47,22 +50,36 @@ export const ModelItem = memo(({
return url.toString();
})();
const copyContent = (value: string, id?: string) => {
navigator.clipboard.writeText(value);
setIdContentCopied(() => id);
setTimeout(() => {
setIdContentCopied(() => null);
}, 700);
}
return (
<motion.li className={`relative w-52 h-52 cursor-pointer ${className ?? ""}`} onHoverStart={() => setHovered(() => true)} onHoverEnd={() => setHovered(() => false)}>
// TODO TRANSLATE ALL TEXT
<motion.li className={`relative w-52 h-52 cursor-pointer ${className ?? ""}`} onHoverStart={() => setHovered(() => true)} onHoverEnd={() => setHovered(() => false)} onClick={onClick}>
<GlowEffect visible={selected || hovered}/>
<div className="absolute top-0 left-0 w-full h-full rounded-lg overflow-hidden blur-none">
<BsmImage className="absolute top-0 left-0 w-full h-full" image={thumbnail} loading="lazy"/>
<motion.div className="absolute cursor-default top-[80%] left-0 w-full h-full p-2 flex flex-col gap-1.5 bg-main-color-2 z-[1] bg-opacity-60 backdrop-blur-md transition-all hover:top-0">
<BsmLink className={`block w-full max-w-full overflow-hidden font-bold whitespace-nowrap text-ellipsis ${id ? "cursor-pointer hover:underline" : ""}`} href={modelPageUrl}>{name}</BsmLink>
<BsmLink className={`block w-full max-w-full overflow-hidden whitespace-nowrap text-ellipsis brightness-200 ${authorPageUrl ? "cursor-pointer hover:underline" : ""}`} style={{color}} href={authorPageUrl}>{author ?? ''}</BsmLink>
<BsmImage className="absolute top-0 left-0 w-full h-full object-cover" image={thumbnail} placeholder={defaultImage} loading="lazy"/>
<motion.div className="absolute cursor-default top-[80%] left-0 w-full h-full p-2 flex flex-col gap-1.5 bg-main-color-3 bg-opacity-60 backdrop-blur-md transition-all hover:top-0" onClick={e =>{e.stopPropagation(); e.preventDefault()}}>
<BsmLink className={`block w-fit max-w-full overflow-hidden font-bold whitespace-nowrap text-ellipsis ${id ? "cursor-pointer hover:underline" : ""}`} href={modelPageUrl}>{name}</BsmLink>
<BsmLink className={`block w-fit max-w-full overflow-hidden whitespace-nowrap text-ellipsis brightness-200 ${authorPageUrl ? "cursor-pointer hover:underline" : ""}`} style={{color}} href={authorPageUrl}>{author ?? ''}</BsmLink>
<ul className="flex flex-row flex-wrap gap-1">
{tags?.map(tag => (
<li key={tag} className="inline-block mr-1 text-xs bg-white text-black px-1 rounded-full p-0.5">{tag}</li>
))}
</ul>
<Tippy placement="top" content="Copy hash" followCursor="horizontal" plugins={[followCursor]}>
<div className="cursor-copy block w-full max-w-full overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-2 rounded-md p-1 uppercase text-sm pointer-events-auto">{hash}</div>
<Tippy placement="top" content={idContentCopied === "hash" ? "Copied!" : "Copy hash"} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
<span className="mt-0.5 cursor-copy block w-full max-w-full overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 uppercase text-xs" onClick={() => copyContent(`${hash}`, 'hash')}>{hash}</span>
</Tippy>
{id && (
<Tippy placement="top" content={idContentCopied === "id" ? "Copied!" : "Copy id"} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
<span className="cursor-copy w-fit max-w-full overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 uppercase text-xs" onClick={() => copyContent(`${id}`, 'id')}>{id}</span>
</Tippy>
)}
</motion.div>
</div>
</motion.li>
@@ -43,7 +43,7 @@ export function ModelsGrid({className, version, type}: Props) {
{isLoading && <>loading</>}
{!hasModels && <>no models</>}
{hasModels && !isLoading && (
<ul className="flex w-full h-full overflow-scroll p-4 gap-4">
<ul className="flex flex-wrap justify-center w-full h-full overflow-scroll p-4 gap-4">
{models.extra.map(localModel => (
<ModelItem
key={localModel.model?.hash ?? localModel.hash}
@@ -6,6 +6,7 @@ import { useOnUpdate } from "renderer/hooks/use-on-update.hook"
import { ModelsManagerService } from "renderer/services/models-management/models-manager.service"
import { BsContentNavBar, BsContentNavBarTab } from "../shared/bs-content-nav-bar.component"
import { useConstant } from "renderer/hooks/use-constant.hook"
import { BsmIcon } from "../svgs/bsm-icon.component"
type Props = {
className?: string,
@@ -24,8 +25,8 @@ export function ModelsTabsNavbar({className, version, tabIndex, onTabChange}: Pr
})
return (
<BsContentNavBar className={`!rounded-none shadow-sm ${className ?? ""}`} tabIndex={tabIndex} onTabChange={onTabChange} tabs={tabs} renderTab={(props, tab) => (
<ModelTab version={version} modelType={tab.extra} {...props}/>
<BsContentNavBar className={`!rounded-none shadow-sm ${className ?? ""}`} tabIndex={tabIndex} onTabChange={onTabChange} tabs={tabs} renderTab={(props, tab, activeTab) => (
<ModelTab version={version} modelType={tab.extra} {...props} active={tab === activeTab}/>
)}/>
)
}
@@ -33,11 +34,12 @@ export function ModelsTabsNavbar({className, version, tabIndex, onTabChange}: Pr
type TabProps = {
version?: BSVersion,
modelType: MSModelType,
active?: boolean,
onLink?: (type: MSModelType) => void,
onUnlink?: (type: MSModelType) => void,
} & DetailedHTMLProps<React.HTMLAttributes<HTMLLIElement>, HTMLLIElement>;
} & React.ComponentProps<"li">;
function ModelTab({version, modelType, onClick, onLink, onUnlink}: TabProps){
function ModelTab({version, modelType, active, onClick, onLink, onUnlink}: TabProps){
const modelsManager = useConstant(() => ModelsManagerService.getInstance());
@@ -45,7 +47,7 @@ function ModelTab({version, modelType, onClick, onLink, onUnlink}: TabProps){
const [linkBtnDisabled, setLinkBtnDisabled] = useState(false);
useOnUpdate(() => {
if(!version){ return; }
if(!version){ return null; }
const sub = modelsManager.$modelsLinkingPending(version, modelType).subscribe(async (pending) => {
if(pending){ return setLinkBtnDisabled(() => pending); }
@@ -69,24 +71,27 @@ function ModelTab({version, modelType, onClick, onLink, onUnlink}: TabProps){
}
return (
<li className="relative w-full text-center text-lg font-bold hover:backdrop-brightness-75 flex justify-center items-center content-center px-6" onClick={onClick}>
<span className="text-main-color-1 dark:text-gray-200 font-thin italic text-xs">{modelType}</span>
<div className="flex items-center absolute top-0 left-0">
{!!version && (
<LinkButton
variants={{ hover: {rotate: 22.5}, tap: {rotate: 45} }}
disabled={linkBtnDisabled}
whileHover="hover"
whileTap="tap"
initial={{rotate: 0}}
className="block w-6 h-6 aspect-square blur-0 hover:brightness-75"
linked={modelsAreLinked}
title={"aaa"}
onClick={onClickLink}
/>
)}
</div>
</li>
<li className={`relative w-full text-center text-lg font-bold hover:backdrop-brightness-75 flex justify-center items-center content-center px-6 ${active ? "backdrop-brightness-75" : ""}`} onClick={onClick}>
<div className="flex flex-col gap-0.5">
<BsmIcon icon="trash"/>
<span className="text-main-color-1 dark:text-gray-200 font-thin italic text-xs">{modelType}</span>
</div>
<div className="flex items-center absolute top-1 left-1">
{!!version && (
<LinkButton
variants={{ hover: {rotate: 22.5}, tap: {rotate: 45} }}
disabled={linkBtnDisabled}
whileHover="hover"
whileTap="tap"
initial={{rotate: 0}}
className="block w-6 h-6 aspect-square blur-0 hover:brightness-75"
linked={modelsAreLinked}
title={"aaa"} //TODO ADD TRANSLATION
onClick={onClickLink}
/>
)}
</div>
</li>
)
}
@@ -1,4 +1,5 @@
import { DetailedHTMLProps, Fragment } from "react";
import { BsmIconType } from "../svgs/bsm-icon.component";
type Props<T> = {
className?: string;
@@ -10,6 +11,7 @@ type Props<T> = {
export type BsContentNavBarTab<T = unknown> = {
text: string;
icon?: BsmIconType;
extra?: T
}
@@ -1,7 +1,7 @@
import { Observable } from "rxjs";
import { useState, useEffect } from "react";
export function useSwitchableObservable<T>(observable?: Observable<T>, clearOnSwitch = true): [T, React.Dispatch<React.SetStateAction<Observable<T>>>]{
export function useSwitchableObservable<T>(observable?: Observable<T>, clearOnSwitch = true): [T, React.Dispatch<React.SetStateAction<Observable<T>>>, Observable<T>]{
const [currentObs, setCurrentObs] = useState(observable);
const [obsValue, setObsValue] = useState<T>();
@@ -15,5 +15,5 @@ export function useSwitchableObservable<T>(observable?: Observable<T>, clearOnSw
}
}, [currentObs])
return [obsValue, setCurrentObs];
return [obsValue, setCurrentObs, currentObs];
}
+7
View File
@@ -14,4 +14,11 @@ export const MODELS_TYPE_MS_PAGE_ROOT = {
[MSModelType.Bloq]: "Bloqs",
[MSModelType.Platfrom]: "Platforms",
[MSModelType.Saber]: "Sabers"
}
export const MODEL_FILE_EXTENSIONS = {
[MSModelType.Avatar]: ".avatar",
[MSModelType.Bloq]: ".bloq",
[MSModelType.Platfrom]: ".plat",
[MSModelType.Saber]: ".saber"
}