Skip to content

Commit

Permalink
feat: Settings UI rework
Browse files Browse the repository at this point in the history
  • Loading branch information
lideming committed Jun 3, 2023
1 parent 7258985 commit 5195d50
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 63 deletions.
17 changes: 10 additions & 7 deletions src/I18n/i18n-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[
["en", "zh", "ja"],
["English", "中文", "日本語"],
["Language: {0}", "语言:{0}", "言語:{0}"],
["Language:", "语言:", "言語:"],
[" (auto-detected)", "(自动检测)", "(自動検出)"],
["Reload to fully apply changes", "刷新以完全应用更改", "変更を完全に反映するにはリロードしてください"],
["Pin", "固定", "ピン留め"],
Expand Down Expand Up @@ -119,12 +119,13 @@
["Select all", "全选", "すべて選択"],
["Cancel", "取消", "キャンセル"],
["Settings", "设置", "設定"],
["UI color: {0}", "界面色彩:{0}", "UIの色:{0}"],
["UI style: {0}", "界面样式:{0}", "UIのスタイル:{0}"],
["Preferred bitrate (0: original file)", "首选码率(0:原始文件)", "望ましいビットレート(0: オリジナルファイル)"],
["UI color:", "界面色彩:", "UIの色:"],
["UI style:", "界面样式:", "UIのスタイル:"],
["Preferred bitrate:", "首选码率", "ビットレート:"],
["Custom server URL", "自定义服务器 URL", "カスタムサーバーURL"],
["Enable notification", "启用通知", "通知を有効にする"],
["Disable notification", "禁用通知", "通知を無効にする"],
["Notification:", "通知:", "通知:"],
["enabled", "启用", "有効"],
["disabled", "禁用", "無効"],
["Now Playing", "正在播放", "再生中"],
["Lyrics", "歌词", "歌詞"],
["Edit Lyrics", "编辑歌词", "歌詞を編集する"],
Expand Down Expand Up @@ -170,6 +171,8 @@
["make_it_visibility_1", "Make it public", "转为公开", "公開に変更する"],
["make_{0}_visibility_0", "Make {0} items private", "转换 {0} 个项目为私有", "{0} 個アイテムを非公開に変更する"],
["make_{0}_visibility_1", "Make {0} items public", "转换 {0} 个项目为公开", "{0} 個アイテムを公開に変更する"],
["social-link-error_already-linked", "The account has been already linked.", "该账户已经被关联。", "そのアカウントはすでにリンクされています。"]
["social-link-error_already-linked", "The account has been already linked.", "该账户已经被关联。", "そのアカウントはすでにリンクされています。"],
["language_auto", "(auto)", "(自动)", "(auto)"],
["bitrate_original", "original", "原始", "オリジナル"]
]
]
224 changes: 168 additions & 56 deletions src/Settings/SettingsUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ import { jsx } from "../Infra/utils";
import { appVersion } from "./AppVersion";
import buildInfo from "./buildInfo";
import { playerFX } from "../Player/PlayerFX";
import { TextBtn } from "@yuuza/webfx";
import {
Action,
BuildDomExpr,
Callbacks,
ContainerView,
ItemActiveHelper,
TextBtn,
TextView,
} from "@yuuza/webfx";
import { settings } from "./Settings";
import { api } from "../API/Api";
import { PluginsUI } from "../Plugins/pluginsUI";
Expand All @@ -22,10 +30,7 @@ export const settingsUI = new (class {

const themes = ["light", "dark"];
const styles = ["", "-rounded"];

function loopInArray<T>(arr: T[], current: T) {
return arr[(arr.indexOf(current) + 1) % arr.length];
}
const bitrates = [128, 256, 0];

function getThemeAndStyle() {
let [theme, style] = ui.theme.current.split("-");
Expand All @@ -40,62 +45,175 @@ function setThemeAndStyle(options: { theme?: string; style?: string }) {
ui.theme.set(`${theme}${style}` as any);
}

class RadioContainer extends ContainerView<RadioOption> {
currentValue: any = null;
currentActive = new ItemActiveHelper<RadioOption>({
funcSetActive: (item, val) => {
if (val) item.select();
else item.unselect();
},
});
onCurrentChange = new Callbacks<Action<RadioOption>>();
setCurrent(current: RadioOption) {
this.currentActive.set(current);
this.onCurrentChange.invoke(current);
}
protected createDom(): BuildDomExpr {
return <div class="radio-container" tabIndex="0"></div>;
}
protected postCreateDom(): void {
super.postCreateDom();
this.dom.addEventListener("keydown", (ev) => {
if (ev.code === "ArrowLeft" || ev.code === "ArrowRight") {
const next = this.childViews[
(this.childViews.length +
(this.currentActive.current?._position ?? 0) +
(ev.code === "ArrowRight" ? 1 : -1)) %
this.childViews.length
] as RadioOption;
this.setCurrent(next);
}
});
}
addView(view: RadioOption, pos?: number) {
super.addView(view, pos);
if (this.currentValue !== null && view.value === this.currentValue) {
this.setCurrent(view);
}
}
}

class RadioOption extends TextView {
protected createDom(): BuildDomExpr {
return <div class="radio-option btn" onclick={() => this.select()}></div>;
}
value: any = null;
select() {
const parent = this.parentView as RadioContainer;
if (parent.currentActive.current !== this) {
parent.setCurrent(this);
}
this.toggleClass("selected", true);
}
unselect() {
this.toggleClass("selected", false);
}
}

class SettingItem extends View {
label: () => string;
protected createDom(): BuildDomExpr {
return (
<div class="setting-item">
<span class="setting-label">{this.label}</span>
</div>
);
}
}

class SettingsDialog extends Dialog {
btnSwitchTheme = new ButtonView({ type: "big" });
btnSwitchStyle = new ButtonView({ type: "big" });
btnSwitchLang = new ButtonView({ type: "big" });
inputPreferBitrate = new LabeledInput();
inputServer = new LabeledInput();
btnNotification = new ButtonView({ type: "big" });

constructor() {
super();
this.addContent(this.btnSwitchTheme);
this.btnSwitchTheme.onActive.add(() => {
const current = getThemeAndStyle();
setThemeAndStyle({ theme: loopInArray(themes, current.theme) });
this.updateDom();
});
this.addContent(this.btnSwitchStyle);
this.btnSwitchStyle.onActive.add(() => {
const current = getThemeAndStyle();
setThemeAndStyle({ style: loopInArray(styles, current.style) });
this.updateDom();
});
this.addContent(this.btnSwitchLang);
this.btnSwitchLang.onActive.add(() => {
var origUsingLang = ui.lang.curLang;
var curlang = ui.lang.siLang.data;
var langs = ["", ...ui.lang.availableLangs];
curlang = langs[(langs.indexOf(curlang) + 1) % langs.length];
ui.lang.siLang.set(curlang);
});
this.addContent(this.inputPreferBitrate);
this.onShown.add(() => {
this.inputPreferBitrate.value = (
playerCore.siPlayer.data.preferBitrate ?? "0"
).toString();
});
this.onClose.add(() => {
var val = parseInt(this.inputPreferBitrate.value);
if (!isNaN(val)) {
playerCore.siPlayer.data.preferBitrate = val;
playerCore.siPlayer.save();
}
});
const { theme, style } = getThemeAndStyle();
this.addContent(
<SettingItem label={() => I`UI color:`}>
<RadioContainer
currentValue={theme}
onCurrentChange={(option) => {
setThemeAndStyle({ theme: option.value });
}}
>
{themes.map((option) => (
<RadioOption value={option}>
{() => i18n.get("colortheme_" + option)}
</RadioOption>
))}
</RadioContainer>
</SettingItem>,
);

this.addContent(
<SettingItem label={() => I`UI style:`}>
<RadioContainer
currentValue={style}
onCurrentChange={(option) => {
setThemeAndStyle({ style: option.value });
}}
>
{styles.map((option) => (
<RadioOption value={option}>
{() => i18n.get("styletheme_" + option)}
</RadioOption>
))}
</RadioContainer>
</SettingItem>,
);

this.addContent(
<SettingItem label={() => I`Language:`}>
<RadioContainer
currentValue={ui.lang.siLang.data}
onCurrentChange={(option) => {
ui.lang.siLang.set(option.value);
}}
>
{["", ...ui.lang.availableLangs].map((option) => (
<RadioOption value={option}>
{() =>
option ? i18n.get2("English", [], option) : I`language_auto`
}
</RadioOption>
))}
</RadioContainer>
</SettingItem>,
);

this.addContent(
<SettingItem label={() => I`Preferred bitrate:`}>
<RadioContainer
currentValue={playerCore.siPlayer.data.preferBitrate ?? 0}
onCurrentChange={(option) => {
playerCore.siPlayer.data.preferBitrate = option.value;
playerCore.siPlayer.save();
}}
>
{bitrates.map((option) => (
<RadioOption value={option}>
{() => (option ? `${option}k` : I`bitrate_original`)}
</RadioOption>
))}
</RadioContainer>
</SettingItem>,
);

this.addContent(
<SettingItem label={() => I`Notification:`}>
<RadioContainer
currentValue={ui.notification.config.enabled}
onCurrentChange={(option) => {
ui.notification.setEnable(option.value);
}}
>
{[true, false].map((option) => (
<RadioOption value={option}>
{() => (option ? I`enabled` : I`disabled`)}
</RadioOption>
))}
</RadioContainer>
</SettingItem>,
);

this.addContent(this.inputServer);
this.inputServer.value = localStorage.getItem("mcloud-server") || "";
this.inputServer.dominput.placeholder = settings.defaultApiBaseUrl;
this.inputServer.dominput.addEventListener("change", (e) => {
localStorage.setItem("mcloud-server", this.inputServer.value);
settings.apiBaseUrl = this.inputServer.value;
});
this.addContent(this.btnNotification);
this.btnNotification.onActive.add(() => {
ui.notification
.setEnable(!ui.notification.config.enabled)
.then(() => this.updateDom());
});

this.addContent(
new ButtonView({
text: () => I`Plugins`,
Expand All @@ -105,6 +223,7 @@ class SettingsDialog extends Dialog {
},
}),
);

const devFeatures = new View(
(
<div>
Expand All @@ -121,6 +240,7 @@ class SettingsDialog extends Dialog {
devFeatures.hidden = true;
let devClickCount = 0;
this.addContent(devFeatures);

this.addContent(
<div style="margin: 5px 0; display: flex; flex-wrap: wrap; justify-content: space-between;">
<div
Expand Down Expand Up @@ -153,14 +273,6 @@ class SettingsDialog extends Dialog {
this.title = I`Settings`;
this.btnClose.updateWith({ text: I`Close` });
super.updateDom();
const { theme, style } = getThemeAndStyle();
this.btnSwitchTheme.text = I`UI color: ${i18n.get("colortheme_" + theme)}`;
this.btnSwitchStyle.text = I`UI style: ${i18n.get("styletheme_" + style)}`;
this.btnSwitchLang.text = I`Language: ${I`English`}`;
if (!ui.lang.siLang.data) this.btnSwitchLang.text += I` (auto-detected)`;
this.inputPreferBitrate.updateWith({
label: I`Preferred bitrate (0: original file)`,
});
this.inputServer.updateWith({ label: I`Custom server URL` });
this.btnNotification.text = ui.notification.config.enabled
? I`Disable notification`
Expand Down
26 changes: 26 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,14 @@ body {
opacity: 0.7;
}

.dialog .btn {
margin: initial;
}

.dialog-content > .btn-big {
margin: 10px 0;
}

.rounded .btn,
.rounded .item,
.rounded .sidebar-toggle,
Expand Down Expand Up @@ -1514,3 +1522,21 @@ body {
overflow: hidden;
position: relative;
}

.setting-item {
display: flex;
justify-content: space-between;
margin: 10px 0;
}

.radio-option.btn {
display: inline-block;
}

.radio-option.btn:not(.selected) {
background: none;
color: var(--color-text-gray);
text-shadow: none;
box-shadow: none;
border: none;
}

0 comments on commit 5195d50

Please sign in to comment.