re-orderable mods: menu elements can be dragged

This commit is contained in:
dragonwocky 2020-10-01 13:32:50 +10:00
parent a3e8758280
commit 74a03a176c
Signed by: dragonwocky
GPG Key ID: C7A48B7846AA706D
5 changed files with 201 additions and 46 deletions

View File

@ -115,6 +115,7 @@ module.exports = (store, __exports) => {
'--theme--interactive_hover-border', '--theme--interactive_hover-border',
'--theme--button_close', '--theme--button_close',
'--theme--button_close-fill', '--theme--button_close-fill',
'--theme--selected',
'--theme--primary', '--theme--primary',
'--theme--primary_click', '--theme--primary_click',
'--theme--option-color', '--theme--option-color',

View File

@ -254,6 +254,9 @@ s {
/* module meta */ /* module meta */
#modules {
position: relative;
}
#modules section { #modules section {
background: var(--theme--sidebar); background: var(--theme--sidebar);
border: 1px solid var(--theme--table-border); border: 1px solid var(--theme--table-border);
@ -589,3 +592,64 @@ s {
height: 132.5px; height: 132.5px;
} }
} }
/* draggable re-ordering of mods */
#draggable-toggle {
background: none;
border: none;
margin-top: 0.8em;
padding-left: 0;
cursor: pointer;
color: var(--theme--text_ui);
}
[data-bolded] {
display: inline-flex;
flex-direction: column;
}
[data-bolded]::after {
content: attr(data-bolded);
height: 0;
visibility: hidden;
overflow: hidden;
user-select: none;
pointer-events: none;
font-weight: bold;
}
#modules.reorder section {
margin-bottom: 0.4em;
}
#modules.reorder [draggable] {
margin-bottom: 1.15em;
}
#modules.reorder [draggable]::after {
content: '';
height: 0.3em;
width: 99%;
position: absolute;
margin: 0.5em 0;
opacity: 0.7;
background: var(--theme--table-border);
}
#modules.reorder [draggable].dragged-over::after {
background: var(--theme--selected);
}
#modules.reorder .switch,
#modules.reorder .tags,
#modules.reorder .desc,
#modules.reorder .options,
#modules.reorder .author,
#modules.reorder .version {
display: none;
}
#modules.reorder label {
cursor: pointer;
}
#modules.reorder label::before {
content: '::';
margin-right: 0.4em;
color: var(--theme--text_ui);
}

View File

@ -20,6 +20,10 @@
</svg> </svg>
<input type="text" placeholder="search ('/' to focus)" /> <input type="text" placeholder="search ('/' to focus)" />
<div id="tags"></div> <div id="tags"></div>
<button id="draggable-toggle">
<b data-bolded="configure">configure</b> |
<span data-bolded="reorder">reorder</span>
</button>
</div> </div>
<div id="modules"></div> <div id="modules"></div>
</main> </main>

View File

@ -184,12 +184,12 @@ window['__start'] = async () => {
}; };
function innerText(elem) { function innerText(elem) {
let text = ''; let text = '';
for (let node of elem.childNodes) { for (let $node of elem.childNodes) {
if (node.nodeType === 3) text += node.textContent; if ($node.nodeType === 3) text += $node.textContent;
if (node.nodeType === 1) if ($node.nodeType === 1)
text += ['text', 'number'].includes(node.type) text += ['text', 'number'].includes($node.type)
? node.value ? $node.value
: innerText(node); : innerText($node);
} }
return text; return text;
} }
@ -284,20 +284,20 @@ window['__start'] = async () => {
return parsed; return parsed;
} }
let modified_notice; let $modified_notice;
function modified() { function modified() {
if (modified_notice) return; if ($modified_notice) return;
modified_notice = createAlert( $modified_notice = createAlert(
'info', 'info',
`changes may not fully apply until <span data-relaunch>app relaunch</span>.` `changes may not fully apply until <span data-relaunch>app relaunch</span>.`
); );
modified_notice.el $modified_notice.el
.querySelector('[data-relaunch]') .querySelector('[data-relaunch]')
.addEventListener('click', (event) => { .addEventListener('click', (event) => {
electron.remote.app.relaunch(); electron.remote.app.relaunch();
electron.remote.app.quit(); electron.remote.app.quit();
}); });
modified_notice.append(); $modified_notice.append();
} }
const file_icon = await fs.readFile( const file_icon = await fs.readFile(
@ -389,15 +389,8 @@ window['__start'] = async () => {
} }
const $modules = document.querySelector('#modules'); const $modules = document.querySelector('#modules');
for (let mod of modules.loaded.sort((a, b) =>
a.tags.includes('core') || for (let mod of modules.loaded) {
store('mods', { [a.id]: { pinned: false } }).pinned
? -1
: b.tags.includes('core') ||
store('mods', { [b.id]: { pinned: false } }).pinned
? 1
: a.name.localeCompare(b.name)
)) {
for (let fonts of mod.fonts || []) { for (let fonts of mod.fonts || []) {
document document
.querySelector('head') .querySelector('head')
@ -417,37 +410,39 @@ window['__start'] = async () => {
avatar: `https://github.com/${mod.author}.png`, avatar: `https://github.com/${mod.author}.png`,
}; };
mod.elem = helpers.createElement(` mod.elem = helpers.createElement(`
<section class="${ <section class="${
mod.tags.includes('core') || enabled ? 'enabled' : 'disabled' mod.tags.includes('core') || enabled ? 'enabled' : 'disabled'
}" id="${mod.id}"> }" id="${mod.id}">
<div class="meta"> <div class="meta">
<h3 ${ <h3 ${
mod.tags.includes('core') mod.tags.includes('core')
? `>${mod.name}` ? `>${mod.name}`
: `class="toggle"> : `class="toggle">
<input type="checkbox" id="enable_${mod.id}" <input type="checkbox" id="enable_${mod.id}"
${enabled ? 'checked' : ''} /> ${enabled ? 'checked' : ''} />
<label for="enable_${mod.id}"> <label for="enable_${mod.id}">
<span class="name">${mod.name}</span> <span class="name">${mod.name}</span>
<span class="switch"><span class="dot"></span></span> <span class="switch"><span class="dot"></span></span>
</label>` </label>`
}</h3> }</h3>
<p class="tags">${mod.tags <p class="tags">${mod.tags
.map((tag) => (tag.startsWith('#') ? tag : `#${tag}`)) .map((tag) => (tag.startsWith('#') ? tag : `#${tag}`))
.join(' ')}</p> .join(' ')}</p>
<div class="desc">${markdown(mod.desc)}</div> <div class="desc">${markdown(mod.desc)}</div>
<p> <p>
<a href="${author.link}" class="author"> <a href="${author.link}" class="author">
<img src="${author.avatar}" onerror="this.src='./icons/user.png'"> <img src="${author.avatar}" onerror="this.src='./icons/user.png'">
${author.name} ${author.name}
</a> </a>
<span class="version">v${mod.version}</span> <span class="version">v${mod.version}</span>
</p> </p>
</div> </div>
${ ${
mod.options && mod.options.length ? '<div class="options"></div>' : '' mod.options && mod.options.length
} ? '<div class="options"></div>'
</section> : ''
}
</section>
`); `);
const $enable = mod.elem.querySelector(`#enable_${mod.id}`); const $enable = mod.elem.querySelector(`#enable_${mod.id}`);
if ($enable) if ($enable)
@ -500,12 +495,96 @@ window['__start'] = async () => {
} }
$options.appendChild($opt); $options.appendChild($opt);
} }
$modules.append(mod.elem); if (mod.tags.includes('core')) $modules.append(mod.elem);
} }
document document
.querySelectorAll('input[type="checkbox"]') .querySelectorAll('input[type="checkbox"]')
.forEach((checkbox) => .forEach((checkbox) =>
checkbox.addEventListener('click', (event) => event.target.blur()) checkbox.addEventListener('click', (event) => event.target.blur())
); );
// draggable re-ordering
const draggable = {
state: 0,
tags: ['b', 'span'],
$toggle: document.querySelector('#draggable-toggle'),
list: modules.loaded
.filter((m) => !m.tags.includes('core'))
.map((m) => m.elem),
target: null,
render() {
draggable.target = null;
for (let $node of draggable.list) {
$node.draggable = false;
$modules.append($node);
}
},
mouseover(event) {
if (!draggable.target && event.target.innerText) {
for (let $node of draggable.list) $node.draggable = false;
const $node = draggable.list.find(
(node) => node.innerText === event.target.innerText
);
if ($node) $node.draggable = draggable.state;
}
},
};
document.addEventListener('dragstart', (event) => {
draggable.target = event.target;
event.target.style.opacity = 0.5;
});
document.addEventListener('dragend', (event) => {
event.target.style.opacity = '';
});
document.addEventListener('dragover', (event) => {
event.preventDefault();
document
.querySelectorAll('.dragged-over')
.forEach((el) => el.classList.remove('dragged-over'));
const $node = draggable.list.find(
(node) => node.innerText === event.target.innerText
);
if ($node) $node.classList.add('dragged-over');
});
document.addEventListener('drop', (event) => {
event.preventDefault();
document
.querySelectorAll('.dragged-over')
.forEach((el) => el.classList.remove('dragged-over'));
if (
draggable.target &&
draggable.target.innerText !== event.target.innerText
) {
const from = draggable.list.findIndex(
(node) => node.innerText === draggable.target.innerText
),
to = draggable.list.findIndex(
(node) => node.innerText === event.target.innerText
);
// [draggable.list[from], draggable.list[to]] = [
// draggable.list[to],
// draggable.list[from],
// ]; -- swap
if (to >= draggable.list.length) {
let k = to - draggable.list.length;
while (k--) draggable.list.push(undefined);
}
draggable.list.splice(to, 0, draggable.list.splice(from, 1)[0]);
}
draggable.render();
});
document.addEventListener('mouseover', draggable.mouseover);
draggable.render();
draggable.$toggle.addEventListener('click', (event) => {
draggable.state = !draggable.state;
draggable.tags = draggable.tags.reverse();
draggable.$toggle.innerHTML = `
<${draggable.tags[0]} data-bolded="configure">configure</${draggable.tags[0]}> |
<${draggable.tags[1]} data-bolded="reorder">reorder</${draggable.tags[1]}>
`;
$modules.classList[draggable.state ? 'add' : 'remove']('reorder');
$modules
.querySelectorAll('input')
.forEach((input) => (input.disabled = draggable.state));
});
}; };

View File

@ -140,6 +140,13 @@ function getEnhancements() {
modules.invalid.push(dir); modules.invalid.push(dir);
} }
} }
modules.loaded = modules.loaded.sort((a, b) =>
a.tags.includes('core')
? -1
: b.tags.includes('core')
? 1
: a.name.localeCompare(b.name)
);
return modules; return modules;
} }