mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c140d30e90 | |||
| a6826cc899 | |||
| 1266da74d8 | |||
| bb05b14236 | |||
| 4f475d4224 | |||
| e55c146824 | |||
| 696eec5f2b | |||
| 2699b4a7cd | |||
| 36749a39cc | |||
| e13c641ba3 | |||
| 5385797e06 | |||
| b13ee858b6 | |||
| 7c792d6520 | |||
| d03ae069ca | |||
| 3bf2d8814f | |||
| 0e9f1fc8a1 | |||
| d3e65f9f92 | |||
| 5909ac4373 |
@@ -0,0 +1,48 @@
|
||||
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"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright deps
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- 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
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
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,243 @@
|
||||
/**
|
||||
* 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],
|
||||
env: {
|
||||
...process.env,
|
||||
E2E_BUILD: "true",
|
||||
},
|
||||
executablePath: appInfo.executable,
|
||||
recordVideo: {
|
||||
dir: getRecordingPath(appInfo.platform),
|
||||
size: {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const appWindow = await electronApp.firstWindow();
|
||||
|
||||
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,55 @@
|
||||
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 = "8948172000430595334";
|
||||
await addVersionPanel.selectYear("2021");
|
||||
await pause(1000);
|
||||
|
||||
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(3000);
|
||||
|
||||
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
+226
-1
@@ -55,6 +55,7 @@
|
||||
},
|
||||
"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",
|
||||
@@ -81,6 +82,7 @@
|
||||
"@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",
|
||||
@@ -114,6 +116,8 @@
|
||||
"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",
|
||||
@@ -137,7 +141,8 @@
|
||||
"webpack-bundle-analyzer": "^4.6.1",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.10.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
"webpack-merge": "^5.8.0",
|
||||
"xvfb-maybe": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
@@ -2015,6 +2020,21 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz",
|
||||
"integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright": "1.38.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.5.tgz",
|
||||
@@ -4123,6 +4143,48 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asar": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz",
|
||||
"integrity": "sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==",
|
||||
"deprecated": "Please use @electron/asar moving forward. There is no API change, just a package name change",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"commander": "^5.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"minimatch": "^3.0.4"
|
||||
},
|
||||
"bin": {
|
||||
"asar": "bin/asar.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@types/glob": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/asar/node_modules/@types/glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/minimatch": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/asar/node_modules/commander": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
|
||||
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
@@ -14451,6 +14513,36 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz",
|
||||
"integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright-core": "1.38.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz",
|
||||
"integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/plist": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
||||
@@ -19081,6 +19173,46 @@
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/xvfb-maybe": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/xvfb-maybe/-/xvfb-maybe-0.2.1.tgz",
|
||||
"integrity": "sha512-9IyRz3l6Qyhl6LvnGRF5jMPB4oBEepQnuzvVAFTynP6ACLLSevqigICJ9d/+ofl29m2daeaVBChnPYUnaeJ7yA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^2.2.0",
|
||||
"which": "^1.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"xvfb-maybe": "src/xvfb-maybe.js"
|
||||
}
|
||||
},
|
||||
"node_modules/xvfb-maybe/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xvfb-maybe/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/xvfb-maybe/node_modules/which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"which": "bin/which"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
@@ -20675,6 +20807,15 @@
|
||||
"rimraf": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz",
|
||||
"integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"playwright": "1.38.1"
|
||||
}
|
||||
},
|
||||
"@pmmmwh/react-refresh-webpack-plugin": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.5.tgz",
|
||||
@@ -22400,6 +22541,38 @@
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
"integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="
|
||||
},
|
||||
"asar": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz",
|
||||
"integrity": "sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/glob": "^7.1.1",
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"commander": "^5.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"minimatch": "^3.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/minimatch": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
|
||||
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
@@ -30236,6 +30409,22 @@
|
||||
"find-up": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"playwright": {
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz",
|
||||
"integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.38.1"
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.38.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz",
|
||||
"integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==",
|
||||
"dev": true
|
||||
},
|
||||
"plist": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
||||
@@ -33600,6 +33789,42 @@
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||
},
|
||||
"xvfb-maybe": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/xvfb-maybe/-/xvfb-maybe-0.2.1.tgz",
|
||||
"integrity": "sha512-9IyRz3l6Qyhl6LvnGRF5jMPB4oBEepQnuzvVAFTynP6ACLLSevqigICJ9d/+ofl29m2daeaVBChnPYUnaeJ7yA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^2.2.0",
|
||||
"which": "^1.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
||||
+9
-2
@@ -14,7 +14,9 @@
|
||||
"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"
|
||||
"publish": "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}": [
|
||||
@@ -149,6 +151,7 @@
|
||||
},
|
||||
"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",
|
||||
@@ -175,6 +178,7 @@
|
||||
"@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",
|
||||
@@ -208,6 +212,8 @@
|
||||
"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",
|
||||
@@ -231,7 +237,8 @@
|
||||
"webpack-bundle-analyzer": "^4.6.1",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.10.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
"webpack-merge": "^5.8.0",
|
||||
"xvfb-maybe": "^0.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/fuses": "^1.6.2",
|
||||
|
||||
@@ -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: 1,
|
||||
});
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bs-manager",
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
+3
-2
@@ -23,6 +23,7 @@ import { LivShortcut } from "./services/liv/liv-shortcut.service";
|
||||
import { SteamLauncherService } from "./services/bs-launcher/steam-launcher.service";
|
||||
|
||||
const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true";
|
||||
const isE2E = process.env.E2E_BUILD === "true";
|
||||
|
||||
log.transports.file.level = "info";
|
||||
log.transports.file.resolvePath = () => {
|
||||
@@ -54,7 +55,7 @@ const installExtensions = async () => {
|
||||
.catch(log.error);
|
||||
};
|
||||
|
||||
const createWindow = async (window: AppWindow = "launcher.html") => {
|
||||
const createWindow = async (window: AppWindow) => {
|
||||
if (isDebug) {
|
||||
await installExtensions();
|
||||
}
|
||||
@@ -98,7 +99,7 @@ if (!gotTheLock) {
|
||||
const deepLink = process.argv.find(arg => DeepLinkService.getInstance().isDeepLink(arg));
|
||||
|
||||
if (!deepLink) {
|
||||
createWindow();
|
||||
createWindow(!isE2E ? "launcher.html" : "index.html");
|
||||
} else {
|
||||
DeepLinkService.getInstance().dispatchLinkOpened(deepLink);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export const AvailableVersionItem = memo(function AvailableVersionItem({version,
|
||||
const formatedDate = useConstant(() => dateFormat(+version.ReleaseDate * 1000, "ddd. d mmm yyyy"));
|
||||
|
||||
return (
|
||||
<motion.li className="group relative w-72 h-60 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 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"}`}>
|
||||
{version.recommended && (
|
||||
|
||||
@@ -29,7 +29,7 @@ export function AvailableVersionsSlide({ versions }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<ol className="w-full flex items-start justify-center gap-6 shrink-0 content-start flex-wrap px-3.5 py-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 px-3.5 py-4 overflow-x-hidden overflow-y-scroll scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900">
|
||||
{getVersions().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 &&
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { LaserSlider } from "./laser-slider.component";
|
||||
|
||||
type Props = {
|
||||
id?: string,
|
||||
tabIndex: number;
|
||||
tabsText: string[];
|
||||
onTabChange: (index: number) => void;
|
||||
@@ -22,7 +23,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}`}>
|
||||
<LaserSlider className="absolute w-full h-1 bottom-0" mode="horizontal" color={secondColor} nbSteps={props.tabsText.length} step={currentIndex} />
|
||||
<ul className="grid" style={{ gridTemplateColumns: `repeat(${props.tabsText.length}, minmax(0, 1fr))` }}>
|
||||
{props.tabsText.map((text, index) =>
|
||||
|
||||
@@ -56,7 +56,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