Decoupled a bunch of components from the colour picker.

This commit is contained in:
flash 2024-01-22 00:28:00 +00:00
parent 506d5d29d9
commit c7c22e4125
14 changed files with 464 additions and 235 deletions

View file

@ -4,19 +4,14 @@
border: 2px solid var(--colpick-colour, #000);
padding: 3px;
display: flex;
flex-direction: column;
position: absolute;
box-sizing: border-box;
background-color: #444;
color: #fff;
box-shadow: 0 3px 10px #000;
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
font-size: 12px;
line-height: 20px;
width: 290px;
}
.colpick-form {
width: 100%;
}
.colpick-tabbed {
@ -25,10 +20,10 @@
}
.colpick-tabbed-container {
border: 2px solid var(--colpick-colour, #000);
box-sizing: border-box;
border-radius: 5px 5px 0 0;
height: 234px;
overflow: auto;
scrollbar-width: thin;
}
.colpick-tabbed-list {
background-color: #222;
@ -45,7 +40,6 @@
padding: 3px 5px;
height: 24px;
margin-right: 1px;
box-sizing: border-box;
cursor: pointer;
}
.colpick-tab-button:hover {
@ -103,7 +97,6 @@
.colpick-values-child-input {
display: inline-block;
border: 1px solid #222;
box-sizing: border-box;
background: #333;
border-radius: 0;
color: #fff;
@ -150,16 +143,19 @@
}
.colpick-tab-presets-container {
width: 100%;
max-width: 280px;
padding: 2px;
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
display: flex;
flex-wrap: wrap;
gap: 3px;
}
.colpick-presets-option {
display: block;
flex-shrink: 0;
border: 0;
width: 40px;
height: 40px;
width: 42px;
height: 42px;
border-radius: 5px;
text-decoration: none;
color: #fff;
@ -223,7 +219,6 @@
.colpick-slider-value-input {
display: inline-block;
border: 1px solid #222;
box-sizing: border-box;
background: #333;
border-radius: 0;
color: #fff;

64
src/mami.js/args.js Normal file
View file

@ -0,0 +1,64 @@
const MamiArguments = (function() {
return {
type: (name, type, fallback, throwOnFail) => {
if(typeof name !== 'string')
throw 'name must be a string';
if(typeof type !== 'string')
throw 'type must be a string';
return { name: name, type: type, fallback: fallback, throwOnFail: !!throwOnFail };
},
check: (name, fallback, check, throwOnFail) => {
if(typeof name !== 'string')
throw 'name must be a string';
if(typeof check !== 'function')
throw 'check must be a function';
return { name: name, fallback: fallback, check: check, throwOnFail: !!throwOnFail };
},
filter: (name, type, fallback, filter, throwOnFail) => {
if(typeof name !== 'string')
throw 'name must be a string';
if(typeof type !== 'string')
throw 'type must be a string';
if(typeof filter !== 'function')
throw 'filter must be a function';
return { name: name, type: type, fallback: fallback, filter: filter, throwOnFail: !!throwOnFail };
},
verify: (args, options) => {
if(!Array.isArray(options))
throw 'options must be an array';
if(typeof args !== 'object')
args = {};
for(const option of options) {
if(typeof option !== 'object')
throw 'entries of options must be objects';
if(!('name' in option) || typeof option.name !== 'string')
throw 'option.name must be a string';
const name = option.name;
const type = 'type' in option && typeof option.type === 'string' ? option.type : undefined;
const fallback = 'fallback' in option ? option.fallback : undefined;
const check = 'check' in option && typeof option.check === 'function' ? option.check : undefined;
const filter = 'filter' in option && typeof option.filter === 'function' ? option.filter : undefined;
const throwOnFail = 'throwOnFail' in option && option.throwOnFail;
if(!(name in args)
|| (type !== undefined && typeof args[name] !== type)
|| (check !== undefined && !check(args[name]))) {
if(throwOnFail)
throw `${name} is invalid`;
args[name] = fallback;
} else if(filter !== undefined)
args[name] = filter(args[name]);
}
return args;
},
};
})();

View file

@ -65,5 +65,21 @@ const MamiColour = (() => {
return `#${str}`;
};
pub.fromHex = str => {
while(str.substring(0, 1) === '#')
str = str.substring(1);
str = str.substring(0, 6);
if(str.length === 3)
str = str.substring(0, 1) + str.substring(0, 1)
+ str.substring(1, 2) + str.substring(1, 2)
+ str.substring(2, 3) + str.substring(2, 3);
if(str.length !== 6)
throw 'not a valid hex string';
return parseInt(str, 16);
};
return pub;
})();

View file

@ -1,191 +1,149 @@
#include args.js
#include colour.js
#include utility.js
#include controls/tabs.js
#include colpick/tgrid.jsx
#include colpick/tpresets.jsx
#include colpick/tsliders.jsx
#include colpick/vhex.jsx
#include colpick/vraw.jsx
const MamiColourPicker = function(callback, options, colour, onClose) {
if(typeof callback !== 'function')
return;
if(typeof options !== 'object')
options = {};
if(typeof colour !== 'number')
colour = parseInt(colour || 0);
const MamiColourPicker = function(options) {
options = MamiArguments.verify(options, [
MamiArguments.filter('colour', 'number', 0, num => Math.min(0xFFFFFF, Math.max(0, num))),
MamiArguments.type('posX', 'number', -1),
MamiArguments.type('posY', 'number', -1),
MamiArguments.type('presets', 'object', []),
MamiArguments.type('showPresetsTab', 'boolean', options.presets.length > 0),
MamiArguments.type('showGridTab', 'boolean', true),
MamiArguments.type('showSlidersTab', 'boolean', true),
MamiArguments.type('showHexValue', 'boolean', true),
MamiArguments.type('showRawValue', 'boolean', true),
MamiArguments.type('showDialogButtons', 'boolean', true),
]);
const verifyOption = (name, type, def) => {
if(typeof options[name] !== type)
options[name] = def;
let colour;
const needsColour = [];
let promiseResolve;
const runResolve = () => {
if(promiseResolve !== undefined)
promiseResolve(colour);
close();
};
verifyOption('posX', 'number', -1);
verifyOption('posY', 'number', -1);
verifyOption('presets', 'object', []);
verifyOption('showPresetsTab', 'boolean', options.presets.length > 0);
verifyOption('showGridTab', 'boolean', true);
verifyOption('showSlidersTab', 'boolean', true);
verifyOption('showHexValue', 'boolean', true);
verifyOption('showRawValue', 'boolean', true);
verifyOption('autoClose', 'boolean', true);
const onColourChange = [];
const runOnColourChange = () => {
const text = MamiColour.text(colour);
for(const handler of onColourChange)
handler(colour, text);
let promiseReject;
const runReject = () => {
if(promiseReject !== undefined)
promiseReject();
close();
};
let tabsElem, tabsContainer, tabsList;
let values, buttons;
const html = <form class="colpick" style={{ zIndex: '9001' }} onsubmit={ev => { ev.preventDefault(); runResolve(); return false; }}>
{tabsElem = <div class="colpick-tabbed">
{tabsContainer = <div class="colpick-tabbed-container"/>}
{tabsList = <div class="colpick-tabbed-list"/>}
</div>}
<div class="colpick-middle-row">
<div class="colpick-colour-preview-container">
<div class="colpick-colour-preview"/>
</div>
{values = <div class="colpick-values-container"/>}
</div>
<div class="colpick-buttons-container">
{buttons = <div class="colpick-buttons-container-inner"/>}
</div>
</form>;
const close = () => html.parentNode.removeChild(html);
const setColour = (raw, mask) => {
raw = raw === undefined ? 0 : parseInt(raw);
mask = mask === undefined ? ~0 : parseInt(mask);
raw = typeof raw === 'number' ? (parseInt(raw) & 0xFFFFFF) : 0;
mask = typeof mask === 'number' ? parseInt(mask) : ~0;
colour = (colour & ~mask) | (raw & mask);
runOnColourChange();
const text = MamiColour.text(colour);
html.style.setProperty('--colpick-colour', MamiColour.hex(colour));
html.style.setProperty('--colpick-text', MamiColour.hex(text));
for(const info of needsColour)
if('updateColour' in info)
info.updateColour(colour, text);
};
const apply = () => {
callback(colour);
if(options.autoClose)
close();
const setPosition = pos => {
if(typeof pos !== 'object')
throw 'pos must be an object';
html.style.top = 'y' in pos && pos.y >= 0 ? `${pos.y}px` : null;
html.style.left = 'x' in pos && pos.x >= 0 ? `${pos.x}px` : '';
};
const cancel = () => {
callback(null);
if(options.autoClose)
close();
};
const tabs = new MamiTabsControl({
onAdd: ctx => {
const name = ctx.info.getName(),
containerName = `colpick-tab-${name}-container`,
buttonName = `colpick-tab-${name}-button`;
const close = () => {
if(onClose && onClose())
return;
container.parentNode.removeChild(container);
};
needsColour.push(ctx.info);
ctx.info.onChange(setColour);
let height = 96;
ctx.elem.className = `colpick-tab-container ${containerName} colpick-tab-container-inactive`;
tabsContainer.appendChild(ctx.elem);
const container = <div class="colpick" style={{ zIndex: '9001' }}/>;
onColourChange.push((colour, text) => {
container.style.setProperty('--colpick-colour', MamiColour.hex(colour));
container.style.setProperty('--colpick-text', MamiColour.hex(text));
tabsList.appendChild(<input type="button" value={ctx.title}
class={['colpick-tab-button', buttonName]}
onclick={ctx.onClick}/>);
},
onRemove: ctx => {
const name = ctx.info.getName();
$rq(`.colpick-tab-${name}-button`);
$rq(`.colpick-tab-${name}-container`);
},
onSwitch: ctx => {
console.log(ctx);
if(ctx.from !== undefined) {
ctx.from.elem.classList.toggle('colpick-tab-container-inactive', true);
$q(`.colpick-tab-${ctx.from.info.getName()}-button`).classList.toggle('colpick-tab-button-active', false);
}
ctx.elem.classList.toggle('colpick-tab-container-inactive', false);
$q(`.colpick-tab-${ctx.info.getName()}-button`).classList.toggle('colpick-tab-button-active', true);
},
});
const form = <form class="colpick-form" onsubmit={ev => {
ev.preventDefault();
apply();
return false;
}} />;
container.appendChild(form);
const tabs = {};
let activeTab = undefined;
let tabbedContainer, tabbedList;
const tabbed = <div class="colpick-tabbed">
{tabbedContainer = <div class="colpick-tabbed-container"/>}
{tabbedList = <div class="colpick-tabbed-list"/>}
</div>;
const switchTab = (id) => {
if(activeTab == tabs[id])
return;
if(activeTab) {
activeTab.c.classList.toggle('colpick-tab-container-inactive', true);
activeTab.b.classList.toggle('colpick-tab-button-active', false);
}
activeTab = tabs[id] || undefined;
if(activeTab) {
activeTab.c.classList.toggle('colpick-tab-container-inactive', false);
activeTab.b.classList.toggle('colpick-tab-button-active', true);
}
};
const createTab = (id, name, tabInfo) => {
tabInfo.onUpdate(setColour);
onColourChange.push(value => tabInfo.updateColour(value));
const tabContainer = tabInfo.getElement();
tabContainer.className = `colpick-tab-container colpick-tab-${id}-container colpick-tab-container-inactive`;
tabbedContainer.appendChild(tabContainer);
const tabButton = <input type="button" value={name} class={['colpick-tab-button', `colpick-tab-${id}-button`]} onclick={() => switchTab(id)}/>;
tabbedList.appendChild(tabButton);
tabs[id] = { c: tabContainer, b: tabButton };
if(activeTab === undefined) {
activeTab = tabs[id];
tabContainer.classList.remove('colpick-tab-container-inactive');
tabButton.classList.add('colpick-tab-button-active');
}
};
if(options.showPresetsTab)
createTab('presets', 'Presets', new MamiColourPickerPresetsTab(options.presets));
tabs.add(new MamiColourPickerPresetsTab(options.presets));
if(options.showGridTab)
createTab('grid', 'Grid', new MamiColourPickerGridTab());
tabs.add(new MamiColourPickerGridTab);
if(options.showSlidersTab)
createTab('sliders', 'Sliders', new MamiColourPickerSlidersTab());
tabs.add(new MamiColourPickerSlidersTab);
if(activeTab) {
height += 261;
form.appendChild(tabbed);
}
if(tabs.count() < 1)
html.removeChild(tabsElem);
const values = {};
let valuesContainer;
const middleRow = <div class="colpick-middle-row">
<div class="colpick-colour-preview-container">
<div class="colpick-colour-preview"/>
</div>
{valuesContainer = <div class="colpick-values-container"/>}
</div>;
form.appendChild(middleRow);
const addValue = (id, name, type, format, change) => {
let valueLabel, valueInput;
const valueContainer = <label class={['colpick-values-child', `colpick-${id}-value`]}>
{valueLabel = <div class={['colpick-values-child-label', `colpick-${id}-value-label`]}>{name}</div>}
{valueInput = <input class={['colpick-values-child-input', `colpick-${id}-value-input`]} type={type} value="0" onchange={() => change(valueInput.value)}/>}
</label>;
valuesContainer.appendChild(valueContainer);
onColourChange.push(colour => valueInput.value = format(colour));
values[id] = { c: valueContainer, l: valueLabel, i: valueInput };
const addValue = (id, name, valueInfo) => {
needsColour.push(valueInfo);
valueInfo.onChange(setColour);
values.appendChild(<label class={['colpick-values-child', `colpick-${id}-value`]}>
<div class={['colpick-values-child-label', `colpick-${id}-value-label`]}>{name}</div>
{valueInfo}
</label>);
};
if(options.showHexValue)
addValue('hex', 'Hex', 'text', value => MamiColour.hex(value), value => {
while(value.substring(0, 1) === '#')
value = value.substring(1);
value = value.substring(0, 6);
if(value.length === 3)
value = value.substring(0, 1) + value.substring(0, 1)
+ value.substring(1, 2) + value.substring(1, 2)
+ value.substring(2, 3) + value.substring(2, 3);
if(value.length === 6)
setColour(parseInt(value, 16));
});
addValue('hex', 'Hex', new MamiColourPickerValueHex);
if(options.showRawValue)
addValue('raw', 'Raw', 'number', value => value, value => setColour(Math.min(0xFFFFFF, Math.max(0, parseInt(value)))));
let buttonsContainerInner;
const buttons = {};
const buttonsContainer = <div class="colpick-buttons-container">
{buttonsContainerInner = <div class="colpick-buttons-container-inner"/>}
</div>;
form.appendChild(buttonsContainer);
addValue('raw', 'Raw', new MamiColourPickerValueRaw);
const addButton = (id, name, action) => {
const button = <input class={['colpick-buttons-button', `colpick-buttons-${id}-button`]} value={name}/>;
if(action === null) {
if(action === undefined) {
button.classList.add('colpick-buttons-button-submit');
button.type = 'submit';
} else {
@ -193,55 +151,48 @@ const MamiColourPicker = function(callback, options, colour, onClose) {
button.type = 'button';
}
buttonsContainerInner.appendChild(button);
buttons[id] = { b: button };
buttons.appendChild(button);
};
addButton('cancel', 'Cancel', cancel);
addButton('apply', 'Apply', null);
if(options.showDialogButtons) {
addButton('cancel', 'Cancel', runReject);
addButton('apply', 'Apply');
}
const setPosition = (x, y) => {
container.style.top = y >= 0 ? `${y}px` : '';
container.style.left = x >= 0 ? `${x}px` : '';
};
setPosition({ x: options.posX, y: options.posY });
setColour(options.colour);
setPosition(options.posX, options.posY);
runOnColourChange();
const appendTo = parent => parent.appendChild(container);
const pub = {
getWidth: () => 290,
getHeight: () => height,
return {
getElement: () => html,
getColour: () => colour,
getContainer: () => container,
setColour: setColour,
appendTo: appendTo,
setPosition: setPosition,
switchTab: switchTab,
close: close,
dialog: pos => {
if(pos !== undefined)
setPosition(pos);
return new Promise((resolve, reject) => {
promiseResolve = resolve;
promiseReject = reject;
});
},
suggestPosition: mouseEvent => {
let x = 10, y = 10;
if(document.body.clientWidth > 340) {
if(html.parentNode.clientWidth > 340) {
x = mouseEvent.clientX;
y = mouseEvent.clientY;
let height = pub.getHeight(),
width = pub.getWidth();
const bb = html.getBoundingClientRect();
if(y > height + 20)
y -= height;
if(x > document.body.clientWidth - width - 20)
x -= width;
if(y > bb.height + 20)
y -= bb.height;
if(x > html.parentNode.clientWidth - bb.width - 20)
x -= bb.width;
}
return {
x: x,
y: y,
};
return { x: x, y: y };
},
};
return pub;
};

View file

@ -1,7 +1,7 @@
#include colour.js
const MamiColourPickerGridTab = function() {
let onUpdate = () => {};
let onChange = () => {};
const greys = [0xFFFFFF, 0xEBEBEB, 0xD6D6D6, 0xC2C2C2, 0xADADAD, 0x999999, 0x858585, 0x707070, 0x5C5C5C, 0x474747, 0x333333, 0];
const colours = [0x00A1D8, 0x0061FE, 0x4D22B2, 0x982ABC, 0xB92D5D, 0xFF4015, 0xFF6A00, 0xFFAB01, 0xFDC700, 0xFEFB41, 0xD9EC37, 0x76BB40];
@ -12,19 +12,21 @@ const MamiColourPickerGridTab = function() {
for(const grey of greys)
html.appendChild(<button class={['colpick-grid-option', `colpick-grid-option-${grey}`]}
style={{ background: MamiColour.hex(grey) }}
type="button" onclick={() => onUpdate(grey)}/>);
type="button" onclick={() => onChange(grey)}/>);
for(const shade of shades)
for(const colour of colours) {
const shaded = MamiColour.shaded(colour, shade);
html.appendChild(<button class={['colpick-grid-option', `colpick-grid-option-${shaded}`]}
style={{ background: MamiColour.hex(shaded) }}
type="button" onclick={() => onUpdate(shaded)}/>);
type="button" onclick={() => onChange(shaded)}/>);
}
return {
getName: () => 'grid',
getTitle: () => 'Grid',
getElement: () => html,
onUpdate: handler => onUpdate = handler,
onChange: handler => onChange = handler,
updateColour: colour => {
let elem = html.querySelector('.colpick-grid-option-active');
if(elem !== null)

View file

@ -1,18 +1,20 @@
#include colour.js
const MamiColourPickerPresetsTab = function(presets) {
let onUpdate = () => {};
let onChange = () => {};
const html = <div/>;
for(const preset of presets)
html.appendChild(<button class={['colpick-presets-option', `colpick-presets-option-${preset.c}`]}
style={{ background: MamiColour.hex(preset.c) }} title={preset.n}
type="button" onclick={() => onUpdate(preset.c)}/>);
type="button" onclick={() => onChange(preset.c)}/>);
return {
getName: () => 'presets',
getTitle: () => 'Presets',
getElement: () => html,
onUpdate: handler => onUpdate = handler,
onChange: handler => onChange = handler,
updateColour: colour => {
let elem = html.querySelector('.colpick-presets-option-active');
if(elem !== null)

View file

@ -1,25 +1,25 @@
#include colour.js
const MamiColourPickerSlider = function(name, title, mask, shift) {
let onUpdate = () => {};
let onChange = () => {};
let gradient, slider, input;
const html = <div class={['colpick-slider', `colpick-slider-${name}`]}>
<div class="colpick-slider-name">{title}</div>
<div class="colpick-slider-value">
<div class="colpick-slider-value-slider-container">
{gradient = <div class="colpick-slider-gradient" onmousedown={ev => { if(ev.button === 0) onUpdate(Math.floor(255 * (Math.max(0, Math.min(200, (ev.layerX - 5))) / 200)) << shift, mask); }}/>}
{slider = <input class="colpick-slider-value-slider" type="range" min="0" max="255" oninput={() => onUpdate(slider.value << shift, mask)}/>}
{gradient = <div class="colpick-slider-gradient" onmousedown={ev => { if(ev.button === 0) onChange(Math.floor(255 * (Math.max(0, Math.min(200, (ev.layerX - 5))) / 200)) << shift, mask); }}/>}
{slider = <input class="colpick-slider-value-slider" type="range" min="0" max="255" oninput={() => onChange(slider.value << shift, mask)}/>}
</div>
<div class="colpick-slider-value-input-container">
{input = <input class="colpick-slider-value-input" type="number" min="0" max="255" oninput={() => onUpdate(input.value << shift, mask)}/>}
{input = <input class="colpick-slider-value-input" type="number" min="0" max="255" oninput={() => onChange(input.value << shift, mask)}/>}
</div>
</div>
</div>;
return {
getElement: () => html,
onUpdate: handler => onUpdate = handler,
onChange: handler => onChange = handler,
updateColour: colour => {
const masked = colour & mask,
shifted = masked >> shift,
@ -43,10 +43,12 @@ const MamiColourPickerSlidersTab = function() {
const html = <div>{...sliders}</div>;
return {
getName: () => 'sliders',
getTitle: () => 'Sliders',
getElement: () => html,
onUpdate: handler => {
onChange: handler => {
for(const slider of sliders)
slider.onUpdate(handler);
slider.onChange(handler);
},
updateColour: colour => {
for(const slider of sliders)

View file

@ -0,0 +1,18 @@
#include colour.js
const MamiColourPickerValueHex = function() {
let onChange = () => {};
const html = <input class="colpick-values-child-input"
type="text" value="" onchange={() => {
try {
onChange(MamiColour.fromHex(html.value));
} catch(ex) {}
}}/>;
return {
getElement: () => html,
onChange: handler => onChange = handler,
updateColour: colour => html.value = MamiColour.hex(colour),
};
};

View file

@ -0,0 +1,13 @@
const MamiColourPickerValueRaw = function() {
let onChange = () => {};
const html = <input class="colpick-values-child-input"
type="number" min="0" max={0xFFFFFF} value="0"
onchange={() => onChange(Math.min(html.max, Math.max(html.min, html.value)))}/>;
return {
getElement: () => html,
onChange: handler => onChange = handler,
updateColour: colour => html.value = colour,
};
};

View file

@ -1,14 +1,14 @@
#include txtrigs.js
#include audio/context.js
#include controls/views.js
#include sound/sndmgr.js
#include sound/sndlibrary.js
#include sound/sndpacks.js
#include ui/views.js
const MamiContext = function(targetBody) {
const pub = {};
const viewsCtx = new MamiUIViews(targetBody);
const viewsCtx = new MamiViewsControl({ body: targetBody });
pub.getViews = () => viewsCtx;
let audioCtx = null;

View file

@ -0,0 +1,160 @@
#include args.js
#include uniqstr.js
const MamiTabsControl = function(options) {
options = MamiArguments.verify(options, [
MamiArguments.type('onAdd', 'function', undefined, true),
MamiArguments.type('onRemove', 'function', undefined, true),
MamiArguments.type('onSwitch', 'function', undefined, true),
]);
const onAdd = options.onAdd,
onRemove = options.onRemove,
onSwitch = options.onSwitch;
const tabs = new Map;
let currentTab;
const extractElement = elementInfo => {
let element;
if(elementInfo instanceof Element) {
element = elementInfo;
} else if('getElement' in elementInfo) {
element = elementInfo.getElement();
} else throw 'elementInfo is not a valid type';
if(!(element instanceof Element))
throw 'element is not an instance of Element';
return element;
};
const doTransition = async (transition, ctx) => {
if(transition === undefined)
return;
if(typeof transition !== 'function')
return;
await transition(ctx);
};
const getTabInfoAndId = tabInfoOrId => {
const result = {
id: undefined,
info: undefined,
};
const type = typeof tabInfoOrId;
if(type === 'string') {
result.id = tabInfoOrId;
result.info = tabs.get(result.id);
} else if(type === 'object') {
result.info = tabInfoOrId;
result.id = getExistingTabId(result.info);
} else throw 'tabInfoOrId must be an object or a string';
if(result.id === undefined || result.info === undefined)
throw 'tab not found';
return result;
};
const switchTab = async (tabInfoOrId, transition) => {
const result = getTabInfoAndId(tabInfoOrId);
const prevTab = currentTab;
currentTab = result.info;
const cbCtx = {
id: result.id,
info: result.info,
elem: extractElement(result.info),
from: undefined,
};
if(prevTab !== undefined) {
const prev = getTabInfoAndId(prevTab);
cbCtx.from = {
id: prev.id,
info: prev.info,
elem: extractElement(prev.info),
};
}
await onSwitch(cbCtx);
if(prevTab !== undefined)
await doTransition(transition, cbCtx);
};
const getExistingTabId = tabInfo => {
if(tabInfo !== undefined)
for(const [key, value] of tabs.entries())
if(value === tabInfo)
return key;
return undefined;
};
return {
switch: switchTab,
current: () => currentTab,
currentId: () => getExistingTabId(currentTab),
currentElement: () => {
if(currentTab === undefined)
return undefined;
return extractElement(currentTab);
},
count: () => tabs.size,
has: tabInfo => getExistingTabId(tabInfo) !== undefined,
add: async (tabInfo, title) => {
if(typeof tabInfo !== 'object')
throw 'tabInfo must be an object';
let tabId = getExistingTabId(tabInfo);
if(tabId !== undefined)
throw 'tabInfo has already been added';
tabId = MamiUniqueStr(8);
tabs.set(tabId, tabInfo);
if(title !== 'string' && 'getTitle' in tabInfo)
title = tabInfo.getTitle();
const element = extractElement(tabInfo);
await onAdd({
id: tabId,
info: tabInfo,
elem: element,
title: title,
onClick: () => switchTab(tabInfo),
});
if(currentTab === undefined)
await switchTab(tabInfo);
return tabId;
},
remove: async tabInfoOrId => {
const result = getTabInfoAndId(tabInfoOrId);
if(!tabs.has(result.id))
throw 'this tab does not exist';
const element = extractElement(result.info);
tabs.delete(result.id);
await onRemove({
id: result.id,
info: result.info,
elem: element,
});
if(currentTab === result.info)
await switchTab(tabs.size > 0 ? tabs.keys().next().value : undefined);
},
};
};

View file

@ -1,9 +1,11 @@
#include utility.js
const MamiUIViews = function(targetBody) {
if(!(targetBody instanceof Element))
throw 'targetBody must be an instance of window.Element';
const MamiViewsControl = function(options) {
options = MamiArguments.verify(options, [
MamiArguments.check('body', undefined, value => value instanceof Element, true),
]);
const targetBody = options.body;
targetBody.classList.toggle('views', true);
const views = [];
@ -44,11 +46,10 @@ const MamiUIViews = function(targetBody) {
if(typeof elementInfo !== 'object')
throw 'elementInfo must be an object';
const element = extractElement(elementInfo);
if(!views.includes(elementInfo))
views.push(elementInfo);
const element = extractElement(elementInfo);
element.classList.toggle('hidden', false);
element.classList.toggle('views-item', true);
element.classList.toggle('views-background', false);

View file

@ -18,22 +18,23 @@ Umi.UI.Markup = (function() {
Umi.UI.View.Focus();
};
const insert = function(ev) {
if(this.dataset.umiTagName === 'color' && this.dataset.umiPickerVisible !== 'yes') {
const elem = this;
elem.dataset.umiPickerVisible = 'yes';
const pickerTarget = document.body;
let picker, pickerVisible = false;
const picker = new MamiColourPicker(
function(colour) {
if(colour !== null)
insertRaw(`[color=${MamiColour.hex(colour)}]`, '[/color]');
}, { presets: futami.get('colours') }, null, function() {
elem.dataset.umiPickerVisible = 'no';
}
);
picker.appendTo(document.body);
const pos = picker.suggestPosition(ev);
picker.setPosition(pos.x, pos.y);
const insert = function(ev) {
if(this.dataset.umiTagName === 'color' && !pickerVisible) {
pickerVisible = true;
if(picker === undefined)
picker = new MamiColourPicker({
presets: futami.get('colours'),
});
pickerTarget.appendChild(picker.getElement());
picker.dialog(picker.suggestPosition(ev))
.then(colour => insertRaw(`[color=${MamiColour.hex(colour)}]`, '[/color]'))
.finally(() => pickerVisible = false);
} else
insertRaw(
this.dataset.umiBeforeCursor,

View file

@ -13,6 +13,10 @@ const $ri = function(name) {
$r($i(name));
};
const $rq = function(query) {
$r($q(query));
};
const $ib = function(ref, elem) {
ref.parentNode.insertBefore(elem, ref);
};