Decoupled a bunch of components from the colour picker.
This commit is contained in:
parent
506d5d29d9
commit
c7c22e4125
14 changed files with 464 additions and 235 deletions
|
@ -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
64
src/mami.js/args.js
Normal 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;
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -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;
|
||||
})();
|
||||
|
|
|
@ -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 = [];
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
const setColour = (raw, mask) => {
|
||||
raw = raw === undefined ? 0 : parseInt(raw);
|
||||
mask = mask === undefined ? ~0 : parseInt(mask);
|
||||
|
||||
colour = (colour & ~mask) | (raw & mask);
|
||||
runOnColourChange();
|
||||
};
|
||||
|
||||
const apply = () => {
|
||||
callback(colour);
|
||||
if(options.autoClose)
|
||||
let promiseResolve;
|
||||
const runResolve = () => {
|
||||
if(promiseResolve !== undefined)
|
||||
promiseResolve(colour);
|
||||
close();
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
callback(null);
|
||||
if(options.autoClose)
|
||||
let promiseReject;
|
||||
const runReject = () => {
|
||||
if(promiseReject !== undefined)
|
||||
promiseReject();
|
||||
close();
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
if(onClose && onClose())
|
||||
return;
|
||||
container.parentNode.removeChild(container);
|
||||
};
|
||||
let tabsElem, tabsContainer, tabsList;
|
||||
let values, buttons;
|
||||
|
||||
let height = 96;
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
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));
|
||||
|
||||
if(options.showGridTab)
|
||||
createTab('grid', 'Grid', new MamiColourPickerGridTab());
|
||||
|
||||
if(options.showSlidersTab)
|
||||
createTab('sliders', 'Sliders', new MamiColourPickerSlidersTab());
|
||||
|
||||
if(activeTab) {
|
||||
height += 261;
|
||||
form.appendChild(tabbed);
|
||||
}
|
||||
|
||||
const values = {};
|
||||
let valuesContainer;
|
||||
const middleRow = <div class="colpick-middle-row">
|
||||
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>
|
||||
{valuesContainer = <div class="colpick-values-container"/>}
|
||||
</div>;
|
||||
form.appendChild(middleRow);
|
||||
{values = <div class="colpick-values-container"/>}
|
||||
</div>
|
||||
<div class="colpick-buttons-container">
|
||||
{buttons = <div class="colpick-buttons-container-inner"/>}
|
||||
</div>
|
||||
</form>;
|
||||
|
||||
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);
|
||||
const close = () => html.parentNode.removeChild(html);
|
||||
|
||||
onColourChange.push(colour => valueInput.value = format(colour));
|
||||
const setColour = (raw, mask) => {
|
||||
raw = typeof raw === 'number' ? (parseInt(raw) & 0xFFFFFF) : 0;
|
||||
mask = typeof mask === 'number' ? parseInt(mask) : ~0;
|
||||
|
||||
values[id] = { c: valueContainer, l: valueLabel, i: valueInput };
|
||||
colour = (colour & ~mask) | (raw & mask);
|
||||
|
||||
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 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 tabs = new MamiTabsControl({
|
||||
onAdd: ctx => {
|
||||
const name = ctx.info.getName(),
|
||||
containerName = `colpick-tab-${name}-container`,
|
||||
buttonName = `colpick-tab-${name}-button`;
|
||||
|
||||
needsColour.push(ctx.info);
|
||||
ctx.info.onChange(setColour);
|
||||
|
||||
ctx.elem.className = `colpick-tab-container ${containerName} colpick-tab-container-inactive`;
|
||||
tabsContainer.appendChild(ctx.elem);
|
||||
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
if(options.showPresetsTab)
|
||||
tabs.add(new MamiColourPickerPresetsTab(options.presets));
|
||||
if(options.showGridTab)
|
||||
tabs.add(new MamiColourPickerGridTab);
|
||||
if(options.showSlidersTab)
|
||||
tabs.add(new MamiColourPickerSlidersTab);
|
||||
|
||||
if(tabs.count() < 1)
|
||||
html.removeChild(tabsElem);
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
18
src/mami.js/colpick/vhex.jsx
Normal file
18
src/mami.js/colpick/vhex.jsx
Normal 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),
|
||||
};
|
||||
};
|
13
src/mami.js/colpick/vraw.jsx
Normal file
13
src/mami.js/colpick/vraw.jsx
Normal 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,
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
|
|
160
src/mami.js/controls/tabs.js
Normal file
160
src/mami.js/controls/tabs.js
Normal 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);
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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);
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue