bring back the badge editor
This commit is contained in:
@@ -201,7 +201,7 @@ button, .linkbutton, input[type="submit"], input[type="file"]::file-selector-but
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="password"], textarea, select {
|
||||
input[type="text"], input[type="password"], input[type="url"], textarea, select {
|
||||
--main-color: hsl(from var(--bg-color-primary) h s calc(l + 10));
|
||||
--active-color: hsl(from var(--main-color) h s calc(l + 5));
|
||||
--border-color: hsl(from var(--main-color) h calc(s * 1.3) 25);
|
||||
@@ -522,6 +522,7 @@ footer {
|
||||
border-radius: var(--base-padding);
|
||||
border: var(--base-padding) outset gray;
|
||||
box-shadow: 0px 0px 12px 2px #0006;
|
||||
align-self: center;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -964,11 +965,52 @@ ol.sortable-list {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&:not(.row) > * {
|
||||
&:not(.row):not(.full) > * {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-editor-badge-container {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--base-padding);
|
||||
|
||||
& > input[type=text], & > input[type=url] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-editor-file-picker {
|
||||
display: flex;
|
||||
gap: var(--base-padding);
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 150px;
|
||||
|
||||
& > input[type=file] {
|
||||
width: 100%;
|
||||
|
||||
&::file-selector-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-editor-badge-select {
|
||||
display: flex;
|
||||
gap: var(--base-padding);
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 200px;
|
||||
& > select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.js-only {
|
||||
display: none;
|
||||
}
|
||||
@@ -1038,4 +1080,9 @@ ol.sortable-list {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.badge-editor-badge-container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
162
data/static/js/bits/badge-editor.js
Normal file
162
data/static/js/bits/badge-editor.js
Normal file
@@ -0,0 +1,162 @@
|
||||
async function getHTML(endpoint, options = {}) {
|
||||
let query = {};
|
||||
if (options._query !== undefined) {
|
||||
query = options._query;
|
||||
delete options._query;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(query);
|
||||
const res = await fetch(`${endpoint}?${params}`, options);
|
||||
|
||||
return { body: await res.text(), status: res.status };
|
||||
}
|
||||
|
||||
const validateBase64Img = dataURL => new Promise(resolve => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
resolve(img.width === 88 && img.height === 31);
|
||||
};
|
||||
img.src = dataURL;
|
||||
});
|
||||
|
||||
export const b = {
|
||||
init: 'badgeEditorInit',
|
||||
}
|
||||
|
||||
const badgeEditorEndpoint = '/hyperapi/badges/editor/'
|
||||
const MAX_BADGES = 10;
|
||||
let badgesCount = 0;
|
||||
let customImageDatas = {};
|
||||
|
||||
export async function badgeEditorInit(_, __, el) {
|
||||
const res = await getHTML(badgeEditorEndpoint);
|
||||
if (res.status != 200) {
|
||||
return;
|
||||
}
|
||||
el.innerHTML = res.body;
|
||||
badgesCount = el.querySelectorAll('.sortable-item').length;
|
||||
b.trigger('badgeEditorAssignImgId');
|
||||
b.trigger('setBadgeCount');
|
||||
}
|
||||
|
||||
export function badgeEditorAssignImgId(_, __, el) {
|
||||
if (el.dataset.imgId) return;
|
||||
|
||||
const id = b.uuid();
|
||||
const filePicker = el.querySelector('input[type=file]');
|
||||
const img = el.querySelector('img.badge-button');
|
||||
console.log(img);
|
||||
el.dataset.imgId = id;
|
||||
filePicker.dataset.imgId = id;
|
||||
img.dataset.imgId = id;
|
||||
}
|
||||
|
||||
export function badgeEditorSetPreview(ev, sender, el) {
|
||||
if (!sender.parentNode.contains(el)) return;
|
||||
|
||||
const selectedItem = sender.selectedOptions[0];
|
||||
if (selectedItem.value !== 'custom') {
|
||||
el.src = selectedItem.dataset.filePath;
|
||||
} else if (customImageDatas[el.dataset.imgId]) {
|
||||
el.src = customImageDatas[el.dataset.imgId];
|
||||
} else {
|
||||
el.removeAttribute('src');
|
||||
}
|
||||
}
|
||||
|
||||
export function badgeEditorSetPreviewCustom(payload, _, el) {
|
||||
if (!payload.badge.contains(el)) return;
|
||||
if (!customImageDatas[el.dataset.imgId]) {
|
||||
el.removeAttribute('src');
|
||||
} else {
|
||||
el.src = customImageDatas[el.dataset.imgId];
|
||||
}
|
||||
}
|
||||
|
||||
export function badgeEditorToggleFilePicker(ev, sender, el) {
|
||||
if (!sender.parentNode.parentNode.contains(el)) return;
|
||||
|
||||
const selectedItem = sender.selectedOptions[0];
|
||||
const picker = el.querySelector('input[type=file]');
|
||||
if (selectedItem.value !== 'custom') {
|
||||
el.classList.add('hidden');
|
||||
picker.required = false;
|
||||
picker.setCustomValidity('');
|
||||
} else {
|
||||
el.classList.remove('hidden');
|
||||
picker.required = true;
|
||||
picker.setCustomValidity(picker.dataset.validity || '');
|
||||
}
|
||||
}
|
||||
|
||||
export function badgeEditorAddBadge(ev, sender, el) {
|
||||
// TODO: page templates do not get updated on mutation
|
||||
const badge = document.getElementById('badge-template').innerText;
|
||||
el.innerHTML += badge;
|
||||
b.trigger('badgeEditorAssignImgId');
|
||||
badgesCount++;
|
||||
b.trigger('setBadgeCount');
|
||||
}
|
||||
|
||||
export function badgeEditorDelete(ev, sender, el) {
|
||||
if (!el.contains(sender)) return;
|
||||
el.remove();
|
||||
badgesCount--;
|
||||
b.trigger('setBadgeCount');
|
||||
}
|
||||
|
||||
export function badgeEditorShowFilePicker(ev, sender, el) {
|
||||
if (sender.nextElementSibling !== el) return;
|
||||
el.showPicker();
|
||||
}
|
||||
|
||||
export async function badgeEditorFileSelected(ev, sender, el) {
|
||||
const file = sender.files[0];
|
||||
const badge = sender.parentNode.parentNode;
|
||||
|
||||
if (
|
||||
!['image/png', 'image/jpeg', 'image/jpg', 'image/webp'].includes(file.type)
|
||||
) {
|
||||
sender.dataset.validity = 'The badge file must be an image.';
|
||||
sender.setCustomValidity(sender.dataset.validity);
|
||||
sender.reportValidity();
|
||||
customImageDatas[sender.dataset.imgId] = null;
|
||||
b.send({ badge: badge }, 'badgeEditorSetPreviewCustom');
|
||||
return;
|
||||
}
|
||||
if (file.size >= 1000 * 500) {
|
||||
sender.dataset.validity = 'The badge image must be smaller than 500KB.';
|
||||
sender.setCustomValidity(sender.dataset.validity);
|
||||
sender.reportValidity();
|
||||
customImageDatas[sender.dataset.imgId] = null;
|
||||
b.send({ badge: badge }, 'badgeEditorSetPreviewCustom');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async e => {
|
||||
const dimsValid = await validateBase64Img(e.target.result);
|
||||
if (!dimsValid) {
|
||||
sender.setCustomValidity('The badge image must be exactly 88x31 pixels.');
|
||||
sender.reportValidity();
|
||||
customImageDatas[sender.dataset.imgId] = null;
|
||||
b.send({ badge: badge }, 'badgeEditorSetPreviewCustom');
|
||||
return;
|
||||
}
|
||||
customImageDatas[sender.dataset.imgId] = e.target.result;
|
||||
|
||||
sender.dataset.validity = '';
|
||||
sender.setCustomValidity('');
|
||||
b.send({ badge: badge }, 'badgeEditorSetPreviewCustom');
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
export function setBadgeCount(_, __, el) {
|
||||
if (el instanceof HTMLButtonElement) {
|
||||
el.disabled = badgesCount === MAX_BADGES;
|
||||
} else {
|
||||
el.innerText = `${badgesCount}/${MAX_BADGES}`;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,9 @@
|
||||
if (!target || target === draggedItem) {
|
||||
return;
|
||||
}
|
||||
if (draggedItem === null) {
|
||||
return;
|
||||
}
|
||||
const inSameList = draggedItem.dataset.sortableListKey === target.dataset.sortableListKey;
|
||||
if (!inSameList) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user