diff --git a/assets/src/components/PresentationCards.js b/assets/src/components/PresentationCards.js new file mode 100644 index 0000000000..909cf9acee --- /dev/null +++ b/assets/src/components/PresentationCards.js @@ -0,0 +1,446 @@ +/** + * @module components/PresentationCards.js + * @name PresentationCards + * @copyright 2023 3Liz + * @author DOUCHIN Michaël + * @license MPL-2.0 + */ + +import { mainLizmap } from '../modules/Globals.js'; + +/** + * @class + * @name PresentationCards + * @augments HTMLElement + */ +export default class PresentationCards extends HTMLElement { + + constructor() { + super(); + + // Id of the component + this.id = this.getAttribute('id'); + + // Presentations + this.presentations = []; + + // Attribute to force the refresh of data + // Store the last refresh timestamp + this.updated = this.getAttribute('updated'); + + // Presentation which must be shown + this.detail = this.getAttribute('detail'); + } + + async load() { + + // Get presentations related to the element scope from query + mainLizmap.presentation.getPresentations() + .then(data => { + // Set property + this.presentations = data; + + // Render + this.render(); + + // If a presentation was running, display it again + if (mainLizmap.presentation.ACTIVE_LIZMAP_PRESENTATION !== null) { + // Get active page + mainLizmap.presentation.runLizmapPresentation( + mainLizmap.presentation.ACTIVE_LIZMAP_PRESENTATION, + mainLizmap.presentation.LIZMAP_PRESENTATION_ACTIVE_PAGE_NUMBER + ); + } + }) + .catch(err => console.log(err)) + + } + + getFieldDisplayHtml(field, fieldValue) { + let fieldHtml = fieldValue; + if (['background_image', 'illustration_media'].includes(field)) { + const mediaUrl = `${lizUrls.media}?repository=${lizUrls.params.repository}&project=${lizUrls.params.project}&path=`; + const fileExtension = fieldValue.split('.').pop().toLowerCase(); + if (['webm', 'mp4'].includes(fileExtension)) { + fieldHtml = ` + + `; + } else if (['png', 'webp', 'jpeg', 'jpg', 'gif'].includes(fileExtension)) { + fieldHtml = ``; + } else { + fieldHtml = fieldValue; + } + } + + return fieldHtml; + } + + render() { + + // Check if a specific presentation must be shown + const activePresentationId = parseInt(this.getAttribute('detail')); + + // Remove previous content + this.innerHTML = ''; + + // Get the base content of a card from the template + const createTemplate = document.getElementById('lizmap-presentation-create-button-template'); + this.innerHTML = createTemplate.innerHTML; + + // Get the base content of a card from the template + const cardTemplate = document.getElementById('lizmap-presentation-card-template'); + for (const a in this.presentations) { + // Get the presentation + const presentation = this.presentations[a]; + + // Create the div and fill it with the template content + let div = document.createElement("div"); + div.classList.add('lizmap-presentation-card'); + div.dataset.id = presentation.id; + + // Change display if given presentation ID is an integer + let cardDisplay = 'normal'; + if (activePresentationId > 0) { + const currentPresentationId = parseInt(presentation.id); + const isDetail = (parseInt(currentPresentationId) == activePresentationId); + if (isDetail) { + cardDisplay = 'detail'; + } else { + cardDisplay = 'none'; + } + } + div.dataset.display = cardDisplay; + div.innerHTML = cardTemplate.innerHTML; + + // Title + div.querySelector('h3.lizmap-presentation-title').innerHTML = ` + ${presentation.title}${presentation.id} + `; + + // Description + div.querySelector('p.lizmap-presentation-description').innerHTML = presentation.description; + + // Detailed information + const table = div.querySelector('table.presentation-detail-table'); + const fields = [ + 'background_color', 'background_image', + 'footer', 'published', 'granted_groups', + 'created_by', 'created_at', 'updated_by', 'updated_at' + ]; + fields.forEach(field => { + let fieldValue = (!presentation[field]) ? '' : presentation[field]; + const fieldHtml = this.getFieldDisplayHtml(field, fieldValue); + table.querySelector(`td.presentation-detail-${field}`).innerHTML = fieldHtml; + }) + + // Buttons + const detailButton = div.querySelector('button.liz-presentation-detail') + detailButton.value = presentation.id; + detailButton.innerText = (cardDisplay != 'detail') ? detailButton.dataset.label : detailButton.dataset.reverseLabel; + div.querySelector('button.liz-presentation-edit').value = presentation.id; + div.querySelector('button.liz-presentation-delete').value = presentation.id; + div.querySelector('button.liz-presentation-launch').value = presentation.id; + div.querySelector('button.liz-presentation-create.page').value = presentation.id; + + // Add pages preview (small vertical view of mini pages) + this.renderPagesPreview(div, presentation); + + // Add the card to the parent + this.appendChild(div); + } + + // Add click event on the create button + const createButtons = this.querySelectorAll("button.liz-presentation-create"); + Array.from(createButtons).forEach(createButton => { + createButton.addEventListener("click", this.onButtonCreateClick); + }); + + // Add click event on the presentation cards buttons + const buttons = this.querySelectorAll("div.lizmap-presentation-card-toolbar button, div.lizmap-presentation-page-preview-toolbar button"); + Array.from(buttons).forEach(button => { + const classes = button.classList; + if (classes.contains('liz-presentation-edit')) { + button.addEventListener('click', this.onButtonEditClick); + } else if (classes.contains('liz-presentation-delete')) { + button.addEventListener('click', this.onButtonDeleteClick); + } else if (classes.contains('liz-presentation-detail')) { + button.addEventListener('click', this.onButtonDetailClick); + } else if (classes.contains('liz-presentation-launch')) { + button.addEventListener('click', this.onButtonLaunchClick); + } + }); + } + + /** + * Render a presentation page preview + * + * @param {Object} page Presentation page object + * + * @return {HTMLDivElement} Page div to insert in the list + */ + getPagePreview(page) { + const pagePreviewTemplate = document.getElementById('lizmap-presentation-page-preview-template'); + const previewHtml = pagePreviewTemplate.innerHTML; + + let pageDiv = document.createElement('div'); + pageDiv.classList.add('lizmap-presentation-page-preview'); + pageDiv.dataset.presentationId = page.presentation_id; + pageDiv.dataset.pageId = page.id; + pageDiv.dataset.pageOrder = page.page_order; + pageDiv.innerHTML = previewHtml; + pageDiv.querySelector('h3.lizmap-presentation-page-preview-title').innerHTML = ` + ${page.title}${page.page_order} + `; + + // Detailed information + const pageTable = pageDiv.querySelector('table.presentation-detail-table'); + const pageFields = [ + // 'description' + //, 'background_image' + ]; + pageFields.forEach(field => { + const pageTd = pageTable.querySelector(`td.presentation-page-${field}`); + if (pageTd) { + let pageFieldValue = (!page[field]) ? '' : page[field]; + const pageFieldHtml = this.getFieldDisplayHtml(field, pageFieldValue); + pageTd.innerHTML = pageFieldHtml; + } + }) + + pageDiv.querySelector('button.liz-presentation-edit').value = page.id; + pageDiv.querySelector('button.liz-presentation-delete').value = page.id; + + return pageDiv; + } + + /** + * Render the pages preview of the given presentation + * and add content in the given presentation div + * + * @param {HTMLDivElement} presentationDiv Div which must contain the pages preview + * @param {Object} presentation Presentation properties + */ + renderPagesPreview(presentationDiv, presentation) { + let pageContainer = presentationDiv.querySelector('div.lizmap-presentation-card-pages-preview'); + presentation.pages.forEach(page => { + const pagePreviewDiv = this.getPagePreview(page); + pagePreviewDiv.setAttribute('draggable', 'true'); + pagePreviewDiv.addEventListener('dragstart', onDragStart) + pagePreviewDiv.addEventListener('drop', OnDropped) + pagePreviewDiv.addEventListener('dragenter', onDragEnter) + pagePreviewDiv.addEventListener('dragover', onDragOver) + pageContainer.appendChild(pagePreviewDiv); + }) + + // Utility functions for drag & drop capability + function onDragStart (e) { + const index = [].indexOf.call(e.target.parentElement.children, e.target); + e.dataTransfer.setData('text/plain', index) + } + + function onDragEnter (e) { + cancelDefault(e); + } + + function onDragOver (e) { + cancelDefault(e); + } + + function OnDropped (e) { + cancelDefault(e) + + // Get item + const item = e.currentTarget; + + // Get dragged item old and new index + const oldIndex = e.dataTransfer.getData('text/plain'); + const newIndex = [].indexOf.call(item.parentElement.children, item); + + // Get the dropped item + const dropped = item.parentElement.children[oldIndex]; + + // Move the dropped items at new place + if (newIndex < oldIndex) { + item.before(dropped); + } else { + item.after(dropped); + } + + // Recalculate page numbers + let i = 1; + let pagesNumbers = {}; + let pagesIds = []; + let presentationId = null; + for (const child of item.parentElement.children) { + if (!child.classList.contains('lizmap-presentation-page-preview')) { + continue; + } + const pageOrder = i; + child.dataset.pageOrder = pageOrder; + child.querySelector('h3.lizmap-presentation-page-preview-title > span').innerText = pageOrder; + presentationId = child.dataset.presentationId; + const pageId = child.dataset.pageId; + pagesNumbers[pageId] = pageOrder; + pagesIds.push(pageId); + i++; + } + + if (presentationId !== null) { + // Set the component presentation pages object + const presentation = mainLizmap.presentation.getPresentationById(presentationId); + let newPages = []; + for (const i in pagesIds) { + const correspondingPage = presentation.pages.find(x => x.id === pagesIds[i]); + newPages.push(correspondingPage); + } + presentation.pages = newPages; + + // Send new pagesNumbers to the server + mainLizmap.presentation.setPresentationPagination(presentationId, pagesNumbers) + .then(data => { + console.log('pagination modifiée'); + + }) + .catch(err => console.log(err)) + } + } + + function cancelDefault (e) { + e.preventDefault(); + e.stopPropagation(); + + return false; + } + + } + + connectedCallback() { + this.load(); + } + + static get observedAttributes() { return ['updated']; } + + attributeChangedCallback(name, oldValue, newValue) { + // Listen to the change of the updated attribute + // This will trigger the load (refresh the content) + if (name === 'updated') { + this.load(); + } + } + + getPresentationById(presentationId) { + for (let a in this.presentations) { + let presentation = this.presentations[a]; + if (presentation.id == presentationId) { + return presentation; + } + } + + return null; + } + + onButtonCreateClick(event) { + // Get the host component + const host = event.target.closest("lizmap-presentation-cards"); + const button = event.currentTarget; + const item = (button.classList.contains('presentation')) ? 'presentation' : 'page'; + if (item == 'presentation') { + mainLizmap.presentation.launchPresentationCreationForm(item); + } else { + const presentation_id = button.value; + mainLizmap.presentation.launchPresentationCreationForm(item, null, presentation_id); + } + } + + onButtonEditClick(event) { + const button = event.currentTarget; + const item = (button.classList.contains('presentation')) ? 'presentation' : 'page'; + const id = button.value; + mainLizmap.presentation.launchPresentationCreationForm(item, id); + } + + onButtonDetailClick(event) { + const host = event.target.closest("lizmap-presentation-cards"); + const button = event.currentTarget; + const presentationId = button.value; + + // Chosen card + const chosenCard = host.querySelector(`[data-id='${presentationId}']`); + const isActive = (chosenCard.dataset.display == 'detail'); + + // Set other cards status + host.presentations.forEach(item => { + const card = host.querySelector(`[data-id='${item.id}']`); + const display = (isActive) ? 'normal' : 'none'; + card.dataset.display = display; + }) + + // Set the clicked card display property + chosenCard.dataset.display = (isActive) ? 'normal' : 'detail'; + + // Set its detail button label & title + button.innerText = (isActive) ? button.dataset.label : button.dataset.reverseLabel; + button.setAttribute('title', (isActive) ? button.dataset.title : button.dataset.reverseTitle); + + // Set the full panel class + const parentDiv = document.getElementById('presentation-container'); + parentDiv.dataset.display = (isActive) ? 'normal' : 'detail'; + } + + onButtonDeleteClick(event) { + const host = event.target.closest("lizmap-presentation-cards"); + const button = event.currentTarget; + const item = (button.classList.contains('presentation')) ? 'presentation' : 'page'; + const id = button.value; + // Confirmation message + const confirmMessage = button.dataset.confirm; + const areYourSure = window.confirm(confirmMessage); + if (!areYourSure) { + return false; + } + mainLizmap.presentation.deletePresentation(item, id); + + // Set all presentations visible + const parentDiv = document.getElementById('presentation-container'); + parentDiv.dataset.display = 'normal'; + host.presentations.forEach(item => { + const card = host.querySelector(`[data-id='${item.id}']`); + card.dataset.display = 'normal'; + }) + } + + onButtonLaunchClick(event) { + const host = event.target.closest("lizmap-presentation-cards"); + const button = event.currentTarget; + const presentationId = button.value; + + // Get presentation item + const presentation = host.getPresentationById(presentationId); + if (presentation === null) { + return false; + } + + mainLizmap.presentation.runLizmapPresentation(presentationId); + } + + + disconnectedCallback() { + // Remove click events on the presentation buttons + const createButton = this.querySelector("button.liz-presentation-create"); + createButton.removeEventListener("click", this.onButtonCreateClick); + const buttons = this.querySelectorAll("div.lizmap-presentation-card-toolbar button, div.lizmap-presentation-page-preview-toolbar button"); + Array.from(buttons).forEach(button => { + if (button.classList.contains('liz-presentation-edit')) { + button.removeEventListener('click', this.onButtonEditClick); + } else if (button.classList.contains('liz-presentation-delete')) { + button.removeEventListener('click', this.onButtonDeleteClick); + } else if (button.classList.contains('liz-presentation-detail')) { + button.removeEventListener('click', this.onButtonDetailClick); + } else if (button.classList.contains('liz-presentation-launch')) { + button.removeEventListener('click', this.onButtonLaunchClick); + } + }); + } +} diff --git a/assets/src/components/PresentationPage.js b/assets/src/components/PresentationPage.js new file mode 100644 index 0000000000..dba516dc70 --- /dev/null +++ b/assets/src/components/PresentationPage.js @@ -0,0 +1,252 @@ +/** + * @module components/PresentationPage.js + * @name PresentationPage + * @copyright 2023 3Liz + * @author DOUCHIN Michaël + * @license MPL-2.0 + */ + +import { mainLizmap } from '../modules/Globals.js'; + +/** + * @class + * @name PresentationPage + * @augments HTMLElement + */ +export default class PresentationPage extends HTMLElement { + + constructor() { + super(); + + // UUID of the page + this.uuid = this.getAttribute('data-uuid'); + + // Page number + this.number = this.getAttribute('data-page-number'); + + // Page visibility + this.active = this.getAttribute('data-active'); + + // Presentation properties + this._presentation = null; + + // Properties + this._properties = null; + } + + load() { + this.render(); + } + + render() { + // Base URL for media files + const mediaUrl = `${lizUrls.media}?repository=${lizUrls.params.repository}&project=${lizUrls.params.project}&path=`; + + // Remove previous content + this.innerHTML = ''; + + // Get the base content of a card from the template + const pageTemplate = document.getElementById('lizmap-presentation-page-template'); + this.innerHTML = pageTemplate.innerHTML; + + // Set the content of the child HTML elements + if (this._properties === null) { + return; + } + + // Anchor + const pageAnchor = this.querySelector('a.lizmap-presentation-page-anchor'); + pageAnchor.setAttribute('name', this._properties['page_order']); + + // Toolbar buttons + const editButton = this.querySelector('button.liz-presentation-edit.page'); + editButton.value = this._properties['id']; + if (editButton) { + editButton.addEventListener('click', function(event) { + const button = event.currentTarget; + const id = button.value; + mainLizmap.presentation.launchPresentationCreationForm('page', id); + }); + } + // title of the page + const pageTitle = this.querySelector('h2.lizmap-presentation-page-title'); + pageTitle.innerHTML = this._properties['title']; + + // Content + const pageContent = this.querySelector('div.lizmap-presentation-page-content'); + const textDiv = pageContent.querySelector('.lizmap-presentation-page-text'); + const illustrationDiv = pageContent.querySelector('.lizmap-presentation-page-media'); + + // Page model + const pageModel = this._properties['model']; + + // Description + const description = this._properties['description']; + if (pageModel != 'media') { + textDiv.innerHTML = description; + } + + // Illustration + let illustrationHtml = ''; + let illustrationValue = ''; + if (pageModel != 'text') { + switch (this._properties['illustration_type']) { + case 'none': + case '': + case null: + break; + case 'image': + illustrationValue = this._properties['illustration_media']; + illustrationHtml = ` +
+ `; + break; + case 'video': + illustrationValue = this._properties['illustration_media']; + illustrationHtml = ` + + `; + break; + case 'iframe': + illustrationValue = this._properties['illustration_url']; + illustrationHtml = ` +