Browse Source

try storybook

tripeur 4 years ago
parent
commit
74c4109390

+ 2 - 1
.eslintignore

@@ -3,4 +3,5 @@ webpack.*.js
 #Exclude output generated files
 lib/
 lib-esm/
-_bundles/
+_bundles/
+doc/

+ 11 - 0
.storybook/main.js

@@ -0,0 +1,11 @@
+module.exports = {
+  "stories": [
+    "../src/**/*.stories.mdx",
+    "../src/**/*.stories.@(js|jsx|ts|tsx)"
+  ],
+  "addons": [    
+    "@storybook/addon-docs",
+    "@storybook/addon-links",
+    "@storybook/addon-essentials"
+  ]
+}

+ 10 - 0
.storybook/preview.js

@@ -0,0 +1,10 @@
+
+export const parameters = {
+  actions: { argTypesRegex: "^on[A-Z].*" },
+  controls: {
+    matchers: {
+      color: /(background|color)$/i,
+      date: /Date$/,
+    },
+  },
+}

+ 2 - 2
dev/index.ts

@@ -67,13 +67,13 @@ for(let i = 0 ; i < ressources.length-1;i++){
     ressources[i+1].parent=ressources[i]
 }
 ressources.forEach(r=>nestedTimeline.addRessource(r));
-ressources[0].collapseChildren=true
+ressources[0].collapseChildren = true
 const nItem = 32;
 const timeslots2 = Array(nItem).fill(0).map((_,i)=>{
     return {
         id:""+i,
         ressourceId:"" + (i % 8),
-        title:"I "+i,
+        title:"Item "+i,
         start:data.add(i/2 - 5, 'h').toDate(),
         end:data.add(i/2 - 4, 'h').toDate(),
         bgColor:"hsl(" + Math.round(i / (nItem-1) * 360) + ", 100%, 50%)"

+ 5 - 4
doc/genericTimeline.html

@@ -1,18 +1,19 @@
 <!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>
+        <script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
+        <script src="../_bundles/Timeline.min.js"></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>

+ 3 - 6
doc/js/genericTimeline.js

@@ -1,4 +1,3 @@
-// eslint-disable-next-line no-undef
 const timeline = document.getElementById("timeline");
 const rows = [ 
     {id:'1',title:"Ressource 1 DarkSlate",eventBgColor:"DarkSlateBlue"},
@@ -8,7 +7,6 @@ const rows = [
     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"},
@@ -16,10 +14,9 @@ const timeslots = [
     {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);
+console.log(timeline.addRessources(rows));
 
-timeline.addTimeSlots(timeslots);
+timeline.addEvents(timeslots);
 
-// eslint-disable-next-line no-undef
 const timeline2 = document.getElementById("timeline2");
-timeline2.setLegendUnitFormat("d","MMM D")
+            timeline2.setLegendUnitFormat("d","MMM D")

File diff suppressed because it is too large
+ 6508 - 1012
package-lock.json


+ 23 - 9
package.json

@@ -6,9 +6,11 @@
   "types": "./lib/main.d.ts",
   "private": true,
   "scripts": {
-    "prod ": "webpack --config webpack.prod.js ",
+    "prod": "webpack --config webpack.prod.js ",
     "serve": "webpack serve --config webpack.dev.js",
-    "export": "tsc -m commonjs --outDir lib && tsc -m es6 --outDir lib-esm"
+    "export": "tsc -m commonjs --outDir lib && tsc -m es6 --outDir lib-esm",
+    "storybook:start": "start-storybook -p 6006",
+    "storybook:build": "build-storybook -o _site"
   },
   "keywords": [
     "typescript",
@@ -20,23 +22,35 @@
   "author": "Tripeur",
   "license": "ISC",
   "devDependencies": {
+    "@babel/core": "^7.13.16",
+    "@storybook/addon-actions": "^6.2.8",
+    "@storybook/addon-docs": "^6.2.8",
+    "@storybook/addon-essentials": "^6.2.8",
+    "@storybook/addon-links": "^6.2.8",
+    "@storybook/web-components": "^6.2.8",
+    "@types/simplebar": "^5.1.2",
     "@typescript-eslint/eslint-plugin": "^4.19.0",
     "@typescript-eslint/parser": "^4.19.0",
+    "babel-loader": "^8.2.2",
     "clean-webpack-plugin": "^3.0.0",
+    "css-loader": "^5.2.4",
     "eslint": "^7.22.0",
-    "html-webpack-plugin": "^4.5.0",
+    "html-minifier": "^3.5.0",
+    "html-webpack-plugin": "^4.5.2",
+    "lit-html": "^1.3.0",
+    "minify-template-literal-loader": "0.0.3",
+    "postcss": "^8.2.12",
+    "style-loader": "^2.0.0",
     "ts-loader": "^8.0.12",
     "typescript": "^4.1.3",
-    "webpack": "^5.10.3",
+    "webpack": "^4.46.0",
     "webpack-cli": "^4.2.0",
     "webpack-dev-server": "^3.11.0",
-    "webpack-merge": "^5.7.2",
-    "html-minifier": "^4.0.0",
-    "minify-template-literal-loader": "0.0.3"
+    "webpack-merge": "^5.7.2"
   },
   "dependencies": {
+    "dayjs": "^1.10.4",
     "lit-element": "^2.4.0",
-    "tsc": "^1.20150623.0",
-    "dayjs": "^1.10.4"
+    "simplebar": "^5.3.0"
   }
 }

+ 13 - 1
src/Ressource.ts

@@ -33,7 +33,7 @@ export class Ressource implements IRessource,Selectable{
         }else{
             this.children = []
         }
-        this.collapseChildren = obj?.collapseChildren === undefined ? false : obj.collapseChildren; 
+        this.collapseChildren = obj.collapseChildren ? obj.collapseChildren : false; 
         this.parent = obj.parent ? Ressource.toRessource(obj.parent) : undefined;
         this.height = obj.height;
         this.eventEditable = obj?.eventEditable === undefined ? true : obj.eventEditable;
@@ -62,6 +62,18 @@ export class Ressource implements IRessource,Selectable{
             parent: parent
         }
     }
+    toJSON():IRessource{
+        return {
+            id:this.id,
+            title:this.title,
+            collapseChildren:this.collapseChildren,
+            parent : this.parent,
+            height : this.height,
+            eventEditable:this.eventEditable,
+            eventRessourceEditable:this.eventRessourceEditable,
+            eventBgColor:this.eventBgColor
+        }
+    }
     static toRessource(obj:IRessource):Ressource{
         if (obj instanceof Ressource){
             return obj 

+ 62 - 45
src/Timeline.ts

@@ -1,19 +1,19 @@
 import dayjs, { Dayjs } from 'dayjs'
-import { Event, IEvent } from './Event'
+import { LitElement, html, customElement, property, TemplateResult, internalProperty } from 'lit-element';
+import { styleMap } from 'lit-html/directives/style-map';
+import SimpleBar from 'simplebar';
+import {SimpleBarStyle} from './styles/SimplbeBar.styles';
 
+import { Event, IEvent } from './Event'
 import { Ressource, IRessource } from './Ressource'
 
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { HorizontalResizer } from './components/horizontal-resizer'
-import { LitElement, html, customElement, property, TemplateResult } from 'lit-element';
-import { styleMap } from 'lit-html/directives/style-map';
-
+export { HorizontalResizer } from './components/horizontal-resizer'
 import syncronizeElementsScrolling from './utils/syncroScroll';
 import Selectable from './utils/selectable';
-
 import { TimelineStyle } from './styles/Timeline.style';
 
-interface TimelineOptions {
+
+export interface TimelineOptions {
     ressources?: Array<IRessource>
     items?: Array<IEvent>
 }
@@ -28,7 +28,7 @@ interface TimeInterval {
     slots: Array<Event>
 }
 type dayjsUnit = "y"|"M"|"d"|"h"|"m"|'s'
-type UnitLegend = {
+export type UnitLegend = {
     [k in dayjsUnit]: string;
 };
 interface legendItem {
@@ -38,28 +38,36 @@ interface legendItem {
 // TODO define std zoom level
 // TODO add selectable Slot 
 // TODO enable to rearrange between different component.
-
+/**
+ * This is a component that can display event in a Timeline.
+ * 
+ */
 @customElement('jc-timeline')
 class Timeline extends LitElement {
-    static styles = [TimelineStyle] 
-    @property({ type: Array })
+    static styles = [TimelineStyle,SimpleBarStyle] 
+    @internalProperty() // important for the refresh
     private rows: Array<Ressource>
-    @property({ type: Array })
+    @internalProperty() // important for the refresh
     private items: Array<Event>
-
     private selectedList: Array<Selectable>
+
     @property({ type: Number })
-    private ressourceWidth: number
-    @property({ type: Object })
+    public ressourceWidth: number
 
+    @property({ type: Object })
     private _start: Dayjs
     @property({ type: String })
     public get start(): string {
         return this._start.toISOString();
     }
     public set start(value: string){
-        this._start = dayjs(value);
-        this.updateLegend();
+        const d = dayjs(value);
+        if( d.isValid()){
+            this._start = d < this._end ? d : this._end;
+            this.updateLegend();
+        }else{
+            console.log("Invalid Date :" ,value)
+        }
     }
     
     private _end: Dayjs
@@ -68,8 +76,11 @@ class Timeline extends LitElement {
         return this._end.toISOString();
     }
     public set end(value: string){
-        this._end = dayjs(value);
-        this.updateLegend();
+        const d = dayjs(value);
+        if( d.isValid()){
+            this._end = this._start < d ? d:this._start;
+            this.updateLegend();
+        }
     }
 
     private _slotDuration = 30; // in minute
@@ -96,7 +107,7 @@ class Timeline extends LitElement {
     private rowHeight = 32 // in px
 
     @property({ type: Number })
-    private slotWidth = 20 // in px
+    public slotWidth = 20 // in px
 
     @property({ type: String })
     private rowsTitle: string
@@ -108,9 +119,8 @@ class Timeline extends LitElement {
     
     constructor(options: TimelineOptions = {}) {
         super()
-        this.rows = options.ressources ? options.ressources.map(Ressource.toRessource) : [];
-        this.items = options.items ? options.items.map(Event.toTimeSlot) : []
-
+        this.rows = [];
+        this.items = [];
         this._start = dayjs().startOf("day");
         this._end = this._start.endOf("day");
         this.rowsTitle = "Ressources"
@@ -118,6 +128,12 @@ class Timeline extends LitElement {
         this.selectedList = [];
         this.legend = [];
         this.defaultBackground = "";
+        if (options.ressources){
+            this.addRessources(options.ressources)
+        }
+        if (options.items){
+            this.addEvents(options.items)
+        }
         this.updateLegend();
         this.render();
     }
@@ -128,7 +144,6 @@ class Timeline extends LitElement {
     }
     get defaultBackground(): string {
         return this.style.getPropertyValue("--default-background");
-    
     }
     setLegendUnitFormatAll(legend:Partial<UnitLegend>):void{
         this.legendUnitFormat  = {...this.legendUnitFormat, ...legend}
@@ -145,7 +160,7 @@ class Timeline extends LitElement {
     /**
      * Add the ressource the the timeline or return the existing ressource with the same id
      * @param ressource 
-     * @returns 
+     * @returns The Ressource object registered to in the timeline 
      */
     addRessource(ressource: IRessource): Ressource {
         const existingRessource = this.getRessourceFromId(ressource.id)
@@ -522,25 +537,27 @@ class Timeline extends LitElement {
         const root = this.shadowRoot;
         if (root !== null) {
             const gridContainer = root.querySelector(".jc-timeline-grid-container") as HTMLBaseElement;
-            syncronizeElementsScrolling([gridContainer,
+            const simplebar = new SimpleBar(gridContainer).getScrollElement() as HTMLBaseElement;
+            syncronizeElementsScrolling([simplebar,
                 root.querySelector(".jc-timeline-grid-title-container") as HTMLBaseElement], "h")
-            syncronizeElementsScrolling([gridContainer,
+            syncronizeElementsScrolling([simplebar,
                 root.querySelector(".jc-timeline-rows") as HTMLBaseElement], "v")
+                
+                    
         }
         if (this.defaultBackground === "") {
             this.style.setProperty("--default-background", "SteelBlue");
         }
     }
-
     // RENDERING
-    renderTimeslot(slot: Event): TemplateResult {
-        if (!slot.isDisplayed) {
+    renderTimeslot(evt: Event): TemplateResult {
+        if (!evt.isDisplayed) {
             return html``
         }
         let rowTop = 0
         let ressource: Ressource;
         let i: number;
-        for (i = 0; i < this.rows.length && this.rows[i].id !== slot.ressourceId; i++) {
+        for (i = 0; i < this.rows.length && this.rows[i].id !== evt.ressourceId; i++) {
             ressource = this.rows[i];
             if (ressource.show){
                 rowTop += ressource.height ? ressource.height : this.rowHeight;
@@ -548,16 +565,16 @@ class Timeline extends LitElement {
         }
         ressource = this.rows[i];
         const minute2pixel = this.slotWidth / this.slotDuration;
-        const left = dayjs(slot.start).diff(this._start, "m") * minute2pixel ;
-        const right = - dayjs(slot.end).diff(this._end, "m") * minute2pixel ;
+        const left = dayjs(evt.start).diff(this._start, "m") * minute2pixel ;
+        const right = - dayjs(evt.end).diff(this._end, "m") * minute2pixel ;
         const style = {
             height: this.rowHeight - 4 + "px",
-            top: rowTop + slot.offset * this.rowHeight + "px",
+            top: rowTop + evt.offset * this.rowHeight + "px",
             left: left + "px",
             right: right + "px",
             backgroundColor:""
         };
-        const bgColor = slot.bgColor ? slot.bgColor : ressource.eventBgColor;
+        const bgColor = evt.bgColor ? evt.bgColor : ressource.eventBgColor;
         if (bgColor) {
             style.backgroundColor = bgColor;
         }
@@ -569,18 +586,18 @@ class Timeline extends LitElement {
             return html`<div class="jc-timeslot empty" style="${styleMap(style)}"></div>`
         }
 
-        let content: TemplateResult = html`${slot.title}`
-        const resizer = slot.editable === null ? ressource.eventEditable : slot.editable;
-        const editableRessource = slot.ressourceEditable === null ? ressource.eventRessourceEditable : slot.ressourceEditable;
+        let content: TemplateResult = html`${evt.title}`
+        const resizer = evt.editable === null ? ressource.eventEditable : evt.editable;
+        const editableRessource = evt.ressourceEditable === null ? ressource.eventRessourceEditable : evt.ressourceEditable;
         if (resizer) {
-            content = html`<div class="jc-timeslot-resizer-start" @mousedown="${this._getEventResizerHandler(slot, "start")}"></div>${content}
-                            <div class="jc-timeslot-resizer-end" @mousedown="${this._getEventResizerHandler(slot, "end")}"></div>`;
+            content = html`<div class="jc-timeslot-resizer-start" @mousedown="${this._getEventResizerHandler(evt, "start")}"></div>${content}
+                            <div class="jc-timeslot-resizer-end" @mousedown="${this._getEventResizerHandler(evt, "end")}"></div>`;
         }
-        return html`<div class="jc-timeslot ${slot.moving ? "moving" : ""} ${slot.selected ? "selected" : ""}" 
-            start="${slot.start.getHours()}" 
-            end="${slot.end.getHours()}" 
+        return html`<div class="jc-timeslot ${evt.moving ? "moving" : ""} ${evt.selected ? "selected" : ""}" 
+            start="${evt.start.getHours()}" 
+            end="${evt.end.getHours()}" 
             style="${styleMap(style)}"
-            @mousedown="${this._getEventGrabHandler(slot, resizer, editableRessource, this._getEventClickHandler(slot))}"
+            @mousedown="${this._getEventGrabHandler(evt, resizer, editableRessource, this._getEventClickHandler(evt))}"
             >${content}</div>`;
 
 

+ 96 - 0
src/components/Timeline-event.ts.tmp

@@ -0,0 +1,96 @@
+import dayjs from 'dayjs';
+import { LitElement, html, customElement, property, TemplateResult } from 'lit-element';
+import { styleMap } from 'lit-html/directives/style-map';
+import { Event } from './Event'
+
+@customElement('timeline-event')
+class TimelineEvent extends LitElement {
+    @property({type:Object})
+    public evt:Event | undefined
+    @property({type:Number})
+    public rowHeight: number;
+
+    public parentShow:boolean
+
+    _getEventResizerHandler(direction: "end" | "start") {
+        return (evt: MouseEvent):void => {
+            evt.stopPropagation();
+            evt.preventDefault()
+            const startPos = evt.clientX;
+            const localSlot = this.evt;
+            const localDir = direction;
+            const startDate = this.evt[direction];
+
+            const resizeListener = (e: MouseEvent) => {
+                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;
+                }
+            }
+
+            const mouseUpListener = (_e: MouseEvent):void => {
+                window.removeEventListener("mousemove", resizeListener)
+                window.removeEventListener("mouseup", mouseUpListener)
+                localSlot.moving = false;
+            }
+            localSlot.moving = true
+            window.addEventListener("mousemove", resizeListener)
+            window.addEventListener("mouseup", mouseUpListener)
+        }
+    }
+ render(){
+    const evt = this.evt || new Event()
+    if (!evt.isDisplayed) {
+        return html``
+    }
+    let rowTop = 0
+    let ressource: Ressource;
+    let i: number;
+    for (i = 0; i < this.rows.length && this.rows[i].id !== evt.ressourceId; i++) {
+        ressource = this.rows[i];
+        if (ressource.show){
+            rowTop += ressource.height ? ressource.height : this.rowHeight;
+        }
+    }
+    ressource = this.rows[i];
+    
+    const minute2pixel = this.slotWidth / this.slotDuration;
+    const left = dayjs(evt.start).diff(this._start, "m") * minute2pixel ;
+    const right = - dayjs(evt.end).diff(this._end, "m") * minute2pixel ;
+    const style = {
+        height: this.rowHeight - 4 + "px",
+        top: rowTop + evt.offset * this.rowHeight + "px",
+        left: left + "px",
+        right: right + "px",
+        backgroundColor:""
+    };
+    const bgColor = evt.bgColor ? evt.bgColor : ressource.eventBgColor;
+    if (bgColor) {
+        style.backgroundColor = bgColor;
+    }
+
+    // Show collapsed ressource
+    if (! ressource.show) {
+        style.height = ""
+        style.top = rowTop - 6 + "px";
+        return html`<div class="jc-timeslot empty" style="${styleMap(style)}"></div>`
+    }
+
+    let content: TemplateResult = html`${evt.title}`
+    const resizer = evt.editable === null ? ressource.eventEditable : evt.editable;
+    const editableRessource = evt.ressourceEditable === null ? ressource.eventRessourceEditable : evt.ressourceEditable;
+    if (resizer) {
+        content = html`<div class="jc-timeslot-resizer-start" @mousedown="${this._getEventResizerHandler("start")}"></div>${content}
+                        <div class="jc-timeslot-resizer-end" @mousedown="${this._getEventResizerHandler("end")}"></div>`;
+    }
+    return html`<div class="jc-timeslot ${evt.moving ? "moving" : ""} ${evt.selected ? "selected" : ""}" 
+        start="${evt.start.getHours()}" 
+        end="${evt.end.getHours()}" 
+        style="${styleMap(style)}"
+        @mousedown="${this._getEventGrabHandler(evt, resizer, editableRessource, this._getEventClickHandler(evt))}"
+        >${content}</div>`;
+
+ }
+}
+
+export default TimelineEvent

+ 1 - 1
src/components/horizontal-resizer.ts

@@ -4,7 +4,7 @@ import {LitElement, html, customElement, css, TemplateResult} from 'lit-element'
 export class HorizontalResizer extends LitElement {
   static styles = css`
     :host {
-      min-width:4px;
+      min-width:3px;
       background-color:lightgray;
       cursor:col-resize;
     }

+ 34 - 0
src/components/legend.html

@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <jc-timeline>
+        <div>
+            <div>${ressource}</div>>
+            <horizontal-resizer></horizontal-resizer>
+            <timeline-legend></timeline-legend>
+        </div>
+        <div>
+            <timeline-ressource>
+                <div>
+                    <div>${label}</div>
+                    <horizontal-resizer></horizontal-resizer>
+                    <timeline-grid>
+                        <slot name="events">
+                            <timeline-event></timeline-event>
+                            <timeline-event></timeline-event>
+                            <timeline-event></timeline-event>
+                        </slot>
+                    </timeline-grid>                    
+                </div>
+                <slot name="children"></slot>
+            </timeline-ressource>
+        </div>
+    </jc-timeline>
+</body>
+</html>

+ 100 - 0
src/stories/Timeline.stories.ts

@@ -0,0 +1,100 @@
+import { Story } from '@storybook/web-components';
+import { IEvent } from '../Event';
+import Ressource, { IRessource } from '../Ressource';
+import Timeline, { UnitLegend } from '../Timeline';
+
+export default {
+    title: 'Javascript/Timeline',
+    component: 'jc-timeline',
+    argTypes:{
+      start:{
+        control:{type:"date"}
+      },
+      end:{
+        control:{type:"date"}
+      }
+    }
+  };
+
+interface TimelineProps {
+  start:string,
+  end:string,
+  slotduration:number,
+  legendSpan?:number,
+  slotWidth?:number,
+  legendFormat?:Partial<UnitLegend>,
+  ressources?:Array<IRessource>,
+  events?:Array<IEvent>
+}
+
+const Template: Story<TimelineProps> = (args:TimelineProps) => {
+  const t = new Timeline();
+  t.start = args.start;
+  t.end = args.end
+  if(args.legendSpan) t.legendSpan = args.legendSpan;
+  if(args.slotduration) t.slotDuration = args.slotduration;
+  if(args.slotWidth) t.slotWidth = args.slotWidth;
+  if(args.legendSpan) t.legendSpan = args.legendSpan;
+  if(args.legendFormat) t.setLegendUnitFormatAll(args.legendFormat);
+  if(args.ressources) t.addRessources(args.ressources);
+  if(args.events) t.addEvents(args.events);
+  
+  return t;
+};
+
+export const Generic = Template.bind({});
+Generic.args = {
+  start:"2021-04-04T22:00:00.000Z",
+  end:"2021-04-05T21:59:00.000Z",
+  ressources:[{id:"1",title:"Ressource 1"},{id:"2",title:"Ressource 2"}]
+}
+
+export const Week = Template.bind({});
+Week.args = {
+  start:"2021-04-04T22:00:00.000Z",
+  end:"2021-04-11T21:59:00.000Z",
+  slotduration:360,
+  legendSpan:4,
+  slotWidth:30,
+  legendFormat:{"d":"MMM D"}
+}
+
+
+export const NestedRessource = Template.bind({});
+NestedRessource.args = {
+  start:"2021-04-05T05:00:00.000Z",
+  end:"2021-04-05T17:59:00.000Z",
+  ressources:[{id:"2",title:"Ressource 2",parent:{id:"1",title:"Ressource 1"}}]
+}
+
+export const WithEvents = Template.bind({});
+WithEvents.args = {
+  start:"2021-04-05T05:00:00.000Z",
+  end:"2021-04-05T17:59:00.000Z",
+  ressources:[{id:"1",title:"Ressource 1"},{id:"2",title:"Ressource 2"}],
+  events: [
+    {id:'1', title:"Fixed ressource" ,ressourceId:'1', start:new Date("2021-04-05T05:00:00.000Z"), end:new Date("2021-04-05T07:59:00.000Z"), ressourceEditable:false,bgColor:"darkgreen"},
+    {id:'2', title:"Fixed time" , ressourceId:'1', start:new Date("2021-04-05T09:00:00.000Z"), end:new Date("2021-04-05T10:59:00.000Z"), editable:false,bgColor:"FireBrick"},
+    {id:'3', ressourceId:'2', start:new Date("2021-04-05T10:00:00.000Z"), end:new Date("2021-04-05T11:59:00.000Z")},
+    {id:'4', ressourceId:'2', start:new Date("2021-04-05T11:00:00.000Z"), end:new Date("2021-04-05T12:59:00.000Z")},
+    {id:'5', ressourceId:'2', start:new Date("2021-04-05T12:00:00.000Z"), end:new Date("2021-04-05T13:59:00.000Z")}
+]
+}
+const nItem = 25;
+const date = (new Date("2021-04-05T05:00:00.000Z")).valueOf()
+export const Rainbow = Template.bind({});
+Rainbow.args = {
+  start:"2021-04-05T05:00:00.000Z",
+  end:"2021-04-05T17:59:00.000Z",
+  ressources:[Array(8).fill(0).map((_,i)=>new Ressource({id:""+i,title:"level "+i}))
+              .reduce((acc:IRessource|undefined,o:IRessource)=>{o.parent=acc;return o },undefined) as IRessource],
+  events:  Array(nItem).fill(0).map((_,i)=>{
+      return {
+          id:""+i,
+          ressourceId:"" + (i % 8),
+          title:"Item "+i,
+          start: new Date(date + 1800000 * i),
+          end:new Date(date + 3600_000+ 1800000 * i),
+          bgColor:"hsl(" + Math.round(i / (nItem-1) * 360) + ", 100%, 50%)"
+      }})
+}

+ 213 - 0
src/styles/SimplbeBar.styles.ts

@@ -0,0 +1,213 @@
+import { css, CSSResult } from "lit-element";
+
+export const SimpleBarStyle:CSSResult = css`[data-simplebar] {
+    position: relative;
+    flex-direction: column;
+    flex-wrap: wrap;
+    justify-content: flex-start;
+    align-content: flex-start;
+    align-items: flex-start;
+  }
+  
+  .simplebar-wrapper {
+    overflow: hidden;
+    width: inherit;
+    height: inherit;
+    max-width: inherit;
+    max-height: inherit;
+  }
+  
+  .simplebar-mask {
+    direction: inherit;
+    position: absolute;
+    overflow: hidden;
+    padding: 0;
+    margin: 0;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    right: 0;
+    width: auto !important;
+    height: auto !important;
+    z-index: 0;
+  }
+  
+  .simplebar-offset {
+    direction: inherit !important;
+    box-sizing: inherit !important;
+    resize: none !important;
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    padding: 0;
+    margin: 0;
+    -webkit-overflow-scrolling: touch;
+  }
+  
+  .simplebar-content-wrapper {
+    direction: inherit;
+    box-sizing: border-box !important;
+    position: relative;
+    display: block;
+    height: 100%; /* Required for horizontal native scrollbar to not appear if parent is taller than natural height */
+    width: auto;
+    max-width: 100%; /* Not required for horizontal scroll to trigger */
+    max-height: 100%; /* Needed for vertical scroll to trigger */
+    scrollbar-width: none;
+    -ms-overflow-style: none;
+  }
+  
+  .simplebar-content-wrapper::-webkit-scrollbar,
+  .simplebar-hide-scrollbar::-webkit-scrollbar {
+    width: 0;
+    height: 0;
+  }
+  
+  .simplebar-content:before,
+  .simplebar-content:after {
+    content: ' ';
+    display: table;
+  }
+  
+  .simplebar-placeholder {
+    max-height: 100%;
+    max-width: 100%;
+    width: 100%;
+    pointer-events: none;
+  }
+  
+  .simplebar-height-auto-observer-wrapper {
+    box-sizing: inherit !important;
+    height: 100%;
+    width: 100%;
+    max-width: 1px;
+    position: relative;
+    float: left;
+    max-height: 1px;
+    overflow: hidden;
+    z-index: -1;
+    padding: 0;
+    margin: 0;
+    pointer-events: none;
+    flex-grow: inherit;
+    flex-shrink: 0;
+    flex-basis: 0;
+  }
+  
+  .simplebar-height-auto-observer {
+    box-sizing: inherit;
+    display: block;
+    opacity: 0;
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 1000%;
+    width: 1000%;
+    min-height: 1px;
+    min-width: 1px;
+    overflow: hidden;
+    pointer-events: none;
+    z-index: -1;
+  }
+  
+  .simplebar-track {
+    z-index: 1;
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    pointer-events: none;
+    overflow: hidden;
+  }
+  
+  [data-simplebar].simplebar-dragging .simplebar-content {
+    pointer-events: none;
+    user-select: none;
+    -webkit-user-select: none;
+  }
+  
+  [data-simplebar].simplebar-dragging .simplebar-track {
+    pointer-events: all;
+  }
+  
+  .simplebar-scrollbar {
+    position: absolute;
+    left: 0;
+    right: 0;
+    min-height: 10px;
+  }
+  
+  .simplebar-scrollbar:before {
+    position: absolute;
+    content: '';
+    background: black;
+    border-radius: 7px;
+    left: 2px;
+    right: 2px;
+    opacity: 0;
+    transition: opacity 0.2s linear;
+  }
+  
+  .simplebar-scrollbar.simplebar-visible:before {
+    /* When hovered, remove all transitions from drag handle */
+    opacity: 0.5;
+    transition: opacity 0s linear;
+  }
+  
+  .simplebar-track.simplebar-vertical {
+    top: 0;
+    width: 11px;
+  }
+  
+  .simplebar-track.simplebar-vertical .simplebar-scrollbar:before {
+    top: 2px;
+    bottom: 2px;
+  }
+  
+  .simplebar-track.simplebar-horizontal {
+    left: 0;
+    height: 11px;
+  }
+  
+  .simplebar-track.simplebar-horizontal .simplebar-scrollbar:before {
+    height: 100%;
+    left: 2px;
+    right: 2px;
+  }
+  
+  .simplebar-track.simplebar-horizontal .simplebar-scrollbar {
+    right: auto;
+    left: 0;
+    top: 2px;
+    height: 7px;
+    min-height: 0;
+    min-width: 10px;
+    width: auto;
+  }
+  
+  /* Rtl support */
+  [data-simplebar-direction='rtl'] .simplebar-track.simplebar-vertical {
+    right: auto;
+    left: 0;
+  }
+  
+  .hs-dummy-scrollbar-size {
+    direction: rtl;
+    position: fixed;
+    opacity: 0;
+    visibility: hidden;
+    height: 500px;
+    width: 500px;
+    overflow-y: hidden;
+    overflow-x: scroll;
+  }
+  
+  .simplebar-hide-scrollbar {
+    position: fixed;
+    left: 0;
+    visibility: hidden;
+    overflow-y: scroll;
+    scrollbar-width: none;
+    -ms-overflow-style: none;
+  }` 

+ 14 - 8
src/styles/Timeline.style.ts

@@ -1,8 +1,10 @@
 import { css, CSSResult } from "lit-element";
 
 export const TimelineStyle:CSSResult = css`
-body{
-    font-family:Roboto;
+* {
+    font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+    font-size: 16px;
+    font-weight: 400;
 }
 div {
     box-sizing: border-box;
@@ -80,8 +82,8 @@ i.jc-spacer.collapse:after{
 .jc-timeline-grid-container{
     overflow-x: auto;
 }
-.jc-timeline-grid-title-container > table,
-.jc-timeline-grid-container > table {
+.jc-timeline-grid-title-container table,
+.jc-timeline-grid-container table {
     width:100%;
     table-layout: fixed;
     border-collapse: collapse;
@@ -99,6 +101,8 @@ i.jc-spacer.collapse:after{
 }
 .jc-timeline-grid-title:first-child > th{
     border-top:0;
+    border-left-color:#ffff;
+    border-right-color:#ffff;
 }    
 .jc-timeline-grid-title:first-child > th:before,
 .jc-timeline-grid-title:first-child > th:last-child:after {
@@ -106,9 +110,9 @@ i.jc-spacer.collapse:after{
     display: block;
     position:absolute;
     left:-1px;
-    top:0px;
-    height:calc( 100% - 8px);
-    border-left: 2px solid white;
+    bottom:0px;
+    height: 8px;
+    border-left: 1px solid lightgrey;
     z-index:2;
 }
 .jc-timeline-grid-title:first-child > th:last-child:after
@@ -125,7 +129,7 @@ i.jc-spacer.collapse:after{
 .jc-timeline-grid-title > th,
 .jc-slot {      
     height:100%;
-    border: solid 1px lightgray;
+    border: solid 1px lightgrey;
     border-left-style: dotted;
     border-right:0;
     text-align: center;
@@ -156,6 +160,8 @@ i.jc-spacer.collapse:after{
     border-radius:3px;
     padding:4px;
     margin:2px 0px;
+    font-size:14px;
+    font-weight:600;
     z-index:1;
     cursor:auto;
 }

+ 4 - 0
webpack.common.js

@@ -7,6 +7,10 @@ module.exports = {
   ],
   module: {
     rules: [      
+      {
+        test: /\.css$/i,
+        use: ["style-loader", "css-loader"],
+      },
       {
         test: /\.tsx?$/,
         use: 'ts-loader',

+ 1 - 4
webpack.dev.js

@@ -7,10 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
 module.exports = merge(common,{
   entry: {
     Timeline:'./src/Timeline.ts',
-    index:{
-      import:'./dev/index.ts',
-      dependOn:"Timeline"
-    },
+    index:'./dev/index.ts',
   },
   plugins: [
     new HtmlWebpackPlugin({

Some files were not shown because too many files changed in this diff