tripeur il y a 4 ans
Parent
commit
748b1e7c73
10 fichiers modifiés avec 187 ajouts et 41 suppressions
  1. 2 4
      dev/index.ts
  2. 18 0
      doc/genericTimeline.html
  3. 25 0
      doc/js/genericTimeline.js
  4. 69 0
      package-lock.json
  5. 6 3
      package.json
  6. 2 1
      src/Ressource.ts
  7. 52 33
      src/Timeline.ts
  8. 3 0
      src/main.ts
  9. 2 0
      src/styles/Timeline.style.ts
  10. 8 0
      webpack.common.js

+ 2 - 4
dev/index.ts

@@ -47,8 +47,7 @@ timeline.addRessource(rows[0]);
 timeline.addRessource(rows[2]);
 timeline.addRessource(rows[3]);
 
-timeslots.map(o=>timeline.addTimeSlot(o))
-
+timeline.addEvents(timeslots)
 timeline.addEventListener("item-selected",(e)=>{console.log((e as CustomEvent).detail)})
 
 title = document.createElement("h2");
@@ -79,5 +78,4 @@ const timeslots2 = Array(nItem).fill(0).map((_,i)=>{
         end:data.add(i/2 - 4, 'h').toDate(),
         bgColor:"hsl(" + Math.round(i / (nItem-1) * 360) + ", 100%, 50%)"
     }});
-
-timeslots2.forEach(r=>nestedTimeline.addTimeSlot(r));
+nestedTimeline.addEvents(timeslots2);

+ 18 - 0
doc/genericTimeline.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script src="../_bundles/Timeline.min.js" defer></script>
+        <script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js" defer></script>
+        
+        <script src="js/genericTimeline.js" defer></script>
+    </head>
+    <body>
+        <h2>Generic Timeline</h2>
+        <jc-timeline id="timeline"></jc-timeline>
+        <h2>Week Timeline W14 2021</h2>
+        <jc-timeline id="timeline2" 
+        start="2021-04-04T22:00:00.000Z" 
+        end="2021-04-11T21:59:00.000Z" 
+        slotduration="360" legendspan="4" slotwidth="30"></jc-timeline>
+    </body>
+</html>

+ 25 - 0
doc/js/genericTimeline.js

@@ -0,0 +1,25 @@
+// eslint-disable-next-line no-undef
+const timeline = document.getElementById("timeline");
+const rows = [ 
+    {id:'1',title:"Ressource 1 DarkSlate",eventBgColor:"DarkSlateBlue"},
+    {id:'3',title:"Ressource 3"},
+    {id:'4',title:"Ressource 4"},
+    {id:'5',title:"Ressource 5"}];
+    rows[0].parent = rows[1];
+    rows[2].parent = rows[1];
+
+// eslint-disable-next-line no-undef
+const data = dayjs().startOf("hour").hour(9);
+const timeslots = [
+    {id:'1', title:"Fixed ressource" ,ressourceId:'3', start:data.subtract(3,"h").toDate(), end:data.toDate(), ressourceEditable:false,bgColor:"darkgreen"},
+    {id:'2', title:"Fixed time" , ressourceId:'3', start:data.toDate(), end:data.endOf("hour").add(1,"h").toDate(), editable:false,bgColor:"FireBrick"},
+    {id:'3', ressourceId:'4', start:data.add(1,"h").toDate(), end:data.endOf("hour").add(2,"h").toDate()},
+    {id:'4', ressourceId:'3', start:data.startOf("day").subtract(1,"h").toDate(), end:dayjs().endOf("hour").add(1,"h").toDate()}
+]
+timeline.addRessources(rows);
+
+timeline.addTimeSlots(timeslots);
+
+// eslint-disable-next-line no-undef
+const timeline2 = document.getElementById("timeline2");
+timeline2.setLegendUnitFormat("d","MMM D")

+ 69 - 0
package-lock.json

@@ -2751,6 +2751,57 @@
       "integrity": "sha512-/VulV3SYni1taM7a4RMdceqzJWR39gpZHjBwUnsCFKWV/GJkD14CJ5F7eWcZozmHJK0/f/H5U3b3SiPkuvxMgg==",
       "dev": true
     },
+    "html-minifier": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
+      "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
+      "dev": true,
+      "requires": {
+        "camel-case": "^3.0.0",
+        "clean-css": "^4.2.1",
+        "commander": "^2.19.0",
+        "he": "^1.2.0",
+        "param-case": "^2.1.1",
+        "relateurl": "^0.2.7",
+        "uglify-js": "^3.5.1"
+      },
+      "dependencies": {
+        "camel-case": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+          "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
+          "dev": true,
+          "requires": {
+            "no-case": "^2.2.0",
+            "upper-case": "^1.1.1"
+          }
+        },
+        "lower-case": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+          "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=",
+          "dev": true
+        },
+        "no-case": {
+          "version": "2.3.2",
+          "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+          "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+          "dev": true,
+          "requires": {
+            "lower-case": "^1.1.1"
+          }
+        },
+        "param-case": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
+          "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
+          "dev": true,
+          "requires": {
+            "no-case": "^2.2.0"
+          }
+        }
+      }
+    },
     "html-minifier-terser": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz",
@@ -3572,6 +3623,12 @@
       "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
       "dev": true
     },
+    "minify-template-literal-loader": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/minify-template-literal-loader/-/minify-template-literal-loader-0.0.3.tgz",
+      "integrity": "sha1-/IGvRLVROXdBAAlzc1a2KWunYMI=",
+      "dev": true
+    },
     "minimalistic-assert": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -5488,6 +5545,12 @@
       "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==",
       "dev": true
     },
+    "uglify-js": {
+      "version": "3.13.2",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.2.tgz",
+      "integrity": "sha512-SbMu4D2Vo95LMC/MetNaso1194M1htEA+JrqE9Hk+G2DhI+itfS9TRu9ZKeCahLDNa/J3n4MqUJ/fOHMzQpRWw==",
+      "dev": true
+    },
     "union-value": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -5552,6 +5615,12 @@
       "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
       "dev": true
     },
+    "upper-case": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+      "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
+      "dev": true
+    },
     "uri-js": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",

+ 6 - 3
package.json

@@ -1,8 +1,9 @@
 {
   "name": "jc-timeline",
-  "version": "0.0.1",
+  "version": "0.1.0",
   "description": "web component to manage ressources in time",
-  "main": "dist/index.js",
+  "main": "./lib/main.js",
+  "types": "./lib/main.d.ts",
   "private": true,
   "scripts": {
     "prod ": "webpack --config webpack.prod.js ",
@@ -29,7 +30,9 @@
     "webpack": "^5.10.3",
     "webpack-cli": "^4.2.0",
     "webpack-dev-server": "^3.11.0",
-    "webpack-merge": "^5.7.2"
+    "webpack-merge": "^5.7.2",
+    "html-minifier": "^4.0.0",
+    "minify-template-literal-loader": "0.0.3"
   },
   "dependencies": {
     "lit-element": "^2.4.0",

+ 2 - 1
src/Ressource.ts

@@ -29,7 +29,7 @@ export class Ressource implements IRessource,Selectable{
         this.id = obj.id
         this.title = obj.title || obj.id
         if (obj.children){
-            this.children = obj.children.map(Ressource.toRessource)
+            this.children = obj.children.map(Ressource.toRessource)            
         }else{
             this.children = []
         }
@@ -40,6 +40,7 @@ export class Ressource implements IRessource,Selectable{
         this.eventRessourceEditable = obj?.eventRessourceEditable === undefined ? true : obj.eventRessourceEditable;
         this.eventBgColor = obj.eventBgColor;
         this.selected = false;
+        this.children.forEach(o=>o.parent = this)
     }
     get parentId():string{
         return this.parent?.id || ""

+ 52 - 33
src/Timeline.ts

@@ -11,7 +11,7 @@ import { styleMap } from 'lit-html/directives/style-map';
 import syncronizeElementsScrolling from './utils/syncroScroll';
 import Selectable from './utils/selectable';
 
-import { TimelineStyle } from './styles/TimelineStyle';
+import { TimelineStyle } from './styles/Timeline.style';
 
 interface TimelineOptions {
     ressources?: Array<IRessource>
@@ -39,7 +39,6 @@ interface legendItem {
 // TODO add selectable Slot 
 // TODO enable to rearrange between different component.
 
-
 @customElement('jc-timeline')
 class Timeline extends LitElement {
     static styles = [TimelineStyle] 
@@ -139,28 +138,38 @@ class Timeline extends LitElement {
         this.legendUnitFormat[unit] = format;
         this.updateLegend()
     }
-    addRessources(list:Array<IRessource>):Array<Ressource | undefined>{
+    addRessources(list:Array<IRessource>):Array<Ressource>{
         return list.map(r=>this.addRessource(r));
     }
     // Ressource management
-    addRessource(ressource: IRessource): Ressource | undefined {
-        if (this.rows.filter(o => o.id == ressource.id).length > 0) {
-            return
+    /**
+     * Add the ressource the the timeline or return the existing ressource with the same id
+     * @param ressource 
+     * @returns 
+     */
+    addRessource(ressource: IRessource): Ressource {
+        const existingRessource = this.getRessourceFromId(ressource.id)
+        if (existingRessource) {
+            return existingRessource
         }
         const r = Ressource.toRessource(ressource)
         
         if (r.parent !== undefined) {
-            const idx = this.rows.indexOf(r.parent)
+            r.parent = this.getRessourceFromId(r.parent.id) ?? this.addRessource(r.parent);
+            const idx = this.rows.indexOf(r.parent as Ressource)
             if (idx > -1) {
                 this.rows[idx].children.push(r);
                 this.rows.splice(idx + 1, 0, r);
             } else {
-                return
+                throw new Error("Not able to create ressource parent.\n" + r.id)
             }
         } else {
             this.rows = [...this.rows, r]
         }
-        this.updateTimeslotPosition(r)
+    
+        this.addRessources(r.children);
+        
+        this._updateEventPosition(r)
         return r;
     }
     removeRessourceById(id: string): TimelineContent {
@@ -203,41 +212,51 @@ class Timeline extends LitElement {
     setRowsTitle(title: string):void {
         this.rowsTitle = title;
     }
-    addTimeSlots(list:Array<IEvent>):Array<Event | undefined>{
-        return list.map((e)=>this.addTimeSlot(e));
+    getEventById(id:string):Event | undefined{
+        return this.items.find(o=>o.id==id);
+    }
+    addEvents(list:Array<IEvent>):Array<Event | undefined>{
+        return list.map((e)=>this.addEvent(e));
     }
     // TimeSlot management
-    addTimeSlot(slot: IEvent): Event | undefined {
-        if (this.items.filter(o => o.id == slot.id).length > 0) {
-            return undefined
+    /**
+     * Add the event the the timeline or return the existing event with the same id
+     * return undefined if the event ressource is not defined in the timeline
+     * @param event 
+     * @returns 
+     */
+    addEvent(event: IEvent): Event | undefined {
+        const existingEvent = this.getEventById(event.id)
+        if (existingEvent) {
+            return existingEvent
         }
-        const ressource = this.rows.find(r => r.id === slot.ressourceId);
+        const ressource = this.rows.find(r => r.id === event.ressourceId);
         if (ressource === undefined) {
             return undefined
         }
-        const timeslot = Event.toTimeSlot(slot);
+        const timeslot = Event.toTimeSlot(event);
         this.items = [...this.items, timeslot]
         // Update timeslot status
         timeslot.isDisplayed = timeslot.end > this._start.toDate() || timeslot.start < this._end.toDate();
-        this.updateTimeslotPosition(ressource)
+        this._updateEventPosition(ressource)
         return timeslot;
     }
 
-    removeTimeslotById(id: string): Array<Event> {
+    removeEventById(id: string): Array<Event> {
         const output = this.items.filter(o => o.id === id);
         this.items = this.items.filter(o => o.id !== id);
         return output
     }
-    updateTimeslotById(id: string): Event | null {
-        const output = this.removeTimeslotById(id)
+    updateEventById(id: string): Event | null {
+        const output = this.removeEventById(id)
         if (output.length > 0) {
-            this.addTimeSlot(output[0])
+            this.addEvent(output[0])
             return output[0];
         } else {
             return null;
         }
     }
-    private updateTimeslotPosition(ressource: Ressource): void {
+    private _updateEventPosition(ressource: Ressource): void {
         const timeslots = this.items.filter(i => i.ressourceId === ressource.id);
         if (timeslots.length === 0) {
             ressource.height = this.rowHeight + (ressource.collapseChildren ? 5:0);
@@ -301,7 +320,7 @@ class Timeline extends LitElement {
 
         })
     }
-    getTimeSlots(): Array<Event> {
+    getEvents(): Array<Event> {
         return this.items;
     }
 
@@ -379,7 +398,7 @@ class Timeline extends LitElement {
                 const newDate = dayjs(startDate).add(Math.round((e.clientX - startPos) / this.slotWidth) * this.slotDuration, "m").toDate();
                 if (direction === "start" ? (newDate < localSlot.end) : (localSlot.start < newDate)) {
                     localSlot[localDir] = newDate;
-                    this.updateTimeslotById(slot.id);
+                    this.updateEventById(slot.id);
                 }
             }
 
@@ -387,7 +406,7 @@ class Timeline extends LitElement {
                 window.removeEventListener("mousemove", resizeListener)
                 window.removeEventListener("mouseup", mouseUpListener)
                 localSlot.moving = false;
-                this.updateTimeslotById(slot.id);
+                this.updateEventById(slot.id);
             }
             localSlot.moving = true
             window.addEventListener("mousemove", resizeListener)
@@ -428,7 +447,7 @@ class Timeline extends LitElement {
                     if (ressourceId !== localSlot.ressourceId) {
                         const oldRessource = this.getRessourceFromId(localSlot.ressourceId) as Ressource;
                         localSlot.ressourceId = ressourceId;
-                        this.updateTimeslotPosition(oldRessource);
+                        this._updateEventPosition(oldRessource);
                         return true;
                     }
                 }
@@ -440,7 +459,7 @@ class Timeline extends LitElement {
                 const a = updatePosition(e);
                 if (updateRessource(e) || a) {
                     hasChanged = true;
-                    this.updateTimeslotById(localSlot.id);
+                    this.updateEventById(localSlot.id);
                 }
             }
 
@@ -448,7 +467,7 @@ class Timeline extends LitElement {
                 window.removeEventListener("mousemove", moveListener);
                 window.removeEventListener("mouseup", mouseUpListener);
                 localSlot.moving = false;
-                this.updateTimeslotById(slot.id);
+                this.updateEventById(slot.id);
                 callback(e,hasChanged);
             }
             localSlot.moving = true;
@@ -459,7 +478,7 @@ class Timeline extends LitElement {
     private _clearSelectedItems(){
         this.selectedList.map(selectable => {
             selectable.selected = false;
-            this.updateTimeslotById(selectable.id)
+            this.updateEventById(selectable.id)
         });
         this.selectedList = []
     }
@@ -477,7 +496,7 @@ class Timeline extends LitElement {
                     if (e.ctrlKey) {
                         this.selectedList.splice(idx, 1);
                         item.selected = false;
-                        this.updateTimeslotById(item.id)
+                        this.updateEventById(item.id)
                     } else {
                         this._clearSelectedItems()
                     }
@@ -488,7 +507,7 @@ class Timeline extends LitElement {
                 } 
                 item.selected = true;
                 this.selectedList.push(item)
-                this.updateTimeslotById(item.id)
+                this.updateEventById(item.id)
             }
             const myEvent = new CustomEvent('item-selected', {
                 detail: { items: this.selectedList },
@@ -569,7 +588,7 @@ class Timeline extends LitElement {
     _getCollapseRessourceHandler(item:Ressource):(e:MouseEvent)=>void{
         return (_e:MouseEvent) => {
             item.collapseChildren = ! item.collapseChildren;
-            this.updateTimeslotPosition(item);
+            this._updateEventPosition(item);
             // Force rows refresh TODO improve this rerendering
             this.rows = [...this.rows];
         };
@@ -624,7 +643,7 @@ class Timeline extends LitElement {
                 }
                 // Add moved children and associated slots
                 this.addRessources(movedContent.ressources);
-                this.addTimeSlots(movedContent.items);
+                this.addEvents(movedContent.items);
             }
         }
     }

+ 3 - 0
src/main.ts

@@ -0,0 +1,3 @@
+export * from './Ressource';
+export * from './Event';
+export * from './Timeline';

+ 2 - 0
src/styles/TimelineStyle.ts → src/styles/Timeline.style.ts

@@ -21,6 +21,7 @@ div {
 .jc-timeline-rows > tr > td{        
     padding: 8px;
     min-width:40px;
+    box-sizing: border-box;
 }
 .jc-timeline-rows > tr > td {
     max-width:calc( var(--width) - 8px );
@@ -64,6 +65,7 @@ i.jc-spacer.collapse:after{
 }
 .jc-timeline-rows,
 .jc-timeline-rows-title{
+    height: inherit;
     width:var(--width, 200px);
     overflow: hidden;
     border-collapse:collapse;

+ 8 - 0
webpack.common.js

@@ -12,6 +12,14 @@ module.exports = {
         use: 'ts-loader',
         exclude: /node_modules/,
       },
+      {
+        test: /\.style.ts$/,
+        loader: 'minify-template-literal-loader',
+        options: {
+          caseSensitive: true,
+          collapseWhitespace: true
+        }
+      }
     ],
   },
   resolve: {