mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[chore] add simple windows e2e tests
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
name : e2e-tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
e2e-tests-windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Cleanup files
|
||||
run: cmd /r dir
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: "npm"
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Package
|
||||
run: npm exec -c 'electron-builder build --publish "never" --win --x64'
|
||||
|
||||
- name: Run e2e tests
|
||||
run: npm run e2e-test-ci
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: playwright-output
|
||||
path: test-results
|
||||
@@ -27,3 +27,5 @@ npm-debug.log.*
|
||||
*.css.d.ts
|
||||
*.sass.d.ts
|
||||
*.scss.d.ts
|
||||
|
||||
test-results
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Code taken from https://github.com/kubeshop/monokle/blob/main/tests/electronHelpers.ts
|
||||
* don't tunch unless you know what you're doing
|
||||
*/
|
||||
|
||||
import { Page } from "@playwright/test";
|
||||
import path from "path";
|
||||
import {ElectronApplication, _electron as electron} from 'playwright-core';
|
||||
import { pause } from "shared/helpers/promise.helpers";
|
||||
import log from "electron-log";
|
||||
import * as fs from 'fs';
|
||||
import * as ASAR from 'asar';
|
||||
|
||||
export const TEST_OUTPUT_DIR = 'test-results';
|
||||
|
||||
export async function startApp(): Promise<StartAppResponse> {
|
||||
const latestBuild = findLatestBuild();
|
||||
const appInfo = parseElectronApp(latestBuild);
|
||||
const electronApp = await electron.launch({
|
||||
args: [appInfo.main],
|
||||
executablePath: appInfo.executable,
|
||||
recordVideo: {
|
||||
dir: getRecordingPath(appInfo.platform),
|
||||
size: {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await electronApp.firstWindow();
|
||||
|
||||
// wait for auto-updater to pass
|
||||
let appWindow: Page;
|
||||
while (!appWindow) {
|
||||
appWindow = getMainWindow(electronApp.windows());
|
||||
await pause(500);
|
||||
}
|
||||
|
||||
if (!appWindow) {
|
||||
throw new Error('Unable to get main window');
|
||||
}
|
||||
|
||||
await pause(3000);
|
||||
|
||||
appWindow.on('console', log.info);
|
||||
appWindow.screenshot({path: path.join(TEST_OUTPUT_DIR, "initial-screen.png")});
|
||||
|
||||
return {appWindow, appInfo, electronApp};
|
||||
}
|
||||
|
||||
export function findLatestBuild(): string {
|
||||
const rootDir = path.resolve('./');
|
||||
const outDir = path.join(rootDir, 'release', 'build');
|
||||
const builds = fs.readdirSync(outDir);
|
||||
const platforms = ['win32', 'win', 'windows', 'darwin', 'mac', 'macos', 'osx', 'linux', 'ubuntu'];
|
||||
|
||||
const latestBuild = builds
|
||||
.map(fileName => {
|
||||
// make sure it's a directory with "-" delimited platform in its name
|
||||
const stats = fs.statSync(path.join(outDir, fileName));
|
||||
const isBuild = fileName.toLocaleLowerCase().split('-').some(part => platforms.includes(part));
|
||||
|
||||
if(!stats.isDirectory() || !isBuild){ return undefined; }
|
||||
|
||||
return {
|
||||
name: fileName,
|
||||
time: fs.statSync(path.join(outDir, fileName)).mtimeMs,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.time - a.time)
|
||||
.map(file => file?.name)[0];
|
||||
|
||||
if (!latestBuild) {
|
||||
throw new Error('No build found in out directory');
|
||||
}
|
||||
|
||||
return path.join(outDir, latestBuild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a directory containing an Electron app build,
|
||||
* return the path to the app's executable and the path to the app's main file.
|
||||
*/
|
||||
export function parseElectronApp(buildDir: string): ElectronAppInfo {
|
||||
log.info(`Parsing Electron app in ${buildDir}`);
|
||||
|
||||
let platform: string | undefined;
|
||||
|
||||
if (buildDir.endsWith('.app')) {
|
||||
buildDir = path.dirname(buildDir);
|
||||
platform = 'darwin';
|
||||
}
|
||||
else if (buildDir.endsWith('.exe')) {
|
||||
buildDir = path.dirname(buildDir);
|
||||
platform = 'win32';
|
||||
}
|
||||
|
||||
const baseName = path.basename(buildDir).toLowerCase();
|
||||
if (!platform) {
|
||||
// parse the directory name to figure out the platform
|
||||
if (baseName.includes('win')) {
|
||||
platform = 'win32';
|
||||
}
|
||||
if (baseName.includes('linux') || baseName.includes('ubuntu') || baseName.includes('debian')) {
|
||||
platform = 'linux';
|
||||
}
|
||||
if (baseName.includes('darwin') || baseName.includes('mac') || baseName.includes('osx')) {
|
||||
platform = 'darwin';
|
||||
}
|
||||
}
|
||||
|
||||
if (!platform) {
|
||||
throw new Error(`Platform not found in directory name: ${baseName}`);
|
||||
}
|
||||
|
||||
let arch: Architecture;
|
||||
if (baseName.includes('x32') || baseName.includes('i386')) {
|
||||
arch = 'x32';
|
||||
}
|
||||
if (baseName.includes('x64')) {
|
||||
arch = 'x64';
|
||||
}
|
||||
if (baseName.includes('arm64')) {
|
||||
arch = 'arm64';
|
||||
}
|
||||
|
||||
let executable: string;
|
||||
let main: string;
|
||||
let name: string;
|
||||
let asar: boolean;
|
||||
let resourcesDir: string;
|
||||
|
||||
if (platform === 'darwin') {
|
||||
// MacOS Structure
|
||||
// <buildDir>/
|
||||
// <appName>.app/
|
||||
// Contents/
|
||||
// MacOS/
|
||||
// <appName> (executable)
|
||||
// Info.plist
|
||||
// PkgInfo
|
||||
// Resources/
|
||||
// electron.icns
|
||||
// file.icns
|
||||
// app.asar (asar bundle) - or -
|
||||
// app
|
||||
// package.json
|
||||
// (your app structure)
|
||||
|
||||
const list = fs.readdirSync(buildDir);
|
||||
const appBundle = list.find(fileName => {
|
||||
return fileName.endsWith('.app');
|
||||
});
|
||||
|
||||
const appDir = path.join(buildDir, appBundle, 'Contents', 'MacOS');
|
||||
const appName = fs.readdirSync(appDir)[0];
|
||||
executable = path.join(appDir, appName);
|
||||
|
||||
resourcesDir = path.join(buildDir, appBundle, 'Contents', 'Resources');
|
||||
const resourcesList = fs.readdirSync(resourcesDir);
|
||||
asar = resourcesList.includes('app.asar');
|
||||
|
||||
let packageJson: {main: string; name: string};
|
||||
if (asar) {
|
||||
const asarPath = path.join(resourcesDir, 'app.asar');
|
||||
packageJson = JSON.parse(ASAR.extractFile(asarPath, 'package.json').toString('utf8'));
|
||||
main = path.join(asarPath, packageJson.main);
|
||||
} else {
|
||||
packageJson = JSON.parse(fs.readFileSync(path.join(resourcesDir, 'app', 'package.json'), 'utf8'));
|
||||
main = path.join(resourcesDir, 'app', packageJson.main);
|
||||
}
|
||||
name = packageJson.name;
|
||||
}
|
||||
else if (platform === 'win32') {
|
||||
// Windows Structure
|
||||
// <buildDir>/
|
||||
// <appName>.exe (executable)
|
||||
// resources/
|
||||
// app.asar (asar bundle) - or -
|
||||
// app
|
||||
// package.json
|
||||
// (your app structure)
|
||||
|
||||
const list = fs.readdirSync(buildDir);
|
||||
const exe = list.find(fileName => {
|
||||
return fileName.endsWith('.exe');
|
||||
});
|
||||
|
||||
executable = path.join(buildDir, exe);
|
||||
|
||||
resourcesDir = path.join(buildDir, 'resources');
|
||||
const resourcesList = fs.readdirSync(resourcesDir);
|
||||
asar = resourcesList.includes('app.asar');
|
||||
|
||||
let packageJson: {main: string; name: string};
|
||||
|
||||
if (asar) {
|
||||
const asarPath = path.join(resourcesDir, 'app.asar');
|
||||
packageJson = JSON.parse(ASAR.extractFile(asarPath, 'package.json').toString('utf8'));
|
||||
main = path.join(asarPath, packageJson.main);
|
||||
} else {
|
||||
packageJson = JSON.parse(fs.readFileSync(path.join(resourcesDir, 'app', 'package.json'), 'utf8'));
|
||||
main = path.join(resourcesDir, 'app', packageJson.main);
|
||||
}
|
||||
name = packageJson.name;
|
||||
}
|
||||
else {
|
||||
/** @todo add support for linux */
|
||||
throw new Error(`Platform not supported: ${platform}`);
|
||||
}
|
||||
|
||||
return { executable, main, asar, name, platform, resourcesDir, arch };
|
||||
}
|
||||
|
||||
export function getRecordingPath(...paths: string[]): string {
|
||||
return path.join(TEST_OUTPUT_DIR, ...paths);
|
||||
}
|
||||
|
||||
export function getMainWindow(windows: Page[]): Page {
|
||||
const mainWindow = windows.find(w => w.url().includes('index.html'));
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
type Architecture = 'x64' | 'x32' | 'arm64';
|
||||
export interface ElectronAppInfo {
|
||||
/** Path to the app's executable file */
|
||||
executable: string;
|
||||
/** Path to the app's main (JS) file */
|
||||
main: string;
|
||||
/** Name of the app */
|
||||
name: string;
|
||||
/** Resources directory */
|
||||
resourcesDir: string;
|
||||
/** True if the app is using asar */
|
||||
asar: boolean;
|
||||
/** OS platform */
|
||||
platform: 'darwin' | 'win32' | 'linux';
|
||||
arch: Architecture;
|
||||
}
|
||||
|
||||
interface StartAppResponse {
|
||||
electronApp: ElectronApplication;
|
||||
appWindow: Page;
|
||||
appInfo: ElectronAppInfo;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import {Page} from 'playwright';
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { getRecordingPath, startApp } from './helpers/electron.helpers';
|
||||
import { pause } from 'shared/helpers/promise.helpers';
|
||||
import { MainWindow } from './models/main-window.class';
|
||||
import { AddVersionPanel } from './models/add-version-panel.class';
|
||||
|
||||
|
||||
let appWindow: Page;
|
||||
let mainWindow: MainWindow;
|
||||
let addVersionPanel: AddVersionPanel;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
const startAppResponse = await startApp();
|
||||
appWindow = startAppResponse.appWindow;
|
||||
mainWindow = new MainWindow(appWindow);
|
||||
addVersionPanel = new AddVersionPanel(appWindow);
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await pause(1000);
|
||||
await mainWindow.clickAddVersionButton();
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await pause(1000);
|
||||
});
|
||||
|
||||
test('should be able to select then unselect a bs version', async () => {
|
||||
const versionManifest = "5325635033564462932";
|
||||
await addVersionPanel.selectYear("2018");
|
||||
|
||||
await addVersionPanel.clickVersion(versionManifest); // select version 0.12.2
|
||||
await pause(1000);
|
||||
|
||||
expect(await addVersionPanel.downloadVersionButton.isVisible()).toBeTruthy();
|
||||
|
||||
await addVersionPanel.clickVersion(versionManifest); // deselect version 0.12.2
|
||||
await pause(1000);
|
||||
|
||||
expect(await addVersionPanel.downloadVersionButton.isVisible()).toBeFalsy();
|
||||
|
||||
// sadly we can't test if the download works or not :( (Steam ids, dot-net, etc)
|
||||
});
|
||||
|
||||
test('should be able to download a map then delete it', async () => {
|
||||
// TODO: implement this test
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await pause(3000);
|
||||
await appWindow.screenshot({path: getRecordingPath("final-screen.png")});
|
||||
await appWindow.close();
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Page, Locator } from "playwright";
|
||||
import { MainWindow } from "./main-window.class";
|
||||
|
||||
export class AddVersionPanel extends MainWindow {
|
||||
|
||||
private readonly _yearsTabBar: Locator;
|
||||
private readonly _downloadVersionButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
|
||||
this._yearsTabBar = page.locator("#version-years-tab-bar");
|
||||
this._downloadVersionButton = page.locator("#download-version-btn");
|
||||
}
|
||||
|
||||
selectYear(year: string): Promise<void>{
|
||||
return this._yearsTabBar.getByText(year).click();
|
||||
}
|
||||
|
||||
clickVersion(manifest: string): Promise<void>{
|
||||
return this._page.locator(`#version-item-${manifest}`).click();
|
||||
}
|
||||
|
||||
get downloadVersionButton(): Locator { return this._downloadVersionButton; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Page, Locator } from "playwright"
|
||||
|
||||
export class MainWindow {
|
||||
|
||||
protected readonly _page: Page;
|
||||
|
||||
private readonly _bsmLogo: Locator;
|
||||
private readonly _addVersionButton: Locator;
|
||||
private readonly _settingsButton: Locator;
|
||||
private readonly _sharedContentButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this._page = page;
|
||||
|
||||
this._bsmLogo = page.locator("#bsm-logo");
|
||||
this._addVersionButton = page.locator("#add-version-btn");
|
||||
this._settingsButton = page.locator("#settings-btn");
|
||||
this._sharedContentButton = page.locator("#shared-contents-btn");
|
||||
}
|
||||
public async clickBsmLogo(): Promise<void> {
|
||||
return this._bsmLogo.click();
|
||||
}
|
||||
|
||||
public async clickAddVersionButton(): Promise<void> {
|
||||
return this._addVersionButton.click();
|
||||
}
|
||||
|
||||
public async clickSettingsButton(): Promise<void> {
|
||||
return this._settingsButton.click();
|
||||
}
|
||||
|
||||
public async clickSharedContentButton(): Promise<void> {
|
||||
return this._sharedContentButton.click();
|
||||
}
|
||||
|
||||
}
|
||||
Generated
+33839
-33614
File diff suppressed because it is too large
Load Diff
+308
-300
@@ -1,310 +1,318 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"description": "Manage maps, mods and more for Beat Saber",
|
||||
"scripts": {
|
||||
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\"",
|
||||
"build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts",
|
||||
"build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts",
|
||||
"rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
|
||||
"lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never",
|
||||
"postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts && opencollective-postinstall",
|
||||
"start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer",
|
||||
"start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only ./src/main/main.ts",
|
||||
"start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
|
||||
"start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
|
||||
"test": "jest",
|
||||
"publish": "npm run build && electron-builder -c.win.certificateSha1=842a817a51e2a1d360fcd62f54bf5f9193e919e1 --publish always --win --x64"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"cross-env NODE_ENV=development eslint --cache"
|
||||
],
|
||||
"*.json,.{eslintrc,prettierrc}": [
|
||||
"prettier --ignore-path .eslintignore --parser json --write"
|
||||
],
|
||||
"*.{css,scss}": [
|
||||
"prettier --ignore-path .eslintignore --single-quote --write"
|
||||
],
|
||||
"*.{html,md,yml}": [
|
||||
"prettier --ignore-path .eslintignore --single-quote --write"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"extraResources": [
|
||||
"./assets/**"
|
||||
],
|
||||
"productName": "BSManager",
|
||||
"appId": "org.erb.BSManager",
|
||||
"asarUnpack": "**\\*.{node,dll}",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"node_modules",
|
||||
"package.json"
|
||||
],
|
||||
"afterSign": ".erb/scripts/notarize.js",
|
||||
"mac": {
|
||||
"target": {
|
||||
"target": "default",
|
||||
"arch": [
|
||||
"arm64",
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
"type": "distribution",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "assets/entitlements.mac.plist",
|
||||
"entitlementsInherit": "assets/entitlements.mac.plist",
|
||||
"gatekeeperAssess": false
|
||||
},
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"nsis-web"
|
||||
],
|
||||
"icon": "assets/favicon.ico"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
"directories": {
|
||||
"app": "release/app",
|
||||
"buildResources": "assets",
|
||||
"output": "release/build"
|
||||
},
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"owner": "Zagrios"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Zagrios/bs-manager.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Zagrios",
|
||||
"email": "peroz.mathieu@gmail.com",
|
||||
"url": "https://github.com/Zagrios"
|
||||
},
|
||||
"contributors": [],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Zagrios/bs-manager/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"downgrade",
|
||||
"launcher-application",
|
||||
"beatsaber",
|
||||
"beat-saber"
|
||||
"name": "bs-manager",
|
||||
"description": "Manage maps, mods and more for Beat Saber",
|
||||
"scripts": {
|
||||
"clean": "ts-node ./.erb/scripts/clean.js dist",
|
||||
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\"",
|
||||
"build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts",
|
||||
"build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts",
|
||||
"rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
|
||||
"lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"package": "npm run clean dist && npm run build && electron-builder build --publish never",
|
||||
"postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts && opencollective-postinstall",
|
||||
"start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer",
|
||||
"start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only ./src/main/main.ts",
|
||||
"start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
|
||||
"start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
|
||||
"test": "jest",
|
||||
"publish": "npm run clean dist && npm run build && electron-builder -c.win.certificateSha1=842a817a51e2a1d360fcd62f54bf5f9193e919e1 --publish always --win --x64",
|
||||
"e2e-test": "npm run package && xvfb-maybe npx playwright test --workers=1",
|
||||
"e2e-test-ci": "xvfb-maybe npx playwright test --workers=1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"cross-env NODE_ENV=development eslint --cache"
|
||||
],
|
||||
"homepage": "https://github.com/Zagrios/bs-manager#readme",
|
||||
"jest": {
|
||||
"testURL": "http://localhost/",
|
||||
"testEnvironment": "jsdom",
|
||||
"transform": {
|
||||
"\\.(ts|tsx|js|jsx)$": "ts-jest"
|
||||
},
|
||||
"moduleNameMapper": {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/.erb/mocks/fileMock.js",
|
||||
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"jsx",
|
||||
"ts",
|
||||
"tsx",
|
||||
"json"
|
||||
],
|
||||
"moduleDirectories": [
|
||||
"node_modules",
|
||||
"src"
|
||||
],
|
||||
"testPathIgnorePatterns": [
|
||||
"release/app/dist"
|
||||
],
|
||||
"setupFiles": [
|
||||
"./.erb/scripts/check-build-exists.ts"
|
||||
"*.json,.{eslintrc,prettierrc}": [
|
||||
"prettier --ignore-path .eslintignore --parser json --write"
|
||||
],
|
||||
"*.{css,scss}": [
|
||||
"prettier --ignore-path .eslintignore --single-quote --write"
|
||||
],
|
||||
"*.{html,md,yml}": [
|
||||
"prettier --ignore-path .eslintignore --single-quote --write"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"extraResources": [
|
||||
"./assets/**"
|
||||
],
|
||||
"productName": "BSManager",
|
||||
"appId": "org.erb.BSManager",
|
||||
"asarUnpack": "**\\*.{node,dll}",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"node_modules",
|
||||
"package.json"
|
||||
],
|
||||
"afterSign": ".erb/scripts/notarize.js",
|
||||
"mac": {
|
||||
"target": {
|
||||
"target": "default",
|
||||
"arch": [
|
||||
"arm64",
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
"type": "distribution",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "assets/entitlements.mac.plist",
|
||||
"entitlementsInherit": "assets/entitlements.mac.plist",
|
||||
"gatekeeperAssess": false
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "^3.2.13",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.5",
|
||||
"@teamsupercell/typings-for-css-modules-loader": "^2.5.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@types/archiver": "^5.3.1",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/node-fetch": "^2.6.3",
|
||||
"@types/react": "^18.0.33",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-outside-click-handler": "^1.3.1",
|
||||
"@types/react-test-renderer": "^17.0.2",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/recursive-readdir": "^2.2.1",
|
||||
"@types/terser-webpack-plugin": "^5.0.4",
|
||||
"@types/to-ico": "^1.1.1",
|
||||
"@types/use-double-click": "^1.0.1",
|
||||
"@types/webpack-bundle-analyzer": "^4.4.2",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.34.0",
|
||||
"@typescript-eslint/parser": "^5.34.0",
|
||||
"autoprefixer": "^10.4.8",
|
||||
"browserslist-config-erb": "^0.0.3",
|
||||
"chalk": "^4.1.2",
|
||||
"concurrently": "^7.2.2",
|
||||
"core-js": "^3.24.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^4.1.0",
|
||||
"detect-port": "^1.3.0",
|
||||
"electron": "^26.2.1",
|
||||
"electron-builder": "^24.6.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-notarize": "^1.2.1",
|
||||
"electronmon": "^2.0.2",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-erb": "^4.0.3",
|
||||
"eslint-import-resolver-typescript": "^2.7.1",
|
||||
"eslint-import-resolver-webpack": "^0.13.2",
|
||||
"eslint-plugin-compat": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jest": "^26.8.7",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.30.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^27.5.1",
|
||||
"lint-staged": "^12.5.0",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"opencollective-postinstall": "^2.0.3",
|
||||
"postcss": "^8.4.16",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"prettier": "^2.7.1",
|
||||
"ps-scrollbar-tailwind": "0.0.1",
|
||||
"react-refresh": "^0.12.0",
|
||||
"react-refresh-typescript": "^2.0.7",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.54.5",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwind-scrollbar": "^2.0.1",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"terser-webpack-plugin": "^5.3.5",
|
||||
"ts-jest": "^27.1.5",
|
||||
"ts-loader": "^9.3.0",
|
||||
"ts-node": "^10.8.2",
|
||||
"typescript": "^4.7.4",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-bundle-analyzer": "^4.6.1",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.10.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@node-steam/vdf": "^2.2.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"archiver": "^6.0.1",
|
||||
"color": "^4.2.3",
|
||||
"dateformat": "^5.0.3",
|
||||
"dot-prop": "^8.0.2",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^6.1.4",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"framer-motion": "^10.16.4",
|
||||
"fs-extra": "^11.1.1",
|
||||
"history": "^5.3.0",
|
||||
"jszip": "^3.10.1",
|
||||
"md5-file": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"ps-list": "^7.2.0",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-range": "^1.8.14",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.12",
|
||||
"react-window": "^1.8.8",
|
||||
"recursive-readdir": "^2.2.3",
|
||||
"regedit": "^5.1.2",
|
||||
"rfdc": "^1.3.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.5.4",
|
||||
"striptags": "^4.0.0-alpha.4",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"tailwindcss-scoped-groups": "^2.0.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"to-ico": "^1.1.5",
|
||||
"use-double-click": "^1.0.5",
|
||||
"use-fit-text": "^2.4.0"
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"nsis-web"
|
||||
],
|
||||
"icon": "assets/favicon.ico"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": ">=14.x",
|
||||
"npm": ">=7.x"
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
},
|
||||
"collective": {
|
||||
"url": "https://www.patreon.com/bsmanager"
|
||||
"directories": {
|
||||
"app": "release/app",
|
||||
"buildResources": "assets",
|
||||
"output": "release/build"
|
||||
},
|
||||
"browserslist": [],
|
||||
"prettier": {
|
||||
"printWidth": 1000,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "avoid",
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"tabWidth": 4,
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
".prettierrc",
|
||||
".eslintrc"
|
||||
],
|
||||
"options": {
|
||||
"parser": "json"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"electronmon": {
|
||||
"patterns": [
|
||||
"!src/__tests__/**",
|
||||
"!release/**",
|
||||
"!assets/**"
|
||||
],
|
||||
"logLevel": "quiet"
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"owner": "Zagrios"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Zagrios/bs-manager.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Zagrios",
|
||||
"email": "peroz.mathieu@gmail.com",
|
||||
"url": "https://github.com/Zagrios"
|
||||
},
|
||||
"contributors": [],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Zagrios/bs-manager/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"downgrade",
|
||||
"launcher-application",
|
||||
"beatsaber",
|
||||
"beat-saber"
|
||||
],
|
||||
"homepage": "https://github.com/Zagrios/bs-manager#readme",
|
||||
"jest": {
|
||||
"testURL": "http://localhost/",
|
||||
"testEnvironment": "jsdom",
|
||||
"transform": {
|
||||
"\\.(ts|tsx|js|jsx)$": "ts-jest"
|
||||
},
|
||||
"moduleNameMapper": {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/.erb/mocks/fileMock.js",
|
||||
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"jsx",
|
||||
"ts",
|
||||
"tsx",
|
||||
"json"
|
||||
],
|
||||
"moduleDirectories": [
|
||||
"node_modules",
|
||||
"src"
|
||||
],
|
||||
"testPathIgnorePatterns": [
|
||||
"release/app/dist"
|
||||
],
|
||||
"setupFiles": [
|
||||
"./.erb/scripts/check-build-exists.ts"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "^3.2.13",
|
||||
"@playwright/test": "^1.38.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.5",
|
||||
"@teamsupercell/typings-for-css-modules-loader": "^2.5.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@types/archiver": "^5.3.1",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/node-fetch": "^2.6.3",
|
||||
"@types/react": "^18.0.33",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-outside-click-handler": "^1.3.1",
|
||||
"@types/react-test-renderer": "^17.0.2",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/recursive-readdir": "^2.2.1",
|
||||
"@types/terser-webpack-plugin": "^5.0.4",
|
||||
"@types/to-ico": "^1.1.1",
|
||||
"@types/use-double-click": "^1.0.1",
|
||||
"@types/webpack-bundle-analyzer": "^4.4.2",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.34.0",
|
||||
"@typescript-eslint/parser": "^5.34.0",
|
||||
"asar": "^3.2.0",
|
||||
"autoprefixer": "^10.4.8",
|
||||
"browserslist-config-erb": "^0.0.3",
|
||||
"chalk": "^4.1.2",
|
||||
"concurrently": "^7.2.2",
|
||||
"core-js": "^3.24.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^4.1.0",
|
||||
"detect-port": "^1.3.0",
|
||||
"electron": "^26.2.1",
|
||||
"electron-builder": "^24.6.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-notarize": "^1.2.1",
|
||||
"electronmon": "^2.0.2",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-erb": "^4.0.3",
|
||||
"eslint-import-resolver-typescript": "^2.7.1",
|
||||
"eslint-import-resolver-webpack": "^0.13.2",
|
||||
"eslint-plugin-compat": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jest": "^26.8.7",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.30.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^27.5.1",
|
||||
"lint-staged": "^12.5.0",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"opencollective-postinstall": "^2.0.3",
|
||||
"playwright": "^1.38.1",
|
||||
"playwright-core": "^1.38.1",
|
||||
"postcss": "^8.4.16",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"prettier": "^2.7.1",
|
||||
"ps-scrollbar-tailwind": "0.0.1",
|
||||
"react-refresh": "^0.12.0",
|
||||
"react-refresh-typescript": "^2.0.7",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.54.5",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwind-scrollbar": "^2.0.1",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"terser-webpack-plugin": "^5.3.5",
|
||||
"ts-jest": "^27.1.5",
|
||||
"ts-loader": "^9.3.0",
|
||||
"ts-node": "^10.8.2",
|
||||
"typescript": "^4.7.4",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-bundle-analyzer": "^4.6.1",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.10.0",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"xvfb-maybe": "^0.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@node-steam/vdf": "^2.2.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"archiver": "^6.0.1",
|
||||
"color": "^4.2.3",
|
||||
"dateformat": "^5.0.3",
|
||||
"dot-prop": "^8.0.2",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^6.1.4",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"framer-motion": "^10.16.4",
|
||||
"fs-extra": "^11.1.1",
|
||||
"history": "^5.3.0",
|
||||
"jszip": "^3.10.1",
|
||||
"md5-file": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"ps-list": "^7.2.0",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-range": "^1.8.14",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.12",
|
||||
"react-window": "^1.8.8",
|
||||
"recursive-readdir": "^2.2.3",
|
||||
"regedit": "^5.1.2",
|
||||
"rfdc": "^1.3.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.5.4",
|
||||
"striptags": "^4.0.0-alpha.4",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"tailwindcss-scoped-groups": "^2.0.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"to-ico": "^1.1.5",
|
||||
"use-double-click": "^1.0.5",
|
||||
"use-fit-text": "^2.4.0"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": ">=14.x",
|
||||
"npm": ">=7.x"
|
||||
},
|
||||
"collective": {
|
||||
"url": "https://www.patreon.com/bsmanager"
|
||||
},
|
||||
"browserslist": [],
|
||||
"prettier": {
|
||||
"printWidth": 1000,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "avoid",
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"tabWidth": 4,
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
".prettierrc",
|
||||
".eslintrc"
|
||||
],
|
||||
"options": {
|
||||
"parser": "json"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"electronmon": {
|
||||
"patterns": [
|
||||
"!src/__tests__/**",
|
||||
"!release/**",
|
||||
"!assets/**"
|
||||
],
|
||||
"logLevel": "quiet"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./e2e-tests",
|
||||
testMatch: "**/*.test.ts",
|
||||
timeout: 200000,
|
||||
fullyParallel: true,
|
||||
retries: 3,
|
||||
});
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.1-alpha.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bs-manager",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.1-alpha.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -30,7 +30,7 @@ export const AvailableVersionItem = memo(function AvailableVersionItem({version,
|
||||
const formatedDate = (() => dateFormat(+version.ReleaseDate * 1000, "ddd. d mmm yyyy"))();
|
||||
|
||||
return (
|
||||
<motion.li className="group relative w-72 h-60 transition-transform active:scale-[.98]" onClick={onClick} onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)}>
|
||||
<motion.li id={`version-item-${version.BSManifest}`} className="group relative w-72 h-60 transition-transform active:scale-[.98]" onClick={onClick} onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)}>
|
||||
<GlowEffect visible={hovered || selected} className="absolute" />
|
||||
<div className={`relative flex flex-col overflow-hidden rounded-md w-72 h-60 cursor-pointer group-hover:shadow-none duration-300 bg-light-main-color-2 dark:bg-main-color-2 ${!selected && "shadow-lg shadow-gray-900"}`}>
|
||||
<BsmImage image={version.ReleaseImg ? version.ReleaseImg : defaultImage} errorImage={defaultImage} placeholder={defaultImage} className="absolute top-0 right-0 w-full h-full opacity-40 blur-xl object-cover" loading="lazy" />
|
||||
|
||||
@@ -20,7 +20,7 @@ export function AvailableVersionsSlide({ versions }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<ol className="w-full flex items-start justify-center gap-6 shrink-0 content-start flex-wrap p-4 overflow-x-hidden overflow-y-scroll scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900">
|
||||
<ol id={`version-slide-${versions}`} className="w-full flex items-start justify-center gap-6 shrink-0 content-start flex-wrap p-4 overflow-x-hidden overflow-y-scroll scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900">
|
||||
{versions.map(version => (
|
||||
<AvailableVersionItem key={version.BSManifest} version={version} selected={equal(version, context.selectedVersion)} onClick={() => setSelectedVersion(version)}/>
|
||||
))}
|
||||
|
||||
@@ -26,7 +26,7 @@ export function AvailableVersionsSlider() {
|
||||
|
||||
return (
|
||||
<div className="w-full h-fit max-h-full flex flex-col items-center grow min-h-0 gap-3">
|
||||
<TabNavBar tabIndex={yearIndex} tabsText={availableYears} onTabChange={setSelectedYear} />
|
||||
<TabNavBar id="version-years-tab-bar" tabIndex={yearIndex} tabsText={availableYears} onTabChange={setSelectedYear} />
|
||||
<ol className="w-full min-h-0 flex transition-transform duration-300" style={{ transform: `translate(${-(yearIndex * 100)}%, 0)` }}>
|
||||
{availableYears.map(year => (
|
||||
<AvailableVersionsSlide key={year} versions={getVersionOfYear(year)} />
|
||||
|
||||
@@ -31,7 +31,7 @@ export const BsManagerIcon = memo(({ className }: { className?: string }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div className="cursor-pointer" whileTap={{ rotate: -10 }} variants={transitions} animate={playing && bpm > 0 ? "playing" : "idle"} onClick={clickAction}>
|
||||
<motion.div id="bsm-logo" className="cursor-pointer" whileTap={{ rotate: -10 }} variants={transitions} animate={playing && bpm > 0 ? "playing" : "idle"} onClick={clickAction}>
|
||||
<svg className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
|
||||
<g fill={firstColor}>
|
||||
<path d="M626.75,485.91l20.82,351.94a35.21,35.21,0,0,1-15.49,31.36L487.37,965.35l-.06,0A35.56,35.56,0,0,1,476,970.12a34.85,34.85,0,0,1-23.51-2.87L136.58,809.9a115.45,115.45,0,0,1-63.75-96.49L52,361.46a35.26,35.26,0,0,1,15.23-31.21l145.05-96.36a35.72,35.72,0,0,1,11.27-4.7,34.87,34.87,0,0,1,23.51,2.87L563,389.42a115.41,115.41,0,0,1,63.75,96.49Z" />
|
||||
|
||||
@@ -7,13 +7,14 @@ type Props = {
|
||||
progress?: number;
|
||||
isActive?: boolean;
|
||||
onCancel?: (e: React.MouseEvent) => void;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export function NavBarItem({ progress, isDownloading, children, isActive, onCancel }: Props) {
|
||||
export function NavBarItem({ progress, isDownloading, children, isActive, onCancel, id }: Props) {
|
||||
const { firstColor, secondColor } = useThemeColor();
|
||||
|
||||
return (
|
||||
<li className={`outline-none relative p-[1px] overflow-hidden rounded-xl flex justify-center items-center mb-1 ${isDownloading && "nav-item-download"} active:translate-y-[1px]`}>
|
||||
<li id={id} className={`outline-none relative p-[1px] overflow-hidden rounded-xl flex justify-center items-center mb-1 ${isDownloading && "nav-item-download"} active:translate-y-[1px]`}>
|
||||
{isDownloading && <div className="download-progress absolute top-0 w-full h-full" style={{ transform: `translate(${-(100 - progress)}%, 0)`, background: `linear-gradient(90deg, ${firstColor}, ${secondColor}, ${firstColor}, ${secondColor})` }} />}
|
||||
<div className={`wrapper z-[1] px-1 py-[3px] w-full rounded-xl ${isDownloading && "bg-white dark:bg-black"} ${!isDownloading && "hover:bg-light-main-color-3 dark:hover:bg-main-color-3"} ${isActive && !isDownloading && "bg-light-main-color-3 dark:bg-main-color-3"}`}>
|
||||
{children}
|
||||
|
||||
@@ -16,7 +16,7 @@ export function SharedNavBarItem() {
|
||||
const color = useThemeColor("first-color");
|
||||
|
||||
return (
|
||||
<NavBarItem isActive={route === "/shared"}>
|
||||
<NavBarItem id="shared-contents-btn" isActive={route === "/shared"}>
|
||||
<Tippy content={t("nav-bar.shared.tooltip")} className="font-bold !bg-neutral-900" placement="right-end" arrow={false} duration={[100, 0]} animation="shift-away-subtle">
|
||||
<Link to="shared" className="w-full flex items-center justify-start content-center max-w-full h-[30px]">
|
||||
<BsmIcon className="w-[19px] h-[19px] mr-[5px] shrink-0 brightness-125" icon="link" style={{ color }} />
|
||||
|
||||
@@ -48,12 +48,12 @@ export function NavBar() {
|
||||
<NavBarSpliter />
|
||||
<div className="w-full pb-2 flex flex-col items-center content-center justify-start gap-1">
|
||||
<Tippy placement="right" content={t("nav-bar.add-version")} className="!bg-neutral-900" arrow={false}>
|
||||
<Link className="rounded-md w-9 h-9 flex justify-center items-center hover:bg-light-main-color-3 dark:hover:bg-main-color-3" to="blah">
|
||||
<Link id="add-version-btn" className="rounded-md w-9 h-9 flex justify-center items-center hover:bg-light-main-color-3 dark:hover:bg-main-color-3" to="blah">
|
||||
<BsmIcon icon="add" className="text-blue-500 h-[34px]" style={{ color }} />
|
||||
</Link>
|
||||
</Tippy>
|
||||
<Tippy placement="right" content={t("nav-bar.settings")} className="!bg-neutral-900" arrow={false}>
|
||||
<Link className="rounded-md w-9 h-9 flex justify-center items-center hover:bg-light-main-color-3 dark:hover:bg-main-color-3" to="settings">
|
||||
<Link id="settings-btn" className="rounded-md w-9 h-9 flex justify-center items-center hover:bg-light-main-color-3 dark:hover:bg-main-color-3" to="settings">
|
||||
<BsmIcon icon="settings" className="text-blue-500 h-7" style={{ color }} />
|
||||
</Link>
|
||||
</Tippy>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { getCorrectTextColor } from "renderer/helpers/correct-text-color";
|
||||
type BsmButtonType = "primary" | "secondary" | "success" | "cancel" | "error";
|
||||
|
||||
type Props = {
|
||||
id?: string;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
imgClassName?: string;
|
||||
@@ -29,7 +30,7 @@ type Props = {
|
||||
textClassName?: string;
|
||||
};
|
||||
|
||||
export function BsmButton({ className, style, imgClassName, iconClassName, icon, image, text, type, active, withBar = true, disabled, onClickOutside, onClick, typeColor, color, title, iconColor, textClassName }: Props) {
|
||||
export function BsmButton({ id, className, style, imgClassName, iconClassName, icon, image, text, type, active, withBar = true, disabled, onClickOutside, onClick, typeColor, color, title, iconColor, textClassName }: Props) {
|
||||
const t = useTranslation();
|
||||
const { firstColor, secondColor } = useThemeColor();
|
||||
const ref = useRef(null);
|
||||
@@ -72,7 +73,7 @@ export function BsmButton({ className, style, imgClassName, iconClassName, icon,
|
||||
const handleClick = (e: MouseEvent<HTMLDivElement>) => !disabled && onClick?.(e);
|
||||
|
||||
return (
|
||||
<div ref={ref} onClick={handleClick} title={t(title)} className={`${className} overflow-hidden group ${!withBar && !disabled && (!!typeColor || !!color) && "hover:brightness-[1.15]"} ${disabled ? "brightness-75 cursor-not-allowed" : "cursor-pointer"} ${renderTypeColor}`} style={{ ...style, backgroundColor: primaryColor || color }}>
|
||||
<div id={id} ref={ref} onClick={handleClick} title={t(title)} className={`${className} overflow-hidden group ${!withBar && !disabled && (!!typeColor || !!color) && "hover:brightness-[1.15]"} ${disabled ? "brightness-75 cursor-not-allowed" : "cursor-pointer"} ${renderTypeColor}`} style={{ ...style, backgroundColor: primaryColor || color }}>
|
||||
{image && <BsmImage image={image} className={imgClassName} />}
|
||||
{icon && <BsmIcon icon={icon} className={iconClassName ?? "h-full w-full text-gray-800 dark:text-white"} style={{ color: iconColor || textColor }} />}
|
||||
{text &&
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
type Props = {
|
||||
id?: string,
|
||||
tabIndex: number;
|
||||
tabsText: string[];
|
||||
onTabChange: (index: number) => void;
|
||||
@@ -21,7 +22,7 @@ export function TabNavBar(props: Props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className={`relative h-8 shrink-0 cursor-pointer rounded-md overflow-hidden shadow-md shadow-black bg-light-main-color-2 dark:bg-main-color-2 ${props.className}`}>
|
||||
<nav id={props.id} className={`relative h-8 shrink-0 cursor-pointer rounded-md overflow-hidden shadow-md shadow-black bg-light-main-color-2 dark:bg-main-color-2 ${props.className}`}>
|
||||
<div className="absolute w-full h-1 bottom-0" style={{ color: secondColor }}>
|
||||
<span className="absolute h-full w-full bg-current brightness-50" />
|
||||
<span className="absolute h-full block bg-current transition-transform duration-300 shadow-center shadow-current" style={{ transform: `translate(${currentIndex * 100}%, 0)`, width: `calc(100% / ${props.tabsText.length})` }} />
|
||||
|
||||
@@ -89,7 +89,7 @@ export function AvailableVersionsList() {
|
||||
<AnimatePresence>
|
||||
{selectedVersion && !downloading && (
|
||||
<motion.div initial={{ y: "150%" }} animate={{ y: "0%" }} exit={{ y: "150%" }} className="absolute bottom-5" onClick={startDownload}>
|
||||
<BsmButton text="misc.download" className="relative text-gray-800 dark:text-gray-100 rounded-md text-3xl font-bold italic tracking-wide px-3 pb-2 pt-1 shadow-md shadow-black" />
|
||||
<BsmButton id="download-version-btn" text="misc.download" className="relative text-gray-800 dark:text-gray-100 rounded-md text-3xl font-bold italic tracking-wide px-3 pb-2 pt-1 shadow-md shadow-black" />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -19,3 +19,9 @@ export async function allSettled<T>(promises: Promise<T>[], options?: AllSettled
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export async function pause(ms: number): Promise<void>{
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), ms);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user