From 7167ebb0960fb414c299427476a2cb59ff2c2556 Mon Sep 17 00:00:00 2001 From: Qiyun Dai Date: Wed, 8 May 2024 12:58:30 -0500 Subject: [PATCH] un-repeatable event materials basic structures built --- .../event-materials-component.css | 96 +++++++++++++++++++ .../event-materials-component.js | 88 +++++++++++++++++ blocks/form-handler/form-handler.css | 4 + .../img-upload-component.js | 4 +- icons/cross.svg | 10 ++ icons/upload-cloud.svg | 6 ++ utils/utils.js | 9 +- 7 files changed, 209 insertions(+), 8 deletions(-) create mode 100644 blocks/event-materials-component/event-materials-component.css create mode 100644 blocks/event-materials-component/event-materials-component.js create mode 100644 icons/cross.svg create mode 100644 icons/upload-cloud.svg diff --git a/blocks/event-materials-component/event-materials-component.css b/blocks/event-materials-component/event-materials-component.css new file mode 100644 index 00000000..1439fab0 --- /dev/null +++ b/blocks/event-materials-component/event-materials-component.css @@ -0,0 +1,96 @@ +.event-materials-component > div { + margin-bottom: 32px; +} + +.event-materials-component .image-dropzones { + display: grid; + gap: 16px; + margin-bottom: 24px; +} + +.event-materials-component sp-textfield { + width: 100%; +} + +.event-materials-component .material-file-input-wrapper { + border: 2px dashed var(--color-gray-400); + border-radius: 8px; + height: 124px; + width: 124px; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + align-items: center; +} + +.event-materials-component .material-file-input-wrapper .preview-wrapper { + width: 100%; + height: 100%; + position: relative; + z-index: 1; +} + +.event-materials-component .material-file-input-wrapper .preview-wrapper .icon-delete { + position: absolute; + top: 8px; + right: 8px; + cursor: pointer; +} + +.event-materials-component .material-file-input-wrapper .preview-material-placeholder { + height: 100%; + overflow: hidden; + border-radius: 8px; + +} + +.event-materials-component .material-file-input-wrapper .preview-material-placeholder img { + height: 100%; + width: 100%; + object-fit: cover; +} + +.event-materials-component .material-file-input-wrapper label { + border-radius: 8px; + display: flex; + flex-direction: column; + box-sizing: border-box; + align-items: center; + padding: 16px; + height: 100%; + width: 100%; + line-height: var(--type-body-xxs-lh); +} + +.event-materials-component .material-file-input-wrapper label:hover { + background-color: var(--color-gray-100); +} + +.event-materials-component .material-file-input-wrapper label input.material-file-input { + display: none; +} + +.event-materials-component .material-file-input-wrapper label .icon { + width: 40px; + opacity: 0.5; +} + +.event-materials-component .material-file-input-wrapper label p { + margin: 0; + font-size: var(--type-body-xs-size);; +} + +.event-materials-component .material-file-input-wrapper .hidden { + display: none; +} + +.event-materials-component .attr-text { + text-align: right; +} + +@media (min-width: 900px) { + .event-materials-component .file-dropzones { + grid-template-columns: 1fr 1fr 1fr; + } +} diff --git a/blocks/event-materials-component/event-materials-component.js b/blocks/event-materials-component/event-materials-component.js new file mode 100644 index 00000000..c02933c5 --- /dev/null +++ b/blocks/event-materials-component/event-materials-component.js @@ -0,0 +1,88 @@ +import { getLibs } from '../../scripts/utils.js'; +import { getIcon, generateToolTip } from '../../utils/utils.js'; + +const { createTag } = await import(`${getLibs()}/utils/utils.js`); + +async function decorateSWCTextField(row, id, extraOptions) { + const miloLibs = getLibs(); + await Promise.all([ + import(`${miloLibs}/deps/lit-all.min.js`), + import(`${miloLibs}/features/spectrum-web-components/dist/textfield.js`), + ]); + + row.classList.add('text-field-row'); + + const existingFileInput = document.querySelectorAll('.material-file-input'); + const cols = row.querySelectorAll(':scope > div'); + if (!cols.length) return; + const [placeholderCol, maxLengthCol] = cols; + const text = placeholderCol.textContent.trim(); + + let maxCharNum, attrTextEl; + if (maxLengthCol) { + attrTextEl = createTag('div', { class: 'attr-text' }, maxLengthCol.textContent.trim()); + maxCharNum = maxLengthCol.querySelector('strong')?.textContent.trim(); + } + + const isRequired = attrTextEl?.textContent.trim().endsWith('*'); + + const input = createTag('sp-textfield', { + id, class: 'text-input', placeholder: text, ...extraOptions + }); + + if (isRequired) input.required = true; + + if (maxCharNum) input.setAttribute('maxlength', maxCharNum); + + const wrapper = createTag('div', { class: 'info-field-wrapper' }); + row.innerHTML = ''; + wrapper.append(input); + if (attrTextEl) wrapper.append(attrTextEl); + row.append(wrapper); +} + +function decorateFileDropzone(row) { + row.classList.add('file-dropzones'); + const cols = row.querySelectorAll(':scope > div'); + const dropzones = []; + + cols.forEach((c, i) => { + c.classList.add('file-dropzone'); + const text = c.textContent.trim(); + const existingFileInputs = document.querySelectorAll('.material-file-input'); + const inputId = `material-file-input-${existingFileInputs.length + i + 1}`; + const fileInput = createTag('input', { id: inputId, type: 'file', class: 'material-file-input' }); + const inputWrapper = createTag('div', { class: 'material-file-input-wrapper' }); + const inputLabel = createTag('label', { class: 'material-file-input-label' }); + + const previewWrapper = createTag('div', { class: 'preview-wrapper hidden' }); + const previewImg = createTag('div', { class: 'preview-img-placeholder' }); + const previewDeleteButton = getIcon('delete'); + + previewWrapper.append(previewImg, previewDeleteButton); + + inputWrapper.append(previewWrapper, inputLabel); + inputLabel.append(fileInput, getIcon('upload-cloud'), text); + dropzones.push(inputWrapper); + }) + + row.innerHTML = ''; + dropzones.forEach((dz) => { + row.append(dz); + }); +} + +export default function init(el) { + const existingFileInputs = document.querySelectorAll('.event-materials-component'); + const blockIndex = Array.from(existingFileInputs).findIndex((b) => b === el); + + el.classList.add('form-component'); + generateToolTip(el); + + const rows = el.querySelectorAll(':scope > div'); + rows.forEach(async (r, i) => { + if (i === 0) decorateFileDropzone(r); + if (i === 1) await decorateSWCTextField(r, `event-material-url-${blockIndex}`); + if (i === 2) await decorateSWCTextField(r, `event-material-name-${blockIndex}`, { quiet: true, size: 'xl' }); + }); +} diff --git a/blocks/form-handler/form-handler.css b/blocks/form-handler/form-handler.css index 36f2bdd4..c788123c 100644 --- a/blocks/form-handler/form-handler.css +++ b/blocks/form-handler/form-handler.css @@ -75,6 +75,10 @@ box-shadow: 0 3px 6px 0 rgb(0 0 0 / 16%); } +.form-handler .main-frame .section .content { + max-width: none; +} + .form-handler .fragment.hidden { display: none; } diff --git a/blocks/img-upload-component/img-upload-component.js b/blocks/img-upload-component/img-upload-component.js index cc3b3d27..acab2cf4 100644 --- a/blocks/img-upload-component/img-upload-component.js +++ b/blocks/img-upload-component/img-upload-component.js @@ -13,8 +13,8 @@ function decorateImageDropzones(row) { const uploadName = c.querySelector(':scope > p:first-of-type')?.textContent.trim(); const paragraphs = c.querySelectorAll(':scope > p'); const existingFileInput = document.querySelectorAll('.img-file-input'); - const inputId = uploadName ? `${handlize(uploadName)}` : `img-file-input-${existingFileInput.length + i}`; - const fileInput = createTag('input', { id: inputId, type: 'file', class: 'img-file-input' }); + const inputId = uploadName ? `${handlize(uploadName)}` : `img-file-input-${existingFileInput.length + i + 1}`; + const fileInput = createTag('input', { id: inputId, type: 'file', class: 'img-file-input', accept: 'image/png, image/jpeg' }); const inputWrapper = createTag('div', { class: 'img-file-input-wrapper' }); const inputLabel = createTag('label', { class: 'img-file-input-label' }); diff --git a/icons/cross.svg b/icons/cross.svg new file mode 100644 index 00000000..c303303a --- /dev/null +++ b/icons/cross.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/icons/upload-cloud.svg b/icons/upload-cloud.svg new file mode 100644 index 00000000..f7a24eff --- /dev/null +++ b/icons/upload-cloud.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/utils/utils.js b/utils/utils.js index b4b2cd4e..b1b5365c 100644 --- a/utils/utils.js +++ b/utils/utils.js @@ -43,13 +43,10 @@ export function addTooltipToHeading(em, heading) { export function generateToolTip(formComponent) { const heading = formComponent.querySelector(':scope > div:first-of-type h2, :scope > div:first-of-type h3'); + const em = formComponent.querySelector('p > em'); - if (heading) { - const em = formComponent.querySelector('p > em'); - - if (em) { - addTooltipToHeading(em, heading); - } + if (heading && em) { + addTooltipToHeading(em, heading); } }