@vnejs/plugins.views.settings 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ export const SUBSCRIBE_EVENTS = {
2
+ SHOW: "vne:settings_view:show",
3
+ HIDE: "vne:settings_view:hide",
4
+ UPDATE: "vne:settings_view:update",
5
+ PRELOAD: "vne:settings_view:preload",
6
+ TAB_SET: "vne:settings_view:tab_set",
7
+ };
@@ -0,0 +1,43 @@
1
+ export const TRANSITION = 500;
2
+ export const ZINDEX = 4000;
3
+ export const LOC_LABEL = "settings";
4
+
5
+ export const BACKGROUND = "Menu/save";
6
+
7
+ export const TABS = {};
8
+
9
+ export const VALUE_MAPS = {};
10
+
11
+ export const VIEW_PROPS = {
12
+ screen: { withBackgroundDark: true, withBackgroundBlur: false, transition: TRANSITION, zIndex: ZINDEX },
13
+
14
+ background: { withDark: true, withBlur: false },
15
+
16
+ cross: { position: { bottom: 90, right: 120 }, svg: { size: 60 } },
17
+
18
+ exampleText: { size: 60, height: 300, color: "var(--vne-color-active)" },
19
+
20
+ fieldsGap: 48,
21
+
22
+ fieldProps: {
23
+ widthTitle: 1440,
24
+ width: 3120,
25
+
26
+ sizeTitle: 96,
27
+ sizeText: 72,
28
+
29
+ gapBetweenItems: 48,
30
+ gapBetweenTitleAndContent: 120,
31
+ gapBetweenInnerItems: 120,
32
+
33
+ rangeHeight: 24,
34
+
35
+ widthInnerLeft: 960,
36
+ widthInnerRight: 240,
37
+ },
38
+
39
+ controls: {
40
+ position: { top: 90, left: 120 },
41
+ props: { textWidth: 660, wrapWidth: 3600, flexGap: 36, flexWithWrap: false, textSize: 96, textAlign: "center" },
42
+ },
43
+ };
package/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import { regPlugin } from "@vnejs/shared";
2
+
3
+ import { SUBSCRIBE_EVENTS } from "./const/events";
4
+ import * as params from "./const/params";
5
+
6
+ import { SettingsController } from "./modules/controller";
7
+ import { SettingsView } from "./modules/view";
8
+
9
+ regPlugin("SETTINGS_VIEW", { events: SUBSCRIBE_EVENTS, params }, [SettingsController, SettingsView]);
@@ -0,0 +1,132 @@
1
+ import { ModuleController } from "@vnejs/module.components";
2
+
3
+ const filterTabsToShow = ({ showInSettings = false }) => Boolean(showInSettings);
4
+
5
+ export class SettingsController extends ModuleController {
6
+ name = "settings.controller";
7
+
8
+ emitHide = () => this.emit(this.EVENTS.SETTINGS_VIEW.HIDE);
9
+
10
+ onArrowLeft = () => {
11
+ const { currentTab, currentItemInner, currentItem } = this.state;
12
+
13
+ if (currentItem === null) return;
14
+
15
+ const { settingKey = "", step = 0, min = 0, type = "", options = [] } = this.tabs[currentTab].fields[currentItem];
16
+
17
+ if (options?.length) {
18
+ const newValue = (currentItemInner === 0 ? options.length : currentItemInner) - 1;
19
+
20
+ return this.updateStateAndViewFast({ currentItemInner: newValue });
21
+ }
22
+
23
+ if (type === "toggle") {
24
+ return this.shared.settings[settingKey] === true && this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: false });
25
+ }
26
+
27
+ if (type === "range") {
28
+ const newValue = Number(Number(this.shared.settings[settingKey] - step).toFixed(4));
29
+
30
+ return newValue >= min && this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: newValue });
31
+ }
32
+ };
33
+
34
+ onArrowRight = () => {
35
+ const { currentTab, currentItemInner, currentItem } = this.state;
36
+
37
+ if (currentItem === null) return;
38
+
39
+ const { settingKey = "", step = 0, max = 0, type = "", options = [] } = this.tabs[currentTab].fields[currentItem];
40
+
41
+ if (options?.length) {
42
+ return this.updateStateAndViewFast({ currentItemInner: (currentItemInner + 1) % options.length });
43
+ }
44
+
45
+ if (type === "toggle") {
46
+ return this.shared.settings[settingKey] === false && this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: true });
47
+ }
48
+
49
+ if (type === "range") {
50
+ const newValue = Number(Number(this.shared.settings[settingKey] + step).toFixed(4));
51
+
52
+ return newValue <= max && this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: newValue });
53
+ }
54
+ };
55
+
56
+ onPrevTab = () => this.emit(this.EVENTS.SETTINGS_VIEW.TAB_SET, { tab: (this.state.currentTab === 0 ? this.tabs.length : this.state.currentTab) - 1 });
57
+ onNextTab = () => this.emit(this.EVENTS.SETTINGS_VIEW.TAB_SET, { tab: (this.state.currentTab + 1) % this.tabs.length });
58
+
59
+ onInteract = () => {
60
+ const { currentTab, currentItemInner, currentItem } = this.state;
61
+
62
+ if (currentItem === null) return;
63
+
64
+ const { settingKey = "", type = "", options = [] } = this.tabs[currentTab].fields[currentItem];
65
+
66
+ if (options?.length)
67
+ return options[currentItemInner].action
68
+ ? this.PARAMS.SETTINGS_VIEW.ACTIONS[options[currentItemInner].action](this, this.state.locs)
69
+ : this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: options[currentItemInner].value });
70
+
71
+ if (type === "toggle") return this.emit(this.EVENTS.SETTINGS.SET, { name: settingKey, value: !this.shared.settings[settingKey] });
72
+ };
73
+
74
+ bgName = this.PARAMS.SETTINGS_VIEW.BACKGROUND;
75
+
76
+ updateEvent = this.EVENTS.SETTINGS_VIEW.UPDATE;
77
+ controls = {
78
+ [this.CONST.CONTROLS.BUTTONS.BACK]: this.emitHide,
79
+ [this.CONST.CONTROLS.BUTTONS.MENU]: this.emitHide,
80
+ [this.CONST.CONTROLS.BUTTONS.GAMEMENU]: this.emitHide,
81
+ [this.CONST.CONTROLS.BUTTONS.INTERACT]: this.onInteract,
82
+ [this.CONST.CONTROLS.BUTTONS.ACCEPT]: this.onInteract,
83
+ [this.CONST.CONTROLS.BUTTONS.TAB_PREV]: this.onPrevTab,
84
+ [this.CONST.CONTROLS.BUTTONS.TAB_NEXT]: this.onNextTab,
85
+ [this.CONST.CONTROLS.BUTTONS.ARROW_LEFT]: this.onArrowLeft,
86
+ [this.CONST.CONTROLS.BUTTONS.ARROW_RIGHT]: this.onArrowRight,
87
+ [this.CONST.CONTROLS.BUTTONS.ARROW_UP]: this.decCurrentItem,
88
+ [this.CONST.CONTROLS.BUTTONS.ARROW_BOTTOM]: this.incCurrentItem,
89
+ };
90
+ controlsIndex = this.PARAMS.SETTINGS_VIEW.ZINDEX;
91
+
92
+ subscribe = () => {
93
+ this.on(this.EVENTS.SETTINGS_VIEW.SHOW, this.onShow);
94
+ this.on(this.EVENTS.SETTINGS_VIEW.HIDE, this.onHide);
95
+ this.on(this.EVENTS.SETTINGS_VIEW.TAB_SET, this.onTabSet);
96
+ this.on(this.EVENTS.SETTINGS_VIEW.PRELOAD, this.loadBgMedia);
97
+
98
+ this.on(this.EVENTS.SETTINGS.CHANGED, this.onSettingChange);
99
+
100
+ this.on(this.EVENTS.LOCS.CHANGED, this.updateViewFast);
101
+
102
+ this.on(this.EVENTS.STATE.SET, this.onHide);
103
+ this.on(this.EVENTS.STATE.CLEAR, this.onHide);
104
+
105
+ this.on(this.EVENTS.MEMORY.CLEARED, this.onMemoryCleared);
106
+ };
107
+ init = () => {
108
+ this.tabs = Object.values(this.PARAMS.SETTINGS_VIEW.TABS).filter(filterTabsToShow);
109
+
110
+ this.setMaxCurrentItemByTabIndex(0);
111
+
112
+ return this.emit(this.EVENTS.SETTINGS.INIT);
113
+ };
114
+
115
+ beforeShow = async () => this.updateState({ bgSrc: (await this.loadBgMedia()).src, tabs: this.tabs, currentTab: 0 });
116
+ afterHide = () => this.setDefaultState();
117
+
118
+ onSettingChange = () => this.isReady && this.state.isShow && this.updateViewFast();
119
+
120
+ onTabSet = ({ tab = 0 } = {}) => {
121
+ this.setMaxCurrentItemByTabIndex(tab);
122
+ this.unsetCurrentItem();
123
+
124
+ return this.updateStateAndViewFast({ currentTab: tab, currentItemInner: 0 });
125
+ };
126
+
127
+ onMemoryCleared = async () => this.updateStateAndViewFast({ bgSrc: (await this.loadBgMedia()).src });
128
+
129
+ setMaxCurrentItemByTabIndex = (i) => {
130
+ this.maxCurrentItem = this.tabs[i].fields.length - 1;
131
+ };
132
+ }
@@ -0,0 +1,14 @@
1
+ import { ModuleView } from "@vnejs/module.components";
2
+
3
+ import { render } from "../view";
4
+
5
+ export class SettingsView extends ModuleView {
6
+ name = "settings.view";
7
+
8
+ locLabel = this.PARAMS.SETTINGS_VIEW.LOC_LABEL;
9
+ animationTime = this.PARAMS.SETTINGS_VIEW.TRANSITION;
10
+ updateEvent = this.EVENTS.SETTINGS_VIEW.UPDATE;
11
+
12
+ renderFunc = render;
13
+ updateHandler = this.onUpdateStoreComponent;
14
+ }
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@vnejs/plugins.views.settings",
3
+ "version": "0.1.1",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1",
7
+ "publish:major:plugin": "npm run publish:major",
8
+ "publish:minor:plugin": "npm run publish:minor",
9
+ "publish:patch:plugin": "npm run publish:patch",
10
+ "publish:major": "npm version major && npm publish --access public",
11
+ "publish:minor": "npm version minor && npm publish --access public",
12
+ "publish:patch": "npm version patch && npm publish --access public"
13
+ },
14
+ "author": "",
15
+ "license": "ISC",
16
+ "description": ""
17
+ }
@@ -0,0 +1,22 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+
3
+ import { PositionBox, TextControls } from "@vnejs/uis.react";
4
+
5
+ export const SettingsControls = ({ tabs = [], locs = {}, currentTab = 0, ...props } = {}) => {
6
+ const [texts, setTexts] = useState();
7
+
8
+ useEffect(() => void setTexts(Object.values(tabs).map(({ titleLocKey }, i) => ({ value: i, text: locs[titleLocKey] }))), [locs, tabs]);
9
+
10
+ const onClick = useCallback((tab) => props.emit(props.EVENTS.SETTINGS_VIEW.TAB_SET, { tab }), []);
11
+
12
+ const propsControls = useMemo(
13
+ () => ({ ...props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.controls.props, selectedIndex: currentTab, texts, onClick }),
14
+ [currentTab, texts],
15
+ );
16
+
17
+ return (
18
+ <PositionBox {...props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.controls.position}>
19
+ <TextControls {...propsControls} />
20
+ </PositionBox>
21
+ );
22
+ };
@@ -0,0 +1,13 @@
1
+ import { useMemo } from "react";
2
+
3
+ import { PositionBox, Svg } from "@vnejs/uis.react";
4
+
5
+ export const SettingsCross = (props = {}) => {
6
+ const propsCross = useMemo(() => ({ ...props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.cross.svg, onClick: () => props.emit(props.EVENTS.SETTINGS_VIEW.HIDE) }), []);
7
+
8
+ return (
9
+ <PositionBox {...props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.cross.position}>
10
+ <Svg.Cross {...propsCross} />
11
+ </PositionBox>
12
+ );
13
+ };
@@ -0,0 +1,41 @@
1
+ import { useEffect, useMemo, useState } from "react";
2
+
3
+ import { SmoothText } from "@vnejs/uis.react";
4
+
5
+ const EMPTY_ARRAY = [];
6
+
7
+ const mapTokens = (token) => ({ token });
8
+
9
+ export const SettingsExampleText = ({ isShow = false, locs = {}, ...props } = {}) => {
10
+ const [tokens, setTokens] = useState(EMPTY_ARRAY);
11
+
12
+ const isForce = Boolean(props.shared.textNoAnimationSources.length);
13
+
14
+ const text = useMemo(() => locs["fields.text.example"] ?? "", [locs]);
15
+
16
+ useEffect(() => {
17
+ if (!isShow) return;
18
+
19
+ setTokens([...text].map(mapTokens));
20
+ }, [isShow, text]);
21
+
22
+ useEffect(() => {
23
+ if (isShow || isForce) return;
24
+
25
+ const tm = setTimeout(() => setTokens(EMPTY_ARRAY), props.PARAMS.SETTINGS_VIEW.TRANSITION);
26
+
27
+ return () => clearTimeout(tm);
28
+ }, [isShow]);
29
+
30
+ return (
31
+ <SmoothText
32
+ tokens={tokens}
33
+ isForce={isForce}
34
+ charDelay={props.PARAMS.TEXT.TOKEN_RENDER_DELAY / props.shared.settings[props.SETTINGS.TEXT.TOKEN_RENDER_SPEED]}
35
+ size={props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.exampleText.size}
36
+ color={props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.exampleText.color}
37
+ height={props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.exampleText.height}
38
+ scrollBarTextSize={props.shared.settings[props.SETTINGS.TEXTSIZE.VALUE]}
39
+ />
40
+ );
41
+ };
@@ -0,0 +1,65 @@
1
+ import { useCallback } from "react";
2
+
3
+ import { PositionBox, Settings, Flex } from "@vnejs/uis.react";
4
+
5
+ import { SettingsExampleText } from "./SettingsExampleText";
6
+
7
+ export const SettingsTabContent = ({ isShow = false, isForce = false, locs = {}, fields = [], ...props } = {}) => {
8
+ const mapFields = useCallback(
9
+ ({ locKey = "", type = "", settingKey = "", options = [], titleToggledLocKey = "", titleUntoggledLocKey = "", valueMap = "", ...fieldProps } = {}) => {
10
+ const onChange = (value) => props.emit(props.EVENTS.SETTINGS.SET, { name: settingKey, value });
11
+
12
+ const titles = { title: locs[locKey], titleToggled: locs[titleToggledLocKey], titleUntoggled: locs[titleUntoggledLocKey] };
13
+ const valueMapReal = valueMap ? props.PARAMS.SETTINGS_VIEW.VALUE_MAPS[valueMap] : null;
14
+
15
+ const optionsReal = options.map((option) => {
16
+ const result = { ...option };
17
+
18
+ if (option.locKey) {
19
+ delete result.locKey;
20
+ result.text = locs[option.locKey];
21
+ }
22
+
23
+ if (option.action) {
24
+ delete result.action;
25
+ result.onClick = () => props.PARAMS.SETTINGS_VIEW.ACTIONS[option.action](props, locs);
26
+ }
27
+
28
+ return result;
29
+ });
30
+
31
+ return { ...titles, value: props.shared.settings[settingKey], type, onChange, props: fieldProps, options: optionsReal, valueMap: valueMapReal };
32
+ },
33
+ [locs],
34
+ );
35
+
36
+ return (
37
+ <PositionBox
38
+ isCentered={true}
39
+ opacity={isShow ? 1 : 0}
40
+ transition={isForce ? 0 : props.PARAMS.SETTINGS_VIEW.TRANSITION}
41
+ >
42
+ <Flex
43
+ direction={Flex.DIRECTION.COLUMN}
44
+ gap={props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.fieldsGap}
45
+ >
46
+ <Settings
47
+ selectedIndex={isShow ? props.currentItem : -1}
48
+ selectedIndexInner={isShow ? props.currentItemInner : -1}
49
+ transition={isForce ? 0 : props.PARAMS.SETTINGS_VIEW.TRANSITION}
50
+ settings={fields.map(mapFields)}
51
+ {...props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.fieldProps}
52
+ />
53
+
54
+ {props.showTextExample && (
55
+ <SettingsExampleText
56
+ {...props}
57
+ isShow={isShow}
58
+ isForce={isForce}
59
+ text={locs["fields.text.example"] || ""}
60
+ />
61
+ )}
62
+ </Flex>
63
+ </PositionBox>
64
+ );
65
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./SettingsControls";
2
+ export * from "./SettingsCross";
3
+ export * from "./SettingsExampleText";
4
+ export * from "./SettingsTabContent";
package/view/index.jsx ADDED
@@ -0,0 +1,42 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+
4
+ import { Screen } from "@vnejs/uis.react";
5
+
6
+ import { SettingsControls, SettingsCross, SettingsTabContent } from "./components";
7
+
8
+ const Settings = ({ store, onMount, ...props } = {}) => {
9
+ const [state, setState] = useState({});
10
+
11
+ const { isShow = false, isForce = false, bgSrc = "", tabs = [], locs = {}, currentTab = 0, currentItem = null, currentItemInner = null } = state;
12
+
13
+ useEffect(() => store.subscribe(setState), []);
14
+ useEffect(() => void onMount(), []);
15
+
16
+ const propsTab = useMemo(() => ({ ...props, isForce, currentItem, currentItemInner, locs }), [isForce, currentItem, currentItemInner, locs]);
17
+ const propsScreen = useMemo(() => ({ ...props.PARAMS.SETTINGS_VIEW.VIEW_PROPS.screen, isShow, isForce, bgSrc }), [isShow, isForce, bgSrc]);
18
+ const propsControls = useMemo(() => ({ ...props, locs, tabs, currentTab, isForce }), [locs, tabs, currentTab, isForce]);
19
+
20
+ const renderTabContent = useCallback(
21
+ ({ fields = [], showTextExample = false } = {}, i) => (
22
+ <SettingsTabContent
23
+ {...propsTab}
24
+ key={i}
25
+ isShow={currentTab === i}
26
+ fields={fields}
27
+ showTextExample={showTextExample}
28
+ />
29
+ ),
30
+ [currentTab, propsTab],
31
+ );
32
+
33
+ return (
34
+ <Screen {...propsScreen}>
35
+ <SettingsCross {...props} />
36
+ <SettingsControls {...propsControls} />
37
+ {Object.values(tabs).map(renderTabContent)}
38
+ </Screen>
39
+ );
40
+ };
41
+
42
+ export const render = (props, root) => createRoot(root).render(<Settings {...props} />);