Skip to content

Commit bf5276d

Browse files
committed
feat(settings): advancedMap
1 parent 36e3dc1 commit bf5276d

File tree

7 files changed

+182
-4
lines changed

7 files changed

+182
-4
lines changed

src/utils/settings/index.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as hover from '../hover.js';
77
import each from '../each.js';
88
import wrap from '../2.pokemon.js';
99
import SettingType from './types/setting.js';
10+
import AdvancedMap from './types/map2.js';
1011
import * as types from './types/index.js';
1112
import { translateText } from '../translate.js';
1213
import RegisteredSetting from './RegisteredSetting.js';
@@ -108,6 +109,7 @@ function createSetting(setting = defaultSetting) {
108109
removeSetting() {
109110
removeSetting(setting, el);
110111
},
112+
untilClose,
111113
}));
112114
if (!el.find(`#${key.replaceAll('.', '\\.')}`).length) {
113115
el.attr({
@@ -232,6 +234,19 @@ export function register(data) {
232234
if (typeof setting.type === 'string') {
233235
setting.type = registry.get(setting.type);
234236
}
237+
if (typeof data.type === 'object') {
238+
try {
239+
const left = data.type.key || data.type.left || data.type[0];
240+
const right = data.type.value || data.type.right || data.type[1];
241+
const type = new AdvancedMap(left, right);
242+
setting.type = type;
243+
registerTypeStyle(type);
244+
} catch (e) {
245+
const logger = data.page?.logger || console;
246+
logger.error('Error setting up AdvancedMap', e);
247+
setting.type = undefined;
248+
}
249+
}
235250
if (!(setting.type instanceof SettingType)) return undefined; // TODO: Throw error?
236251

237252
const conf = init(page);
@@ -421,7 +436,12 @@ export function registerType(type, addStyle = style.add) {
421436
const name = type.name;
422437
if (!name || registry.has(name)) throw new Error(`SettingType: "${name}" already exists`);
423438
registry.set(name, type);
424-
addStyle(...type.styles().map((s) => `.underscript-dialog .${getCSSName(name)} ${s}`));
439+
registerTypeStyle(type, addStyle);
440+
}
441+
442+
function registerTypeStyle(type, addStyle = style.add) {
443+
if (type instanceof AdvancedMap && type.isRegistered) return;
444+
addStyle(...type.styles().map((s) => `.underscript-dialog .${getCSSName(type.name)} ${s}`));
425445
}
426446

427447
each(types, (Type) => registerType(new Type()));

src/utils/settings/types/boolean.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export default class extends Setting {
2525
labelFirst() {
2626
return false;
2727
}
28+
29+
get isBasic() {
30+
return true;
31+
}
2832
}
2933

3034
function getValue(el, remove = false) {

src/utils/settings/types/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ export { default as Slider } from './slider.js';
99
export { default as Text } from './text.js';
1010
export { default as MapType } from './map.js';
1111
export { default as Keybind } from './keybind.js';
12+
13+
// Must always be last
14+
export { default as AdvancedMap } from './map2.js';

src/utils/settings/types/map.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ export default class MapList extends Setting {
4444
'{ border-top: 1px solid white; border-bottom: 1px solid white; padding: 5px 0; }',
4545
'label { align-self: flex-end; }',
4646
'.btn { padding: 3px 6px; }',
47-
'.item { display: inline-flex; flex-wrap: wrap; align-items: center; padding-top: 5px; }',
48-
'.item > input { margin: 0 5px; }',
49-
'.error input:first-child { border-color: red; }',
47+
'.item { display: inline-flex; flex-wrap: wrap; align-items: center; padding-top: 5px; width: 100%; }',
48+
'.item > * { margin: 0 5px; }',
49+
'.error *:first-child { border-color: red; }',
5050
'.warning { display: none; color: red; flex-basis: 100%; user-select: none; }',
5151
'.error .warning { display: block; }',
5252
];

src/utils/settings/types/map2.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import Setting from './map.js';
2+
import SettingType from './setting.js';
3+
import { getSettingType, isSettingType } from '../settingRegistry.js';
4+
5+
const mapTypes = new Set();
6+
let baseRegistered = false;
7+
8+
export default class AdvancedMap extends Setting {
9+
/**
10+
* @type {SettingType}
11+
*/
12+
#keyType;
13+
/**
14+
* @type {SettingType}
15+
*/
16+
#valueType;
17+
#name;
18+
19+
constructor(keyType = 'text', valueType = keyType) {
20+
super('advancedMap');
21+
this.#keyType = getSettingType(keyType);
22+
this.#valueType = getSettingType(valueType);
23+
if (!isSettingType(this.#keyType) || !isSettingType(this.#valueType)) throw new Error('AdvancedMap requires setting types');
24+
const uniqueTypes = [...new Set([this.#keyType, this.#valueType])];
25+
// const invalidTypes = uniqueTypes.filter((type) => !type.isBasic);
26+
// if (invalidTypes.length) throw new Error(`AdvancedMap can only use basic setting types. (invalid: ${invalidTypes.join(', ')})`);
27+
this.#name = uniqueTypes.join('_').replaceAll(' ', '-');
28+
if (baseRegistered) {
29+
this.name += `_${this.#name}`;
30+
}
31+
}
32+
33+
/**
34+
* @param {Map<any, any>} val
35+
*/
36+
element(val, update, {
37+
container, data: { keyData, valueData, leftData, rightData } = {}, disabled, key, name, untilClose,
38+
} = {}) {
39+
// TODO: validate that disabled propagates properly
40+
const data = [...val.entries()];
41+
let entries = data.length;
42+
const add = (lineValue, id) => {
43+
function save(remove) {
44+
if (remove) data.splice(data.indexOf(lineValue), 1);
45+
const ret = data.filter(([_]) => _);
46+
update(ret);
47+
}
48+
const line = $('<div class="item">');
49+
const options = { container: $('<div>'), name, disabled, remove: false, removeSetting() {}, key: `${key}.${id}`, child: true };
50+
const dataKey = keyData || leftData;
51+
const left = $(this.#keyType.element(this.#keyValue(lineValue[0], dataKey), (newValue) => {
52+
// TODO: validate this is how it's supposed to work
53+
const isInvalid = newValue !== lineValue[0] && data.some(([keyValue]) => keyValue === newValue);
54+
line.toggleClass('error', isInvalid);
55+
if (isInvalid || newValue === lineValue[0]) return;
56+
lineValue[0] = newValue;
57+
save();
58+
}, {
59+
...options,
60+
data: dataKey,
61+
}));
62+
const dataValue = valueData || rightData;
63+
const right = $(this.#valueType.element(this.#value(lineValue[1], dataValue), (newValue) => {
64+
if (newValue === lineValue[1]) return;
65+
lineValue[1] = newValue;
66+
save();
67+
}, {
68+
...options,
69+
data: dataValue,
70+
}));
71+
const button = $('<button class="btn btn-danger glyphicon glyphicon-trash">').on('click', () => {
72+
save(true);
73+
line.remove();
74+
});
75+
const warning = $('<div class="warning clickable">')
76+
.text('Duplicate value, not updated! Click here to reset.')
77+
.on('click', () => left.val(this.#keyValue(lineValue[0], dataKey))
78+
.parent().removeClass('error'));
79+
function refresh() {
80+
left.prop('disabled', disabled);
81+
right.prop('disabled', disabled);
82+
button.prop('disabled', disabled);
83+
}
84+
refresh();
85+
untilClose(`refresh:${key}`, refresh, `create:${key}`);
86+
container.append(line.append(left, ' : ', right, button, warning));
87+
};
88+
data.forEach(add);
89+
return $('<button class="btn btn-success glyphicon glyphicon-plus">').on('click', () => {
90+
const item = [
91+
this.#keyType.default(keyData) ?? '',
92+
this.#valueType.default(valueData) ?? '',
93+
];
94+
data.push(item);
95+
add(item, entries);
96+
entries += 1;
97+
});
98+
}
99+
100+
encode(value = []) {
101+
if (value instanceof Map) {
102+
return super.encode(this.#encodeEntries([...value.entries()]));
103+
}
104+
return super.encode(this.#encodeEntries(value));
105+
}
106+
107+
styles() {
108+
return [...new Set([
109+
...super.styles(),
110+
...this.#keyType.styles(),
111+
...this.#valueType.styles(),
112+
]).values()];
113+
}
114+
115+
#keyValue(value, data) {
116+
return this.#keyType.value(value, data);
117+
}
118+
119+
#value(value, data) {
120+
return this.#valueType.value(value, data);
121+
}
122+
123+
#encodeEntries(data = []) {
124+
return data.map(([key, val]) => ([
125+
this.#keyType.encode(key),
126+
this.#valueType.encode(val),
127+
]));
128+
}
129+
130+
get isRegistered() {
131+
const registered = mapTypes.has(this.#name);
132+
if (!registered) {
133+
mapTypes.add(this.#name);
134+
baseRegistered = true;
135+
}
136+
return registered;
137+
}
138+
}

src/utils/settings/types/setting.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,13 @@ export default class SettingType {
8080
labelFirst() {
8181
return true;
8282
}
83+
84+
get isBasic() {
85+
// return typeof this.default() === 'string';
86+
return false;
87+
}
88+
89+
toString() {
90+
return this.name;
91+
}
8392
}

src/utils/settings/types/text.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ export default class Text extends Setting {
1717
'background-color': 'transparent',
1818
});
1919
}
20+
21+
get isBasic() {
22+
return true;
23+
}
2024
}

0 commit comments

Comments
 (0)