From 1ec80db1ba58888423ba119613633a0b8d09b350 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Tue, 11 Jun 2024 12:04:31 -0400 Subject: [PATCH 01/18] Initial farmOS timeline module. --- modules/core/timeline/farm_timeline.info.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 modules/core/timeline/farm_timeline.info.yml diff --git a/modules/core/timeline/farm_timeline.info.yml b/modules/core/timeline/farm_timeline.info.yml new file mode 100644 index 0000000000..89d9971cf9 --- /dev/null +++ b/modules/core/timeline/farm_timeline.info.yml @@ -0,0 +1,5 @@ +name: farmOS Timeline +description: Provides farmOS timeline features. +type: module +package: farmOS +core_version_requirement: ^10 From 77f62c171b8ac22335a521ffd13bd61fe83e9909 Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Tue, 11 Jun 2024 13:43:16 -0400 Subject: [PATCH 02/18] Add timeline row and task data type plugins/definitions. These are copied directly from the contrib farm_crop_plan module, originally authored by @paul121. --- .../src/Plugin/DataType/TimelineRow.php | 36 ++++++++++++ .../src/Plugin/DataType/TimelineTask.php | 40 +++++++++++++ .../src/TypedData/TimelineRowDefinition.php | 54 +++++++++++++++++ .../src/TypedData/TimelineTaskDefinition.php | 58 +++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 modules/core/timeline/src/Plugin/DataType/TimelineRow.php create mode 100644 modules/core/timeline/src/Plugin/DataType/TimelineTask.php create mode 100644 modules/core/timeline/src/TypedData/TimelineRowDefinition.php create mode 100644 modules/core/timeline/src/TypedData/TimelineTaskDefinition.php diff --git a/modules/core/timeline/src/Plugin/DataType/TimelineRow.php b/modules/core/timeline/src/Plugin/DataType/TimelineRow.php new file mode 100644 index 0000000000..580093cc64 --- /dev/null +++ b/modules/core/timeline/src/Plugin/DataType/TimelineRow.php @@ -0,0 +1,36 @@ + FALSE, + 'expanded' => FALSE, + ]; + parent::setValue($values); + } + +} diff --git a/modules/core/timeline/src/Plugin/DataType/TimelineTask.php b/modules/core/timeline/src/Plugin/DataType/TimelineTask.php new file mode 100644 index 0000000000..396f98576f --- /dev/null +++ b/modules/core/timeline/src/Plugin/DataType/TimelineTask.php @@ -0,0 +1,40 @@ +parent instanceof ItemList && $this->parent->parent instanceof TimelineRow) { + $values['resource_id'] = $this->parent->parent->get('id')->getValue(); + } + + // Set default values. + $values += [ + 'enable_dragging' => FALSE, + ]; + parent::setValue($values); + } + +} diff --git a/modules/core/timeline/src/TypedData/TimelineRowDefinition.php b/modules/core/timeline/src/TypedData/TimelineRowDefinition.php new file mode 100644 index 0000000000..8544679551 --- /dev/null +++ b/modules/core/timeline/src/TypedData/TimelineRowDefinition.php @@ -0,0 +1,54 @@ +propertyDefinitions)) { + + $this->propertyDefinitions['id'] = DataDefinition::create('string') + ->setLabel($this->t('ID')) + ->setRequired(TRUE); + + $this->propertyDefinitions['label'] = DataDefinition::create('string') + ->setLabel($this->t('Label')) + ->setRequired(TRUE); + + $this->propertyDefinitions['link'] = DataDefinition::create('uri') + ->setLabel($this->t('Link')); + + $this->propertyDefinitions['enable_dragging'] = DataDefinition::create('boolean') + ->setLabel($this->t('Enable dragging')) + ->addConstraint('NotNull'); + + $this->propertyDefinitions['classes'] = ListDataDefinition::create('string') + ->setLabel($this->t('Classes')); + + $this->propertyDefinitions['tasks'] = ListDataDefinition::create('farm_timeline_task') + ->setLabel($this->t('Tasks')); + + $this->propertyDefinitions['expanded'] = DataDefinition::create('boolean') + ->setLabel($this->t('Expanded')); + + $this->propertyDefinitions['children'] = ListDataDefinition::create('farm_timeline_row') + ->setLabel($this->t('Children')); + + } + return $this->propertyDefinitions; + } + +} diff --git a/modules/core/timeline/src/TypedData/TimelineTaskDefinition.php b/modules/core/timeline/src/TypedData/TimelineTaskDefinition.php new file mode 100644 index 0000000000..9c542231c7 --- /dev/null +++ b/modules/core/timeline/src/TypedData/TimelineTaskDefinition.php @@ -0,0 +1,58 @@ +propertyDefinitions)) { + + $this->propertyDefinitions['id'] = DataDefinition::create('string') + ->setLabel($this->t('ID')) + ->setRequired(TRUE); + + $this->propertyDefinitions['resource_id'] = DataDefinition::create('string') + ->setLabel($this->t('Resource ID')) + ->setRequired(TRUE); + + $this->propertyDefinitions['label'] = DataDefinition::create('string') + ->setLabel($this->t('Label')); + + $this->propertyDefinitions['edit_url'] = DataDefinition::create('uri') + ->setLabel($this->t('Edit URL')); + + $this->propertyDefinitions['start'] = DataDefinition::create('timestamp') + ->setLabel($this->t('Start Time')) + ->setRequired(TRUE); + + $this->propertyDefinitions['end'] = DataDefinition::create('timestamp') + ->setLabel($this->t('End Time')) + ->setRequired(TRUE); + + $this->propertyDefinitions['enable_dragging'] = DataDefinition::create('boolean') + ->setLabel($this->t('Enable dragging')); + + $this->propertyDefinitions['meta'] = DataDefinition::create('any') + ->setLabel($this->t('Meta')); + + $this->propertyDefinitions['classes'] = ListDataDefinition::create('string') + ->setLabel($this->t('Classes')); + + } + return $this->propertyDefinitions; + } + +} From 433a0b1a224fe465ae1ca0483a9b505f8a92e026 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Tue, 11 Jun 2024 13:46:17 -0400 Subject: [PATCH 03/18] Add timeline library JS + CSS. These are copied directly from the contrib farm_crop_plan module. Only crop plan specific CSS styles have been removed. --- modules/core/timeline/css/timeline.css | 105 +++++++++ .../core/timeline/farm_timeline.libraries.yml | 20 ++ modules/core/timeline/js/timeline.js | 212 ++++++++++++++++++ 3 files changed, 337 insertions(+) create mode 100644 modules/core/timeline/css/timeline.css create mode 100644 modules/core/timeline/farm_timeline.libraries.yml create mode 100644 modules/core/timeline/js/timeline.js diff --git a/modules/core/timeline/css/timeline.css b/modules/core/timeline/css/timeline.css new file mode 100644 index 0000000000..3976692a47 --- /dev/null +++ b/modules/core/timeline/css/timeline.css @@ -0,0 +1,105 @@ +/* Set the width to 100% instead of using flex. This prevents auto-expanding width of timeline. */ +.layout__region--top { + width: 100%; +} + +.sg-table-header-cell, +.column-header-cell { + font: var(--gin-font) !important; +} +.sg-table-header-cell { + font-size: var(--gin-font-size-m) !important; + font-weight: var(--gin-font-weight-bold) !important; +} +.column-header-cell { + font-size: var(--gin-font-size-s) !important; + font-weight: var(--gin-font-weight-normal) !important; +} + +.sg-task { + --task-bg-color: #74bfff; + box-shadow: var(--gin-shadow-l1); + background-color: var(--task-bg-color) !important; + border-left: 2px solid #666666; +} +.sg-task:hover { + background-color: var(--task-bg-color) !important; +} +.sg-task-selected { + box-shadow: inset 0 0 0 1px var(--gin-color-focus-border), inset 0 0 0 4px var(--gin-color-focus), var(--gin-shadow-l2); +} + +.sg-task .sg-task-content { + color: var(--gin-color-text-light); +} + +.sg-task.last-location, +.sg-task.last-location:hover { + background-color: unset !important; + background: linear-gradient(90deg, var(--task-bg-color), rgba(255,255,255,0)); +} + +.sg-task.log { + margin-top: 4px; + margin-left: -5px; + width: 10px !important; + height: 10px !important; + background-color: var(--task-bg-color); + border: 2px solid var(--task-bg-color); + border-radius: 50% !important; +} +.sg-task.log.sg-task-selected { + --task-bg-color: var(--gin-color-focus); + background-color: var(--task-bg-color); + border: none; +} + +.sg-task.log--status-pending { + background-color: transparent !important; +} + +.sg-task.log--activity { + --task-bg-color: #f1c40f; +} +.sg-task.log--harvest { + --task-bg-color: #e67e22; +} +.sg-task.log--input { + --task-bg-color: #9b59b6; +} +.sg-task.log--observation { + --task-bg-color: #2980b9; +} +.sg-task.log--seeding { + --task-bg-color: #2ecc71; +} +.sg-task.log--transplanting { + --task-bg-color: #2ecc71; +} + +.sg-popup { + z-index: 1000; + background-color: var(--gin-bg-layer); + border: 1px solid var(--gin-border-color-layer); + box-shadow: var(--gin-shadow-l2); + border-radius: var(--gin-border-s); + padding: var(--gin-spacing-xs); + font-size: var(--gin-font-size-s); +} + +.sg-tree-expander { + /*@TODO do this properly.*/ + mask-image: url('/themes/gin/dist/media/sprite.svg#handle-view'); + -webkit-mask-image: url('/themes/gin/dist/media/sprite.svg#handle-view'); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-position: center center; + -webkit-mask-position: center center; + margin-right: 0.5em; + background-color: var(--gin-color-text); + transform: rotate(90deg); + transition: transform var(--details-transform-transition-duration) ease-in 0s; +} +.sg-row-expanded .sg-tree-expander { + transform: rotate(270deg); +} diff --git a/modules/core/timeline/farm_timeline.libraries.yml b/modules/core/timeline/farm_timeline.libraries.yml new file mode 100644 index 0000000000..5be02814bb --- /dev/null +++ b/modules/core/timeline/farm_timeline.libraries.yml @@ -0,0 +1,20 @@ +timeline: + js: + js/timeline.js: {} + css: + theme: + css/timeline.css: { } + dependencies: + - core/drupal.dialog + - core/drupal.dialog.ajax + - core/once + - farm_timeline/svelte_gantt +svelte_gantt: + remote: https://github.com/ANovokmet/svelte-gantt + version: 4.2.4 + license: + name: MIT + js: + https://cdn.jsdelivr.net/npm/svelte-gantt@4.2.4/index.iife.min.js: + type: external + minified: true diff --git a/modules/core/timeline/js/timeline.js b/modules/core/timeline/js/timeline.js new file mode 100644 index 0000000000..a5bab03228 --- /dev/null +++ b/modules/core/timeline/js/timeline.js @@ -0,0 +1,212 @@ +(function (Drupal, drupalSettings, once) { + Drupal.behaviors.farm_timeline_gantt = { + attach: function (context, settings) { + once('timelineGantt', '#timeline', context).forEach(function (element) { + var gantt = new SvelteGantt({ + target: element, + props: { + target: element, + from: Date.now()-86400, + to: Date.now(), + fitWidth: true, + columnUnit: 'day', + columnOffset: 7, + rowHeight: 34, + rowPadding: 8, + reflectOnParentRows: false, + tableHeaders: [{ title: element.dataset.tableHeader, property: 'label', type: 'tree' }], + tableWidth: 240, + ganttTableModules: [SvelteGanttTable], + headers: [ + {unit: 'year', format: 'YYYY'}, + {unit: 'month', format: 'MM'}, + ], + rows: [ + {id: 'first', label: 'Loading...'} + ], + taskElementHook: (node, task) => { + let popup; + function onHover() { + popup = createPopup(task, node); + } + function onLeave() { + if(popup) { + popup.remove(); + } + } + node.addEventListener('mouseenter', onHover); + node.addEventListener('mouseleave', onLeave); + return { + destroy() { + node.removeEventListener('mouseenter', onHover); + node.removeEventListener('mouseleave', onLeave); + } + } + }, + } + }); + + function createPopup(task, node) { + const rect = node.getBoundingClientRect(); + const div = document.createElement('div'); + div.className = 'sg-popup'; + div.innerHTML = ` +
${task.label}
+
From: ${new Date(task.from).toLocaleDateString()}
+
To: ${new Date(task.to).toLocaleDateString()}
+ `; + div.style.position = 'absolute'; + div.style.top = `${rect.bottom + window.scrollY + 5}px`; + div.style.left = `${rect.left + rect.width / 2}px`; + + if (task?.meta?.entity_type === 'log') { + div.innerHTML = ` +
Log: ${task.meta.label}
+
Type: ${task.meta.entity_bundle}
+
Timestamp: ${new Date(task.from).toLocaleDateString()}
+ `; + } + + if (task?.meta?.stage) { + div.innerHTML = ` +
Stage: ${task.meta.stage}
+
From: ${new Date(task.from).toLocaleDateString()}
+
To: ${new Date(task.to).toLocaleDateString()}
+ `; + } + + document.body.appendChild(div); + return div; + } + + // Open entity page on click. + gantt.api.tasks.on.select((task) => { + task = task[0]; + if (task.model?.editUrl) { + var ajaxSettings = { + url: task.model.editUrl, + dialogType: 'dialog', + dialogRenderer: 'off_canvas', + }; + var myAjaxObject = Drupal.ajax(ajaxSettings); + myAjaxObject.execute(); + } else { + let dialog = document.getElementById('drupal-off-canvas'); + if (dialog) { + Drupal.dialog(dialog, {}).close(); + } + } + }); + + // Helper function to map row properties. + const mapRow = function(row) { + return { + id: row.id, + label: row.label, + headerHtml: row.link, + expanded: row.expanded, + }; + } + + // Helper function to map task properties. + const mapTask = function(task) { + return { + id: task.id, + resourceId: task.resource_id, + from: new Date(task.start), + to: new Date(task.end), + label: ' ', //task.label, + editUrl: task.edit_url, + enableDragging: task.enable_dragging, + meta: task?.meta, + classes: task.classes, + }; + } + + // Helper function to process a row. + // Collect tasks and child rows and child tasks. + const processRow = function(row) { + + // Map to a row object. + let mappedRow = mapRow(row); + + // Collect all tasks for the row. + let tasks = row?.tasks?.map(mapTask) ?? []; + + // Process children rows. + // Only create the children array if there are child rows. + let processedChildren = row?.children?.map(processRow) ?? []; + if (processedChildren.length) { + mappedRow.children = []; + processedChildren.forEach((child) => { + mappedRow.children.push(child.row); + tasks.push(...child.tasks) + }); + } + + return {row: mappedRow, tasks}; + } + + // Build a URL to the plan timeline API. + const url = new URL(element.dataset.timelineUrl, window.location.origin + drupalSettings.path.baseUrl); + const response = fetch(url) + .then(res => res.json()) + .then(data => { + + // Build new list of rows/tasks. + const rows = []; + const tasks = []; + + // Process each row. + for (let i in data.rows) { + let row = processRow(data.rows[i]); + rows.push(row.row); + tasks.push(...row.tasks) + } + + // Keep track of first/last timestamps. + let first = null; + let last = null; + + // Update the first and last from each task. + for (let i in tasks) { + if (!first || tasks[i].from < first) { + first = tasks[i].from; + } + if (!last || tasks[i].to > last) { + last = tasks[i].to; + } + } + + // Define the start, end, and now timestamps. + // Start and end are padded by a week. + const start = first.getTime() - (86400 * 7 * 1000); + const end = last.getTime() + (86400 * 7 * 1000); + const now = new Date().getTime(); + + // If the start of this timeline is in the past, build a time range to + // represent "the past". + let timeRanges = []; + if (start < now) { + timeRanges.push({ + id: 'past', + from: start, + to: now, + label: 'Past', + resizable: false, + }); + } + + // Update gantt. + gantt.$set({ + rows: rows, + tasks: tasks, + timeRanges: timeRanges, + from: start, + to: end, + }); + }); + }); + }, + }; +}(Drupal, drupalSettings, once)); From 47b8fbae4a2f57adf395d60da7f2cc1cb5ce768c Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Tue, 2 Jul 2024 12:55:14 -0700 Subject: [PATCH 04/18] Add farmOS-timeline library --- composer.libraries.json | 18 +++++++++++++++++- .../core/timeline/farm_timeline.libraries.yml | 10 ++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/composer.libraries.json b/composer.libraries.json index e0fa7fb802..d5800509f1 100644 --- a/composer.libraries.json +++ b/composer.libraries.json @@ -1,6 +1,7 @@ { "require": { - "farmos/farmos-map": "*" + "farmos/farmos-map": "*", + "farmos/farmos-timeline": "*" }, "repositories": { "farmos-map": { @@ -17,6 +18,21 @@ "installer-name": "farmOS-map" } } + }, + "farmos-timeline": { + "type": "package", + "package": { + "name": "farmos/farmos-timeline", + "version": "0.0.1", + "type": "drupal-library", + "dist": { + "url": "https://github.com/farmOS/farmOS-timeline/releases/download/v0.0.1/v0.0.1-dist.zip", + "type": "zip" + }, + "extra": { + "installer-name": "farmOS-timeline" + } + } } } } diff --git a/modules/core/timeline/farm_timeline.libraries.yml b/modules/core/timeline/farm_timeline.libraries.yml index 5be02814bb..12ee722b73 100644 --- a/modules/core/timeline/farm_timeline.libraries.yml +++ b/modules/core/timeline/farm_timeline.libraries.yml @@ -1,3 +1,13 @@ +farmOS-timeline: + remote: https://github.com/farmOS/farmOS-timeline + license: + name: MIT + url: https://github.com/farmOS/farmOS-timeline/blob/main/LICENSE + gpl-compatible: true + js: + /libraries/farmOS-timeline/farmOS-timeline.js: + preprocess: false + minified: true timeline: js: js/timeline.js: {} From ae547233821218cb7b8c865c76b6bc28d938cedd Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Tue, 2 Jul 2024 12:55:32 -0700 Subject: [PATCH 05/18] Use farmOS-timeline library --- .../core/timeline/farm_timeline.libraries.yml | 11 +-- modules/core/timeline/js/timeline.js | 78 +++++++++---------- 2 files changed, 36 insertions(+), 53 deletions(-) diff --git a/modules/core/timeline/farm_timeline.libraries.yml b/modules/core/timeline/farm_timeline.libraries.yml index 12ee722b73..ca536c410d 100644 --- a/modules/core/timeline/farm_timeline.libraries.yml +++ b/modules/core/timeline/farm_timeline.libraries.yml @@ -18,13 +18,4 @@ timeline: - core/drupal.dialog - core/drupal.dialog.ajax - core/once - - farm_timeline/svelte_gantt -svelte_gantt: - remote: https://github.com/ANovokmet/svelte-gantt - version: 4.2.4 - license: - name: MIT - js: - https://cdn.jsdelivr.net/npm/svelte-gantt@4.2.4/index.iife.min.js: - type: external - minified: true + - farm_timeline/farmOS-timeline diff --git a/modules/core/timeline/js/timeline.js b/modules/core/timeline/js/timeline.js index a5bab03228..e86dc73cbe 100644 --- a/modules/core/timeline/js/timeline.js +++ b/modules/core/timeline/js/timeline.js @@ -1,50 +1,42 @@ -(function (Drupal, drupalSettings, once) { +(function (Drupal, drupalSettings, once, farmOS) { Drupal.behaviors.farm_timeline_gantt = { attach: function (context, settings) { once('timelineGantt', '#timeline', context).forEach(function (element) { - var gantt = new SvelteGantt({ - target: element, - props: { - target: element, - from: Date.now()-86400, - to: Date.now(), - fitWidth: true, - columnUnit: 'day', - columnOffset: 7, - rowHeight: 34, - rowPadding: 8, - reflectOnParentRows: false, - tableHeaders: [{ title: element.dataset.tableHeader, property: 'label', type: 'tree' }], - tableWidth: 240, - ganttTableModules: [SvelteGanttTable], - headers: [ - {unit: 'year', format: 'YYYY'}, - {unit: 'month', format: 'MM'}, - ], - rows: [ - {id: 'first', label: 'Loading...'} - ], - taskElementHook: (node, task) => { - let popup; - function onHover() { - popup = createPopup(task, node); - } - function onLeave() { - if(popup) { - popup.remove(); - } + const opts = { + columnUnit: 'day', + columnOffset: 7, + rowHeight: 34, + rowPadding: 8, + reflectOnParentRows: false, + headers: [ + {unit: 'year', format: 'YYYY'}, + {unit: 'month', format: 'MMMM'}, + ], + rows: [ + {id: 'first', label: 'Loading...'} + ], + taskElementHook: (node, task) => { + let popup; + function onHover() { + popup = createPopup(task, node); + } + function onLeave() { + if(popup) { + popup.remove(); } - node.addEventListener('mouseenter', onHover); - node.addEventListener('mouseleave', onLeave); - return { - destroy() { - node.removeEventListener('mouseenter', onHover); - node.removeEventListener('mouseleave', onLeave); - } + } + node.addEventListener('mouseenter', onHover); + node.addEventListener('mouseleave', onLeave); + return { + destroy() { + node.removeEventListener('mouseenter', onHover); + node.removeEventListener('mouseleave', onLeave); } - }, - } - }); + } + }, + }; + + const gantt = farmOS.timeline.create(element, opts); function createPopup(task, node) { const rect = node.getBoundingClientRect(); @@ -209,4 +201,4 @@ }); }, }; -}(Drupal, drupalSettings, once)); +}(Drupal, drupalSettings, once, farmOS)); From 64e5c36e9d696eec37aeb7337cc8ad3888d0f8c5 Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Thu, 25 Jul 2024 16:13:11 -0700 Subject: [PATCH 06/18] Use timeline methods to add rows, tasks and highlight past --- modules/core/timeline/js/timeline.js | 111 +++++++-------------------- 1 file changed, 29 insertions(+), 82 deletions(-) diff --git a/modules/core/timeline/js/timeline.js b/modules/core/timeline/js/timeline.js index e86dc73cbe..b70303192a 100644 --- a/modules/core/timeline/js/timeline.js +++ b/modules/core/timeline/js/timeline.js @@ -1,42 +1,35 @@ (function (Drupal, drupalSettings, once, farmOS) { Drupal.behaviors.farm_timeline_gantt = { attach: function (context, settings) { - once('timelineGantt', '#timeline', context).forEach(function (element) { + once('timelineGantt', '.farm-timeline', context).forEach(function (element) { const opts = { - columnUnit: 'day', - columnOffset: 7, - rowHeight: 34, - rowPadding: 8, - reflectOnParentRows: false, - headers: [ - {unit: 'year', format: 'YYYY'}, - {unit: 'month', format: 'MMMM'}, - ], - rows: [ - {id: 'first', label: 'Loading...'} - ], - taskElementHook: (node, task) => { - let popup; - function onHover() { - popup = createPopup(task, node); - } - function onLeave() { - if(popup) { - popup.remove(); + props: { + taskElementHook: (node, task) => { + let popup; + + function onHover() { + popup = createPopup(task, node); } - } - node.addEventListener('mouseenter', onHover); - node.addEventListener('mouseleave', onLeave); - return { - destroy() { - node.removeEventListener('mouseenter', onHover); - node.removeEventListener('mouseleave', onLeave); + + function onLeave() { + if (popup) { + popup.remove(); + } + } + + node.addEventListener('mouseenter', onHover); + node.addEventListener('mouseleave', onLeave); + return { + destroy() { + node.removeEventListener('mouseenter', onHover); + node.removeEventListener('mouseleave', onLeave); + } } } }, }; - const gantt = farmOS.timeline.create(element, opts); + const timeline = farmOS.timeline.create(element, opts); function createPopup(task, node) { const rect = node.getBoundingClientRect(); @@ -72,7 +65,7 @@ } // Open entity page on click. - gantt.api.tasks.on.select((task) => { + timeline.timeline.api.tasks.on.select((task) => { task = task[0]; if (task.model?.editUrl) { var ajaxSettings = { @@ -105,9 +98,9 @@ return { id: task.id, resourceId: task.resource_id, - from: new Date(task.start), - to: new Date(task.end), - label: ' ', //task.label, + from: task.start, + to: task.end, + label: task.label, editUrl: task.edit_url, enableDragging: task.enable_dragging, meta: task?.meta, @@ -145,58 +138,12 @@ .then(res => res.json()) .then(data => { - // Build new list of rows/tasks. - const rows = []; - const tasks = []; - // Process each row. for (let i in data.rows) { - let row = processRow(data.rows[i]); - rows.push(row.row); - tasks.push(...row.tasks) - } - - // Keep track of first/last timestamps. - let first = null; - let last = null; - - // Update the first and last from each task. - for (let i in tasks) { - if (!first || tasks[i].from < first) { - first = tasks[i].from; - } - if (!last || tasks[i].to > last) { - last = tasks[i].to; - } + const {row, tasks} = processRow(data.rows[i]); + timeline.addRows([row]); + timeline.addTasks(tasks); } - - // Define the start, end, and now timestamps. - // Start and end are padded by a week. - const start = first.getTime() - (86400 * 7 * 1000); - const end = last.getTime() + (86400 * 7 * 1000); - const now = new Date().getTime(); - - // If the start of this timeline is in the past, build a time range to - // represent "the past". - let timeRanges = []; - if (start < now) { - timeRanges.push({ - id: 'past', - from: start, - to: now, - label: 'Past', - resizable: false, - }); - } - - // Update gantt. - gantt.$set({ - rows: rows, - tasks: tasks, - timeRanges: timeRanges, - from: start, - to: end, - }); }); }); }, From 097fc84531a88ecdcf1e17390c64cacc12b65c84 Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Thu, 25 Jul 2024 17:31:01 -0700 Subject: [PATCH 07/18] Add farm_timeline render element --- modules/core/timeline/farm_timeline.module | 19 +++++++ .../timeline/src/Element/FarmTimeline.php | 52 +++++++++++++++++++ .../templates/farm-timeline.html.twig | 12 +++++ 3 files changed, 83 insertions(+) create mode 100644 modules/core/timeline/farm_timeline.module create mode 100644 modules/core/timeline/src/Element/FarmTimeline.php create mode 100644 modules/core/timeline/templates/farm-timeline.html.twig diff --git a/modules/core/timeline/farm_timeline.module b/modules/core/timeline/farm_timeline.module new file mode 100644 index 0000000000..694e83de67 --- /dev/null +++ b/modules/core/timeline/farm_timeline.module @@ -0,0 +1,19 @@ + [ + 'variables' => [ + 'attributes' => [], + ], + ], + ]; +} diff --git a/modules/core/timeline/src/Element/FarmTimeline.php b/modules/core/timeline/src/Element/FarmTimeline.php new file mode 100644 index 0000000000..ed4d3fff4c --- /dev/null +++ b/modules/core/timeline/src/Element/FarmTimeline.php @@ -0,0 +1,52 @@ + 'farm_timeline', + '#pre_render' => [ + [get_class($this), 'preRenderTimeline'], + ], + ]; + } + + /** + * Pre-render callback for the timeline render array. + * + * @param array $element + * A renderable array for the timeline. + * + * @return array + * The final render array for the timeline. + */ + public static function preRenderTimeline(array $element): array { + + // Set a timeline ID. + if (empty($element['#attributes']['id'])) { + $element['#attributes']['id'] = Html::getUniqueId('timeline'); + } + + // Add the farm-timeline class. + $element['#attributes']['class'][] = 'farm-timeline'; + + // Attach the farmOS-timeline library. + $element['#attached']['library'][] = 'farm_timeline/farmOS-timeline'; + + return $element; + } + +} diff --git a/modules/core/timeline/templates/farm-timeline.html.twig b/modules/core/timeline/templates/farm-timeline.html.twig new file mode 100644 index 0000000000..bcc164c11f --- /dev/null +++ b/modules/core/timeline/templates/farm-timeline.html.twig @@ -0,0 +1,12 @@ +{# +/** + * @file + * Default theme implementation for the farm_timeline render element. + * + * Available variables: + * - attributes: Attributes for the farm_timeline wrapper div. + * + * @ingroup themeable + */ +#} + From 30f0d3349c07ebce6310d2cc3668764a16b1957f Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Thu, 25 Jul 2024 17:31:47 -0700 Subject: [PATCH 08/18] Add data-timeline-rows attribute --- modules/core/timeline/js/timeline.js | 120 +++++++++--------- .../timeline/src/Element/FarmTimeline.php | 5 + 2 files changed, 62 insertions(+), 63 deletions(-) diff --git a/modules/core/timeline/js/timeline.js b/modules/core/timeline/js/timeline.js index b70303192a..ebc0603670 100644 --- a/modules/core/timeline/js/timeline.js +++ b/modules/core/timeline/js/timeline.js @@ -29,8 +29,42 @@ }, }; + // Create the timeline instance. const timeline = farmOS.timeline.create(element, opts); + // Helper function to process a row data object and + // add the row and its tasks to the timeline. + const processRowData = function(row) { + // Map to a row object. + let mappedRow = Drupal.behaviors.farm_timeline_gantt.mapRow(row); + timeline.addRows([mappedRow]); + + // Collect all tasks for the row. + let tasks = row?.tasks?.map(Drupal.behaviors.farm_timeline_gantt.mapTask) ?? []; + timeline.addTasks(tasks); + + // Process children rows. + row?.children?.forEach(processRow) ?? []; + }; + + // Helper function to process a row provided to the timeline element. + // Rows may be objects or URL strings to request dynamic row data. + const processRow = function(row) { + if (typeof row === "object") { + processRowData(row); + } + else if (typeof row === "string") { + const response = fetch(row) + .then(res => res.json()) + .then(data => data.rows ?? []) + .then(rows => rows.forEach(processRowData)); + } + }; + + // Process timeline rows. + const timelineRows = JSON.parse(element.dataset?.timelineRows) ?? []; + timelineRows.forEach(processRow); + function createPopup(task, node) { const rect = node.getBoundingClientRect(); const div = document.createElement('div'); @@ -82,70 +116,30 @@ } } }); - - // Helper function to map row properties. - const mapRow = function(row) { - return { - id: row.id, - label: row.label, - headerHtml: row.link, - expanded: row.expanded, - }; - } - - // Helper function to map task properties. - const mapTask = function(task) { - return { - id: task.id, - resourceId: task.resource_id, - from: task.start, - to: task.end, - label: task.label, - editUrl: task.edit_url, - enableDragging: task.enable_dragging, - meta: task?.meta, - classes: task.classes, - }; - } - - // Helper function to process a row. - // Collect tasks and child rows and child tasks. - const processRow = function(row) { - - // Map to a row object. - let mappedRow = mapRow(row); - - // Collect all tasks for the row. - let tasks = row?.tasks?.map(mapTask) ?? []; - - // Process children rows. - // Only create the children array if there are child rows. - let processedChildren = row?.children?.map(processRow) ?? []; - if (processedChildren.length) { - mappedRow.children = []; - processedChildren.forEach((child) => { - mappedRow.children.push(child.row); - tasks.push(...child.tasks) - }); - } - - return {row: mappedRow, tasks}; - } - - // Build a URL to the plan timeline API. - const url = new URL(element.dataset.timelineUrl, window.location.origin + drupalSettings.path.baseUrl); - const response = fetch(url) - .then(res => res.json()) - .then(data => { - - // Process each row. - for (let i in data.rows) { - const {row, tasks} = processRow(data.rows[i]); - timeline.addRows([row]); - timeline.addTasks(tasks); - } - }); }); }, + // Helper function to map row properties. + mapRow: function(row) { + return { + id: row.id, + label: row.label, + headerHtml: row.link, + expanded: row.expanded, + }; + }, + // Helper function to map task properties. + mapTask: function(task) { + return { + id: task.id, + resourceId: task.resource_id, + from: task.start, + to: task.end, + label: task.label, + editUrl: task.edit_url, + enableDragging: task.enable_dragging, + meta: task?.meta, + classes: task.classes, + }; + }, }; }(Drupal, drupalSettings, once, farmOS)); diff --git a/modules/core/timeline/src/Element/FarmTimeline.php b/modules/core/timeline/src/Element/FarmTimeline.php index ed4d3fff4c..394517b956 100644 --- a/modules/core/timeline/src/Element/FarmTimeline.php +++ b/modules/core/timeline/src/Element/FarmTimeline.php @@ -2,6 +2,7 @@ namespace Drupal\farm_timeline\Element; +use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\Html; use Drupal\Core\Render\Element\RenderElementBase; @@ -21,6 +22,7 @@ public function getInfo() { '#pre_render' => [ [get_class($this), 'preRenderTimeline'], ], + '#rows' => [], ]; } @@ -40,6 +42,9 @@ public static function preRenderTimeline(array $element): array { $element['#attributes']['id'] = Html::getUniqueId('timeline'); } + // Add timeline rows. + $element['#attributes']['data-timeline-rows'] = Json::encode($element['#rows'] ?? []); + // Add the farm-timeline class. $element['#attributes']['class'][] = 'farm-timeline'; From 96356bd15a4d536bb2e227dda46ca5a79ad6f7c4 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Mon, 29 Jul 2024 13:09:12 -0400 Subject: [PATCH 09/18] Rename timeline library to farm_timeline for consistency with farm_map. --- .../core/timeline/css/{timeline.css => farm_timeline.css} | 0 modules/core/timeline/farm_timeline.libraries.yml | 6 +++--- modules/core/timeline/js/{timeline.js => farm_timeline.js} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename modules/core/timeline/css/{timeline.css => farm_timeline.css} (100%) rename modules/core/timeline/js/{timeline.js => farm_timeline.js} (100%) diff --git a/modules/core/timeline/css/timeline.css b/modules/core/timeline/css/farm_timeline.css similarity index 100% rename from modules/core/timeline/css/timeline.css rename to modules/core/timeline/css/farm_timeline.css diff --git a/modules/core/timeline/farm_timeline.libraries.yml b/modules/core/timeline/farm_timeline.libraries.yml index ca536c410d..3875d8102e 100644 --- a/modules/core/timeline/farm_timeline.libraries.yml +++ b/modules/core/timeline/farm_timeline.libraries.yml @@ -8,12 +8,12 @@ farmOS-timeline: /libraries/farmOS-timeline/farmOS-timeline.js: preprocess: false minified: true -timeline: +farm_timeline: js: - js/timeline.js: {} + js/farm_timeline.js: { } css: theme: - css/timeline.css: { } + css/farm_timeline.css: { } dependencies: - core/drupal.dialog - core/drupal.dialog.ajax diff --git a/modules/core/timeline/js/timeline.js b/modules/core/timeline/js/farm_timeline.js similarity index 100% rename from modules/core/timeline/js/timeline.js rename to modules/core/timeline/js/farm_timeline.js From b69bade4a48e69b96f9d69f79420a624508feac2 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Mon, 29 Jul 2024 13:09:35 -0400 Subject: [PATCH 10/18] Allow farmOS-timeline JS to be preprocessed. --- modules/core/timeline/farm_timeline.libraries.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/core/timeline/farm_timeline.libraries.yml b/modules/core/timeline/farm_timeline.libraries.yml index 3875d8102e..618a101f27 100644 --- a/modules/core/timeline/farm_timeline.libraries.yml +++ b/modules/core/timeline/farm_timeline.libraries.yml @@ -6,7 +6,6 @@ farmOS-timeline: gpl-compatible: true js: /libraries/farmOS-timeline/farmOS-timeline.js: - preprocess: false minified: true farm_timeline: js: From aa273b65a5213edfd4fe644444b7f7f7c3e7535f Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Wed, 31 Jul 2024 13:38:02 -0700 Subject: [PATCH 11/18] Use correct library --- modules/core/timeline/src/Element/FarmTimeline.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/timeline/src/Element/FarmTimeline.php b/modules/core/timeline/src/Element/FarmTimeline.php index 394517b956..f5baeac352 100644 --- a/modules/core/timeline/src/Element/FarmTimeline.php +++ b/modules/core/timeline/src/Element/FarmTimeline.php @@ -48,8 +48,8 @@ public static function preRenderTimeline(array $element): array { // Add the farm-timeline class. $element['#attributes']['class'][] = 'farm-timeline'; - // Attach the farmOS-timeline library. - $element['#attached']['library'][] = 'farm_timeline/farmOS-timeline'; + // Attach the farm_timeline library. + $element['#attached']['library'][] = 'farm_timeline/farm_timeline'; return $element; } From a1df523e6397918b103ea4e017709b103c875195 Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Wed, 31 Jul 2024 13:47:32 -0700 Subject: [PATCH 12/18] Rename behavior to match library name --- modules/core/timeline/js/farm_timeline.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/core/timeline/js/farm_timeline.js b/modules/core/timeline/js/farm_timeline.js index ebc0603670..bd2df11d55 100644 --- a/modules/core/timeline/js/farm_timeline.js +++ b/modules/core/timeline/js/farm_timeline.js @@ -1,5 +1,5 @@ (function (Drupal, drupalSettings, once, farmOS) { - Drupal.behaviors.farm_timeline_gantt = { + Drupal.behaviors.farm_timeline = { attach: function (context, settings) { once('timelineGantt', '.farm-timeline', context).forEach(function (element) { const opts = { @@ -36,11 +36,11 @@ // add the row and its tasks to the timeline. const processRowData = function(row) { // Map to a row object. - let mappedRow = Drupal.behaviors.farm_timeline_gantt.mapRow(row); + let mappedRow = Drupal.behaviors.farm_timeline.mapRow(row); timeline.addRows([mappedRow]); // Collect all tasks for the row. - let tasks = row?.tasks?.map(Drupal.behaviors.farm_timeline_gantt.mapTask) ?? []; + let tasks = row?.tasks?.map(Drupal.behaviors.farm_timeline.mapTask) ?? []; timeline.addTasks(tasks); // Process children rows. From 2e84178eb372ca5cf6a2955e750f227c739fa51e Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Fri, 13 Sep 2024 13:02:28 -0700 Subject: [PATCH 13/18] Refactor recursive row processing --- modules/core/timeline/js/farm_timeline.js | 96 ++++++++++++++++------- 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/modules/core/timeline/js/farm_timeline.js b/modules/core/timeline/js/farm_timeline.js index bd2df11d55..6dbe165c82 100644 --- a/modules/core/timeline/js/farm_timeline.js +++ b/modules/core/timeline/js/farm_timeline.js @@ -1,7 +1,7 @@ (function (Drupal, drupalSettings, once, farmOS) { Drupal.behaviors.farm_timeline = { attach: function (context, settings) { - once('timelineGantt', '.farm-timeline', context).forEach(function (element) { + once('timelineGantt', '.farm-timeline', context).forEach(async function (element) { const opts = { props: { taskElementHook: (node, task) => { @@ -32,38 +32,77 @@ // Create the timeline instance. const timeline = farmOS.timeline.create(element, opts); - // Helper function to process a row data object and - // add the row and its tasks to the timeline. - const processRowData = function(row) { - // Map to a row object. - let mappedRow = Drupal.behaviors.farm_timeline.mapRow(row); - timeline.addRows([mappedRow]); + // Helper function to process a single row and its children recursively. + const processRow = async function(row) { - // Collect all tasks for the row. - let tasks = row?.tasks?.map(Drupal.behaviors.farm_timeline.mapTask) ?? []; - timeline.addTasks(tasks); + // Handle URL string to fetch row data dynamically. + if (typeof row === "string") { - // Process children rows. - row?.children?.forEach(processRow) ?? []; - }; - - // Helper function to process a row provided to the timeline element. - // Rows may be objects or URL strings to request dynamic row data. - const processRow = function(row) { - if (typeof row === "object") { - processRowData(row); - } - else if (typeof row === "string") { - const response = fetch(row) + // Fetch and process the array of returned rows. + const data = await fetch(row) .then(res => res.json()) - .then(data => data.rows ?? []) - .then(rows => rows.forEach(processRowData)); + .then(data => data?.rows ?? []); + const awaitedRows = await Promise.all(data.map(processRow)); + + // Aggregate all rows and tasks from processed rows. + return awaitedRows.reduce((accumulator, current) => { + return { + rows: [...accumulator.rows, ...current.rows], + tasks: [...accumulator.tasks, ...current.tasks], + }; + }, { + rows: [], + tasks: [] + }); + } else if (!row) { + // Handle potential null/undefined rows + return { rows: [], tasks: [] }; } + + // Begin processing a single parent row. + // First process all child rows. + const awaitedChildren = await Promise.all((row.children ?? []).map(processRow)); + + // Aggregate child rows and tasks. + const aggregatedChildren = awaitedChildren.reduce((accumulator, current) => { + return { + rows: [...accumulator.rows, ...current.rows], + tasks: [...accumulator.tasks, ...current.tasks], + }; + }, { + rows: [], + tasks: [] + }); + + // Map the parent row to a row object and include children rows. + row.children = aggregatedChildren.rows; + const mappedRow = Drupal.behaviors.farm_timeline.mapRow(row); + + // Collect all tasks from the parent row and add all child tasks. + const tasks = [ + ...(row.tasks?.map(Drupal.behaviors.farm_timeline.mapTask) ?? []), + ...aggregatedChildren.tasks, + ]; + + // Return the final processed parent row and all tasks. + return { + rows: [mappedRow], + tasks: tasks, + }; }; - // Process timeline rows. + // Helper function to add row to timeline after processing. + const addRow = async function(row) { + return processRow(row) + .then(data => { + timeline.addRows(data.rows); + timeline.addTasks(data.tasks); + }) + } + + // Add all provided timeline rows to the timeline. const timelineRows = JSON.parse(element.dataset?.timelineRows) ?? []; - timelineRows.forEach(processRow); + await Promise.all(timelineRows.map(addRow)); function createPopup(task, node) { const rect = node.getBoundingClientRect(); @@ -124,7 +163,10 @@ id: row.id, label: row.label, headerHtml: row.link, - expanded: row.expanded, + expanded: row.expanded ?? false, + // Only provide a children array if there are children + // otherwise an expanded icon will appear for rows without children. + children: row.children.length ? row.children : null, }; }, // Helper function to map task properties. From 93524fb2c37efb02df39b16845b5d60b9e48fa15 Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Fri, 13 Sep 2024 13:54:27 -0700 Subject: [PATCH 14/18] Prevent the timeline label from being null or undefined This causes null or undefined to be rendered as text on the page --- modules/core/timeline/js/farm_timeline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/timeline/js/farm_timeline.js b/modules/core/timeline/js/farm_timeline.js index 6dbe165c82..90c998c8f6 100644 --- a/modules/core/timeline/js/farm_timeline.js +++ b/modules/core/timeline/js/farm_timeline.js @@ -176,7 +176,7 @@ resourceId: task.resource_id, from: task.start, to: task.end, - label: task.label, + label: task.label ?? '', editUrl: task.edit_url, enableDragging: task.enable_dragging, meta: task?.meta, From 416369ed5e6a29a9b6dd5b5bcf8049bc168010ab Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Fri, 13 Sep 2024 14:04:37 -0700 Subject: [PATCH 15/18] Update to farmos-timeline v0.0.2 --- composer.libraries.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.libraries.json b/composer.libraries.json index d5800509f1..d662e14ece 100644 --- a/composer.libraries.json +++ b/composer.libraries.json @@ -23,10 +23,10 @@ "type": "package", "package": { "name": "farmos/farmos-timeline", - "version": "0.0.1", + "version": "0.0.2", "type": "drupal-library", "dist": { - "url": "https://github.com/farmOS/farmOS-timeline/releases/download/v0.0.1/v0.0.1-dist.zip", + "url": "https://github.com/farmOS/farmOS-timeline/releases/download/v0.0.2/v0.0.2-dist.zip", "type": "zip" }, "extra": { From 1186128dc15186198afad48e619c59e303023e2d Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Wed, 18 Sep 2024 11:51:52 -0700 Subject: [PATCH 16/18] Disable dragging and resizing of rows and tasks by default This uses the updated draggable and resizable attributes from the upstream gantt library --- modules/core/timeline/js/farm_timeline.js | 5 ++++- .../core/timeline/src/Plugin/DataType/TimelineRow.php | 3 ++- .../core/timeline/src/Plugin/DataType/TimelineTask.php | 3 ++- .../timeline/src/TypedData/TimelineRowDefinition.php | 6 +++++- .../timeline/src/TypedData/TimelineTaskDefinition.php | 9 +++++++-- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/modules/core/timeline/js/farm_timeline.js b/modules/core/timeline/js/farm_timeline.js index 90c998c8f6..6d6c98ba3c 100644 --- a/modules/core/timeline/js/farm_timeline.js +++ b/modules/core/timeline/js/farm_timeline.js @@ -164,6 +164,8 @@ label: row.label, headerHtml: row.link, expanded: row.expanded ?? false, + draggable: row.draggable ?? false, + resizable: row.resizable ?? false, // Only provide a children array if there are children // otherwise an expanded icon will appear for rows without children. children: row.children.length ? row.children : null, @@ -178,7 +180,8 @@ to: task.end, label: task.label ?? '', editUrl: task.edit_url, - enableDragging: task.enable_dragging, + draggable: task.draggable ?? false, + resizable: task.resizable ?? false, meta: task?.meta, classes: task.classes, }; diff --git a/modules/core/timeline/src/Plugin/DataType/TimelineRow.php b/modules/core/timeline/src/Plugin/DataType/TimelineRow.php index 580093cc64..b18c2d0dd3 100644 --- a/modules/core/timeline/src/Plugin/DataType/TimelineRow.php +++ b/modules/core/timeline/src/Plugin/DataType/TimelineRow.php @@ -27,7 +27,8 @@ public function setValue($values, $notify = TRUE) { // Set default values. $values += [ - 'enable_dragging' => FALSE, + 'draggable' => FALSE, + 'resizable' => FALSE, 'expanded' => FALSE, ]; parent::setValue($values); diff --git a/modules/core/timeline/src/Plugin/DataType/TimelineTask.php b/modules/core/timeline/src/Plugin/DataType/TimelineTask.php index 396f98576f..c47490df4a 100644 --- a/modules/core/timeline/src/Plugin/DataType/TimelineTask.php +++ b/modules/core/timeline/src/Plugin/DataType/TimelineTask.php @@ -32,7 +32,8 @@ public function setValue($values, $notify = TRUE) { // Set default values. $values += [ - 'enable_dragging' => FALSE, + 'draggable' => FALSE, + 'resizable' => FALSE, ]; parent::setValue($values); } diff --git a/modules/core/timeline/src/TypedData/TimelineRowDefinition.php b/modules/core/timeline/src/TypedData/TimelineRowDefinition.php index 8544679551..e48a997955 100644 --- a/modules/core/timeline/src/TypedData/TimelineRowDefinition.php +++ b/modules/core/timeline/src/TypedData/TimelineRowDefinition.php @@ -31,10 +31,14 @@ public function getPropertyDefinitions() { $this->propertyDefinitions['link'] = DataDefinition::create('uri') ->setLabel($this->t('Link')); - $this->propertyDefinitions['enable_dragging'] = DataDefinition::create('boolean') + $this->propertyDefinitions['draggable'] = DataDefinition::create('boolean') ->setLabel($this->t('Enable dragging')) ->addConstraint('NotNull'); + $this->propertyDefinitions['resizable'] = DataDefinition::create('boolean') + ->setLabel($this->t('Enable resizing')) + ->addConstraint('NotNull'); + $this->propertyDefinitions['classes'] = ListDataDefinition::create('string') ->setLabel($this->t('Classes')); diff --git a/modules/core/timeline/src/TypedData/TimelineTaskDefinition.php b/modules/core/timeline/src/TypedData/TimelineTaskDefinition.php index 9c542231c7..fafc650583 100644 --- a/modules/core/timeline/src/TypedData/TimelineTaskDefinition.php +++ b/modules/core/timeline/src/TypedData/TimelineTaskDefinition.php @@ -42,8 +42,13 @@ public function getPropertyDefinitions() { ->setLabel($this->t('End Time')) ->setRequired(TRUE); - $this->propertyDefinitions['enable_dragging'] = DataDefinition::create('boolean') - ->setLabel($this->t('Enable dragging')); + $this->propertyDefinitions['draggable'] = DataDefinition::create('boolean') + ->setLabel($this->t('Enable dragging')) + ->addConstraint('NotNull'); + + $this->propertyDefinitions['resizable'] = DataDefinition::create('boolean') + ->setLabel($this->t('Enable resizing')) + ->addConstraint('NotNull'); $this->propertyDefinitions['meta'] = DataDefinition::create('any') ->setLabel($this->t('Meta')); From 655c1d65255a37e97ae6db16997f9982a6ca467c Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Wed, 31 Jul 2024 14:36:22 -0400 Subject: [PATCH 17/18] Add CHANGELOG.md line for #862 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 801fdd3436..a38604585e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Allow modules to alter dashboard panes #868](https://github.com/farmOS/farmOS/pull/868) - [Add geometry/location fields to CSV importers #815](https://github.com/farmOS/farmOS/pull/815) - [Add an asset.logs service for retrieving logs that reference an asset #850](https://github.com/farmOS/farmOS/pull/850) +- [Add farmOS-timeline library #862](https://github.com/farmOS/farmOS/pull/862) ### Changed From a240c0fa313587e130661c07980310f99bf415bc Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 19 Sep 2024 10:02:21 -0400 Subject: [PATCH 18/18] Designate farm_timeline module as "experimental". --- CHANGELOG.md | 2 +- modules/core/timeline/farm_timeline.info.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a38604585e..f141d88b0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Allow modules to alter dashboard panes #868](https://github.com/farmOS/farmOS/pull/868) - [Add geometry/location fields to CSV importers #815](https://github.com/farmOS/farmOS/pull/815) - [Add an asset.logs service for retrieving logs that reference an asset #850](https://github.com/farmOS/farmOS/pull/850) -- [Add farmOS-timeline library #862](https://github.com/farmOS/farmOS/pull/862) +- [Add farmOS-timeline library (experimental) #862](https://github.com/farmOS/farmOS/pull/862) ### Changed diff --git a/modules/core/timeline/farm_timeline.info.yml b/modules/core/timeline/farm_timeline.info.yml index 89d9971cf9..857584c9d6 100644 --- a/modules/core/timeline/farm_timeline.info.yml +++ b/modules/core/timeline/farm_timeline.info.yml @@ -1,5 +1,6 @@ name: farmOS Timeline description: Provides farmOS timeline features. type: module -package: farmOS +package: farmOS (Experimental) +lifecycle: experimental core_version_requirement: ^10