Compare commits

...

20 Commits

Author SHA1 Message Date
MathieuG-P 5e03039633 Merge pull request #70 from Zagrios/hotfix/close-filter-on-apply
hotfix/close-filter-on-apply
2023-01-04 21:55:39 +01:00
MathieuG-P f13375ea40 Merge branch 'master' into hotfix/close-filter-on-apply 2023-01-04 21:55:06 +01:00
MathieuG-P b5d0a9156b Merge pull request #69 from Zagrios/hotfix/missing-translation-filter-panel
hotfix/missing-translation-filter
2023-01-04 21:52:43 +01:00
MathieuG-P 46e79baae6 close filter on apply 2023-01-04 21:47:43 +01:00
MathieuG-P 55e51461a7 add missing translation 2023-01-04 21:24:57 +01:00
MathieuG-P c086b805a9 change version 2023-01-04 20:51:01 +01:00
MathieuG-P ffaef60f9a Merge pull request #68 from Zagrios/hotfix/change-work-in-progress-text
hotfix/change-work-in-progress-text
2023-01-04 20:47:01 +01:00
MathieuG-P 556a217e41 work in progress to coming soon 2023-01-04 20:46:04 +01:00
MathieuG-P a91ccd49db Merge pull request #67 from Zagrios/feature/remember-choice-delete-map-modal/66
feature/remember-choice-delete-map-modal/66
2023-01-04 20:42:27 +01:00
MathieuG-P e42fffbee7 add remember choice option on delete single map 2023-01-04 20:40:41 +01:00
MathieuG-P 171f17a7e0 Merge pull request #65 from Zagrios/bugfix/notification-not-disappear/56
bugfix/notification-not-disappear/56
2023-01-03 23:24:08 +01:00
MathieuG-P 149b5195ca change notification animation 2023-01-03 23:20:31 +01:00
MathieuG-P b10eb982cd Merge pull request #64 from Zagrios/bugfix/notifications-behind-maps-modal/61
bugfix/notifications-behind-maps-modal/61
2023-01-03 22:34:30 +01:00
MathieuG-P f44c4e66a0 change z indexes 2023-01-03 22:33:19 +01:00
MathieuG-P c99bdbb8d0 Merge pull request #63 from Zagrios/bugfix/map-above-filter-panel/60
bugfix/map-above-filter-panel/60
2023-01-03 22:18:20 +01:00
MathieuG-P ccc8c5c7c1 fix z index bug 2023-01-03 22:17:38 +01:00
MathieuG-P 8dbb39a001 Merge pull request #62 from Zagrios/bugfix/filter-too-complex-to-apply/59
bugfix/filter too complex to apply/59
2023-01-03 22:04:49 +01:00
MathieuG-P 80a6a2049e add optionnal apply button 2023-01-03 22:03:27 +01:00
MathieuG-P 9b62c27afa not used here but will be usefull 2023-01-03 22:02:55 +01:00
MathieuG-P 20ffd88a3a change version 2023-01-01 13:07:03 +01:00
14 changed files with 120 additions and 32 deletions
+5 -1
View File
@@ -10,7 +10,8 @@
"cancel": "Cancel",
"delete": "Delete",
"accept": "Accept",
"refuse": "Refuse"
"refuse": "Refuse",
"apply": "Apply"
},
"pages": {
"version-viewer": {
@@ -381,6 +382,9 @@
}
},
"modals": {
"misc":{
"remember-my-choice": "Remember my choice"
},
"guard": {
"title": "Steam Guard",
"inputs": {
+5 -1
View File
@@ -10,7 +10,8 @@
"cancel": "Cancelar",
"delete": "Borrar",
"accept": "Aceptar",
"refuse": "Rechazar"
"refuse": "Rechazar",
"apply": "Aplicar"
},
"pages": {
"version-viewer": {
@@ -381,6 +382,9 @@
}
},
"modals": {
"misc":{
"remember-my-choice": "Recordar mi elección"
},
"guard": {
"title": "Steam Guard",
"inputs": {
+5 -1
View File
@@ -10,7 +10,8 @@
"cancel": "Annuler",
"delete": "Supprimer",
"accept": "Accepter",
"refuse": "Refuser"
"refuse": "Refuser",
"apply": "Appliquer"
},
"pages": {
"version-viewer": {
@@ -381,6 +382,9 @@
}
},
"modals": {
"misc":{
"remember-my-choice": "Se souvenir de mon choix"
},
"guard": {
"title": "Steam Guard",
"inputs": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "bs-manager",
"version": "0.2.0",
"version": "1.0.0-alpha.2",
"description": "A foundation for scalable desktop apps",
"main": "./dist/main/main.js",
"author": {
@@ -1,6 +1,6 @@
import { MapFilter, MapSpecificity, MapStyle, MapTag, MapType } from "shared/models/maps/beat-saver.model"
import {motion} from "framer-motion"
import { MutableRefObject} from "react"
import { MutableRefObject, useEffect, useRef, useState} from "react"
import { MAP_TYPES } from "renderer/partials/maps/map-tags/map-types"
import { MAP_STYLES } from "renderer/partials/maps/map-tags/map-styles"
import { BsmCheckbox } from "../shared/bsm-checkbox.component"
@@ -11,19 +11,25 @@ import { useTranslation } from "renderer/hooks/use-translation.hook"
import { MAP_SPECIFICITIES } from "renderer/partials/maps/map-general/map-specificity"
import { MAP_REQUIREMENTS } from "renderer/partials/maps/map-requirements/map-requirements"
import { MAP_DIFFICULTIES_COLORS } from "renderer/partials/maps/map-difficulties/map-difficulties-colors"
import { BsmButton } from "../shared/bsm-button.component"
export type Props = {
className?: string,
ref?: MutableRefObject<undefined>
playlist?: boolean,
filter: MapFilter
onChange?: (filter: MapFilter) => void
filter: MapFilter,
onChange?: (filter: MapFilter) => void,
onApply?: (filter: MapFilter) => void,
onClose?: (filter: MapFilter) => void
}
export function FilterPanel({className, ref, playlist = false, filter, onChange}: Props) {
export function FilterPanel({className, ref, playlist = false, filter, onChange, onApply, onClose}: Props) {
const t = useTranslation();
const [haveChanged, setHaveChanged] = useState(false);
const firstRun = useRef(true);
const MIN_NPS = 0;
const MAX_NPS = 17;
@@ -36,6 +42,15 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange}
const isTagActivated = (tag: MapTag): boolean => filter?.enabledTags?.has(tag) || filter?.excludedTags?.has(tag);
const isTagExcluded = (tag: MapTag): boolean => filter?.excludedTags?.has(tag);
useEffect(() => {
if(firstRun.current){
firstRun.current = false;
return;
}
setHaveChanged(() => true);
}, [filter])
const renderDurationLabel = (sec: number): JSX.Element => {
const textValue = (() => {
@@ -134,6 +149,12 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange}
onChange(newFilter);
}
const handleApply = () => {
onApply(filter);
setHaveChanged(() => false);
onClose?.(filter);
}
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">
@@ -172,10 +193,14 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange}
<span key={tag} onClick={() => handleTagClick(tag)} className={`text-[12.5px] text-black rounded-md px-1 font-bold cursor-pointer ${(!isTagActivated(tag)) && "opacity-40 hover:opacity-90"}`} style={{backgroundColor: isTagExcluded(tag) ? MAP_DIFFICULTIES_COLORS.Expert : MAP_DIFFICULTIES_COLORS.Easy}}>{translateMapStyle(tag)}</span>
))}
</div>
</section>
</div>
{onApply && (
<div className="inline float-right relative w-fit h-fit mt-2">
<BsmButton className="inline float-right rounded-md font-bold px-1 py-0.5 text-sm" text="Appliquer" typeColor="primary" withBar={false} onClick={handleApply}/>
{haveChanged && <div className="glow-on-hover !opacity-100"></div>}
</div>
)}
</motion.div>
) : (
<></>
@@ -133,7 +133,7 @@ export function MapsPlaylistsPanel({version}: Props) {
<LocalMapsListPanel ref={mapsRef} className="w-full h-full shrink-0 flex flex-col" version={version} filter={mapFilter} search={mapSearch}/>
<div className="w-full h-full shrink-0 flex flex-col justify-center items-center content-center gap-2 overflow-hidden">
<BsmImage className="rounded-md" image={wipGif}/>
<span>Work in progress</span>
<span>Coming soon</span>
</div>
</div>
</div>
@@ -1,14 +1,28 @@
import { useEffect, useState } from "react";
import { BsmButton } from "renderer/components/shared/bsm-button.component";
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
import { BsmImage } from "renderer/components/shared/bsm-image.component";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { ConfigurationService } from "renderer/services/configuration.service";
import { MapsManagerService } from "renderer/services/maps-manager.service";
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
import BeatConflict from '../../../../../assets/images/apngs/beat-conflict.png'
export const DeleteMapsModal: ModalComponent<void, {linked: boolean, maps: BsmLocalMap[]}> = ({resolver, data:{linked, maps}}) => {
const config = ConfigurationService.getInstance();
const t = useTranslation();
const [remember, setRemember] = useState(false);
useEffect(() => {
const key = MapsManagerService.REMEMBER_CHOICE_DELETE_MAP_KEY;
config.set(key, remember);
}, [remember])
const multiple = maps.length > 1;
const titleText = multiple ? "modals.maps-actions.delete-maps.title.multiple" : "modals.maps-actions.delete-maps.title.single";
@@ -22,7 +36,13 @@ export const DeleteMapsModal: ModalComponent<void, {linked: boolean, maps: BsmLo
<BsmImage className="mx-auto h-24" image={BeatConflict} />
<p className="max-w-sm w-full">{t(descText, multiple ? {nb: maps.length.toString()} : {name: maps.at(0).rawInfo._songName})}</p>
{linked && <p className="text-sm italic mt-2 cursor-help w-fit" title={t(infoTitleText)}>{t(infoText)}</p>}
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
{!multiple && (
<div className="flex items-center relative py-2 gap-1 mt-1">
<BsmCheckbox className="h-5 relative z-[1]" checked={remember} onChange={(val) => setRemember(val)}/>
<span className="italic">{t("modals.misc.remember-my-choice")}</span>
</div>
)}
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-2">
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.CANCELED})} withBar={false} text="misc.cancel"/>
<BsmButton typeColor="primary" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.COMPLETED})} withBar={false} text="misc.delete"/>
</div>
@@ -28,6 +28,7 @@ export const DownloadMapsModal: ModalComponent<void, BSVersion> = ({data}) => {
const currentDownload = useObservable(mapsDownloader.currentMapDownload$);
const mapsInQueue = useObservable(mapsDownloader.mapsInQueue$);
const t = useTranslation();
const filterContainerRef = useRef(null);
const [filter, setFilter] = useState<MapFilter>({});
const [query, setQuery] = useState("");
const [maps, setMaps] = useState<BsvMapDetail[]>([]);
@@ -156,14 +157,14 @@ export const DownloadMapsModal: ModalComponent<void, BSVersion> = ({data}) => {
return (
<form className="text-gray-800 dark:text-gray-200 flex flex-col max-w-[95vw] w-[970px] h-[85vh] gap-3" onSubmit={e => {e.preventDefault(); handleSearch()}}>
<div className="flex h-9 gap-2 shrink-0">
<BsmDropdownButton className="shrink-0 h-full relative z-[1] flex justify-start" buttonClassName="flex items-center justify-center h-full rounded-full px-2 py-1 !bg-light-main-color-1 dark:!bg-main-color-1" icon="filter" text="pages.version-viewer.maps.search-bar.filters-btn" withBar={false}>
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[450px] h-fit p-2 rounded-md shadow-md shadow-black" filter={filter} onChange={setFilter}/>
<BsmDropdownButton ref={filterContainerRef} className="shrink-0 h-full relative z-[1] flex justify-start" buttonClassName="flex items-center justify-center h-full rounded-full px-2 py-1 !bg-light-main-color-1 dark:!bg-main-color-1" icon="filter" text="pages.version-viewer.maps.search-bar.filters-btn" withBar={false}>
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[450px] h-fit p-2 rounded-md shadow-md shadow-black" filter={filter} onChange={setFilter} onApply={handleSearch} onClose={() => filterContainerRef.current.close()}/>
</BsmDropdownButton>
<input className="h-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2 grow pb-0.5" type="text" name="" id="" placeholder={t("pages.version-viewer.maps.search-bar.search-placeholder")} value={query} onChange={e => setQuery(e.target.value)}/>
<BsmButton className="shrink-0 rounded-full py-1 px-3 !bg-light-main-color-1 dark:!bg-main-color-1 flex justify-center items-center capitalize" icon="search" text="modals.download-maps.search-btn" withBar={false} onClick={e => {e.preventDefault(); handleSearch()}}/>
<BsmSelect className="bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-1 pb-0.5 text-center" options={sortOptions} onChange={handleSortChange}/>
</div>
<ul className="w-full grow flex content-start flex-wrap gap-2 px-2 overflow-y-scroll overflow-x-hidden scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900" >
<ul className="w-full grow flex content-start flex-wrap gap-2 px-2 overflow-y-scroll overflow-x-hidden scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900 z-0" >
{maps.length === 0 ? (
<div className="w-full h-full flex flex-col items-center justify-center">
<img className="w-32 h-32 spin-loading" src={BeatWaitingImg} alt=" "/>
@@ -39,7 +39,7 @@ export function Modal() {
return (
<AnimatePresence>
{ModalComponent && (
<div className="top-0 absolute w-screen h-screen flex content-center items-center justify-center z-50">
<div className="top-0 absolute w-screen h-screen flex content-center items-center justify-center z-[90]">
<motion.span key="modal-overlay" onClick={() => modalSevice.resolve({exitCode: ModalExitCode.NO_CHOICE})} className="absolute top-0 bottom-0 right-0 left-0 bg-black" initial={{opacity: 0}} animate={{opacity: ModalComponent && .60}} exit={{opacity: 0}} transition={{duration: .2}}/>
<motion.div key="modal" initial={{y: "100vh"}} animate={{y: 0}} exit={{y: "100vh"}}>
<div className="relative p-4 text-gray-800 dark:text-gray-200 rounded-md shadow-lg shadow-black bg-gradient-to-br from-light-main-color-3 to-light-main-color-2 dark:from-main-color-3 dark:to-main-color-2">
@@ -37,7 +37,7 @@ export function NotificationItem({resolver, notification}: {resolver?: (value: N
}
return (
<motion.li layout drag="x" dragConstraints={{left: 0, right: 0}} dragElastic={{left: 0, right: .3}} onDragEnd={handleDragEnd} initial={{x: "110%"}} animate={{x: "0%"}} exit={{x:"110%"}} className="relative w-[290px] rounded-md overflow-hidden -left-[300px] mb-4 cursor-grab active:cursor-grabbing shadow-md shadow-black bg-gradient-to-br from-light-main-color-2 to-light-main-color-2 dark:from-main-color-2 dark:to-main-color-2 text-gray-800 dark:text-white">
<motion.li layout drag="x" dragConstraints={{left: 0, right: 0}} dragElastic={{left: 0, right: .3}} onDragEnd={handleDragEnd} initial={{x: 0}} animate={{x: "-105%"}} exit={{x: 0}} className="relative w-[290px] rounded-md overflow-hidden mb-4 cursor-grab active:cursor-grabbing shadow-md shadow-black bg-gradient-to-br from-light-main-color-2 to-light-main-color-2 dark:from-main-color-2 dark:to-main-color-2 text-gray-800 dark:text-white">
<div className="w-full flex flex-col">
<div className="flex items-center justify-start pl-1">
<BsmImage className="h-14 mr-1 pointer-events-none" image={renderImage}/>
@@ -11,7 +11,7 @@ export function NotificationOverlay() {
return (
<ul className="absolute h-full w-0 top-0 right-0 z-40 pt-10">
<ul className="absolute h-full w-0 top-0 right-0 z-[100] pt-10">
<AnimatePresence>
{notifications?.map(n => <NotificationItem key={n.id} resolver={n.resolver} notification={n.notification}/>)}
</AnimatePresence>
@@ -1,4 +1,4 @@
import { useRef, useState } from "react"
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react"
import { BsmIconType, BsmIcon } from "../svgs/bsm-icon.component"
import { BsmButton } from "./bsm-button.component"
import { useTranslation } from "renderer/hooks/use-translation.hook"
@@ -17,30 +17,42 @@ type Props = {
menuTranslationY?: string|number
children?: JSX.Element,
text?: string,
textClassName?: string
}
export function BsmDropdownButton({className, items, align, withBar = true, icon = "settings", buttonClassName, menuTranslationY, children, text, textClassName}: Props) {
export const BsmDropdownButton = forwardRef(({className, items, align, withBar = true, icon = "settings", buttonClassName, menuTranslationY, children, text}: Props, fowardRed) => {
const [expanded, setExpanded] = useState(false)
const t = useTranslation();
const ref = useRef(null)
const ref = useRef(fowardRed)
useClickOutside(ref, () => setExpanded(false));
useImperativeHandle(fowardRed, () => ({
close(){
setExpanded(() => false);
},
open(){
setExpanded(() => true);
}
}),
[],
)
const defaultButtonClassName = "relative z-[1] p-1 rounded-md text-inherit w-full h-full shadow-md shadow-black"
const handleClickOutside = () => {
if(children){ return; }
setExpanded(false);
if(children){ return; }
setExpanded(false);
}
const alignClass = (() => {
if(align === "center"){ return "right-1/2 origin-top-right translate-x-[50%]" }
if(align === "left"){ return "left-0 origin-top-left"; }
return "right-0 origin-top-right";
if(align === "center"){ return "right-1/2 origin-top-right translate-x-[50%]" }
if(align === "left"){ return "left-0 origin-top-left"; }
return "right-0 origin-top-right";
})()
return (
// @ts-ignore
<div ref={ref} className={className}>
<BsmButton onClick={() => setExpanded(!expanded)} className={buttonClassName ?? defaultButtonClassName} icon={icon} active={expanded} onClickOutside={handleClickOutside} withBar={withBar} text={text}/>
<div className={`py-1 w-fit absolute cursor-pointer top-[calc(100%-4px)] rounded-md bg-inherit text-sm text-gray-800 dark:text-gray-200 shadow-md shadow-black transition-[scale] ease-in-out ${alignClass}`} style={{scale: expanded ? "1" : "0", translate: `0 ${menuTranslationY}`}}>
@@ -60,4 +72,4 @@ export function BsmDropdownButton({className, items, align, withBar = true, icon
)}
</div>
)
}
})
@@ -0,0 +1,10 @@
import { useState } from "react";
import { BehaviorSubject } from "rxjs";
import { useObservable } from "./use-observable.hook";
export function useBehaviorSubject<T>(value: T): [T, BehaviorSubject<T>]{
const [subject] = useState(new BehaviorSubject(value));
const subjectValue = useObservable(subject);
return [subjectValue, subject];
}
+12 -4
View File
@@ -10,6 +10,7 @@ import { DeleteMapsModal } from "renderer/components/modal/modal-types/delete-ma
import { ProgressBarService } from "./progress-bar.service";
import { OpenSaveDialogOption } from "shared/models/ipc";
import { NotificationService } from "./notification.service";
import { ConfigurationService } from "./configuration.service";
export class MapsManagerService {
@@ -20,11 +21,14 @@ export class MapsManagerService {
return MapsManagerService.instance;
}
public static readonly REMEMBER_CHOICE_DELETE_MAP_KEY = "not-confirm-delete-map"
private readonly ipcService: IpcService;
private readonly bsaver: BeatSaverService;
private readonly modal: ModalService;
private readonly progressBar: ProgressBarService;
private readonly notifications: NotificationService;
private readonly config: ConfigurationService;
private readonly lastLinkedVersion$: Subject<BSVersion> = new Subject();
private readonly lastUnlinkedVersion$: Subject<BSVersion> = new Subject();
@@ -35,6 +39,7 @@ export class MapsManagerService {
this.modal = ModalService.getInsance();
this.progressBar = ProgressBarService.getInstance();
this.notifications = NotificationService.getInstance();
this.config = ConfigurationService.getInstance();
}
public getMaps(version?: BSVersion, withDetails = true): Observable<BsmLocalMap[]>{
@@ -115,19 +120,22 @@ export class MapsManagerService {
const versionLinked = await this.versionHaveMapsLinked(version);
const modalRes = await this.modal.openModal(DeleteMapsModal, {linked: versionLinked, maps});
const askModal = maps.length > 1 || !this.config.get<boolean>(MapsManagerService.REMEMBER_CHOICE_DELETE_MAP_KEY);
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return false; }
if(askModal){
const modalRes = await this.modal.openModal(DeleteMapsModal, {linked: versionLinked, maps});
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return false; }
}
const showProgressBar = this.progressBar.require();
if(showProgressBar && maps.length > 1){
if(showProgressBar){
this.progressBar.showFake(.008);
}
const res = await this.ipcService.send<void, {version: BSVersion, maps: BsmLocalMap[]}>("delete-maps", {args: {version, maps}});
if(showProgressBar && maps.length > 1){
if(showProgressBar){
this.progressBar.hide(true);
}