WIP settings panel, added #app[window-state]

This commit is contained in:
booploops 2022-06-28 01:30:39 -07:00
parent c54b1ac363
commit f71ca88298
14 changed files with 2762 additions and 1019 deletions

View file

@ -1,9 +1,9 @@
import {join} from "path";
import {app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen} from "electron";
import { join } from "path";
import { app, BrowserWindow as bw, ipcMain, ShareMenu, shell, screen } from "electron";
import * as windowStateKeeper from "electron-window-state";
import * as express from "express";
import * as getPort from "get-port";
import {search} from "youtube-search-without-api-key";
import { search } from "youtube-search-without-api-key";
import {
existsSync,
rmSync,
@ -16,14 +16,14 @@ import {
rmdirSync,
lstatSync,
} from "fs";
import {Stream} from "stream";
import {networkInterfaces} from "os";
import { Stream } from "stream";
import { networkInterfaces } from "os";
import * as mm from 'music-metadata';
import fetch from 'electron-fetch'
import {wsapi} from "./wsapi";
import {utils} from './utils';
import {Plugins} from "./plugins";
import {watch} from "chokidar";
import { wsapi } from "./wsapi";
import { utils } from './utils';
import { Plugins } from "./plugins";
import { watch } from "chokidar";
import * as os from "os";
import wallpaper from "wallpaper";
import * as AdmZip from "adm-zip";
@ -123,6 +123,7 @@ export class BrowserWindow {
"components/artist-chip",
"components/hello-world",
"components/inline-collection-list",
"components/settings-window",
],
appRoutes: [
{
@ -201,12 +202,12 @@ export class BrowserWindow {
component: `<cider-browse :data="browsepage"></cider-browse>`,
condition: `page == 'browse'`,
onEnter: ``
},{
}, {
page: "groupings",
component: `<cider-groupings :data="browsepage"></cider-groupings>`,
condition: `page == 'groupings'`,
onEnter: ``
},{
}, {
page: "charts",
component: `<cider-charts :data="browsepage"></cider-charts>`,
condition: `page == 'charts'`,
@ -292,7 +293,7 @@ export class BrowserWindow {
show: false,
// backgroundColor: "#1E1E1E",
titleBarStyle: 'hidden',
trafficLightPosition: {x: 15, y: 20},
trafficLightPosition: { x: 15, y: 20 },
webPreferences: {
experimentalFeatures: true,
nodeIntegration: true,
@ -358,7 +359,7 @@ export class BrowserWindow {
* @yields {object} Electron browser window
*/
async createWindow(): Promise<Electron.BrowserWindow> {
this.clientPort = await getPort({port: 9000});
this.clientPort = await getPort({ port: 9000 });
BrowserWindow.verifyFiles();
this.StartWatcher(utils.getPath('themes'));
@ -495,9 +496,9 @@ export class BrowserWindow {
app.get("/cideraudio/impulses/:file", (req, res) => {
const impulseExternals = join(utils.getPath("externals"), "/impulses/")
const impulseFile = join(impulseExternals, req.params.file)
if(existsSync(impulseFile)) {
if (existsSync(impulseFile)) {
res.sendFile(impulseFile)
}else{
} else {
res.sendFile(join(utils.getPath('srcPath'), "./renderer/audio/impulses/" + req.params.file))
}
})
@ -550,11 +551,13 @@ export class BrowserWindow {
app.get("/ciderlocal/:songs", (req, res) => {
const audio = atob(req.params.songs.replace(/_/g, '/').replace(/-/g, '+'));
console.log('auss', audio)
let data = {data:
this.localSongs.filter((f: any) => audio.split(',').includes(f.id))};
let data = {
data:
this.localSongs.filter((f: any) => audio.split(',').includes(f.id))
};
res.send(data);
});
app.get("/themes/:theme/*", (req: { params: { theme: string, 0: string } }, res) => {
const theme = req.params.theme;
@ -638,7 +641,7 @@ export class BrowserWindow {
remote.use(express.static(join(utils.getPath('srcPath'), "./web-remote/")))
remote.set("views", join(utils.getPath('srcPath'), "./web-remote/views"));
remote.set("view engine", "ejs");
getPort({port: 6942}).then((port: number) => {
getPort({ port: 6942 }).then((port: number) => {
this.remotePort = port;
// Start Remote Discovery
this.broadcastRemote()
@ -671,11 +674,11 @@ export class BrowserWindow {
});
} else if (details.url.includes("ciderlocal")) {
let text = details.url.toString().includes('ids=') ? decodeURIComponent(details.url.toString()).split("?ids=")[1] : decodeURIComponent(details.url.toString().substring(details.url.toString().lastIndexOf('/') + 1));
console.log('localurl',text)
console.log('localurl', text)
callback({
redirectURL: `http://localhost:${this.clientPort}/ciderlocal/${Buffer.from(text).toString('base64url')}`,
});
}else {
} else {
callback({
cancel: false,
});
@ -717,7 +720,7 @@ export class BrowserWindow {
'KHTML, like Gecko) Mobile/17D50 UCBrowser/12.8.2.1268 Mobile AliApp(TUnionSDK/0.1.20.3) '
details.requestHeaders['Referer'] = "https://y.qq.com/portal/player.html"
}
callback({requestHeaders: details.requestHeaders});
callback({ requestHeaders: details.requestHeaders });
}
);
@ -774,7 +777,7 @@ export class BrowserWindow {
const Jimp = require("jimp")
const img = await Jimp.read(wpPath)
const blurAmount = args.blurAmount ?? 256
if(blurAmount) {
if (blurAmount) {
img.blur(blurAmount)
}
const screens = await screen.getAllDisplays()
@ -811,7 +814,7 @@ export class BrowserWindow {
}
// if path is directory, delete it
if (lstatSync(path).isDirectory()) {
await rmdirSync(path, {recursive: true});
await rmdirSync(path, { recursive: true });
} else {
// if path is file, delete it
await unlinkSync(path);
@ -842,7 +845,7 @@ export class BrowserWindow {
// remove WidevineCDM from appdata folder
const widevineCdmPath = join(app.getPath("userData"), "./WidevineCdm");
if (existsSync(widevineCdmPath)) {
rmSync(widevineCdmPath, {recursive: true, force: true})
rmSync(widevineCdmPath, { recursive: true, force: true })
}
// reinstall WidevineCDM
app.relaunch()
@ -1134,7 +1137,7 @@ export class BrowserWindow {
// Move window
ipcMain.on("windowmove", (_event, x, y) => {
BrowserWindow.win.setBounds({x, y});
BrowserWindow.win.setBounds({ x, y });
});
//Fullscreen
@ -1149,7 +1152,7 @@ export class BrowserWindow {
//Fullscreen
ipcMain.on('detachDT', (_event, _) => {
BrowserWindow.win.webContents.openDevTools({mode: 'detach'});
BrowserWindow.win.webContents.openDevTools({ mode: 'detach' });
})
ipcMain.handle('relaunchApp', (_event, _) => {
@ -1187,76 +1190,76 @@ export class BrowserWindow {
ipcMain.on("scanLibrary", async (event, folders) => {
async function getFiles(dir : any) {
async function getFiles(dir: any) {
const dirents = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(dirents.map((dirent: any) => {
const res = path.resolve(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
const res = path.resolve(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
}));
return Array.prototype.concat(...files);
}
if (folders == null || folders.length == null || folders.length == 0) folders = ["D:\\Music"]
console.log('folders', folders)
let files: any[] = []
for (var folder of folders){
// get files from the Music folder
files = files.concat(await getFiles(folder))
}
//console.log("cider.files", files2);
let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"]
let audiofiles = files.filter(f => supporttedformats.includes(f.substring(f.lastIndexOf('.') + 1)));
// console.log("cider.files2", audiofiles, audiofiles.length);
let metadatalist = []
let numid = 0;
for (var audio of audiofiles) {
try{
const metadata = await mm.parseFile(audio);
if (metadata != null){
let form = {
"id": "ciderlocal" + numid,
"type": "podcast-episodes",
"href": audio,
"attributes": {
"artwork": {
"width": 3000,
"height": 3000,
"url": metadata.common.picture != undefined ? "data:image/png;base64,"+metadata.common.picture[0].data.toString('base64')+"" : "",
},
"topics": [],
"url": "",
"subscribable": true,
"mediaKind": "audio",
"genreNames": [
""
],
// "playParams": {
// "id": "ciderlocal" + numid,
// "kind": "podcast",
// "isLibrary": true,
// "reporting": false },
"trackNumber": metadata.common.track?.no ?? 0,
"discNumber": metadata.common.disk?.no ?? 0,
"name": metadata.common.title ?? audio.substring(audio.lastIndexOf('\\') + 1),
"albumName": metadata.common.album,
"artistName": metadata.common.artist,
"copyright": metadata.common.copyright ?? "",
"assetUrl": "file:///" +audio,
"contentAdvisory": "",
"releaseDateTime": "2022-05-13T00:23:00Z",
"durationInMilliseconds": Math.floor((metadata.format.duration?? 0) * 1000),
"offers": [
{
"kind": "get",
"type": "STDQ"
}
],
"contentRating": "clean"
}
};
numid += 1;
}
if (folders == null || folders.length == null || folders.length == 0) folders = ["D:\\Music"]
console.log('folders', folders)
let files: any[] = []
for (var folder of folders) {
// get files from the Music folder
files = files.concat(await getFiles(folder))
}
//console.log("cider.files", files2);
let supporttedformats = ["mp3", "aac", "webm", "flac", "m4a", "ogg", "wav", "opus"]
let audiofiles = files.filter(f => supporttedformats.includes(f.substring(f.lastIndexOf('.') + 1)));
// console.log("cider.files2", audiofiles, audiofiles.length);
let metadatalist = []
let numid = 0;
for (var audio of audiofiles) {
try {
const metadata = await mm.parseFile(audio);
if (metadata != null) {
let form = {
"id": "ciderlocal" + numid,
"type": "podcast-episodes",
"href": audio,
"attributes": {
"artwork": {
"width": 3000,
"height": 3000,
"url": metadata.common.picture != undefined ? "data:image/png;base64," + metadata.common.picture[0].data.toString('base64') + "" : "",
},
"topics": [],
"url": "",
"subscribable": true,
"mediaKind": "audio",
"genreNames": [
""
],
// "playParams": {
// "id": "ciderlocal" + numid,
// "kind": "podcast",
// "isLibrary": true,
// "reporting": false },
"trackNumber": metadata.common.track?.no ?? 0,
"discNumber": metadata.common.disk?.no ?? 0,
"name": metadata.common.title ?? audio.substring(audio.lastIndexOf('\\') + 1),
"albumName": metadata.common.album,
"artistName": metadata.common.artist,
"copyright": metadata.common.copyright ?? "",
"assetUrl": "file:///" + audio,
"contentAdvisory": "",
"releaseDateTime": "2022-05-13T00:23:00Z",
"durationInMilliseconds": Math.floor((metadata.format.duration ?? 0) * 1000),
"offers": [
{
"kind": "get",
"type": "STDQ"
}
],
"contentRating": "clean"
}
};
numid += 1;
// let form = {"id": "/ciderlocal?" + audio,
// "type": "library-songs",
// "href": "/ciderlocal?" + audio,
@ -1273,15 +1276,16 @@ export class BrowserWindow {
// "name": metadata.common.title,
// "albumName": metadata.common.album,
// "artistName": metadata.common.artist}}
metadatalist.push(form)}
} catch (e){}
}
// console.log('metadatalist', metadatalist);
this.localSongs = metadatalist;
BrowserWindow.win.webContents.send('getUpdatedLocalList', metadatalist);
metadatalist.push(form)
}
} catch (e) { }
}
// console.log('metadatalist', metadatalist);
this.localSongs = metadatalist;
BrowserWindow.win.webContents.send('getUpdatedLocalList', metadatalist);
}
)
)
ipcMain.on('writeWAV', (event, leftpcm, rightpcm, bufferlength) => {
@ -1449,8 +1453,8 @@ export class BrowserWindow {
console.log('sc', SoundCheckTag)
BrowserWindow.win.webContents.send('SoundCheckTag', SoundCheckTag)
}).catch(err => {
console.log(err)
});
console.log(err)
});
});
@ -1502,35 +1506,38 @@ export class BrowserWindow {
/* *********************************************************************************************
* Window Events
* **********************************************************************************************/
if (process.platform === "win32") {
let WND_STATE = {
MINIMIZED: 0,
NORMAL: 1,
MAXIMIZED: 2,
FULL_SCREEN: 3,
};
let wndState = WND_STATE.NORMAL;
let WND_STATE = {
MINIMIZED: 0,
NORMAL: 1,
MAXIMIZED: 2,
FULL_SCREEN: 3,
};
let wndState = WND_STATE.NORMAL;
BrowserWindow.win.on("resize", (_: any) => {
const isMaximized = BrowserWindow.win.isMaximized();
const isMinimized = BrowserWindow.win.isMinimized();
const isFullScreen = BrowserWindow.win.isFullScreen();
const state = wndState;
if (isMinimized && state !== WND_STATE.MINIMIZED) {
wndState = WND_STATE.MINIMIZED;
BrowserWindow.win.webContents.send('window-state-changed', 'minimized');
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
wndState = WND_STATE.FULL_SCREEN;
BrowserWindow.win.webContents.send('window-state-changed', 'fullscreen')
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
wndState = WND_STATE.MAXIMIZED;
BrowserWindow.win.webContents.send('window-state-changed', 'maximized')
BrowserWindow.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
} else if (state !== WND_STATE.NORMAL) {
wndState = WND_STATE.NORMAL;
BrowserWindow.win.webContents.send('window-state-changed', 'normal')
BrowserWindow.win.webContents.executeJavaScript(
`app.chrome.maximized = false`
);
}
});
BrowserWindow.win.on("resize", (_: any) => {
const isMaximized = BrowserWindow.win.isMaximized();
const isMinimized = BrowserWindow.win.isMinimized();
const isFullScreen = BrowserWindow.win.isFullScreen();
const state = wndState;
if (isMinimized && state !== WND_STATE.MINIMIZED) {
wndState = WND_STATE.MINIMIZED;
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
wndState = WND_STATE.FULL_SCREEN;
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
wndState = WND_STATE.MAXIMIZED;
BrowserWindow.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
} else if (state !== WND_STATE.NORMAL) {
wndState = WND_STATE.NORMAL;
BrowserWindow.win.webContents.executeJavaScript(
`app.chrome.maximized = false`
);
}
});
}
let isQuiting = false
@ -1573,10 +1580,10 @@ export class BrowserWindow {
// Set window Handler
BrowserWindow.win.webContents.setWindowOpenHandler((x: any) => {
if (x.url.includes("apple") || x.url.includes("localhost")) {
return {action: "allow"};
return { action: "allow" };
}
shell.openExternal(x.url).catch(console.error);
return {action: "deny"};
return { action: "deny" };
});
}
@ -1632,7 +1639,7 @@ export class BrowserWindow {
"CtlN": "Cider",
"iV": "196623"
};
let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, {
let server2 = mdns.createAdvertisement(x, `${await getPort({ port: 3839 })}`, {
name: encoded,
txt: txt_record
});

View file

@ -2432,10 +2432,9 @@ fieldset:disabled .btn {
.nav-pills .nav-link {
background-color: transparent;
border: 0;
border-radius: 50px;
border-radius: 6px;
color: #eee;
-webkit-user-drag: none;
// transition: transform .35s var(--appleEase), background-color .35s var(--appleEase);
font-weight: 500;
margin: 0px 4px;
&:hover {
@ -2447,7 +2446,7 @@ fieldset:disabled .btn {
.nav-pills .show > .nav-link {
color: #fff;
background-color: var(--selected);
outline:2px solid var(--keyColor);
// outline:2px solid var(--keyColor);
}
.nav-fill > .nav-link,

View file

@ -2168,107 +2168,116 @@ input[type=checkbox][switch]:checked:active::before {
}
// fancy pills
.nav-pills {
position: relative;
.nav-link {
transition: transform .3s var(--appleEase);
.fancy-pills {
.nav-pills {
position: relative;
&:after {
--dist: 1px;
content: "";
position: absolute;
top: var(--dist);
bottom: var(--dist);
left: var(--dist);
right: var(--dist);
// width : 100%;
// height : 100%;
.nav-link {
transition: transform .3s var(--appleEase);
position: relative;
background-color: transparent;
border: 0;
border-radius: 50px;
z-index: -1;
opacity: 0;
transition: background-color .5s var(--appleEase), opacity 0.25s var(--appleEase), border-radius .32s var(--appleEase);
}
&:hover {
outline: none;
transform: scale(1.1);
// background: #eee;
background: transparent;
color: #333;
&:after {
opacity: 1;
background-color: #eee;
transition: background-color .25s var(--appleEase),
border-radius .25s var(--appleEase),
color .0s var(--appleEase),
opacity 0.0s var(--appleEase);
}
}
&.active {
outline: none;
transform: scale(1.1);
// background: #eee;
background: transparent;
color: #333;
font-weight: 600;
&:after {
opacity: 1;
background-color: #eee;
}
}
}
&:hover {
.nav-link.active {
outline: none;
transform: scale(1.0);
background: transparent;
color: #eee;
transform: scale(1.0);
-webkit-user-drag: none;
font-weight: 500;
margin: 0px 4px;
&:after {
background: rgb(200 200 200 / 15%);
opacity: 1;
transition: color 0s;
// border-radius: 5px;
--dist: 4px;
--dist: 1px;
content: "";
position: absolute;
top: var(--dist);
bottom: var(--dist);
left: var(--dist);
right: var(--dist);
// width : 100%;
// height : 100%;
background-color: transparent;
border-radius: 50px;
z-index: -1;
opacity: 0;
transition: background-color .5s var(--appleEase), opacity 0.25s var(--appleEase), border-radius .32s var(--appleEase);
}
&:hover {
outline: none;
transform: scale(1.1);
z-index: 1;
// background: #eee;
background: transparent;
color: #333;
&:after {
background: #eee;
border-radius: inherit;
--dist: 1px;
opacity: 1;
background-color: #eee;
transition: background-color .25s var(--appleEase),
border-radius .25s var(--appleEase),
color .0s var(--appleEase),
opacity 0.0s var(--appleEase);
}
}
&.active {
outline: none;
transform: scale(1.1);
// background: #eee;
background: transparent;
color: #333;
font-weight: 600;
&:after {
opacity: 1;
background-color: #eee;
}
}
}
&:hover {
.nav-link.active {
outline: none;
transform: scale(1.0);
background: transparent;
color: #eee;
transform: scale(1.0);
&:after {
background: rgb(200 200 200 / 15%);
opacity: 1;
transition: color 0s;
// border-radius: 5px;
--dist: 4px;
}
&:hover {
transform: scale(1.1);
z-index: 1;
color: #333;
&:after {
background: #eee;
border-radius: inherit;
--dist: 1px;
}
}
}
}
}
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgb(200 200 200 / 10%);
border-radius: 50px;
z-index: 0;
pointer-events: none;
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgb(200 200 200 / 10%);
border-radius: 50px;
z-index: 0;
pointer-events: none;
}
}
}

View file

@ -6,6 +6,7 @@ body[platform="darwin"] {
&.notransparency::before {
display: none;
}
#app {
&.simplebg {
background: transparent;
@ -33,6 +34,16 @@ body[platform="darwin"] {
background-color: var(--macOSChromeColor);
}
}
// &::after {
// position: fixed;
// top:0;left:0;right:0;bottom:0;
// box-shadow: inset 0px 0px .5px 1px rgb(200 200 200 / 40%);
// border-radius: 10px;
// content: " ";
// z-index: 999999;
// pointer-events: none;
// }
}
#app-main {

File diff suppressed because it is too large Load diff

View file

@ -168,6 +168,7 @@ const app = new Vue({
location: "",
info: {}
},
windowState: "normal",
desiredPageTransition: "wpfade_transform",
hideUserInfo: ipcRenderer.sendSync("is-dev") || false,
artworkReady: false,
@ -217,6 +218,7 @@ const app = new Vue({
castMenu: false,
moreInfo: false,
airplayPW: false,
settings: false
},
socialBadges: {
badgeMap: {},
@ -849,6 +851,10 @@ const app = new Vue({
this.library.localsongs = data;
})
ipcRenderer.on('window-state-changed', (event, data) => {
this.chrome.windowState = data
})
ipcRenderer.on('SoundCheckTag', (event, tag) => {
// let replaygain = self.parseSCTagToRG(tag)
try {
@ -4325,7 +4331,8 @@ const app = new Vue({
"name": app.getLz('settings.option.audio.audioLab'),
"hidden": true,
"action": function () {
app.appRoute('audiolabs')
app.$store.state.pageState.settings.currentTabIndex = 2
app.modals.settings = true
}
},
]

View file

@ -12,6 +12,10 @@ const store = new Vuex.Store({
nextUrl: null,
items: [],
size: "normal"
},
settings: {
currentTabIndex: 0,
fullscreen: false
}
},
artwork: {

View file

@ -15,6 +15,7 @@
:root {
--appleEase: cubic-bezier(0.42, 0, 0.58, 1);
--borderColor: rgb(200 200 200 / 16%);
--mediaItemShadow: inset 0px 0px 0px 1px rgb(200 200 200 / 16%);
--mediaItemShadow-Shadow: 0 8px 40px rgb(0 0 0 / 0.55);
--mediaItemShadow-ShadowSubtle: 0 4px 14px rgb(0 0 0 / 10%);
@ -50,6 +51,7 @@
--textColor: #eee;
--replayGradient: linear-gradient(45deg, hsl(248deg 58% 29%), hsl(13deg 41% 42%));
--glassFilter: blur(16px) saturate(180%);
--glassFilterHeavy: blur(64px) saturate(280%);
--sidebarWidth: 260px;
}

View file

@ -105,7 +105,7 @@
$root.getLz("term.about")
}}</span>
</button>
<button class="usermenu-item" @click="appRoute('settings')">
<button class="usermenu-item" @click="modals.settings = true">
<span class="usermenu-item-icon">
<%- include("../svg/settings.svg") %>
</span>

View file

@ -31,6 +31,9 @@
<transition name="modal">
<plugin-menu v-if="modals.pluginMenu"></plugin-menu>
</transition>
<transition name="modal">
<settings-window v-if="modals.settings"></settings-window>
</transition>
<transition name="modal">
<eq-view v-if="modals.equalizer"></eq-view>
</transition>

File diff suppressed because it is too large Load diff

View file

@ -71,7 +71,9 @@
<div id="LOADER">
<%- include("../assets/cider-round.svg") %>
</div>
<div id="app" :class="getAppClasses()" :style="getAppStyle()" :library-visible="(chrome.sidebarCollapsed ? 0 : 1)" :window-style="cfg.visual.directives.windowLayout">
<div id="app" :class="getAppClasses()"
:window-state="chrome.windowState"
:style="getAppStyle()" :library-visible="(chrome.sidebarCollapsed ? 0 : 1)" :window-style="cfg.visual.directives.windowLayout">
<transition name="fsModeSwitch">
<div id="app-main" v-show="appMode == 'player'">
<%- include('app/chrome-top'); %>

View file

@ -1,5 +1,5 @@
<script type="text/x-template" id="audiolabs-page">
<div class="content-inner audiolabs-page ">
<div class="audiolabs-page">
<div class="md-option-container">
<div class="settings-option-body">
<div class="md-option-line">
@ -120,7 +120,7 @@
</select>
</div>
</div>
<div style="opacity: 0.5; pointer-events: none">
<div style="opacity: 0.5; pointer-events: none" v-if="false">
<div class="md-option-header">
<span>{{$root.getLz('settings.header.unfinished')}}</span>
</div>

View file

@ -178,7 +178,7 @@
</div>
</div>
<div class="playlist-body scrollbody">
<b-tabs pills class="track-pills pilldim" align="center" content-class="mt-3" :nav-wrapper-class="navClass(data)">
<b-tabs pills class="track-pills pilldim fancy-pills" align="center" content-class="mt-3" :nav-wrapper-class="navClass(data)">
<b-tab :title="$root.getLz('term.tracks')">
<div @wheel="minClass(true)" @scroll="minClass(true)">
<div class="">