-
Notifications
You must be signed in to change notification settings - Fork 0
/
timeline.min.js
17 lines (17 loc) · 18.4 KB
/
timeline.min.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*! Timeline
*Copyright (C) 2021-2024 Aonghus Storey
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
!function(t,s){"object"==typeof exports&&"undefined"!=typeof module?module.exports=s():"function"==typeof define&&define.amd?define(s):(t="undefined"!=typeof globalThis?globalThis:t||self).Timeline=s()}(this,(function(){"use strict";const t="http://www.w3.org/2000/svg";class s{static draw({start:s,end:e,stroke:i,colour:n,markers:o=[],dashes:r="",title:h=""}={}){const c=2*i,a=e.x-s.x,d=e.y-s.y,l={x1:c,y1:c,x2:a+c,y2:d+c};e.x<s.x&&(l.x1+=Math.abs(a),l.x2+=Math.abs(a)),e.y<s.y&&(l.y1+=Math.abs(d),l.y2+=Math.abs(d));const u=Math.min(s.x,e.x)-c,f=Math.min(s.y,e.y)-c;let m=document.createElementNS(t,"svg");m.setAttribute("width",Math.abs(a)+2*c),m.setAttribute("height",Math.abs(d)+2*c),m.setAttribute("style","position: absolute; left: "+u+"px; top: "+f+"px"),m.style.color=n;const p=this.drawLine(l,i,r,h);return p.setAttribute("data-coords",`[ ${s.x}, ${s.y} ], [ ${e.x}, ${e.y} ]`),m.append(p),m=this.t(m,o[0],"start",l,i),m=this.t(m,o[1],"end",l,i),m}static t(t,s,e,i,n){return"circle"==s&&t.append(this.i(e,i,n)),"square"==s&&t.append(this.o(e,i,n)),"dots"==s&&"end"==e&&(t.setAttribute("width",parseInt(t.getAttribute("width"))+2*n),t.append(this.h(i,n))),t}static o(t,s,e){let[i,n]=[s.x1-e,s.y1-e];return"end"==t&&([i,n]=[s.x2-e,s.y2-e]),this.drawSquare(i,n,2.5*e)}static i(t,s,e){let[i,n]=[s.x1,s.y1];return"end"==t&&([i,n]=[s.x2,s.y2]),this.drawCircle(i,n,e)}static h(t,s){let e=t.x2;t.x2<t.x1&&(e=t.x2-5*s),t.x2>t.x1&&(e=t.x2+5*s);let i=t.y2;t.y2<t.y1&&(i=t.y2-2*s),t.y2>t.y1&&(i=t.y2+2*s);const n={x1:t.x2,y1:t.y2,x2:e,y2:i};return this.drawLine(n,s,`0 ${s} ${s} ${s} ${s}`)}static drawLine(s,e,i="",n=""){const o=document.createElementNS(t,"line");return o.setAttribute("x1",s.x1),o.setAttribute("y1",s.y1),o.setAttribute("x2",s.x2),o.setAttribute("y2",s.y2),o.setAttribute("stroke-width",e),o.setAttribute("stroke-dasharray",i),o.setAttribute("stroke","currentColor"),n&&o.append(this.l(n)),o}static drawCircle(s,e,i,n=""){const o=document.createElementNS(t,"circle");return o.setAttribute("cx",s),o.setAttribute("cy",e),o.setAttribute("r",i),o.setAttribute("fill","currentColor"),n&&o.append(this.l(n)),o}static drawSquare(s,e,i,n=""){const o=document.createElementNS(t,"rect");return o.setAttribute("x",s),o.setAttribute("y",e),o.setAttribute("width",i),o.setAttribute("height",i),o.setAttribute("fill","currentColor"),n&&o.append(this.l(n)),o}static l(s){const e=document.createElementNS(t,"title");return e.append(document.createTextNode(s)),e.dataset.title=s,e}}class e{constructor(t,s,e){this.u=t,this.m=s,this.p=e,this._=e-s,this.v=this.k(t),this.$=this.I(t);let i={};for(const t of this.$)i[t]=this.k();this.S=i}calculate(){const t=[...this.u].filter((t=>t.dataset.group)),s=[...this.u].filter((t=>!t.dataset.group));this.O={};for(const s of t)this.C(s,!0);let e=0;this.$.forEach(((t,s)=>{const i=[...this.u].filter((s=>s.dataset.group==t));if(0!=s){const i=this.S[this.$[s-1]],n=this.G(i,this.S[t]);e-=n}for(const t of i)t.dataset.row=parseInt(t.dataset.groupRow)+e;this.O[t]=[e,e+this.S[t].length],e+=this.S[t].length}));const i=this.j(this.u);this.M(this.v,i);for(const t of[...this.u].filter((t=>t.dataset.row)))this.F(t.dataset.row,this.R(t.dataset.start),this.R(t.dataset.end),this.v);for(const s of t)(s.dataset.split||s.dataset.merge)&&this.T(s);for(const t of s)this.C(t)}get rows(){return this.v.length}I(t){return[...t].reduce(((t,s)=>(s.dataset.group&&!t.includes(s.dataset.group)&&t.push(s.dataset.group),t)),[])}T(t){const s=t.dataset.split?t.dataset.split:t.dataset.merge,e=document.getElementById(s);if(e.dataset.group!==t.dataset.group){let s=e.dataset.row-t.dataset.row>0?this.O[t.dataset.group][1]:this.O[t.dataset.group][0];const i=this.A(s,t.dataset.row,this.R(t.dataset.start),this.R(this.D(t)),this.v);if(void 0!==i){this.q(t,i);const s=[...this.u].filter((s=>s.dataset.split==t.id||s.dataset.merge==t.id));for(const e of s)if(e.dataset.group==t.dataset.group){const s=this.A(t.dataset.row,e.dataset.row,this.R(e.dataset.start),this.R(this.D(e)),this.v);void 0!==s&&this.q(e,s)}return}}}q(t,s){const e=this.R(t.dataset.start),i=this.R(this.D(t));this.P(t.dataset.row,e,i,this.v),t.dataset.row=s,this.J(t,"row",this.v),this.F(t.dataset.row,e,i,this.v)}C(t,s=!1){let e=this.v,i="row";if(s){if(!t.dataset.group)return;e=this.S[t.dataset.group],i="groupRow"}if(t.dataset[i])return;const n=this.R(t.dataset.start),o=this.R(this.D(t));let r=null,h=null;if(t.dataset.split&&(r=document.getElementById(t.dataset.split)),t.dataset.merge){const s=document.getElementById(t.dataset.merge);s.dataset.split!==t.id&&(r=s)}!r||s&&r.dataset.group!=t.dataset.group||(Object.hasOwn(r.dataset,i)||this.C(r,s),h=parseInt(r.dataset[i]));const c=this.L(n,o,e,h);t.dataset[i]=c,this.J(t,i,e);try{this.F(c,n,o,e)}catch(s){console.warn(`${s}: called for ${t.id} with row ${c}`)}}J(t,s,e){if(t.dataset.become){const i=document.getElementById(t.dataset.become);t.dataset.group!==i.dataset.group&&(console.warn(`${t.id} and ${i.id} are directly connected but in separate groups. Amending ${i.id} to ${t.dataset.group}`),i.dataset.group=t.dataset.group),Object.hasOwn(i.dataset,s)&&this.P(i.dataset[s],this.R(i.dataset.start),this.R(i.dataset.end),e),i.dataset[s]=t.dataset[s],this.J(i,s,e)}}D(t){let s=t.dataset.end;return t.dataset.become&&(s=this.D(document.getElementById(t.dataset.become))),s}G(t,s){let e=Math.min(t.length,s.length)-1;for(;e>0;){let i=!0;for(let n=0;n<e&&n<t.length;n++)if(!this.W(t[t.length-e+n],s[n])){i=!1;break}if(1==i)return e;e--}return e}k(t=null){let s=1,e=[];t&&(s=this.j(t));let i=Array.from(Array(s),(()=>new Array(this._).fill(!1)));for(const t of e)i=this.F(t.dataset.row,this.R(t.start),this.R(t.end),i);return i}j(t){const s=[...t].filter((t=>t.dataset.row));return s.length>0?Math.max(...s.map((t=>parseInt(t.dataset.row))))+1:1}L(t,s,e,i=null){let n=i||Math.floor(e.length/2),o=!1;for(let i=0;i<e.length;i++)if(n=o?n-i:n+i,!(n<0||n>e.length-1)){if(this.N(n,t,s,e))return n;o=!o}return this.H(e),e.length-1}R(t){return parseInt(t)-parseInt(this.m)}M(t,s){for(;t.length-1!==s;)this.H(t);return t}H(t){return t.push(new Array(this._).fill(!1)),t}A(t,s,e,i,n){for(;t!=s;){if(this.N(t,e,i,n))return t;t=t>s?parseInt(t)-1:parseInt(t)+1}}N(t,s,e,i){s===e&&(e+=1);return i[t].slice(s,e).every((t=>!1===t))}F(t,s,e,i){return this.U(t,s,e,i,!0),i}P(t,s,e,i){return this.U(t,s,e,i,!1),i}U(t,s,e,i,n){if(!i[t])throw new Error(`Attempt to mark non-existent grid row ${t}. Grid has length ${i.length}`);let o=0;for(;o<e-s;)i[t][s+o]=n,o++;return s>0&&(i[t][s-1]=n),e<i[0].length-1&&(i[t][e]=n),i}W(t,s){for(const[e,i]of t.entries())if(i&&s[e])return!1;return!0}}function i(t,s){let e={};for(const i in t)Object.hasOwn(s,i)?e[i]=s[i]:e[i]=t[i];return e}const n={yearStart:1900,yearEnd:(new Date).getFullYear()+1,strokeWidth:4,yearWidth:50,rowHeight:50,padding:5,boxWidth:100,guides:!0,guideInterval:5,entrySelector:"div",linkDashes:"4",irregularDashes:"88 4 4 4"};class o{constructor(t,s={}){this.X=this.Y(s),this.B(),this.K=document.getElementById(t),this.u=document.querySelectorAll("#"+t+" > "+this.X.entrySelector+":not(.timeline-exclude):not(.event)"),this.V=this.K.querySelectorAll(".event")}Y(t){const s=i(n,t);return s.boxHeight=s.rowHeight-2*s.padding,s.boxMinWidth=s.boxHeight,s}Z(t,s){this.X[t]=s}create(){return this.tt(),this.st(),this.et(),!0===this.X.guides&&this.it(),this.K}tt(){this.nt(),this.ot(),this.K.classList.add("timeline-container"),this.K.style.height=(this.X.rows+2)*this.X.rowHeight+"px",this.K.style.width=(this.X.yearEnd+1-this.X.yearStart)*this.X.yearWidth+"px",this.rt(),this.ht()}nt(){for(const t of this.u){t.classList.add("entry"),t.dataset.end=this.ct(t),parseInt(t.dataset.start)<this.X.yearStart&&(t.classList.add("preexists"),t.dataset.start=this.X.yearStart);for(const s of["become","split","merge","links"])if(Object.hasOwn(t.dataset,s))for(const e of t.dataset[s].split(" "))document.getElementById(e)||(console.warn(`${t.id}: Given ${s} ID "${e}" doesn't exist. Ignoring.`),delete t.dataset[s])}}ot(){let t=1;for(const s of this.u)parseInt(s.dataset.row)>t&&(t=parseInt(s.dataset.row));const s=new e(this.u,this.X.yearStart,this.X.yearEnd);s.calculate(),t=s.rows,this.Z("rows",t)}rt(){for(const t of this.u)t.style.left=this.dt(t.dataset.start)+"px",t.style.top=this.lt(t)+"px",t.dataset.colour&&(t.style.borderColor=t.dataset.colour),!0===this.ut(t)&&t.classList.add("min");for(const t of this.K.querySelectorAll(this.X.entrySelector+"[data-become]"))t.dataset.start==document.getElementById(t.dataset.become).dataset.start&&(t.style.left=parseFloat(t.style.left)-this.X.boxMinWidth/2+"px",document.getElementById(t.dataset.become).style.left=parseFloat(document.getElementById(t.dataset.become).style.left)+this.X.boxMinWidth/2+"px")}ht(){for(const t of this.V){if(t.dataset.target&&!document.getElementById(t.dataset.target)){console.warn(`Event has an invalid target – skipping: ${JSON.stringify(t)}`);continue}let s=this.X.rowHeight-t.offsetHeight,e=this.dt(t.dataset.year);const i=[...this.V].filter((s=>!s.dataset.target&&s.dataset.year==t.dataset.year));i.length>1&&0!==i.indexOf(t)&&(s-=.5*t.offsetHeight*i.indexOf(t));const n=document.createElement("span");n.dataset.year=t.dataset.year,n.innerText=t.innerText,t.innerText="",t.append(n);let o=null;if(t.dataset.colour&&(o=t.dataset.colour),t.dataset.target){const i=document.getElementById(t.dataset.target);s=this.lt(i)+.5*(this.X.boxHeight-t.offsetHeight),e-=.5*t.offsetWidth,i.dataset.colour&&(o=i.dataset.colour)}if(o){const s=`colour-${o.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g,"")}`;t.classList.add(s);let e=`.event.${s}:after { color: ${o}; border-color: ${o} }`;e+=`.event.${s}:hover { color: ${o} }`,this.ft(e)}t.style.left=e+"px",t.style.top=s+"px"}}ft(t){if(!document.getElementById("tl-styles")){const t=document.createElement("style");t.id="tl-styles",t.setAttribute("type","text/css"),document.head.append(t)}document.getElementById("tl-styles").append(document.createTextNode(t))}et(){const t=document.createElement("div");t.classList.add("dates");let s=this.X.yearStart;for(;s<this.X.yearEnd;){const e=document.createElement("date");e.style.left=this.dt(s)+"px";const i=document.createTextNode(s);e.append(i),t.append(e),s+=5}this.K.prepend(t);const e=t.cloneNode(!0);e.style.top=this.X.rows*this.X.rowHeight+"px",this.K.append(e)}it(){let t=this.X.yearStart;for(;t<Math.ceil(this.X.yearEnd/this.X.guideInterval)*this.X.guideInterval;){const s=document.createElement("div");s.classList.add("guide"),s.style.left=this.dt(t)+"px",s.style.width=this.X.yearWidth*this.X.guideInterval+"px",(t-this.X.yearStart)/this.X.guideInterval%2==1&&s.classList.add("odd"),this.K.append(s),t+=this.X.guideInterval}}st(){for(const t of this.u){const e=t.dataset.colour?t.dataset.colour:"var(--tl-colour-stroke)",i="true"==t.dataset.irregular?this.X.irregularDashes:"";let n="",o="end",r=this.wt(t,"right"),h={x:this.dt(t.dataset.end),y:r.y};if(Object.hasOwn(t.dataset,"merge")||Object.hasOwn(t.dataset,"become")||(n=t.dataset.endEstimate?"dots":"circle"),Object.hasOwn(t.dataset,"become")&&(h=this.wt(document.getElementById(t.dataset.become),"left"),o="become"),Object.hasOwn(t.dataset,"merge")){t.dataset.start==t.dataset.end&&(h.x+=this.X.yearWidth);const i={x:h.x,y:this.yt(document.getElementById(t.dataset.merge))};h.x=h.x-this.X.yearWidth;const n=s.draw({start:h,end:i,stroke:this.X.strokeWidth,colour:e});n.classList.add("merge"),this.K.append(n),o="merge"}if(t.dataset.start!==t.dataset.end){const t=s.draw({start:r,end:h,stroke:this.X.strokeWidth,colour:e,markers:["",n],dashes:i});t.classList.add(o),this.K.append(t)}Object.hasOwn(t.dataset,"split")&&this.gt(t,e),Object.hasOwn(t.dataset,"links")&&this._t(t,e)}}gt(t,e){const i=document.getElementById(t.dataset.split);let n="top";parseInt(t.dataset.row)<parseInt(i.dataset.row)&&(n="bottom");const o={x:this.dt(t.dataset.start),y:this.yt(i)},r=this.wt(t,n),h=s.draw({start:o,end:r,stroke:this.X.strokeWidth,colour:e});h.classList.add("split"),this.K.append(h)}_t(t,e){const i=t.dataset.links.split(" ");let n={top:-1,bottom:-1,left:-1,right:-1};for(const o of i){const i=document.getElementById(o);let r,h,c={x:0,y:0},a={x:0,y:0};const d=parseInt(t.dataset.row),l=parseInt(i.dataset.row);d===l&&t.dataset.start<i.dataset.start&&(n.right=n.right+1,r="right",h="left"),d===l&&t.dataset.start>i.dataset.start&&(n.left=n.left+1,r="left",h="right"),d>l&&(n.top=n.top+1,r="top",h="bottom"),d<l&&(n.bottom=n.bottom+1,r="bottom",h="top"),c=this.wt(t,r,n[r]),a={x:c.x,y:this.yt(i)},t.dataset.start>=i.dataset.end&&(a.x=this.dt(i.dataset.end)),t.dataset.start==i.dataset.start&&(a=this.wt(i,h));const u=s.draw({start:c,end:a,stroke:this.X.strokeWidth/2,colour:e,markers:["square","square"],dashes:this.X.linkDashes});u.classList.add("link"),this.K.append(u)}}B(){const t=document.documentElement;t.style.setProperty("--tl-width-year",this.X.yearWidth+"px"),t.style.setProperty("--tl-height-row",this.X.rowHeight+"px"),t.style.setProperty("--tl-width-box",this.X.boxWidth+"px"),t.style.setProperty("--tl-height-box",this.X.boxHeight+"px"),t.style.setProperty("--tl-width-box-min",this.X.boxHeight+"px"),t.style.setProperty("--tl-padding",this.X.padding+"px")}wt(t,s,e=0){const i=window.getComputedStyle(t),n=parseFloat(t.style.left),o=parseFloat(t.style.top),r=parseFloat(i.getPropertyValue("width")),h=parseFloat(i.getPropertyValue("height"));switch(s){case"left":return{x:n,y:o+h/2+5*e};case"right":return{x:n+r,y:o+h/2+5*e};case"top":return{x:n+r/2+5*e,y:o};case"bottom":return{x:n+r/2+5*e,y:o+h};default:throw`Invalid element side specified: Called with ${s}. Entry: ${t}`}}ct(t){return t.dataset.end?parseInt(t.dataset.end):t.dataset.become?parseInt(document.getElementById(t.dataset.become).dataset.start):parseInt(this.X.yearEnd)}lt(t){return parseInt((parseInt(t.dataset.row)+1)*this.X.rowHeight+this.X.padding)}ut(t){const s=t.dataset.start;return t.dataset.end-s<this.X.boxWidth/this.X.yearWidth}xt(t){return parseFloat(t.style.left)+this.X.boxWidth/2}yt(t){return parseFloat(t.style.top)+this.X.boxHeight/2}dt(t){return parseInt((t-this.X.yearStart)*this.X.yearWidth)}}const r={panzoom:null,findForm:"timeline-find",zoomIn:"timeline-zoom-in",zoomOut:"timeline-zoom-out",zoomReset:"timeline-zoom-reset"};return class{constructor(t="diagram",s={},e=[],i=[]){this.K=t,this.vt(s);for(const t of e)this.addEntry(t);for(const t of i)this.addEvent(t)}create(){const t=new o(this.K,this.bt);this.kt=t.create(),"function"==typeof this.X.panzoom&&(this.$t(),this.It(),window.addEventListener("hashchange",(t=>this.Et(t)))),location.hash&&setTimeout((()=>{this.Et()}))}vt(t){this.X=i(r,t),this.bt=i(n,t)}addEntry(t){if(document.getElementById(t.id))return void console.warn(`Invalid entry: ${t.id} already exists.`);if(!["id","name","start"].every((s=>Object.hasOwn(t,s))))return void console.warn(`Invalid entry: ${JSON.stringify(t)}. Entries must have at least id, name and start values.`);const s=document.createElement("div");s.id=t.id,s.innerText=t.name;for(const e of Object.keys(t))["id","name"].includes(e)||(s.dataset[e]=t[e]);document.getElementById(this.K).append(s)}addEvent(t){if(!t.year||!t.content)return void console.warn(`Invalid event: ${JSON.stringify(t)}. Events must have at least a year and content property.`);const s=document.createElement("div");s.classList.add("event"),s.innerText=t.content;for(const e of Object.keys(t))"content"!=e&&(s.dataset[e]=t[e]);document.getElementById(this.K).append(s)}panToEntry(t){if(void 0===this.St)throw new Error("Panzoom module missing. Include Panzoom to use the pan-to-entry feature.");const s=document.getElementById(t),e=window.innerWidth/2-parseInt(s.style.left)-this.bt.boxWidth/2,i=window.innerHeight/2-parseInt(s.style.top)-this.bt.rowHeight/2;this.St.zoom(1),this.St.pan(e,i);const n=new CustomEvent("timelineFind",{detail:{id:t,name:s.innerText}});document.getElementById(this.K).dispatchEvent(n),setTimeout((()=>{s.classList.add("highlight","hover")}),500),setTimeout((()=>{s.classList.remove("highlight","hover")}),2e3)}It(){const t=document.getElementById(this.X.zoomIn),s=document.getElementById(this.X.zoomOut),e=document.getElementById(this.X.zoomReset),i=document.getElementById(this.X.findForm);t&&t.addEventListener("click",this.St.zoomIn),s&&s.addEventListener("click",this.St.zoomOut),e&&e.addEventListener("click",(()=>this.St.zoom(1))),i&&this.Ot(i)}Ot(t){const s=document.createElement("input");s.name="find-id",s.style.display="none",t.append(s);const e=t.querySelector("input[name=finder]"),i=document.createElement("div"),n=document.createElement("div"),o=document.createElement("ul");i.classList.add("filtered-entries"),i.appendChild(n),n.appendChild(o),e.parentNode.insertBefore(i,e),i.appendChild(e),e.autocomplete="off";const r={form:t,finder:e,id:s,results:o};this.Ct=r,r.finder.value="",t.addEventListener("input",(t=>this.Gt(t))),t.addEventListener("submit",(t=>this.jt(t))),o.addEventListener("click",(t=>this.Mt(t)))}Gt(t){const s=t.target.value;if(""===s.trim())return this.Ct.results.innerHTML="",null;const e=this.Ft(s),i=this.Ct.results;i.innerHTML="";for(const t of e){const s=document.createElement("li");s.dataset.id=t.id,s.innerText=t.name,i.append(s)}}Ft(t){return[...document.querySelectorAll(".entry")].map((t=>({id:t.id,name:t.innerText}))).filter((s=>s.name.toLowerCase().includes(t.toLowerCase())))}Mt(t){if("li"!==t.target.localName)return null;const s=this.Ct.form,e=this.Ct.finder,i=this.Ct.id;e.value=t.target.innerText,i.value=t.target.dataset.id,s.requestSubmit()}jt(t){t.preventDefault();const s=t.target.querySelector("input[name=find-id]").value;document.getElementById(s)&&this.panToEntry(s),this.Ct.results.innerHTML="",this.Ct.finder.value=""}$t(){const t=document.createElement("div");t.classList.add("pz-wrap"),this.kt.parentNode.insertBefore(t,this.kt),t.appendChild(this.kt),this.St=this.X.panzoom(this.kt,{contain:"outside",maxScale:3,minScale:.5,step:.1,handleStartEvent:t=>{t.preventDefault()}}),this.kt.parentElement.addEventListener("wheel",this.St.zoomWithWheel)}Et(){const t=location.hash.replace("#find-","");document.getElementById(t)&&this.St&&this.panToEntry(t)}}}));