mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
supporters full integrated exept sponsors item
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -1,3 +1 @@
|
||||
[
|
||||
|
||||
]
|
||||
[]
|
||||
@@ -4,3 +4,4 @@ import './bs-launcher-ipcs';
|
||||
import './bs-version-ipcs';
|
||||
import './bs-uninstall-ipcs';
|
||||
import './map-ipcs';
|
||||
import './supporters-ipcs';
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ipcMain } from "electron";
|
||||
import { SupportersService } from "../services/supporters.service";
|
||||
import { IpcRequest } from "shared/models/ipc";
|
||||
import { UtilsService } from '../services/utils.service';
|
||||
|
||||
ipcMain.on("get-supporters", (event, request: IpcRequest<void>) => {
|
||||
const utils = UtilsService.getInstance();
|
||||
const supportersService = SupportersService.getInstance();
|
||||
|
||||
supportersService.getSupporters().then(supporters => {
|
||||
utils.ipcSend(request.responceChannel, {success: true, data: supporters});
|
||||
}).catch(() => {
|
||||
utils.ipcSend(request.responceChannel, {success: false});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { writeFileSync } from "fs";
|
||||
import { get } from "https";
|
||||
import isOnline from "is-online";
|
||||
import path from "path";
|
||||
import { Supporter } from "shared/models/supporters/supporter.interface";
|
||||
import { UtilsService } from "./utils.service";
|
||||
|
||||
export class SupportersService {
|
||||
|
||||
private readonly PATREONS_URL = "https://raw.githubusercontent.com/Zagrios/bs-manager/master/assets/jsons/patreons.json"
|
||||
private readonly PATREONS_FILE = "patreons.json";
|
||||
|
||||
private static instance: SupportersService;
|
||||
|
||||
private cache: Supporter[];
|
||||
|
||||
private readonly utilsService: UtilsService;
|
||||
|
||||
public static getInstance(): SupportersService{
|
||||
if(!SupportersService.instance){ SupportersService.instance = new SupportersService(); }
|
||||
return SupportersService.instance;
|
||||
}
|
||||
|
||||
private constructor(){
|
||||
this.utilsService = UtilsService.getInstance();
|
||||
}
|
||||
|
||||
private async updateLocalSupporters(supporters: Supporter[]): Promise<void>{
|
||||
const patreonsPath = path.join(this.utilsService.getAssestsJsonsPath(), this.PATREONS_FILE);
|
||||
writeFileSync(patreonsPath, JSON.stringify(supporters, null, "\t"), {encoding: 'utf-8', flag: 'w'});
|
||||
}
|
||||
|
||||
private async getRemoteSupporters(): Promise<Supporter[]>{
|
||||
return new Promise<Supporter[]>((resolve, reject) => {
|
||||
let body = ''
|
||||
get(this.PATREONS_URL, (res) => {
|
||||
res.on('data', chunk => body += chunk);
|
||||
res.on('end', () => resolve(JSON.parse(body)));
|
||||
res.on('error', () => reject(null))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private async getLocalSupporters(): Promise<Supporter[]>{
|
||||
const patreonsPath = path.join(this.utilsService.getAssestsJsonsPath(), this.PATREONS_FILE);
|
||||
const rawPatreons = (await this.utilsService.readFileAsync(patreonsPath)).toString();
|
||||
return JSON.parse(rawPatreons);
|
||||
}
|
||||
|
||||
private async loadSupporters(): Promise<Supporter[]>{
|
||||
if(!!this.cache && this.cache.length){ return this.cache; }
|
||||
|
||||
const isOnlineRes = await isOnline({timeout: 1500});
|
||||
|
||||
const [localVersions, remoteVersions] = await Promise.all([
|
||||
this.getLocalSupporters(), (isOnlineRes && this.getRemoteSupporters())
|
||||
]);
|
||||
|
||||
const supporters = remoteVersions?.length ? remoteVersions : localVersions;
|
||||
|
||||
if(!!remoteVersions && remoteVersions.length){ this.updateLocalSupporters(remoteVersions); }
|
||||
|
||||
this.cache = supporters;
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
public getSupporters(): Promise<Supporter[]>{
|
||||
return this.loadSupporters();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { CSSProperties } from "react";
|
||||
import { Supporter } from "shared/models/supporters";
|
||||
import { motion } from "framer-motion";
|
||||
import txtBg from "../../../../../assets/images/gifs/txt-bg.gif"
|
||||
|
||||
interface Props { supporter: Supporter, delay?: number }
|
||||
|
||||
export function SupporterItem({supporter, delay}: Props) {
|
||||
|
||||
const additionnalStyles: CSSProperties = (() => {
|
||||
if(supporter.type === "gold"){ return {color: "#ffe270", textShadow: "0px 0px 10px #ffdd59", backgroundImage: `url(${txtBg})`, backgroundSize: "70% 15px", backgroundRepeat: "no-repeat", backgroundPosition: "center"}; }
|
||||
if(supporter.type === "diamond"){ return {color: "#e574fc", textShadow: "0px 0px 15px #e056fd", backgroundImage: `url(${txtBg})`, backgroundSize: "70% 19px", backgroundRepeat: "no-repeat", backgroundPosition: "center"}; }
|
||||
return {};
|
||||
})()
|
||||
|
||||
return (
|
||||
<motion.span className="text-2xl font-bold px-3 pb-1" style={additionnalStyles} initial={{y: "100%", opacity: 0}} animate={{y: 0, opacity: 1}} transition={{delay: delay}}>{supporter.username}</motion.span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Supporter } from "shared/models/supporters";
|
||||
import { SupporterItem } from "./supporter-item.component";
|
||||
|
||||
interface Props {className?: string, title: string, supporters: Supporter[]}
|
||||
|
||||
export function SupportersBlock({className, title, supporters}: Props) {
|
||||
|
||||
const someDelay = () => {
|
||||
const [min, max] = [0, .55]
|
||||
const baseDelay = .15;
|
||||
return baseDelay + Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`mb-10 flex flex-col items-center ${className}`}>
|
||||
<h2 className="uppercase text-3xl font-bold">{title}</h2>
|
||||
<div className="flex justify-center flex-wrap py-5 px-20">
|
||||
{supporters.map(s => <SupporterItem supporter={s} delay={someDelay()}/>)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { motion, AnimatePresence } from "framer-motion"
|
||||
import { useEffect, useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component"
|
||||
import { Supporter } from "shared/models/supporters";
|
||||
import { SupportersService } from "renderer/services/supporters.service";
|
||||
import { SupportersBlock } from "./supporters-block.component";
|
||||
import ManheraChanGif from "../../../../../assets/images/gifs/menhera-chan.gif"
|
||||
import ManheraSadGif from "../../../../../assets/images/gifs/menhera-sad.gif"
|
||||
|
||||
interface Props {isVisible: boolean, setVisible: (b: boolean) => void}
|
||||
|
||||
export function SupportersView({isVisible, setVisible}: Props) {
|
||||
|
||||
const supportersService = SupportersService.getInstance();
|
||||
|
||||
const [supporters, setSupporters] = useState([] as Supporter[]);
|
||||
const [sponsors, setSponsors] = useState([] as Supporter[]);
|
||||
|
||||
useEffect(() => {
|
||||
if(isVisible){
|
||||
supportersService.getSupporters().then(supporters => {
|
||||
setSponsors(supporters.filter(s => s.type === "sponsor"));
|
||||
setSupporters(supporters.filter(s => !s.type || s.type !== "sponsor"));
|
||||
});
|
||||
}
|
||||
}, [isVisible])
|
||||
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
({isVisible &&
|
||||
<motion.div className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-90 z-40 text-gray-200" transition={{duration: .3}} initial={{opacity: 0, y: "-100%"}} animate={{opacity: 1, y: "0%"}} exit={{opacity: 0, y: "-100%"}}>
|
||||
<BsmButton className="absolute right-10 top-10 !bg-transparent w-7 h-7" icon="cross" withBar={false} onClick={() => setVisible(false)}></BsmButton>
|
||||
{(!!sponsors.length || !!supporters.length) && <img className="absolute bottom-5 right-5 rotate-45 w-32 h-32" src={ManheraChanGif}/>}
|
||||
<div className="w-full h-full overflow-y-scroll">
|
||||
{!!sponsors.length && <SupportersBlock className="mt-12" title="sponsors" supporters={sponsors}/>}
|
||||
{!!supporters.length && <SupportersBlock className="mt-12" title="supporters" supporters={supporters}/>}
|
||||
{(!sponsors.length && !supporters.length) && (
|
||||
<>
|
||||
<h2 className="w-full text-center text-3xl font-bold mt-10 mb-24">No supporter yet</h2>
|
||||
<img className="m-auto" src={ManheraSadGif} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
})
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { ModalExitCode, ModalService, ModalType } from "renderer/services/modale
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { ProgressBarService } from "renderer/services/progress-bar.service";
|
||||
import { ThemeService } from "renderer/services/theme.service";
|
||||
import { SupportersView } from "renderer/components/settings/supporters-view/supporters-view.component";
|
||||
|
||||
export function SettingsPage() {
|
||||
|
||||
@@ -45,6 +46,7 @@ export function SettingsPage() {
|
||||
const[themeIdSelected, setThemeIdSelected]= useState(themeItem.find(e => e.value === themeService.getTheme()).id);
|
||||
const[languageSelected, setLanguageSelected]= useState(languagesItems.find(e => e.value === i18nService.currentLanguage).id);
|
||||
const [installationFolder, setInstallationFolder] = useState(null);
|
||||
const [showSupporters, setShowSupporters] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadInstallationFolder();
|
||||
@@ -110,49 +112,56 @@ export function SettingsPage() {
|
||||
ipcService.sendLazy("new-window", {args: "https://www.patreon.com/bsmanager?fan_landing=true"})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex justify-center overflow-y-scroll scrollbar-thin scrollbar-thumb-neutral-900 text-gray-800 dark:text-gray-200">
|
||||
const toogleShowSupporters = () => {
|
||||
setShowSupporters(show => !show);
|
||||
}
|
||||
|
||||
<div className="max-w-2xl w-full mt-10">
|
||||
return (
|
||||
<div className="w-full h-full flex justify-center overflow-y-scroll scrollbar-thin scrollbar-thumb-neutral-900 text-gray-800 dark:text-gray-200">
|
||||
|
||||
<SettingContainer title="pages.settings.steam.title" description="pages.settings.steam.description">
|
||||
<BsmButton onClick={deleteSteamSession} className="w-fit px-3 py-[2px] text-white rounded-md" withBar={false} text="pages.settings.steam.logout" typeColor="error" disabled={!sessionExist}/>
|
||||
</SettingContainer>
|
||||
<div className="max-w-2xl w-full mt-10">
|
||||
|
||||
<SettingContainer title="pages.settings.steam.title" description="pages.settings.steam.description">
|
||||
<BsmButton onClick={deleteSteamSession} className="w-fit px-3 py-[2px] text-white rounded-md" withBar={false} text="pages.settings.steam.logout" typeColor="error" disabled={!sessionExist}/>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.appearance.title" description="pages.settings.appearance.description">
|
||||
<div className="relative w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 flex justify-center rounded-md py-1">
|
||||
<SettingColorChooser color={firstColor} onChange={setFirstColorSetting}/>
|
||||
<SettingColorChooser color={secondColor} onChange={setSecondColorSetting}/>
|
||||
<div className="absolute right-2 top-0 h-full flex items-center">
|
||||
<BsmButton onClick={resetColors} className="px-2 font-bold italic text-sm rounded-md" text="pages.settings.appearance.reset" withBar={false}/>
|
||||
</div>
|
||||
</div>
|
||||
<SettingContainer minorTitle="pages.settings.appearance.sub-title" className="mt-3">
|
||||
<SettingRadioArray items={themeItem} selectedItem={themeIdSelected} onItemSelected={handleChangeTheme}/>
|
||||
</SettingContainer>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.installation-folder.title" description="pages.settings.installation-folder.description">
|
||||
<div className="relative flex items-center justify-between w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 rounded-md pl-2 py-1">
|
||||
<span className="block text-ellipsis overflow-hidden min-w-0" title={installationFolder}>{installationFolder}</span>
|
||||
<BsmButton onClick={setDefaultInstallationFolder} className="shrink-0 whitespace-nowrap mr-2 px-2 font-bold italic text-sm rounded-md" text="pages.settings.installation-folder.choose-folder" withBar={false}/>
|
||||
</div>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.language.title" description="pages.settings.language.description">
|
||||
<SettingRadioArray items={languagesItems} selectedItem={languageSelected} onItemSelected={handleChangeLanguage}/>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.patreon.title" description="pages.settings.patreon.description">
|
||||
<div className="flex">
|
||||
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 whitespace-nowrap mr-2 !text-white" iconClassName="mr-1" text="pages.settings.patreon.buttons.support" icon="patreon" color="#EC6350" withBar={false} onClick={openPatreonPage}></BsmButton>
|
||||
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="pages.settings.patreon.buttons.supporters" color="#6c5ce7" onClick={toogleShowSupporters}></BsmButton>
|
||||
</div>
|
||||
</SettingContainer>
|
||||
|
||||
<div className="h-10"/>
|
||||
|
||||
<SettingContainer title="pages.settings.appearance.title" description="pages.settings.appearance.description">
|
||||
<div className="relative w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 flex justify-center rounded-md py-1">
|
||||
<SettingColorChooser color={firstColor} onChange={setFirstColorSetting}/>
|
||||
<SettingColorChooser color={secondColor} onChange={setSecondColorSetting}/>
|
||||
<div className="absolute right-2 top-0 h-full flex items-center">
|
||||
<BsmButton onClick={resetColors} className="px-2 font-bold italic text-sm rounded-md" text="pages.settings.appearance.reset" withBar={false}/>
|
||||
</div>
|
||||
</div>
|
||||
<SettingContainer minorTitle="pages.settings.appearance.sub-title" className="mt-3">
|
||||
<SettingRadioArray items={themeItem} selectedItem={themeIdSelected} onItemSelected={handleChangeTheme}/>
|
||||
</SettingContainer>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.installation-folder.title" description="pages.settings.installation-folder.description">
|
||||
<div className="relative flex items-center justify-between w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 rounded-md pl-2 py-1">
|
||||
<span className="block text-ellipsis overflow-hidden min-w-0" title={installationFolder}>{installationFolder}</span>
|
||||
<BsmButton onClick={setDefaultInstallationFolder} className="shrink-0 whitespace-nowrap mr-2 px-2 font-bold italic text-sm rounded-md" text="pages.settings.installation-folder.choose-folder" withBar={false}/>
|
||||
<SupportersView isVisible={showSupporters} setVisible={setShowSupporters}/>
|
||||
|
||||
</div>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.language.title" description="pages.settings.language.description">
|
||||
<SettingRadioArray items={languagesItems} selectedItem={languageSelected} onItemSelected={handleChangeLanguage}/>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.patreon.title" description="pages.settings.patreon.description">
|
||||
<div className="flex">
|
||||
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 whitespace-nowrap mr-2 !text-white" iconClassName="mr-1" text="pages.settings.patreon.buttons.support" icon="patreon" color="#EC6350" withBar={false} onClick={openPatreonPage}></BsmButton>
|
||||
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="pages.settings.patreon.buttons.supporters" color="#6c5ce7"></BsmButton>
|
||||
</div>
|
||||
</SettingContainer>
|
||||
|
||||
<div className="h-10"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Supporter } from "shared/models/supporters";
|
||||
import { IpcService } from "./ipc.service";
|
||||
|
||||
|
||||
export class SupportersService {
|
||||
|
||||
private static instance: SupportersService;
|
||||
|
||||
private ipcService: IpcService;
|
||||
|
||||
private constructor(){
|
||||
this.ipcService = IpcService.getInstance();
|
||||
}
|
||||
|
||||
public static getInstance(): SupportersService{
|
||||
if(!SupportersService.instance){ SupportersService.instance = new SupportersService(); }
|
||||
return SupportersService.instance;
|
||||
}
|
||||
|
||||
public getSupporters(): Promise<Supporter[]>{
|
||||
return this.ipcService.send<Supporter[]>("get-supporters").then(res => {
|
||||
if(!res.success){ return null; }
|
||||
return res.data;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { SupporterType } from "./supporter.type";
|
||||
export { Supporter } from "./supporter.interface";
|
||||
@@ -0,0 +1,8 @@
|
||||
import { SupporterType } from "./supporter.type";
|
||||
|
||||
export interface Supporter {
|
||||
username: string,
|
||||
type?: SupporterType,
|
||||
link?: string,
|
||||
image?: string,
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { SupporterType } from "./supporter.type";
|
||||
import { SupporterInterface } from "./supporter.interface";
|
||||
|
||||
export class SupporterModel{
|
||||
constructor(private args: SupporterInterface){}
|
||||
|
||||
public get username(): string{ return this.args.username; }
|
||||
public get type(): SupporterType{ return this.args.type; }
|
||||
|
||||
public get link(): string{
|
||||
if(this.type === "basic"){ return null; }
|
||||
return this.args.link;
|
||||
}
|
||||
|
||||
public get image(): string{
|
||||
if(this.type !== "sponsor"){ return null; }
|
||||
return this.args.image;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export type SupporterType = "gold"|"diamond"|"sponsor"
|
||||
Reference in New Issue
Block a user