diff --git a/.erb/configs/webpack.config.renderer.dev.ts b/.erb/configs/webpack.config.renderer.dev.ts index 9fc73f6d..7462cbc0 100644 --- a/.erb/configs/webpack.config.renderer.dev.ts +++ b/.erb/configs/webpack.config.renderer.dev.ts @@ -10,6 +10,7 @@ import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; import baseConfig from './webpack.config.base'; import webpackPaths from './webpack.paths'; import checkNodeEnv from '../scripts/check-node-env'; +import { AppWindow } from "../../src/shared/models/window-manager/app-window.model" // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's // at the dev webpack config is not accidentally run in a production environment @@ -39,10 +40,10 @@ if ( execSync('npm run postinstall'); } -const getHtmlPageOptions = (page: string): HtmlWebpackPlugin.Options => { +const getHtmlPageOptions = (page: AppWindow): HtmlWebpackPlugin.Options => { return { - filename: path.join(`${page}.html`), - template: path.join(webpackPaths.srcRendererPath, `${page}.ejs`), + filename: path.join(page), + template: path.join(webpackPaths.srcRendererPath, page.replace(".html", ".ejs")), minify: { collapseWhitespace: true, removeAttributeQuotes: true, @@ -163,9 +164,10 @@ const configuration: webpack.Configuration = { new ReactRefreshWebpackPlugin(), - new HtmlWebpackPlugin(getHtmlPageOptions("index")), - new HtmlWebpackPlugin(getHtmlPageOptions("launcher")), - new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-map")), + new HtmlWebpackPlugin(getHtmlPageOptions("index.html")), + new HtmlWebpackPlugin(getHtmlPageOptions("launcher.html")), + new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-map.html")), + new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-playlist.html")), ], diff --git a/.erb/configs/webpack.config.renderer.prod.ts b/.erb/configs/webpack.config.renderer.prod.ts index 7d76f8fc..e03f66b9 100644 --- a/.erb/configs/webpack.config.renderer.prod.ts +++ b/.erb/configs/webpack.config.renderer.prod.ts @@ -14,6 +14,7 @@ import baseConfig from './webpack.config.base'; import webpackPaths from './webpack.paths'; import checkNodeEnv from '../scripts/check-node-env'; import deleteSourceMaps from '../scripts/delete-source-maps'; +import { AppWindow } from "../../src/shared/models/window-manager/app-window.model" checkNodeEnv('production'); deleteSourceMaps(); @@ -26,17 +27,19 @@ const devtoolsConfig = : {}; -const getHtmlPageOptions = (page: string): HtmlWebpackPlugin.Options => { +const getHtmlPageOptions = (page: AppWindow): HtmlWebpackPlugin.Options => { return { - filename: `${page}.html`, - template: path.join(webpackPaths.srcRendererPath, `${page}.ejs`), + filename: path.join(page), + template: path.join(webpackPaths.srcRendererPath, page.replace(".html", ".ejs")), minify: { collapseWhitespace: true, removeAttributeQuotes: true, removeComments: true, }, isBrowser: false, + env: process.env.NODE_ENV, isDevelopment: process.env.NODE_ENV !== 'production', + nodeModules: webpackPaths.appNodeModulesPath, } } @@ -143,9 +146,10 @@ const configuration: webpack.Configuration = { analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', }), - new HtmlWebpackPlugin(getHtmlPageOptions("index")), - new HtmlWebpackPlugin(getHtmlPageOptions("launcher")), - new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-map")), + new HtmlWebpackPlugin(getHtmlPageOptions("index.html")), + new HtmlWebpackPlugin(getHtmlPageOptions("launcher.html")), + new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-map.html")), + new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-playlist.html")), ], }; diff --git a/src/main/helpers/url.helpers.ts b/src/main/helpers/url.helpers.ts new file mode 100644 index 00000000..ea1981b9 --- /dev/null +++ b/src/main/helpers/url.helpers.ts @@ -0,0 +1,9 @@ +export function isValidUrl(url: string): boolean{ + try{ + new URL(url); + return true; + } + catch(e){ + return false; + } +} \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 15425903..5ba3aab0 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -89,7 +89,7 @@ else{ app.whenReady().then(() => { - // process.argv.push("web+bsmap://BDE6A1F8FC7008247443CF3F810BBD8E910DD839"); to force deep-link (oneClick map) + process.argv.push("bsplaylist://playlist/https://api.beatsaver.com/playlists/id/11137/download/beatsaver-11137.bplist"); // to force deep-link (oneClick map) initServicesMustBeInitialized(); diff --git a/src/main/services/additional-content/local-maps-manager.service.ts b/src/main/services/additional-content/local-maps-manager.service.ts index 41e82274..b4c86f0d 100644 --- a/src/main/services/additional-content/local-maps-manager.service.ts +++ b/src/main/services/additional-content/local-maps-manager.service.ts @@ -138,12 +138,12 @@ export class LocalMapsManagerService { } private openOneClickDownloadMapWindow(mapId: string, isHash = false): void{ - - this.windows.openWindow("oneclick-download-map.html"); ipcMain.once("one-click-map-info", async (event, req: IpcRequest) => { this.utils.ipcSend(req.responceChannel, {success: true, data: {id: mapId, isHash}}); }); + + this.windows.openWindow("oneclick-download-map.html"); } diff --git a/src/main/services/additional-content/local-playlists-manager.service.ts b/src/main/services/additional-content/local-playlists-manager.service.ts index 0b190095..049b17f6 100644 --- a/src/main/services/additional-content/local-playlists-manager.service.ts +++ b/src/main/services/additional-content/local-playlists-manager.service.ts @@ -4,9 +4,17 @@ import { Observable } from "rxjs"; import { BSVersion } from "shared/bs-version.interface"; import { BsvPlaylist, BsvPlaylistPage } from "shared/models/maps/beat-saver.model"; import { BSLocalVersionService } from "../bs-local-version.service"; +import { DeepLinkService } from "../deep-link.service"; import { RequestService } from "../request.service"; import { UtilsService } from "../utils.service"; import { LocalMapsManagerService } from "./local-maps-manager.service"; +import log from "electron-log" +import { isValidUrl } from "../../helpers/url.helpers"; +import { ipcMain } from "electron"; +import { WindowManagerService } from "../window-manager.service"; +import { IpcRequest } from "shared/models/ipc"; +import { BPList } from "shared/models/playlists/playlist.interface"; +import { readFileSync } from "fs"; export class LocalPlaylistsManagerService { @@ -25,19 +33,40 @@ export class LocalPlaylistsManagerService { private readonly versions: BSLocalVersionService; private readonly maps: LocalMapsManagerService; - public readonly utils: UtilsService; - public readonly request: RequestService; + private readonly utils: UtilsService; + private readonly request: RequestService; + private readonly deepLink: DeepLinkService; + private readonly windows: WindowManagerService; private constructor(){ this.maps = LocalMapsManagerService.getInstance(); this.versions = BSLocalVersionService.getInstance(); this.utils = UtilsService.getInstance(); this.request = RequestService.getInstance(); + this.deepLink = DeepLinkService.getInstance(); + this.windows = WindowManagerService.getInstance(); + + this.deepLink.addLinkOpenedListener(this.DEEP_LINKS.BeatSaver, link => { + log.info("DEEP-LINK RECEIVED FROM", this.DEEP_LINKS.BeatSaver, link); + const url = new URL(link); + const bplistUrl = url.host === "playlist" ? url.pathname.replace("/", "") : ""; + const playlistId = this.getPlaylistIdFromDownloadUrl(bplistUrl); + + this.openOneClickDownloadPlaylistWindow(playlistId); + + }); + } private getPlaylistIdFromDownloadUrl(url: string): string{ + + if(!isValidUrl(url)){ return ""; } + const splited = url.split("/") const idIndex = splited.indexOf("id"); + + if(idIndex < 0){ return ""; } + return splited[idIndex + 1]; } @@ -52,6 +81,26 @@ export class LocalPlaylistsManagerService { } + private async readPlaylistFile(path: string): Promise{ + + if(!this.utils.pathExist(path)){ return null; } + + const content = readFileSync(path).toJSON(); + + + // TODO + } + + private openOneClickDownloadPlaylistWindow(playlistId: string): void{ + + ipcMain.once("one-click-playlist-info", async (event, req: IpcRequest) => { + this.utils.ipcSend(req.responceChannel, {success: true, data: playlistId}); + }); + + this.windows.openWindow("oneclick-download-playlist.html"); + + } + public downloadPlaylist(playlistPage: BsvPlaylistPage, version: BSVersion): Observable{ const res = new BehaviorSubject([]); diff --git a/src/main/services/window-manager.service.ts b/src/main/services/window-manager.service.ts index 2cc85e87..9ae7afd4 100644 --- a/src/main/services/window-manager.service.ts +++ b/src/main/services/window-manager.service.ts @@ -13,7 +13,8 @@ export class WindowManagerService{ private readonly appWindowsOptions: Record = { "launcher.html": {width: 380, height: 500, minWidth: 380, minHeight: 500, resizable: false}, "index.html": {width: 1080, height: 720, minWidth: 900, minHeight: 500}, - "oneclick-download-map.html": {width: 350, height: 400, minWidth: 350, minHeight: 400, resizable: false} + "oneclick-download-map.html": {width: 350, height: 400, minWidth: 350, minHeight: 400, resizable: false}, + "oneclick-download-playlist.html": {width: 350, height: 400, minWidth: 350, minHeight: 400, resizable: false}, } private readonly baseWindowOption: BrowserWindowConstructorOptions = { diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 3d87940b..79f22484 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -6,15 +6,20 @@ import Launcher from './windows/Launcher'; import 'tailwindcss/tailwind.css'; import './index.css' import OneClickDownloadMap from './windows/OneClick/OneClickDownloadMap'; +import OneClickDownloadPlaylist from './windows/OneClick/OneClickDownloadPlaylist'; const launcherContainer = document.getElementById('launcher'); const oneclickDownloadMapContainer = document.getElementById('oneclick-download-map'); +const oneclickDownloadPlaylistContainer = document.getElementById('oneclick-download-playlist'); if(!!launcherContainer){ - createRoot(launcherContainer).render() + createRoot(launcherContainer).render(); } else if(!!oneclickDownloadMapContainer){ - createRoot(oneclickDownloadMapContainer).render() + createRoot(oneclickDownloadMapContainer).render(); +} +else if(!!oneclickDownloadPlaylistContainer){ + createRoot(oneclickDownloadPlaylistContainer).render(); } else{ const root = document.getElementById('root'); diff --git a/src/renderer/oneclick-download-playlist.ejs b/src/renderer/oneclick-download-playlist.ejs new file mode 100644 index 00000000..059be89d --- /dev/null +++ b/src/renderer/oneclick-download-playlist.ejs @@ -0,0 +1,14 @@ + + + + + + + + +
+ + \ No newline at end of file diff --git a/src/renderer/windows/OneClick/OneClickDownloadMap.tsx b/src/renderer/windows/OneClick/OneClickDownloadMap.tsx index 4785b55b..50a92282 100644 --- a/src/renderer/windows/OneClick/OneClickDownloadMap.tsx +++ b/src/renderer/windows/OneClick/OneClickDownloadMap.tsx @@ -19,7 +19,6 @@ export default function OneClickDownloadMap() { const mapsDownloader = MapsDownloaderService.getInstance(); const themeService = ThemeService.getInstance(); const progressBar = ProgressBarService.getInstance(); - const windows = WindowManagerService.getInstance(); const [mapInfo, setMapInfo] = useState(null); diff --git a/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx b/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx new file mode 100644 index 00000000..a9d289ec --- /dev/null +++ b/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx @@ -0,0 +1,5 @@ +export default function OneClickDownloadPlaylist() { + return ( +
OneClickDownloadPlaylist
+ ) +} diff --git a/src/shared/models/playlists/playlist.interface.ts b/src/shared/models/playlists/playlist.interface.ts index 7f1fd7a1..18c72818 100644 --- a/src/shared/models/playlists/playlist.interface.ts +++ b/src/shared/models/playlists/playlist.interface.ts @@ -1,4 +1,4 @@ -export interface Playlist { +export interface BPList { playlistTitle: string, playlistAuthor: string, playlistDescription?: string, diff --git a/src/shared/models/window-manager/app-window.model.ts b/src/shared/models/window-manager/app-window.model.ts index b37eb43c..03ca56ac 100644 --- a/src/shared/models/window-manager/app-window.model.ts +++ b/src/shared/models/window-manager/app-window.model.ts @@ -1,5 +1,6 @@ export type AppWindow = ( "index.html" | "launcher.html" | - "oneclick-download-map.html" + "oneclick-download-map.html"| + "oneclick-download-playlist.html" ); \ No newline at end of file