├── docs ├── robots.txt ├── readme │ ├── robots.txt │ ├── images │ │ ├── noise-dark.png │ │ └── noise-light.png │ ├── favicons │ │ ├── favicon-16x16.png │ │ ├── favicon-180x180.png │ │ ├── favicon-192x192.png │ │ ├── favicon-32x32.png │ │ ├── favicon-48x48.png │ │ ├── favicon-60x60.png │ │ ├── favicon-96x96.png │ │ ├── apple-touch-icon-120x120-precomposed.png │ │ └── apple-touch-icon-152x152-precomposed.png │ ├── index.html │ └── css │ │ └── page.css ├── resources │ ├── cat.jpg │ └── cat_original.jpg ├── images │ ├── noise-dark.png │ ├── noise-light.png │ ├── gear.svg │ └── resize.svg ├── favicons │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-48x48.png │ ├── favicon-60x60.png │ ├── favicon-96x96.png │ ├── favicon-180x180.png │ ├── favicon-192x192.png │ ├── apple-touch-icon-120x120-precomposed.png │ └── apple-touch-icon-152x152-precomposed.png ├── script │ ├── page.min.js │ ├── main.min.js │ └── page.js ├── index.html └── css │ └── page.css ├── .gitignore ├── .gitattributes ├── src └── readme │ ├── cat.png │ ├── bather.png │ ├── joconde.png │ └── picasso.png ├── README.md └── LICENSE.md /docs/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: GPTBot 2 | Disallow: / -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *generated.* 3 | debug.log 4 | -------------------------------------------------------------------------------- /docs/readme/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: GPTBot 2 | Disallow: / -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/readme/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/src/readme/cat.png -------------------------------------------------------------------------------- /docs/resources/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/resources/cat.jpg -------------------------------------------------------------------------------- /src/readme/bather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/src/readme/bather.png -------------------------------------------------------------------------------- /src/readme/joconde.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/src/readme/joconde.png -------------------------------------------------------------------------------- /src/readme/picasso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/src/readme/picasso.png -------------------------------------------------------------------------------- /docs/images/noise-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/images/noise-dark.png -------------------------------------------------------------------------------- /docs/images/noise-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/images/noise-light.png -------------------------------------------------------------------------------- /docs/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /docs/favicons/favicon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/favicons/favicon-48x48.png -------------------------------------------------------------------------------- /docs/favicons/favicon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/favicons/favicon-60x60.png -------------------------------------------------------------------------------- /docs/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /docs/resources/cat_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/resources/cat_original.jpg -------------------------------------------------------------------------------- /docs/favicons/favicon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/favicons/favicon-180x180.png -------------------------------------------------------------------------------- /docs/favicons/favicon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/favicons/favicon-192x192.png -------------------------------------------------------------------------------- /docs/readme/images/noise-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/images/noise-dark.png -------------------------------------------------------------------------------- /docs/readme/images/noise-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/images/noise-light.png -------------------------------------------------------------------------------- /docs/readme/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /docs/readme/favicons/favicon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/favicons/favicon-180x180.png -------------------------------------------------------------------------------- /docs/readme/favicons/favicon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/favicons/favicon-192x192.png -------------------------------------------------------------------------------- /docs/readme/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /docs/readme/favicons/favicon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/favicons/favicon-48x48.png -------------------------------------------------------------------------------- /docs/readme/favicons/favicon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/favicons/favicon-60x60.png -------------------------------------------------------------------------------- /docs/readme/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /docs/favicons/apple-touch-icon-120x120-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/favicons/apple-touch-icon-120x120-precomposed.png -------------------------------------------------------------------------------- /docs/favicons/apple-touch-icon-152x152-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/favicons/apple-touch-icon-152x152-precomposed.png -------------------------------------------------------------------------------- /docs/readme/favicons/apple-touch-icon-120x120-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/favicons/apple-touch-icon-120x120-precomposed.png -------------------------------------------------------------------------------- /docs/readme/favicons/apple-touch-icon-152x152-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piellardj/image-stylization-threading/HEAD/docs/readme/favicons/apple-touch-icon-152x152-precomposed.png -------------------------------------------------------------------------------- /docs/images/gear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # image-stylization-threading 2 | 3 | ## Description 4 | This tool generates string art from any picture of your choice. Pegs are first placed on the frame, and then a single-color thread is repeatedly ran from peg to peg in a straight line. The stacked segments progressively recreate the original image. This process was popularized by Petros Vrellis. 5 | 6 | The monochrome mode uses a single thread, while the color mode uses 3 distinct threads. The result can be exported in the SVG format. 7 | 8 | 9 | See it live [here](https://piellardj.github.io/image-stylization-threading). 10 | 11 | [![Donate](https://raw.githubusercontent.com/piellardj/piellardj.github.io/master/images/readme/donate-paypal.svg)](https://www.paypal.com/donate/?hosted_button_id=AF7H7GEJTL95E) 12 | 13 | ![Screenshot 1](src/readme/cat.png) 14 | 15 | ![Screenshot 2](src/readme/joconde.png) 16 | 17 | ![Screenshot 3](src/readme/picasso.png) 18 | 19 | ![Screenshot 4](src/readme/bather.png) 20 | -------------------------------------------------------------------------------- /docs/images/resize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /docs/readme/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Threading - Explanations 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 31 |
32 |
33 |
34 |

image-stylization-threading

35 |

Description

36 |

This tool generates string art from any picture of your choice. Pegs are first placed on the frame, and then a single-color thread is repeatedly ran from peg to peg in a straight line. The stacked segments progressively recreate the original image. This process was popularized by Petros Vrellis.

37 |

The monochrome mode uses a single thread, while the color mode uses 3 distinct threads. The result can be exported in the SVG format.

38 |

See it live here.

39 |

Donate

40 |

Screenshot 1

41 |

Screenshot 2

42 |

Screenshot 3

43 |

Screenshot 4

44 |
45 |
46 | 47 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /docs/readme/css/page.css: -------------------------------------------------------------------------------- 1 | a{color:#009688;color:var(--var-color-control-accent, #009688);font-weight:bold;text-decoration:none;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;border-width:0 0 2px;border-style:solid;border-color:rgba(0,0,0,0)}a:focus,a:hover{border-color:#009688;border-color:var(--var-color-control-accent, #009688)}:root{--color-code: #e0e0e0}@media(prefers-color-scheme: dark){:root{--color-code: #343434}}body{max-width:100%}.contents{line-height:1.5em;max-width:900px;margin:auto;padding:16px 32px;border-radius:8px;border:1px solid #c9c9c9;border:var(--var-color-block-border, 1px solid #c9c9c9);background:#eeeeee;background:var(--var-color-block-background, #eeeeee)}h1{text-align:center;margin-bottom:1em}pre{overflow-x:auto;background:var(--color-code);padding:4px 16px;border-radius:8px;line-height:1.45}pre::-webkit-scrollbar{width:16px}pre::-webkit-scrollbar-track{background-color:rgba(0,0,0,0)}pre::-webkit-scrollbar-thumb{border-width:6px;border-style:solid;border-radius:8px;border-color:var(--color-code);background-color:#a5a5a5;background-color:var(--var-color-scrollbar, #a5a5a5)}pre::-webkit-scrollbar-thumb:focus,pre::-webkit-scrollbar-thumb:hover{background-color:#b2b2b2;background-color:var(--var-color-scrollbar-hover, #b2b2b2)}pre::-webkit-scrollbar-thumb:active{background-color:#959595;background-color:var(--var-color-scrollbar-active, #959595)}pre:hover::-webkit-scrollbar-thumb{border-width:5px}pre code{padding:0}code{background:var(--color-code);padding:2px 4px;border-radius:3px;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;line-height:1.5em}video,img{max-width:100%;border-radius:8px} 2 | .logo{display:block;position:relative;width:64px;height:64px;margin:8px auto 16px;border-radius:50%;user-select:none;box-sizing:border-box}.logo,.logo:hover,.logo:focus,.logo:active{border-width:1px;border-style:solid;border-color:#009688;border-color:var(--var-color-control-accent, #009688)}.logo::before,.logo svg.logo-icon,.logo::after{position:absolute;top:-1px;left:-1px;width:64px;height:64px;border-radius:50%;pointer-events:none}.logo svg.logo-icon{stroke:#009688;stroke:var(--var-color-control-accent, #009688);fill:#009688;fill:var(--var-color-control-accent, #009688)}.logo::before{content:"";transform:scale(0);-webkit-transform:scale(0);-ms-transform:scale(0);transition:.1s ease;-webkit-transition:.1s ease}.logo.logo-animate-fill .logo::before{content:"";transform:scale(0);-webkit-transform:scale(0);-ms-transform:scale(0);transition:.1s ease;-webkit-transition:.1s ease}.logo:hover::before{transform:scale(1);-webkit-transform:scale(1);-ms-transform:scale(1)}.logo.logo-animate-fill{background:#eeeeee;background:var(--var-color-block-background, #eeeeee)}.logo.logo-animate-fill::before{background:#009688;background:var(--var-color-control-accent, #009688)}.logo.logo-animate-fill:hover svg.logo-icon{fill:#fff;stroke:#fff}.logo.logo-animate-empty{background:#009688;background:var(--var-color-control-accent, #009688)}.logo.logo-animate-empty::before{top:0;left:0;width:62px;height:62px;background:#eeeeee;background:var(--var-color-block-background, #eeeeee)} 3 | :root{--var-color-theme:white;--var-color-page-background:#ededed;--var-page-background-image:url("../images/noise-light.png");--var-color-block-background:#eeeeee;--var-color-block-border:1px solid #c9c9c9;--var-color-title:#535353;--var-color-text:#676767;--var-color-block-actionitem:#5e5e5e;--var-color-block-actionitem-hover:#7e7e7e;--var-color-block-actionitem-active:#535353;--var-color-scrollbar:#a5a5a5;--var-color-scrollbar-hover:#b2b2b2;--var-color-scrollbar-active:#959595;--var-color-control-neutral:#c9c9c9;--var-color-control-accent:#009688;--var-color-control-accent-hover:#26a69a;--var-color-control-accent-active:#00897b}@media(prefers-color-scheme: dark){:root{--var-color-theme:black;--var-color-page-background:#232323;--var-page-background-image:url("../images/noise-dark.png");--var-color-block-background:#202020;--var-color-block-border:1px solid #535353;--var-color-title:#eeeeee;--var-color-text:#dbdbdb;--var-color-block-actionitem:#dbdbdb;--var-color-block-actionitem-hover:#eeeeee;--var-color-block-actionitem-active:#c9c9c9;--var-color-scrollbar:#7e7e7e;--var-color-scrollbar-hover:#959595;--var-color-scrollbar-active:#676767;--var-color-control-neutral:#5e5e5e;--var-color-control-accent:#26a69a;--var-color-control-accent-hover:#4db6ac;--var-color-control-accent-active:#009688}}:root{color-scheme:light dark}html{display:flex;min-height:100%;font-family:Arial,Helvetica,sans-serif}body{display:flex;flex:1;flex-direction:column;min-height:100vh;margin:0px;background-attachment:fixed;background:#ededed;background:var(--var-color-page-background, #ededed);background-image:url("../images/noise-light.png");background-image:var(--var-page-background-image, url("../images/noise-light.png"));color:#676767;color:var(--var-color-text, #676767)}main{display:block;flex-grow:1;padding-bottom:32px}h1,h2,h3{color:#535353;color:var(--var-color-title, #535353)} 4 | .badge{width:32px;height:32px;margin:8px 12px;border:none}.badge>svg{width:32px;height:32px}.badge,.badge:hover,.badge:focus,.badge:active{border:none}.badge svg{fill:#5e5e5e;fill:var(--var-color-block-actionitem, #5e5e5e)}.badge svg:focus,.badge svg:hover{fill:#7e7e7e;fill:var(--var-color-block-actionitem-hover, #7e7e7e)}.badge svg:active{fill:#535353;fill:var(--var-color-block-actionitem-active, #535353)}.badge-shelf{display:flex;flex-flow:row;justify-content:center}footer{align-items:center;padding:8px;text-align:center;border-top:1px solid #c9c9c9;border-top:var(--var-color-block-border, 1px solid #c9c9c9);background:#eeeeee;background:var(--var-color-block-background, #eeeeee)} 5 | -------------------------------------------------------------------------------- /docs/script/page.min.js: -------------------------------------------------------------------------------- 1 | var Page;!function(e){var e=e.Demopage||(e.Demopage={}),r="error-messages",t=document.getElementById(r);if(!t)throw new Error("Cannot find element '"+r+"'.");function a(e){return t?t.querySelector("span[id=error-message-"+e+"]"):null}e.setErrorMessage=function(e,r){var n;t&&((n=a(e))?n.innerHTML=r:((n=document.createElement("span")).id="error-message-"+e,n.innerText=r,t.appendChild(n),t.appendChild(document.createElement("br"))))},e.removeErrorMessage=function(e){var r;t&&(e=a(e))&&((r=e.nextElementSibling)&&t.removeChild(r),t.removeChild(e))}}(Page=Page||{}); 2 | var Page;!function(n){var e,t,i,a;function s(e){this.queryParameters={};var t=e.indexOf(s.queryDelimiter);if(t<0)this.baseUrl=e;else{this.baseUrl=e.substring(0,t);for(var r=0,n=e.substring(t+s.queryDelimiter.length).split(s.parameterDelimiter);re.length&&(i=this.queryParameters[a],t(a.substring(e.length),i))}},s.prototype.buildUrl=function(){for(var e=[],t=0,r=Object.keys(this.queryParameters);t span"),this.id=this.inputElement.id,this.inputElement.addEventListener("change",function(e){e.stopPropagation();var t=i.inputElement.files;if(t&&1===t.length){i.labelSpanElement.innerText=a.truncate(t[0].name);for(var n=0,l=i.observers;na.filenameMaxSize?e.substring(0,a.filenameMaxSize-1)+"..."+e.substring(e.length-(a.filenameMaxSize-1)):e},a.filenameMaxSize=16,n=a,l=function(e){var l=this;this.observers=[],this.buttonElement=t.Helpers.Utils.selector(e,"input"),this.id=this.buttonElement.id,this.buttonElement.addEventListener("click",function(e){e.stopPropagation();for(var t=0,n=l.observers;t input[id]").map(function(e){e=e.parentElement;return new n(e)})}),r=new t.Helpers.Cache("FileDownload",function(){return t.Helpers.Utils.selectorAll(document,".file-control.download > input[id]").map(function(e){e=e.parentElement;return new l(e)})}),t.Helpers.Events.callAfterDOMLoaded(function(){i.load(),i.load()}),e.addDownloadObserver=function(e,t){r.getById(e).observers.push(t)},e.addUploadObserver=function(e,t){i.getById(e).observers.push(t)},e.clearFileUpload=function(e){i.getById(e).clear()}}(Page=Page||{}); 5 | var Page;!function(a){var e,t,n,l;function o(e){var t=this;this.observers=[],this.id=o.computeShortId(e.id),this.inputElements=[];for(var r=0,n=a.Helpers.Utils.selectorAll(e,"input");r input[type='range']").map(function(e){e=e.parentElement;return new t(e)})}),s=new r.Helpers.Storage("range",function(e){return""+e.value},function(e,t){e=n.getByIdSafe(e);return!!e&&(e.value=+t,e.callObservers(),!0)}),r.Helpers.Events.callAfterDOMLoaded(function(){n.load(),s.applyStoredState()}),i=!!window.MSInputMethodContext&&!!document.documentMode,e.addObserver=function(e,t){e=n.getById(e),(i?e.onChangeObservers:e.onInputObservers).push(t)},e.addLazyObserver=function(e,t){n.getById(e).onChangeObservers.push(t)},e.getValue=function(e){return n.getById(e).value},e.setValue=function(e,t){n.getById(e).value=t},e.storeState=function(e){e=n.getById(e),s.storeState(e)},e.clearStoredState=function(e){e=n.getById(e),s.clearStoredState(e)}}(Page=Page||{}); 7 | var Page;!function(e){var t,r,c,n;function o(e){var t=this;this.observers=[],this.id=e.id,this.element=e,this.reloadValue(),this.element.addEventListener("change",function(){t.reloadValue(),n.storeState(t),t.callObservers()})}t=e.Checkbox||(e.Checkbox={}),Object.defineProperty(o.prototype,"checked",{get:function(){return this._checked},set:function(e){this.element.checked=e,this.reloadValue()},enumerable:!1,configurable:!0}),o.prototype.callObservers=function(){for(var e=0,t=this.observers;e input[type=checkbox][id]").map(function(e){return new r(e)})}),n=new e.Helpers.Storage("checkbox",function(e){return e.checked?"true":"false"},function(e,t){e=c.getByIdSafe(e);return!(!e||"true"!==t&&"false"!==t||(e.checked="true"===t,e.callObservers(),0))}),e.Helpers.Events.callAfterDOMLoaded(function(){c.load(),n.applyStoredState()}),t.addObserver=function(e,t){c.getById(e).observers.push(t)},t.setChecked=function(e,t){c.getById(e).checked=t},t.isChecked=function(e){return c.getById(e).checked},t.storeState=function(e){e=c.getById(e),n.storeState(e)},t.clearStoredState=function(e){e=c.getById(e),n.clearStoredState(e)}}(Page=Page||{}); 8 | 9 | var Page;!function(h){var n,o,i,T,r,t,c,a,s,u,l,v,d,f,g,p,m,y,E,w,e,L,k,M,C,H,U,b,Y,x,S,X,z,A,D;function O(e){var n=document.querySelector(e);return n||console.error("Cannot find element '"+e+"'."),n}function q(e){return h.Helpers.Utils.selector(document,"input[type=checkbox][id="+e+"]")}function B(e){document.body.style.overflow=e?"hidden":"auto"}function P(){var e=i.getBoundingClientRect();return[Math.floor(e.width),Math.floor(e.height)]}function F(e){return e+"px"}function R(){o.style.width="100vw";var e=P();if(r.checked?(o.style.height="100%",o.style.maxWidth="",o.style.maxHeight=""):(e[1]=e[0]*s/a,o.style.height=F(e[1]),o.style.maxWidth=F(a),o.style.maxHeight=F(s)),e[0]!==u[0]||e[1]!==u[1]){u=P();for(var n=0,t=l;n 2 | 3 | 4 | 5 | 6 | 7 | Threading 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 31 |
32 |
33 |
34 |

Threading

35 | 36 |
37 |

This tool generates string art from any picture of your choice. Pegs are first placed on the frame, and then a single-color thread is repeatedly ran from peg to peg in a straight line. The stacked segments progressively recreate the original image. This process was popularized by Petros Vrellis.

38 |

The monochrome mode uses a single thread, while the color mode uses 3 distinct threads. The result can be exported in the SVG format.

39 | 40 |
41 | 42 | 45 |
46 |
47 |
48 | 51 |
52 | 53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 |
62 |
63 |
64 | Pegs count: 65 |
66 |
67 | Segments count: 68 |
69 |
70 | Error (average): 71 |
72 |
73 | Error (mean square): 74 |
75 |
76 | Error (variance): 77 |
78 |
79 |
80 | 81 | 82 |
83 |
84 |
85 | 91 |
92 |

Input

93 | 94 |
95 |
96 |
97 | 98 | 104 |
105 |
106 |
107 |
108 |
109 |
110 |

Pegs

111 | 112 |
113 |
114 | 115 |
116 | 117 | 118 | 119 | 120 |
121 |
122 |
123 | 124 |
125 | 126 |
127 |
128 | 129 |
130 |
131 |
132 | 133 | 134 | 135 |
136 |
137 | 138 |
139 |
140 |
141 | 142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |

Parameters

151 | 152 |
153 |
154 | 155 |
156 | 157 | 158 | 159 | 160 | 161 | 162 |
163 |
164 |
165 | 166 |
167 | 168 | 169 | 170 | 171 |
172 |
173 |
174 | 175 |
176 | 177 | 178 |
179 |
180 |
181 | 182 |
183 | 184 |
185 |
186 | 187 |
188 |
189 |
190 | 191 | 192 | 193 |
194 |
195 | 196 |
197 |
198 |
199 | 200 |
201 |
202 |
203 |
204 |
205 | 206 |
207 | 208 |
209 |
210 | 211 |
212 |
213 |
214 | 215 | 216 | 217 |
218 |
219 | 220 |
221 |
222 |
223 | 224 |
225 |
226 |
227 |
228 |
229 | 230 |
231 | 232 |
233 |
234 | 235 |
236 |
237 |
238 | 239 | 240 | 241 |
242 |
243 | 244 |
245 |
246 |
247 | 248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |

Display

257 | 258 |
259 |
260 | 261 |
262 | 263 | 264 |
265 |
266 |
267 | 268 |
269 | 270 | 271 |
272 |
273 |
274 | 275 |
276 | 277 |
278 |
279 | 280 |
281 |
282 |
283 | 284 | 285 | 286 |
287 |
288 | 289 |
290 |
291 |
292 | 293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |

Output

302 | 303 |
304 |
305 |
306 | 307 | 313 |
314 |
315 |
316 |
317 | 318 | 324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 | 332 | 355 | 362 | 363 | 364 | 365 | 366 | -------------------------------------------------------------------------------- /docs/css/page.css: -------------------------------------------------------------------------------- 1 | body{text-align:center}#error-messages{margin:32px 8px;color:red;font-weight:bold}.demo{display:flex;flex-flow:row wrap;align-items:flex-start;justify-content:center;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none} 2 | .logo{display:block;position:relative;width:64px;height:64px;margin:8px auto 16px;border-radius:50%;user-select:none;box-sizing:border-box}.logo,.logo:hover,.logo:focus,.logo:active{border-width:1px;border-style:solid;border-color:#009688;border-color:var(--var-color-control-accent, #009688)}.logo::before,.logo svg.logo-icon,.logo::after{position:absolute;top:-1px;left:-1px;width:64px;height:64px;border-radius:50%;pointer-events:none}.logo svg.logo-icon{stroke:#009688;stroke:var(--var-color-control-accent, #009688);fill:#009688;fill:var(--var-color-control-accent, #009688)}.logo::before{content:"";transform:scale(0);-webkit-transform:scale(0);-ms-transform:scale(0);transition:.1s ease;-webkit-transition:.1s ease}.logo.logo-animate-fill .logo::before{content:"";transform:scale(0);-webkit-transform:scale(0);-ms-transform:scale(0);transition:.1s ease;-webkit-transition:.1s ease}.logo:hover::before{transform:scale(1);-webkit-transform:scale(1);-ms-transform:scale(1)}.logo.logo-animate-fill{background:#eeeeee;background:var(--var-color-block-background, #eeeeee)}.logo.logo-animate-fill::before{background:#009688;background:var(--var-color-control-accent, #009688)}.logo.logo-animate-fill:hover svg.logo-icon{fill:#fff;stroke:#fff}.logo.logo-animate-empty{background:#009688;background:var(--var-color-control-accent, #009688)}.logo.logo-animate-empty::before{top:0;left:0;width:62px;height:62px;background:#eeeeee;background:var(--var-color-block-background, #eeeeee)} 3 | .intro{margin:auto;padding:16px;border-radius:8px;border:1px solid #c9c9c9;border:var(--var-color-block-border, 1px solid #c9c9c9);background:#eeeeee;background:var(--var-color-block-background, #eeeeee)}.intro h1{margin-top:0;text-align:center}@media only screen and (min-width: 560px){.intro{max-width:512px;border-width:1px}}.description{justify-content:center;line-height:125%;text-align:justify;text-indent:1em}.project-links{display:flex;flex-flow:row;justify-content:space-between;text-indent:0} 4 | a{color:#009688;color:var(--var-color-control-accent, #009688);font-weight:bold;text-decoration:none;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;border-width:0 0 2px;border-style:solid;border-color:rgba(0,0,0,0)}a:focus,a:hover{border-color:#009688;border-color:var(--var-color-control-accent, #009688)} 5 | .canvas-button{width:32px;height:32px;cursor:pointer}#canvas-container{position:relative;margin-bottom:16px;background:#000;overflow:hidden}@media only screen and (min-width: 540px){#canvas-container{margin:16px}}#canvas-container>canvas{width:100%;height:100%;z-index:10}#canvas-container>.loader{display:none}#indicators{display:flex;position:absolute;top:1px;left:1px;flex-direction:column;align-items:flex-start;color:#fff;font-family:"Lucida Console",Monaco,monospace;text-align:left;z-index:20}#indicators>div{flex:0 0 1em;margin:1px;padding:1px 4px;background:#000}#canvas-buttons-column{position:absolute;top:0;right:0;width:32px;z-index:30}#fullscreen-toggle-id{display:block;background-image:url("../images/resize.svg");background-position:0 0;background-size:200%}#fullscreen-toggle-id:hover{background-position-x:100%}#side-pane-toggle-id{display:none;background-image:url("../images/gear.svg");transition:transform .1s ease-in-out;-webkit-transition:transform .1s ease-in-out}#side-pane-toggle-id:hover{transform:rotate(-30deg);-webkit-transform:rotate(-30deg);-ms-transform:rotate(-30deg)}#side-pane-checkbox-id:checked+#canvas-container #side-pane-toggle-id:hover{transform:rotate(30deg);-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg)}.hidden{display:none}#fullscreen-checkbox-id:checked+.demo{position:fixed;overflow:hidden}#fullscreen-checkbox-id:checked+.demo #canvas-container{position:fixed;top:0;left:0;width:100vw;height:100vh;margin:0;overflow:hidden;z-index:5}#fullscreen-checkbox-id:checked+.demo #canvas-container #canvas-buttons-column{transition:transform .2s ease-in-out;-webkit-transition:transform .2s ease-in-out}#fullscreen-checkbox-id:checked+.demo #canvas-container #fullscreen-toggle-id{background-position-y:100%}@media only screen and (min-width: 500px){#fullscreen-checkbox-id:checked+.demo #canvas-container #side-pane-toggle-id{display:block}}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id:checked+#canvas-container #canvas-buttons-column{transform:translateX(-400px)} 6 | .loader{position:absolute;top:0;right:0;bottom:0;left:0;width:120px;height:120px;margin:auto}.loader>span{color:#fff;font-size:32px;line-height:120px;text-shadow:1px 1px #000,-1px 1px #000,1px -1px #000,-1px -1px #000,1px 0 #000,-1px 0 #000,0 1px #000,0 -1px #000}.loader-animation{position:absolute;top:0;left:0;width:120px;height:120px;animation:spin 1.1s linear infinite}.loader-animation:before{position:absolute;top:-1px;left:-1px;width:122px;height:122px;border:6px solid rgba(0,0,0,0);border-top:6px solid #000;border-radius:50%;content:"";z-index:50;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.loader-animation:after{position:absolute;top:0;left:0;width:120px;height:120px;border:4px solid rgba(0,0,0,0);border-top:4px solid #fff;border-radius:50%;content:"";z-index:51;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}} 7 | .canvas-button{width:32px;height:32px;cursor:pointer}.controls-block{flex:1 0 0;max-width:36em;margin:16px 0;padding:12px 0;border-radius:8px;border:1px solid #c9c9c9;border:var(--var-color-block-border, 1px solid #c9c9c9);background:#eeeeee;background:var(--var-color-block-background, #eeeeee);z-index:0}@media only screen and (min-width: 540px){.controls-block{margin:16px}}.controls-block>hr{margin:12px 0;clear:both;border:none;border-top:1px solid #c9c9c9;border-top:var(--var-color-block-border, 1px solid #c9c9c9)}.controls-section{display:flex;flex-flow:row wrap;align-items:baseline;margin:0 16px}.controls-section>h2{width:7em;margin:0;font-size:medium;font-weight:bold;line-height:2em;text-align:left}.controls-section>.controls-list{display:flex;flex-direction:column;flex-grow:1}.controls-list>.control{display:flex;flex-flow:row wrap;align-items:center;min-width:300px;padding:3px 0}.control>label{min-width:8em;font-size:95%;line-height:95%;text-align:left}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id~.controls-block{position:fixed;top:0;left:100%;width:400px;max-height:calc(100% - 48px);margin:0;border-width:0 0 1px 1px;border-radius:0 0 0 8px;z-index:50;overflow-x:hidden;overflow-y:auto;transition:transform .2s ease-in-out;-webkit-transition:transform .2s ease-in-out}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id~.controls-block::-webkit-scrollbar{width:16px}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id~.controls-block::-webkit-scrollbar-track{border-radius:8px;background-color:#eeeeee;background-color:var(--var-color-block-background, #eeeeee)}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id~.controls-block::-webkit-scrollbar-thumb{border-width:3px 5px;border-style:solid;border-radius:8px;border-color:#eeeeee;border-color:var(--var-color-block-background, #eeeeee);background-color:#a5a5a5;background-color:var(--var-color-scrollbar, #a5a5a5)}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id~.controls-block::-webkit-scrollbar-thumb:focus,#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id~.controls-block::-webkit-scrollbar-thumb:hover{background-color:#b2b2b2;background-color:var(--var-color-scrollbar-hover, #b2b2b2)}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id~.controls-block::-webkit-scrollbar-thumb:active{background-color:#959595;background-color:var(--var-color-scrollbar-active, #959595)}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id~.controls-block:hover::-webkit-scrollbar-thumb{border-width:3px}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id:checked~.controls-block{transform:translateX(-100%);-webkit-transform:translateX(-100%);-ms-transform:translateX(-100%)}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id:checked~.controls-block .tooltip{transform:translateX(-100vw) translateX(400px);-webkit-transform:translateX(-100vw) translateX(400px);-ms-transform:translateX(-100vw) translateX(400px)}#fullscreen-checkbox-id:checked+.demo #side-pane-checkbox-id:checked~.controls-block>#side-pane-close-toggle-id{display:block}#side-pane-close-toggle-id{display:none;position:absolute;top:0;right:0}#side-pane-close-toggle-id svg{stroke:#5e5e5e;stroke:var(--var-color-block-actionitem, #5e5e5e)}#side-pane-close-toggle-id svg:focus,#side-pane-close-toggle-id svg:hover{stroke:#7e7e7e;stroke:var(--var-color-block-actionitem-hover, #7e7e7e)}#side-pane-close-toggle-id svg:active{stroke:#535353;stroke:var(--var-color-block-actionitem-active, #535353)} 8 | .file-control{position:relative}.file-control>input.file-input{position:absolute;top:0;left:0;width:1px;height:1px;opacity:0}.file-control>.file-control-button{display:inline-block;position:relative;padding:6px 12px 6px 2em;border-width:2px;border-style:solid;border-radius:4px;font-size:87.5%;font-weight:bold;cursor:pointer;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.file-control>.file-control-button>svg{position:absolute;top:0;left:.2em;width:1.5em;height:100%}.file-control.compact>.file-control-button{padding:4px 12px 4px 2em;font-size:75%}.file-control>input.file-input+.file-control-button{border-color:#009688;border-color:var(--var-color-control-accent, #009688);color:#009688;color:var(--var-color-control-accent, #009688)}.file-control>input.file-input+.file-control-button>svg{fill:#009688;fill:var(--var-color-control-accent, #009688)}.file-control>input.file-input:focus+.file-control-button,.file-control>input.file-input:hover:not(:disabled)+.file-control-button{border-color:#26a69a;border-color:var(--var-color-control-accent-hover, #26a69a);color:#26a69a;color:var(--var-color-control-accent-hover, #26a69a)}.file-control>input.file-input:focus+.file-control-button>svg,.file-control>input.file-input:hover:not(:disabled)+.file-control-button>svg{fill:#26a69a;fill:var(--var-color-control-accent-hover, #26a69a)}.file-control>input.file-input:active:not(:disabled)+.file-control-button{border-color:#00897b;border-color:var(--var-color-control-accent-active, #00897b);color:#00897b;color:var(--var-color-control-accent-active, #00897b);background:rgba(0,150,136,.1)}.file-control>input.file-input:active:not(:disabled)+.file-control-button>svg{fill:#00897b;fill:var(--var-color-control-accent-active, #00897b)}.file-control>input.file-input:disabled+.file-control-button{border-color:#a5a5a5;color:#a5a5a5}.file-control>input.file-input:disabled+.file-control-button>svg{fill:#a5a5a5} 9 | .tabs{display:flex;position:relative;flex-flow:row wrap;flex-grow:1;width:auto;border-radius:4px;background:none;overflow:hidden}.tabs::after{position:absolute;top:0;left:0;width:100%;height:100%;border-width:2px;border-style:solid;border-color:#c9c9c9;border-color:var(--var-color-control-neutral, #c9c9c9);border-radius:4px;content:"";z-index:1;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.tabs.compact>input+label{padding:6px 14px;font-size:75%}.tabs>input{position:absolute;top:0;left:0;width:1px;height:1px;opacity:0}.tabs>input+label{flex:1;padding:8px 14px;font-size:87.5%;font-weight:bold;text-align:center;white-space:nowrap;cursor:pointer;z-index:2}.tabs>input:disabled+label,.tabs>input[type=radio]:checked+label{cursor:default}.tabs>input+label{background:none;color:#009688;color:var(--var-color-control-accent, #009688)}.tabs>input:checked+label{background:#009688;background:var(--var-color-control-accent, #009688);color:#fff}.tabs>input:disabled+label{background:none;color:#a5a5a5}.tabs>input:disabled:checked+label{background:#a5a5a5;color:#fff}.tabs>input[type=checkbox]:not(:disabled):hover+label,.tabs>input[type=checkbox]:not(:disabled):focus+label{background:rgba(0,150,136,.05)}.tabs>input[type=checkbox]:not(:disabled):hover:checked+label,.tabs>input[type=checkbox]:not(:disabled):focus:checked+label{background:#26a69a;background:var(--var-color-control-accent-hover, #26a69a)}.tabs>input[type=checkbox]:not(:disabled):active+label{background:rgba(0,150,136,.1)}.tabs>input[type=checkbox]:not(:disabled):active:checked+label{background:#00897b;background:var(--var-color-control-accent-active, #00897b)}.tabs>input[type=radio]:not(:disabled):not(:checked):hover+label,.tabs>input[type=radio]:not(:disabled):not(:checked):focus+label{background:rgba(0,150,136,.05)}.tabs>input[type=radio]:not(:disabled):not(:checked):active+label{background:rgba(0,150,136,.1)} 10 | .range-container{display:inline-block;position:relative;flex:1 1 0%;width:100%;min-width:15px;height:26px}.range-container input[type=range]{width:100%;min-width:128px;height:100%;margin:0;padding:0;opacity:0}.range-container input[type=range]:not(:disabled){cursor:pointer}.range-container .range-skin-container{display:flex;position:absolute;top:0;left:0;flex-flow:nowrap;width:100%;height:100%;pointer-events:none;user-select:none}.range-container .range-stub{position:relative;flex-grow:0;flex-shrink:0;width:7px}.range-container .range-progress{display:flex;flex:1;flex-flow:row nowrap}.range-container .range-progress-left{position:relative;flex-grow:0;flex-shrink:0;width:85%}.range-container .range-progress-right{position:relative;flex-grow:1}.range-container .range-bar{position:absolute;left:0;width:100%;z-index:0}.range-container .range-bar.range-bar-left{top:12px;height:3px}.range-container .range-bar.range-bar-right{top:12px;height:3px;background:#c9c9c9;background:var(--var-color-control-neutral, #c9c9c9)}.range-container .range-bar.range-stub-left{border-radius:3px 0 0 3px}.range-container .range-bar.range-stub-right{border-radius:0 3px 3px 0}.range-container .range-handle{position:absolute;top:5.5px;right:-7.5px;width:15px;height:15px;border-radius:50%;z-index:1}.range-container .range-bar-left,.range-container .range-handle{background:#009688;background:var(--var-color-control-accent, #009688)}.range-container input[type=range]:not(:disabled):hover+.range-skin-container .range-handle,.range-container input[type=range]:not(:disabled):focus+.range-skin-container .range-handle{background:#26a69a;background:var(--var-color-control-accent-hover, #26a69a)}.range-container input[type=range]:not(:disabled):active+.range-skin-container .range-handle{background:#00897b;background:var(--var-color-control-accent-active, #00897b)}.range-container input[type=range]:disabled+.range-skin-container .range-bar-left,.range-container input[type=range]:disabled+.range-skin-container .range-handle{background:#a5a5a5}.range-container .range-tooltip{position:absolute;top:-28px;right:0;min-width:24px;padding:4px;transform:translateX(50%);transition:opacity .1s ease-in-out;border-radius:4px;background:#535353;color:#eee;font-size:87.5%;text-align:center;opacity:0;z-index:2}.range-container input[type=range]:hover+.range-skin-container .range-tooltip,.range-container input[type=range]:active+.range-skin-container .range-tooltip,.range-container input[type=range]:focus+.range-skin-container .range-tooltip{opacity:1}.range-container .range-tooltip::after{position:absolute;top:100%;left:50%;width:0px;height:12px;margin-left:-6px;border-width:6px;border-style:solid;border-color:#535353 rgba(0,0,0,0) rgba(0,0,0,0);content:""} 11 | .checkbox{display:block;position:relative;text-align:left;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.checkbox>input[type=checkbox]{width:1px;height:1px;opacity:0}.checkbox>input[type=checkbox]+label.checkmark,.checkbox>input[type=checkbox]+label.checkmark-line{margin-left:24px;line-height:26px;cursor:pointer}.checkbox>input[type=checkbox]:disabled+label.checkmark,.checkbox>input[type=checkbox]:disabled+label.checkmark-line{cursor:default}.checkbox>input[type=checkbox]+label.checkmark::before{position:absolute;top:calc(.5*(100% - 20px));left:0;width:20px;height:20px;border-width:2px;border-style:solid;border-radius:2px;background:none;content:"";box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.checkbox>input[type=checkbox]+label.checkmark::after{position:absolute;top:calc(.5*(100% - 20px) + .5*(20px - 14px));right:0;bottom:0;left:6.5px;width:7px;height:14px;border:solid #fff;border-width:0 3px 3px 0;background:none;content:"";box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;transform:translate(0, -1px) scale(0) rotate(45deg);-webkit-transform:translate(0, -1px) scale(0) rotate(45deg);-ms-transform:translate(0, -1px) scale(0) rotate(45deg)}.checkbox>input[type=checkbox]:checked+label.checkmark::after{transform:translate(0, -1px) scale(1) rotate(45deg);-webkit-transform:translate(0, -1px) scale(1) rotate(45deg);-ms-transform:translate(0, -1px) scale(1) rotate(45deg)}.checkbox>input[type=checkbox]+label.checkmark::before{border-color:#009688;border-color:var(--var-color-control-accent, #009688)}.checkbox>input[type=checkbox]:checked+label.checkmark::before{background:#009688;background:var(--var-color-control-accent, #009688)}.checkbox>input[type=checkbox]:hover+label.checkmark::before,.checkbox>input[type=checkbox]:focus+label.checkmark::before{border-color:#26a69a;border-color:var(--var-color-control-accent-hover, #26a69a)}.checkbox>input[type=checkbox]:hover:checked+label.checkmark::before,.checkbox>input[type=checkbox]:focus:checked+label.checkmark::before{background:#26a69a;background:var(--var-color-control-accent-hover, #26a69a)}.checkbox>input[type=checkbox]:active+label.checkmark::before{border-color:#00897b;border-color:var(--var-color-control-accent-active, #00897b)}.checkbox>input[type=checkbox]:active:checked+label.checkmark::before{background:#00897b;background:var(--var-color-control-accent-active, #00897b)}.checkbox>input[type=checkbox]:disabled+label.checkmark::before{border-color:#a5a5a5}.checkbox>input[type=checkbox]:disabled:checked+label.checkmark::before{background:#a5a5a5} 12 | :root{--var-color-theme:white;--var-color-page-background:#ededed;--var-page-background-image:url("../images/noise-light.png");--var-color-block-background:#eeeeee;--var-color-block-border:1px solid #c9c9c9;--var-color-title:#535353;--var-color-text:#676767;--var-color-block-actionitem:#5e5e5e;--var-color-block-actionitem-hover:#7e7e7e;--var-color-block-actionitem-active:#535353;--var-color-scrollbar:#a5a5a5;--var-color-scrollbar-hover:#b2b2b2;--var-color-scrollbar-active:#959595;--var-color-control-neutral:#c9c9c9;--var-color-control-accent:#009688;--var-color-control-accent-hover:#26a69a;--var-color-control-accent-active:#00897b}@media(prefers-color-scheme: dark){:root{--var-color-theme:black;--var-color-page-background:#232323;--var-page-background-image:url("../images/noise-dark.png");--var-color-block-background:#202020;--var-color-block-border:1px solid #535353;--var-color-title:#eeeeee;--var-color-text:#dbdbdb;--var-color-block-actionitem:#dbdbdb;--var-color-block-actionitem-hover:#eeeeee;--var-color-block-actionitem-active:#c9c9c9;--var-color-scrollbar:#7e7e7e;--var-color-scrollbar-hover:#959595;--var-color-scrollbar-active:#676767;--var-color-control-neutral:#5e5e5e;--var-color-control-accent:#26a69a;--var-color-control-accent-hover:#4db6ac;--var-color-control-accent-active:#009688}}:root{color-scheme:light dark}html{display:flex;min-height:100%;font-family:Arial,Helvetica,sans-serif}body{display:flex;flex:1;flex-direction:column;min-height:100vh;margin:0px;background-attachment:fixed;background:#ededed;background:var(--var-color-page-background, #ededed);background-image:url("../images/noise-light.png");background-image:var(--var-page-background-image, url("../images/noise-light.png"));color:#676767;color:var(--var-color-text, #676767)}main{display:block;flex-grow:1;padding-bottom:32px}h1,h2,h3{color:#535353;color:var(--var-color-title, #535353)} 13 | .badge{width:32px;height:32px;margin:8px 12px;border:none}.badge>svg{width:32px;height:32px}.badge,.badge:hover,.badge:focus,.badge:active{border:none}.badge svg{fill:#5e5e5e;fill:var(--var-color-block-actionitem, #5e5e5e)}.badge svg:focus,.badge svg:hover{fill:#7e7e7e;fill:var(--var-color-block-actionitem-hover, #7e7e7e)}.badge svg:active{fill:#535353;fill:var(--var-color-block-actionitem-active, #535353)}.badge-shelf{display:flex;flex-flow:row;justify-content:center}footer{align-items:center;padding:8px;text-align:center;border-top:1px solid #c9c9c9;border-top:var(--var-color-block-border, 1px solid #c9c9c9);background:#eeeeee;background:var(--var-color-block-background, #eeeeee)} 14 | -------------------------------------------------------------------------------- /docs/script/main.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var e={638:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.getQueryStringValue=t.downloadTextFile=t.declarePolyfills=void 0,t.downloadTextFile=function(e,t){var n="text/plain",r=new Blob([e],{type:n});if(void 0!==window.navigator&&void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(r,t);else{var a=URL.createObjectURL(r),o=document.createElement("a");o.download=t,o.href=a,o.dataset.downloadurl="".concat(n,":").concat(o.download,":").concat(o.href),o.style.display="none",document.body.appendChild(o),o.click(),document.body.removeChild(o),setTimeout((function(){URL.revokeObjectURL(a)}),5e3)}},t.getQueryStringValue=function(e){var t=window.location.href,n=t.indexOf("?");if(n>=0){var r=t.substring(n+1);if(r.length>0)for(var a=0,o=r.split("&");a=0}})),"function"!=typeof String.prototype.repeat&&(console.log("Declaring String.repeat polyfill..."),Object.defineProperty(String.prototype,"repeat",{value:function(e){if(e<0||e===1/0)throw new RangeError;for(var t="",n=0;n=1){(0,o.applyCanvasCompositing)(this.context,t,n,r),this.context.lineWidth=a*this.cssPixel;for(var i=0,s=e;i0){this.context.fillStyle=t,this.context.strokeStyle="none";for(var r=0,a=e;r0,this.writer.addLine(''),this.writer.startBlock('')),this.hasBlur&&(this.writer.startBlock(""),this.writer.startBlock('')),this.writer.addLine('')),this.writer.endBlock(""),this.writer.endBlock(""),this.writer.startBlock(''))),this.writer.addLine(''))},t.prototype.finalize=function(){this.hasBlur&&this.writer.endBlock(""),this.writer.endBlock("")},t.prototype.drawLines=function(e,t,n,r,a){if(e.length>=1){var i=void 0;if((0,o.useAdvancedCompositing)()){this.writer.startBlock(""),this.writer.startBlock('"),this.writer.endBlock("");var s=Math.ceil(255*n),h=(0,o.computeRawColor)(t);i="rgb(".concat(h.r*s,", ").concat(h.g*s,", ").concat(h.b*s,")")}else s=(0,o.useAdvancedCompositing)()?255:0,h=(0,o.computeRawColor)(t),i="rgba(".concat(h.r*s,", ").concat(h.g*s,", ").concat(h.b*s,", ").concat(n,")");this.writer.startBlock(''));for(var c=0,d=e;c'))}this.writer.endBlock("")}},t.prototype.drawPoints=function(e,t,n){if(e.length>0){this.writer.startBlock(''));for(var r=0,a=e;r'))}this.writer.endBlock("")}},t.prototype.export=function(){var e=Date.now(),t=this.writer.result;return console.log("Concatenation took ".concat(Date.now()-e," ms.")),t},Object.defineProperty(t.prototype,"size",{get:function(){return{width:1e3,height:1e3}},enumerable:!1,configurable:!0}),t}(i.PlotterBase);t.PlotterSVG=c},775:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.XMLWriter=void 0;var n=function(){function e(){this.indentationLevel=0,this.lines=[]}return Object.defineProperty(e.prototype,"result",{get:function(){return this.lines.join("\n")},enumerable:!1,configurable:!0}),e.prototype.startBlock=function(e){this.addLine(e),this.indentationLevel++},e.prototype.endBlock=function(e){this.indentationLevel--,this.addLine(e)},e.prototype.addLine=function(e){this.lines.push(this.prefix+e)},Object.defineProperty(e.prototype,"prefix",{get:function(){return"\t".repeat(this.indentationLevel)},enumerable:!1,configurable:!0}),e}();t.XMLWriter=n},794:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.ThreadComputer=void 0;var r=n(627),a=n(755),o=n(246),i=n(443),s=n(170),h=-9007199254740991,c=2*Math.PI;function d(e,t,n){return en?n:e}function l(e,t,n){return e*(1-n)+t*n}function u(e){return 0===e.length?null:e[Math.floor(Math.random()*e.length)]}var p=function(){function e(e){this.hiddenCanvasData=null,this.sourceImage=e,this.hiddenCanvas=document.createElement("canvas"),this.hiddenCanvasContext=this.hiddenCanvas.getContext("2d"),this.reset(.0625,1)}return e.prototype.drawThread=function(e,t){var n=this,a=this.computeTransformation(e.size),i=a.scaling*this.hiddenCanvasScale*this.lineThickness,s=r.Parameters.invertColors?o.ECompositingOperation.LIGHTEN:o.ECompositingOperation.DARKEN;this.thread.iterateOnThreads(t,(function(t,r){for(var o=[],h=0,c=t;ha)return this.thread.lowerNbSegments(a),this.resetHiddenCanvas(),this.thread.iterateOnThreads(0,(function(e,n){(0,o.applyCanvasCompositing)(t.hiddenCanvasContext,n,t.lineOpacityInternal,o.ECompositingOperation.LIGHTEN);for(var r=0;r+1t?(t=s,e=[{peg1:o,peg2:i}]):s===t&&e.push({peg1:o,peg2:i})}}return u(e)},e.prototype.computeBestNextPeg=function(e,t){for(var n=[],r=h,a=0,o=this.pegs;ar?(r=s,n=[i]):s===r&&n.push(i)}}return u(n)},e.prototype.uploadCanvasDataToCPU=function(){if(null===this.hiddenCanvasData){var e=this.hiddenCanvas.width,t=this.hiddenCanvas.height;this.hiddenCanvasData=this.hiddenCanvasContext.getImageData(0,0,e,t)}},e.prototype.computeSegmentPotential=function(e,t){this.uploadCanvasDataToCPU();for(var n,r,a,o,i=0,s=(r=t,a=(n=e).x-r.x,o=n.y-r.y,Math.sqrt(a*a+o*o)),h=Math.ceil(s),c=0;c1?{width:t,height:Math.round(t/h)}:{width:Math.round(t*h),height:t};var n=r.Parameters.shape,a=r.Parameters.pegsCount,o=[];if(n===r.EShape.RECTANGLE){this.arePegsTooClose=function(e,t){return e.x===t.x||e.y===t.y};var i=e.width,s=e.height,h=s/i,d=Math.round(.5*a/(1+h)),l=Math.round(.5*(a-2*d));o.push({x:0,y:0});for(var u=1;u=1;u--)o.push({x:i*(u/d),y:s});for(o.push({x:0,y:s}),p=l-1;p>=1;p--)o.push({x:0,y:s*(p/l)})}else{this.arePegsTooClose=function(e,t){var n=Math.abs(e.angle-t.angle);return Math.min(n,c-n)<=c/16};for(var g=.5*e.width,f=.5*e.height,v=Math.PI*(3*(g+f)-Math.sqrt((3*g+f)*(g+3*f)))/a,m=0;o.lengththis.threadComputer.nbSegments&&(this.nbSegmentsDrawn=0),0===this.nbSegmentsDrawn){var e={backgroundColor:r.Parameters.invertColors?"black":"white",blur:r.Parameters.blur};this.plotter.resize(),this.plotter.initialize(e),r.Parameters.displayPegs&&this.threadComputer.drawPegs(this.plotter),this.threadComputer.drawThread(this.plotter,0),this.plotter.finalize()}else this.threadComputer.drawThread(this.plotter,this.nbSegmentsDrawn);this.nbSegmentsDrawn=this.threadComputer.nbSegments}},e}();t.ThreadPlotter=a},323:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ThreadBase=void 0;var n=function(){function e(){this.sampleCanvas=null}return e.lowerNbSegmentsForThread=function(e,t){e.length=t>0?Math.min(e.length,t+1):0},e.computeNbSegments=function(e){return e.length>1?e.length-1:0},e.iterateOnThread=function(t,n,r,a){r0&&this.threadPegsRed.length0&&this.threadPegsGreen.lengthi&&o>s?a.red++:i>o&&i>s?a.green++:a.blue++}return a},t}(i.ThreadBase);t.ThreadRedBlueGreen=s},755:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Transformation=void 0;var n=function(){function e(e,t){var n=e.width/t.width,r=e.height/t.height;this.scaling=Math.min(n,r),this.origin={x:.5*(e.width-this.scaling*t.width),y:.5*(e.height-this.scaling*t.height)}}return e.prototype.transform=function(e){return{x:this.origin.x+e.x*this.scaling,y:this.origin.y+e.y*this.scaling}},e}();t.Transformation=n}},t={};!function n(r){var a=t[r];if(void 0!==a)return a.exports;var o=t[r]={exports:{}};return e[r].call(o.exports,o,o.exports,n),o.exports}(50)}(); -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /docs/script/page.js: -------------------------------------------------------------------------------- 1 | var Page; 2 | (function (Page) { 3 | var Demopage; 4 | (function (Demopage) { 5 | var errorsBlockId = "error-messages"; 6 | var errorsBlock = document.getElementById(errorsBlockId); 7 | if (!errorsBlock) { 8 | throw new Error("Cannot find element '" + errorsBlockId + "'."); 9 | } 10 | function getErrorById(id) { 11 | if (errorsBlock) { 12 | return errorsBlock.querySelector("span[id=error-message-" + id + "]"); 13 | } 14 | return null; 15 | } 16 | function setErrorMessage(id, message) { 17 | if (errorsBlock) { 18 | var existingSpan = getErrorById(id); 19 | if (existingSpan) { 20 | existingSpan.innerHTML = message; 21 | return; 22 | } 23 | else { 24 | var newSpan = document.createElement("span"); 25 | newSpan.id = "error-message-" + id; 26 | newSpan.innerText = message; 27 | errorsBlock.appendChild(newSpan); 28 | errorsBlock.appendChild(document.createElement("br")); 29 | } 30 | } 31 | } 32 | Demopage.setErrorMessage = setErrorMessage; 33 | function removeErrorMessage(id) { 34 | if (errorsBlock) { 35 | var span = getErrorById(id); 36 | if (span) { 37 | var br = span.nextElementSibling; 38 | if (br) { 39 | errorsBlock.removeChild(br); 40 | } 41 | errorsBlock.removeChild(span); 42 | } 43 | } 44 | } 45 | Demopage.removeErrorMessage = removeErrorMessage; 46 | })(Demopage = Page.Demopage || (Page.Demopage = {})); 47 | })(Page || (Page = {})); 48 | 49 | var Page; 50 | (function (Page) { 51 | var Helpers; 52 | (function (Helpers) { 53 | var Utils; 54 | (function (Utils) { 55 | function selectorAll(base, selector) { 56 | var elements = base.querySelectorAll(selector); 57 | var result = []; 58 | for (var i = 0; i < elements.length; i++) { 59 | result.push(elements[i]); 60 | } 61 | return result; 62 | } 63 | Utils.selectorAll = selectorAll; 64 | /** @throws if no element was found */ 65 | function selector(base, selector) { 66 | var element = base.querySelector(selector); 67 | if (!element) { 68 | throw new Error("No element matching '".concat(selector, "'.")); 69 | } 70 | return element; 71 | } 72 | Utils.selector = selector; 73 | function touchArray(touchList) { 74 | var result = []; 75 | for (var i = 0; i < touchList.length; i++) { 76 | result.push(touchList[i]); 77 | } 78 | return result; 79 | } 80 | Utils.touchArray = touchArray; 81 | function findFirst(array, predicate) { 82 | if (typeof Array.prototype.findIndex === "function") { 83 | return array.findIndex(predicate); 84 | } 85 | else { 86 | for (var i = 0; i < array.length; i++) { 87 | if (predicate(array[i])) { 88 | return i; 89 | } 90 | } 91 | return -1; 92 | } 93 | } 94 | Utils.findFirst = findFirst; 95 | })(Utils = Helpers.Utils || (Helpers.Utils = {})); 96 | var URL; 97 | (function (URL) { 98 | var PARAMETERS_PREFIX = "page"; 99 | var URLBuilder = /** @class */ (function () { 100 | function URLBuilder(url) { 101 | this.queryParameters = {}; 102 | var queryStringDelimiterIndex = url.indexOf(URLBuilder.queryDelimiter); 103 | if (queryStringDelimiterIndex < 0) { 104 | this.baseUrl = url; 105 | } 106 | else { 107 | this.baseUrl = url.substring(0, queryStringDelimiterIndex); 108 | var queryString = url.substring(queryStringDelimiterIndex + URLBuilder.queryDelimiter.length); 109 | var splitParameters = queryString.split(URLBuilder.parameterDelimiter); 110 | for (var _i = 0, splitParameters_1 = splitParameters; _i < splitParameters_1.length; _i++) { 111 | var parameter = splitParameters_1[_i]; 112 | var keyValue = parameter.split(URLBuilder.keyValueDelimiter); 113 | if (keyValue.length === 2) { 114 | var key = decodeURIComponent(keyValue[0]); 115 | var value = decodeURIComponent(keyValue[1]); 116 | this.queryParameters[key] = value; 117 | } 118 | else { 119 | console.log("Unable to parse query string parameter '" + parameter + "'."); 120 | } 121 | } 122 | } 123 | } 124 | URLBuilder.prototype.setQueryParameter = function (name, value) { 125 | if (value === null) { 126 | delete this.queryParameters[name]; 127 | } 128 | else { 129 | this.queryParameters[name] = value; 130 | } 131 | }; 132 | URLBuilder.prototype.loopOnParameters = function (prefix, callback) { 133 | for (var _i = 0, _a = Object.keys(this.queryParameters); _i < _a.length; _i++) { 134 | var parameterName = _a[_i]; 135 | if (parameterName.indexOf(prefix) === 0 && parameterName.length > prefix.length) { 136 | var parameterValue = this.queryParameters[parameterName]; 137 | var shortParameterName = parameterName.substring(prefix.length); 138 | callback(shortParameterName, parameterValue); 139 | } 140 | } 141 | }; 142 | URLBuilder.prototype.buildUrl = function () { 143 | var parameters = []; 144 | for (var _i = 0, _a = Object.keys(this.queryParameters); _i < _a.length; _i++) { 145 | var parameterName = _a[_i]; 146 | var parameterValue = this.queryParameters[parameterName]; 147 | var encodedName = encodeURIComponent(parameterName); 148 | var encodedValue = encodeURIComponent(parameterValue); 149 | parameters.push(encodedName + URLBuilder.keyValueDelimiter + encodedValue); 150 | } 151 | var queryString = parameters.join(URLBuilder.parameterDelimiter); 152 | if (queryString) { 153 | return this.baseUrl + URLBuilder.queryDelimiter + queryString; 154 | } 155 | else { 156 | return this.baseUrl; 157 | } 158 | }; 159 | URLBuilder.queryDelimiter = "?"; 160 | URLBuilder.parameterDelimiter = "&"; 161 | URLBuilder.keyValueDelimiter = "="; 162 | return URLBuilder; 163 | }()); 164 | function buildPrefix() { 165 | var prefixes = []; 166 | for (var _i = 0; _i < arguments.length; _i++) { 167 | prefixes[_i] = arguments[_i]; 168 | } 169 | return prefixes.join(":") + ":"; 170 | } 171 | function updateUrl(newUrl) { 172 | window.history.replaceState("", "", newUrl); 173 | } 174 | function loopOnParameters(prefix, callback) { 175 | var urlBuilder = new URLBuilder(window.location.href); 176 | var fullPrefix = buildPrefix(PARAMETERS_PREFIX, prefix); 177 | urlBuilder.loopOnParameters(fullPrefix, callback); 178 | } 179 | URL.loopOnParameters = loopOnParameters; 180 | function setQueryParameter(prefix, name, value) { 181 | var urlBuilder = new URLBuilder(window.location.href); 182 | var fullPrefix = buildPrefix(PARAMETERS_PREFIX, prefix); 183 | urlBuilder.setQueryParameter(fullPrefix + name, value); 184 | updateUrl(urlBuilder.buildUrl()); 185 | } 186 | URL.setQueryParameter = setQueryParameter; 187 | function removeQueryParameter(prefix, name) { 188 | var urlBuilder = new URLBuilder(window.location.href); 189 | var fullPrefix = buildPrefix(PARAMETERS_PREFIX, prefix); 190 | urlBuilder.setQueryParameter(fullPrefix + name, null); 191 | updateUrl(urlBuilder.buildUrl()); 192 | } 193 | URL.removeQueryParameter = removeQueryParameter; 194 | })(URL = Helpers.URL || (Helpers.URL = {})); 195 | var Events; 196 | (function (Events) { 197 | function callAfterDOMLoaded(callback) { 198 | if (document.readyState === "loading") { // Loading hasn't finished yet 199 | document.addEventListener("DOMContentLoaded", callback); 200 | } 201 | else { // `DOMContentLoaded` has already fired 202 | callback(); 203 | } 204 | } 205 | Events.callAfterDOMLoaded = callAfterDOMLoaded; 206 | })(Events = Helpers.Events || (Helpers.Events = {})); 207 | var Cache = /** @class */ (function () { 208 | function Cache(objectsName, loadObjectsFunction) { 209 | this.objectsName = objectsName; 210 | this.loadObjectsFunction = loadObjectsFunction; 211 | this.cacheObject = null; 212 | } 213 | /** @throws An Error if the ID is unknown */ 214 | Cache.prototype.getById = function (id) { 215 | var object = this.safeCacheObject[id]; 216 | if (!object) { 217 | throw new Error("Invalid '".concat(this.objectsName, "' cache object id '").concat(id, "'.")); 218 | } 219 | return object; 220 | }; 221 | /** @returns null if the ID is unknown */ 222 | Cache.prototype.getByIdSafe = function (id) { 223 | return this.safeCacheObject[id] || null; 224 | }; 225 | Cache.prototype.load = function () { 226 | if (!this.cacheObject) { 227 | this.cacheObject = this.loadCacheObject(); 228 | } 229 | }; 230 | Object.defineProperty(Cache.prototype, "safeCacheObject", { 231 | get: function () { 232 | if (!this.cacheObject) { 233 | this.load(); 234 | } 235 | return this.cacheObject; 236 | }, 237 | enumerable: false, 238 | configurable: true 239 | }); 240 | Cache.prototype.loadCacheObject = function () { 241 | var index = {}; 242 | var objects = this.loadObjectsFunction(); 243 | for (var _i = 0, objects_1 = objects; _i < objects_1.length; _i++) { 244 | var object = objects_1[_i]; 245 | if (typeof index[object.id] !== "undefined") { 246 | throw new Error("Object '".concat(object.id, "' is already in cache.")); 247 | } 248 | index[object.id] = object; 249 | } 250 | return index; 251 | }; 252 | return Cache; 253 | }()); 254 | Helpers.Cache = Cache; 255 | var Storage = /** @class */ (function () { 256 | function Storage(prefix, serialize, tryDeserialize) { 257 | this.prefix = prefix; 258 | this.serialize = serialize; 259 | this.tryDeserialize = tryDeserialize; 260 | } 261 | Storage.prototype.storeState = function (control) { 262 | var valueAsString = this.serialize(control); 263 | Page.Helpers.URL.setQueryParameter(this.prefix, control.id, valueAsString); 264 | }; 265 | Storage.prototype.clearStoredState = function (control) { 266 | Page.Helpers.URL.removeQueryParameter(this.prefix, control.id); 267 | }; 268 | Storage.prototype.applyStoredState = function () { 269 | var _this = this; 270 | Page.Helpers.URL.loopOnParameters(this.prefix, function (controlId, value) { 271 | if (!_this.tryDeserialize(controlId, value)) { 272 | console.log("Removing invalid query parameter '" + controlId + "=" + value + "'."); 273 | Page.Helpers.URL.removeQueryParameter(_this.prefix, controlId); 274 | } 275 | }); 276 | }; 277 | return Storage; 278 | }()); 279 | Helpers.Storage = Storage; 280 | })(Helpers = Page.Helpers || (Page.Helpers = {})); 281 | })(Page || (Page = {})); 282 | 283 | 284 | var Page; 285 | (function (Page) { 286 | var Controls; 287 | (function (Controls) { 288 | function getElementBySelector(selector) { 289 | var elt = document.querySelector(selector); 290 | if (!elt) { 291 | console.error("Cannot find control '" + selector + "'."); 292 | } 293 | return elt; 294 | } 295 | function setVisibility(id, visible) { 296 | var control = getElementBySelector("div#control-" + id); 297 | if (control) { 298 | control.style.display = visible ? "" : "none"; 299 | } 300 | } 301 | Controls.setVisibility = setVisibility; 302 | })(Controls = Page.Controls || (Page.Controls = {})); 303 | })(Page || (Page = {})); 304 | (function (Page) { 305 | var Sections; 306 | (function (Sections) { 307 | function getElementBySelector(selector) { 308 | var elt = document.querySelector(selector); 309 | if (!elt) { 310 | console.error("Cannot find section '" + selector + "'."); 311 | } 312 | return elt; 313 | } 314 | function reevaluateSeparatorsVisibility(controlsBlockElement) { 315 | function isHr(element) { 316 | return element.tagName.toLowerCase() === "hr"; 317 | } 318 | function isVisible(element) { 319 | return element.style.display !== "none"; 320 | } 321 | var sectionsOrHr = Page.Helpers.Utils.selectorAll(controlsBlockElement, "section, hr"); 322 | //remove duplicate HRs 323 | var lastWasHr = false; 324 | for (var _i = 0, sectionsOrHr_1 = sectionsOrHr; _i < sectionsOrHr_1.length; _i++) { 325 | var sectionOrHr = sectionsOrHr_1[_i]; 326 | if (isHr(sectionOrHr)) { 327 | sectionOrHr.style.display = lastWasHr ? "none" : ""; 328 | lastWasHr = true; 329 | } 330 | else if (isVisible(sectionOrHr)) { 331 | lastWasHr = false; 332 | } 333 | } 334 | // remove leading HRs 335 | for (var _a = 0, sectionsOrHr_2 = sectionsOrHr; _a < sectionsOrHr_2.length; _a++) { 336 | var sectionOrHr = sectionsOrHr_2[_a]; 337 | if (isHr(sectionOrHr)) { 338 | sectionOrHr.style.display = "none"; 339 | } 340 | else if (isVisible(sectionOrHr)) { 341 | break; 342 | } 343 | } 344 | // remove trailing HRs 345 | for (var i = sectionsOrHr.length - 1; i >= 0; i--) { 346 | var sectionOrHr = sectionsOrHr[i]; 347 | if (isHr(sectionOrHr)) { 348 | sectionOrHr.style.display = "none"; 349 | } 350 | else if (isVisible(sectionOrHr)) { 351 | break; 352 | } 353 | } 354 | } 355 | function setVisibility(id, visible) { 356 | var section = getElementBySelector("section#section-" + id); 357 | if (section && section.parentElement) { 358 | section.style.display = visible ? "" : "none"; 359 | reevaluateSeparatorsVisibility(section.parentElement); 360 | } 361 | } 362 | Sections.setVisibility = setVisibility; 363 | })(Sections = Page.Sections || (Page.Sections = {})); 364 | })(Page || (Page = {})); 365 | 366 | var Page; 367 | (function (Page) { 368 | var FileControl; 369 | (function (FileControl) { 370 | var FileUpload = /** @class */ (function () { 371 | function FileUpload(container) { 372 | var _this = this; 373 | this.observers = []; 374 | this.inputElement = Page.Helpers.Utils.selector(container, "input"); 375 | this.labelSpanElement = Page.Helpers.Utils.selector(container, "label > span"); 376 | this.id = this.inputElement.id; 377 | this.inputElement.addEventListener("change", function (event) { 378 | event.stopPropagation(); 379 | var files = _this.inputElement.files; 380 | if (files && files.length === 1) { 381 | _this.labelSpanElement.innerText = FileUpload.truncate(files[0].name); 382 | for (var _i = 0, _a = _this.observers; _i < _a.length; _i++) { 383 | var observer = _a[_i]; 384 | observer(files); 385 | } 386 | } 387 | }, false); 388 | } 389 | FileUpload.prototype.clear = function () { 390 | this.inputElement.value = ""; 391 | this.labelSpanElement.innerText = this.labelSpanElement.dataset["placeholder"] || "Upload"; 392 | }; 393 | FileUpload.truncate = function (name) { 394 | if (name.length > FileUpload.filenameMaxSize) { 395 | return name.substring(0, FileUpload.filenameMaxSize - 1) + "..." + 396 | name.substring(name.length - (FileUpload.filenameMaxSize - 1)); 397 | } 398 | return name; 399 | }; 400 | FileUpload.filenameMaxSize = 16; 401 | return FileUpload; 402 | }()); 403 | var FileDownload = /** @class */ (function () { 404 | function FileDownload(container) { 405 | var _this = this; 406 | this.observers = []; 407 | this.buttonElement = Page.Helpers.Utils.selector(container, "input"); 408 | this.id = this.buttonElement.id; 409 | this.buttonElement.addEventListener("click", function (event) { 410 | event.stopPropagation(); 411 | for (var _i = 0, _a = _this.observers; _i < _a.length; _i++) { 412 | var observer = _a[_i]; 413 | observer(); 414 | } 415 | }, false); 416 | } 417 | return FileDownload; 418 | }()); 419 | var fileUploadsCache = new Page.Helpers.Cache("FileUpload", function () { 420 | var selector = ".file-control.upload > input[id]"; 421 | var fileUploadInputsElements = Page.Helpers.Utils.selectorAll(document, selector); 422 | return fileUploadInputsElements.map(function (fileUploadInputsElement) { 423 | var container = fileUploadInputsElement.parentElement; 424 | var fileUpload = new FileUpload(container); 425 | return fileUpload; 426 | }); 427 | }); 428 | var fileDownloadsCache = new Page.Helpers.Cache("FileDownload", function () { 429 | var selector = ".file-control.download > input[id]"; 430 | var fileDownloadInputsElements = Page.Helpers.Utils.selectorAll(document, selector); 431 | return fileDownloadInputsElements.map(function (fileDownloadInputsElement) { 432 | var container = fileDownloadInputsElement.parentElement; 433 | return new FileDownload(container); 434 | }); 435 | }); 436 | Page.Helpers.Events.callAfterDOMLoaded(function () { 437 | fileUploadsCache.load(); 438 | fileUploadsCache.load(); 439 | }); 440 | function addDownloadObserver(id, observer) { 441 | var fileDownload = fileDownloadsCache.getById(id); 442 | fileDownload.observers.push(observer); 443 | } 444 | FileControl.addDownloadObserver = addDownloadObserver; 445 | function addUploadObserver(id, observer) { 446 | var fileUpload = fileUploadsCache.getById(id); 447 | fileUpload.observers.push(observer); 448 | } 449 | FileControl.addUploadObserver = addUploadObserver; 450 | function clearFileUpload(id) { 451 | var fileUpload = fileUploadsCache.getById(id); 452 | fileUpload.clear(); 453 | } 454 | FileControl.clearFileUpload = clearFileUpload; 455 | })(FileControl = Page.FileControl || (Page.FileControl = {})); 456 | })(Page || (Page = {})); 457 | 458 | 459 | var Page; 460 | (function (Page) { 461 | var Tabs; 462 | (function (Tabs_1) { 463 | var Tabs = /** @class */ (function () { 464 | function Tabs(container) { 465 | var _this = this; 466 | this.observers = []; 467 | this.id = Tabs.computeShortId(container.id); 468 | this.inputElements = []; 469 | var inputElements = Page.Helpers.Utils.selectorAll(container, "input"); 470 | for (var _i = 0, inputElements_1 = inputElements; _i < inputElements_1.length; _i++) { 471 | var inputElement = inputElements_1[_i]; 472 | this.inputElements.push(inputElement); 473 | inputElement.addEventListener("change", function (event) { 474 | event.stopPropagation(); 475 | _this.reloadValues(); 476 | tabsStorage.storeState(_this); 477 | _this.callObservers(); 478 | }, false); 479 | } 480 | this.reloadValues(); 481 | } 482 | Tabs.computeShortId = function (fullId) { 483 | if (fullId.lastIndexOf(Tabs.ID_SUFFIX) != fullId.length - Tabs.ID_SUFFIX.length) { 484 | throw new Error("Invalid tabs container id: '" + fullId + "'."); 485 | } 486 | return fullId.substring(0, fullId.length - Tabs.ID_SUFFIX.length); 487 | }; 488 | Object.defineProperty(Tabs.prototype, "values", { 489 | get: function () { 490 | return this._values; 491 | }, 492 | set: function (newValues) { 493 | for (var _i = 0, _a = this.inputElements; _i < _a.length; _i++) { 494 | var inputElement = _a[_i]; 495 | var isWanted = false; 496 | for (var _b = 0, newValues_1 = newValues; _b < newValues_1.length; _b++) { 497 | var newValue = newValues_1[_b]; 498 | if (inputElement.value === newValue) { 499 | isWanted = true; 500 | break; 501 | } 502 | } 503 | inputElement.checked = isWanted; 504 | } 505 | this.reloadValues(); 506 | }, 507 | enumerable: false, 508 | configurable: true 509 | }); 510 | Tabs.prototype.callObservers = function () { 511 | for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { 512 | var observer = _a[_i]; 513 | observer(this._values); 514 | } 515 | }; 516 | Tabs.prototype.reloadValues = function () { 517 | var values = []; 518 | for (var _i = 0, _a = this.inputElements; _i < _a.length; _i++) { 519 | var inputElement = _a[_i]; 520 | if (inputElement.checked) { 521 | values.push(inputElement.value); 522 | } 523 | } 524 | this._values = values; 525 | }; 526 | Tabs.ID_SUFFIX = "-id"; 527 | return Tabs; 528 | }()); 529 | var tabsCache = new Page.Helpers.Cache("Tabs", function () { 530 | var containerElements = Page.Helpers.Utils.selectorAll(document, "div.tabs[id]"); 531 | return containerElements.map(function (containerElement) { 532 | return new Tabs(containerElement); 533 | }); 534 | }); 535 | var tabsStorage = new Page.Helpers.Storage("tabs", function (tabs) { 536 | var valuesList = tabs.values; 537 | return valuesList.join(";"); 538 | }, function (id, serializedValue) { 539 | var values = serializedValue.split(";"); 540 | var tabs = tabsCache.getByIdSafe(id); 541 | if (tabs) { 542 | tabs.values = values; 543 | tabs.callObservers(); 544 | return true; 545 | } 546 | return false; 547 | }); 548 | Page.Helpers.Events.callAfterDOMLoaded(function () { 549 | tabsCache.load(); 550 | tabsStorage.applyStoredState(); 551 | }); 552 | function addObserver(tabsId, observer) { 553 | var tabs = tabsCache.getById(tabsId); 554 | tabs.observers.push(observer); 555 | } 556 | Tabs_1.addObserver = addObserver; 557 | function getValues(tabsId) { 558 | var tabs = tabsCache.getById(tabsId); 559 | return tabs.values; 560 | } 561 | Tabs_1.getValues = getValues; 562 | function setValues(tabsId, values, updateURLStorage) { 563 | if (updateURLStorage === void 0) { updateURLStorage = false; } 564 | var tabs = tabsCache.getById(tabsId); 565 | tabs.values = values; 566 | if (updateURLStorage) { 567 | tabsStorage.storeState(tabs); 568 | } 569 | } 570 | Tabs_1.setValues = setValues; 571 | function storeState(tabsId) { 572 | var tabs = tabsCache.getById(tabsId); 573 | tabsStorage.storeState(tabs); 574 | } 575 | Tabs_1.storeState = storeState; 576 | function clearStoredState(tabsIdd) { 577 | var tabs = tabsCache.getById(tabsIdd); 578 | tabsStorage.clearStoredState(tabs); 579 | } 580 | Tabs_1.clearStoredState = clearStoredState; 581 | })(Tabs = Page.Tabs || (Page.Tabs = {})); 582 | })(Page || (Page = {})); 583 | 584 | 585 | var Page; 586 | (function (Page) { 587 | var Range; 588 | (function (Range_1) { 589 | var Range = /** @class */ (function () { 590 | function Range(container) { 591 | var _this = this; 592 | this.onInputObservers = []; 593 | this.onChangeObservers = []; 594 | this.inputElement = Page.Helpers.Utils.selector(container, "input[type='range']"); 595 | this.progressLeftElement = Page.Helpers.Utils.selector(container, ".range-progress-left"); 596 | this.tooltipElement = Page.Helpers.Utils.selector(container, "output.range-tooltip"); 597 | this.id = this.inputElement.id; 598 | var inputMin = +this.inputElement.min; 599 | var inputMax = +this.inputElement.max; 600 | var inputStep = +this.inputElement.step; 601 | this.nbDecimalsToDisplay = Range.getMaxNbDecimals(inputMin, inputMax, inputStep); 602 | this.inputElement.addEventListener("input", function (event) { 603 | event.stopPropagation(); 604 | _this.reloadValue(); 605 | _this.callSpecificObservers(_this.onInputObservers); 606 | }); 607 | this.inputElement.addEventListener("change", function (event) { 608 | event.stopPropagation(); 609 | _this.reloadValue(); 610 | rangesStorage.storeState(_this); 611 | _this.callSpecificObservers(_this.onChangeObservers); 612 | }); 613 | this.reloadValue(); 614 | } 615 | Object.defineProperty(Range.prototype, "value", { 616 | get: function () { 617 | return this._value; 618 | }, 619 | set: function (newValue) { 620 | this.inputElement.value = "" + newValue; 621 | this.reloadValue(); 622 | }, 623 | enumerable: false, 624 | configurable: true 625 | }); 626 | Range.prototype.callObservers = function () { 627 | this.callSpecificObservers(this.onInputObservers); 628 | this.callSpecificObservers(this.onChangeObservers); 629 | }; 630 | Range.prototype.callSpecificObservers = function (observers) { 631 | for (var _i = 0, observers_1 = observers; _i < observers_1.length; _i++) { 632 | var observer = observers_1[_i]; 633 | observer(this.value); 634 | } 635 | }; 636 | Range.prototype.updateAppearance = function () { 637 | var currentLength = +this.inputElement.value - +this.inputElement.min; 638 | var totalLength = +this.inputElement.max - +this.inputElement.min; 639 | var progression = currentLength / totalLength; 640 | progression = Math.max(0, Math.min(1, progression)); 641 | this.progressLeftElement.style.width = (100 * progression) + "%"; 642 | var text; 643 | if (this.nbDecimalsToDisplay < 0) { 644 | text = this.inputElement.value; 645 | } 646 | else { 647 | text = (+this.inputElement.value).toFixed(this.nbDecimalsToDisplay); 648 | } 649 | this.tooltipElement.textContent = text; 650 | }; 651 | Range.prototype.reloadValue = function () { 652 | this._value = +this.inputElement.value; 653 | this.updateAppearance(); 654 | }; 655 | Range.getMaxNbDecimals = function () { 656 | var numbers = []; 657 | for (var _i = 0; _i < arguments.length; _i++) { 658 | numbers[_i] = arguments[_i]; 659 | } 660 | var nbDecimals = -1; 661 | for (var _a = 0, numbers_1 = numbers; _a < numbers_1.length; _a++) { 662 | var n = numbers_1[_a]; 663 | var local = Range.nbDecimals(n); 664 | if (n < 0) { 665 | return -1; 666 | } 667 | else if (nbDecimals < local) { 668 | nbDecimals = local; 669 | } 670 | } 671 | return nbDecimals; 672 | }; 673 | Range.nbDecimals = function (x) { 674 | var xAsString = x.toString(); 675 | if (/^[0-9]+$/.test(xAsString)) { 676 | return 0; 677 | } 678 | else if (/^[0-9]+\.[0-9]+$/.test(xAsString)) { 679 | return xAsString.length - (xAsString.indexOf(".") + 1); 680 | } 681 | return -1; // failed to parse 682 | }; 683 | return Range; 684 | }()); 685 | var rangesCache = new Page.Helpers.Cache("Range", function () { 686 | var selector = ".range-container > input[type='range']"; 687 | var rangeElements = Page.Helpers.Utils.selectorAll(document, selector); 688 | return rangeElements.map(function (rangeElement) { 689 | var container = rangeElement.parentElement; 690 | return new Range(container); 691 | }); 692 | }); 693 | var rangesStorage = new Page.Helpers.Storage("range", function (range) { 694 | return "" + range.value; 695 | }, function (id, serializedValue) { 696 | var range = rangesCache.getByIdSafe(id); 697 | if (range) { 698 | range.value = +serializedValue; 699 | range.callObservers(); 700 | return true; 701 | } 702 | return false; 703 | }); 704 | Page.Helpers.Events.callAfterDOMLoaded(function () { 705 | rangesCache.load(); 706 | rangesStorage.applyStoredState(); 707 | }); 708 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 709 | var isIE11 = !!window.MSInputMethodContext && !!document.documentMode; 710 | function addObserver(rangeId, observer) { 711 | var range = rangesCache.getById(rangeId); 712 | if (isIE11) { // bug in IE 11, input event is never fired 713 | range.onChangeObservers.push(observer); 714 | } 715 | else { 716 | range.onInputObservers.push(observer); 717 | } 718 | } 719 | Range_1.addObserver = addObserver; 720 | /** 721 | * Callback will be called only when the value stops changing. 722 | */ 723 | function addLazyObserver(rangeId, observer) { 724 | var range = rangesCache.getById(rangeId); 725 | range.onChangeObservers.push(observer); 726 | } 727 | Range_1.addLazyObserver = addLazyObserver; 728 | function getValue(rangeId) { 729 | var range = rangesCache.getById(rangeId); 730 | return range.value; 731 | } 732 | Range_1.getValue = getValue; 733 | function setValue(rangeId, value) { 734 | var range = rangesCache.getById(rangeId); 735 | range.value = value; 736 | } 737 | Range_1.setValue = setValue; 738 | function storeState(rangeId) { 739 | var range = rangesCache.getById(rangeId); 740 | rangesStorage.storeState(range); 741 | } 742 | Range_1.storeState = storeState; 743 | function clearStoredState(rangeId) { 744 | var range = rangesCache.getById(rangeId); 745 | rangesStorage.clearStoredState(range); 746 | } 747 | Range_1.clearStoredState = clearStoredState; 748 | })(Range = Page.Range || (Page.Range = {})); 749 | })(Page || (Page = {})); 750 | 751 | 752 | var Page; 753 | (function (Page) { 754 | var Checkbox; 755 | (function (Checkbox_1) { 756 | var Checkbox = /** @class */ (function () { 757 | function Checkbox(element) { 758 | var _this = this; 759 | this.observers = []; 760 | this.id = element.id; 761 | this.element = element; 762 | this.reloadValue(); 763 | this.element.addEventListener("change", function () { 764 | _this.reloadValue(); 765 | checkboxesStorage.storeState(_this); 766 | _this.callObservers(); 767 | }); 768 | } 769 | Object.defineProperty(Checkbox.prototype, "checked", { 770 | get: function () { 771 | return this._checked; 772 | }, 773 | set: function (newChecked) { 774 | this.element.checked = newChecked; 775 | this.reloadValue(); 776 | }, 777 | enumerable: false, 778 | configurable: true 779 | }); 780 | Checkbox.prototype.callObservers = function () { 781 | for (var _i = 0, _a = this.observers; _i < _a.length; _i++) { 782 | var observer = _a[_i]; 783 | observer(this.checked); 784 | } 785 | }; 786 | Checkbox.prototype.reloadValue = function () { 787 | this._checked = this.element.checked; 788 | }; 789 | return Checkbox; 790 | }()); 791 | var checkboxesCache = new Page.Helpers.Cache("Checkbox", function () { 792 | var selector = "div.checkbox > input[type=checkbox][id]"; 793 | var elements = Page.Helpers.Utils.selectorAll(document, selector); 794 | return elements.map(function (element) { 795 | return new Checkbox(element); 796 | }); 797 | }); 798 | var checkboxesStorage = new Page.Helpers.Storage("checkbox", function (checkbox) { 799 | return checkbox.checked ? "true" : "false"; 800 | }, function (id, serializedValue) { 801 | var checkbox = checkboxesCache.getByIdSafe(id); 802 | if (checkbox && (serializedValue === "true" || serializedValue === "false")) { 803 | checkbox.checked = (serializedValue === "true"); 804 | checkbox.callObservers(); 805 | return true; 806 | } 807 | return false; 808 | }); 809 | Page.Helpers.Events.callAfterDOMLoaded(function () { 810 | checkboxesCache.load(); 811 | checkboxesStorage.applyStoredState(); 812 | }); 813 | function addObserver(checkboxId, observer) { 814 | var checkbox = checkboxesCache.getById(checkboxId); 815 | checkbox.observers.push(observer); 816 | } 817 | Checkbox_1.addObserver = addObserver; 818 | function setChecked(checkboxId, value) { 819 | var checkbox = checkboxesCache.getById(checkboxId); 820 | checkbox.checked = value; 821 | } 822 | Checkbox_1.setChecked = setChecked; 823 | function isChecked(checkboxId) { 824 | var checkbox = checkboxesCache.getById(checkboxId); 825 | return checkbox.checked; 826 | } 827 | Checkbox_1.isChecked = isChecked; 828 | function storeState(checkboxId) { 829 | var checkbox = checkboxesCache.getById(checkboxId); 830 | checkboxesStorage.storeState(checkbox); 831 | } 832 | Checkbox_1.storeState = storeState; 833 | function clearStoredState(checkboxId) { 834 | var checkbox = checkboxesCache.getById(checkboxId); 835 | checkboxesStorage.clearStoredState(checkbox); 836 | } 837 | Checkbox_1.clearStoredState = clearStoredState; 838 | })(Checkbox = Page.Checkbox || (Page.Checkbox = {})); 839 | })(Page || (Page = {})); 840 | 841 | 842 | 843 | var Page; 844 | (function (Page) { 845 | var Canvas; 846 | (function (Canvas) { 847 | function getElementBySelector(selector) { 848 | var elt = document.querySelector(selector); 849 | if (!elt) { 850 | console.error("Cannot find element '" + selector + "'."); 851 | } 852 | return elt; 853 | } 854 | function getCanvasById(id) { 855 | return Page.Helpers.Utils.selector(document, "canvas[id=" + id + "]"); 856 | } 857 | function getCheckboxFromId(id) { 858 | return Page.Helpers.Utils.selector(document, "input[type=checkbox][id=" + id + "]"); 859 | } 860 | var canvasContainer = Page.Helpers.Utils.selector(document, "#canvas-container"); 861 | var canvas = getCanvasById("canvas"); 862 | var buttonsColumn = Page.Helpers.Utils.selector(document, "#canvas-buttons-column"); 863 | var fullscreenCheckbox = getCheckboxFromId("fullscreen-checkbox-id"); 864 | var sidePaneCheckbox = getCheckboxFromId("side-pane-checkbox-id"); 865 | var loader = Page.Helpers.Utils.selector(canvasContainer, ".loader"); 866 | var maxWidth = 512; 867 | var maxHeight = 512; 868 | function bindCanvasButtons() { 869 | function hideOverflow(value) { 870 | document.body.style.overflow = value ? "hidden" : "auto"; 871 | } 872 | if (fullscreenCheckbox) { 873 | Page.Helpers.Events.callAfterDOMLoaded(function () { 874 | hideOverflow(fullscreenCheckbox.checked); 875 | fullscreenCheckbox.addEventListener("change", function () { 876 | hideOverflow(fullscreenCheckbox.checked); 877 | }); 878 | }); 879 | if (sidePaneCheckbox) { 880 | fullscreenCheckbox.addEventListener("change", function () { 881 | if (fullscreenCheckbox.checked) { 882 | sidePaneCheckbox.checked = false; 883 | } 884 | }, false); 885 | } 886 | } 887 | } 888 | bindCanvasButtons(); 889 | function getCanvasSize() { 890 | var rect = canvas.getBoundingClientRect(); 891 | return [Math.floor(rect.width), Math.floor(rect.height)]; 892 | } 893 | var lastCanvasSize = [0, 0]; 894 | var canvasResizeObservers = []; 895 | function inPx(size) { 896 | return size + "px"; 897 | } 898 | /** 899 | * Calls callbacks if needed. 900 | */ 901 | function updateCanvasSize() { 902 | canvasContainer.style.width = "100vw"; 903 | var size = getCanvasSize(); 904 | if (fullscreenCheckbox.checked) { 905 | canvasContainer.style.height = "100%"; 906 | canvasContainer.style.maxWidth = ""; 907 | canvasContainer.style.maxHeight = ""; 908 | } 909 | else { 910 | size[1] = size[0] * maxHeight / maxWidth; 911 | canvasContainer.style.height = inPx(size[1]); 912 | canvasContainer.style.maxWidth = inPx(maxWidth); 913 | canvasContainer.style.maxHeight = inPx(maxHeight); 914 | } 915 | if (size[0] !== lastCanvasSize[0] || size[1] !== lastCanvasSize[1]) { 916 | lastCanvasSize = getCanvasSize(); 917 | for (var _i = 0, canvasResizeObservers_1 = canvasResizeObservers; _i < canvasResizeObservers_1.length; _i++) { 918 | var observer = canvasResizeObservers_1[_i]; 919 | observer(lastCanvasSize[0], lastCanvasSize[1]); 920 | } 921 | } 922 | } 923 | Page.Helpers.Events.callAfterDOMLoaded(updateCanvasSize); 924 | fullscreenCheckbox.addEventListener("change", updateCanvasSize, false); 925 | window.addEventListener("resize", updateCanvasSize, false); 926 | var fullscreenToggleObservers = [updateCanvasSize]; 927 | var mouseDownObservers = []; 928 | var mouseUpObservers = []; 929 | var mouseDragObservers = []; 930 | var mouseMoveObservers = []; 931 | var mouseEnterObservers = []; 932 | var mouseLeaveObservers = []; 933 | var mouseWheelObservers = []; 934 | /* Bind fullscreen events */ 935 | if (fullscreenCheckbox) { 936 | fullscreenCheckbox.addEventListener("change", function () { 937 | var isFullscreen = fullscreenCheckbox.checked; 938 | for (var _i = 0, fullscreenToggleObservers_1 = fullscreenToggleObservers; _i < fullscreenToggleObservers_1.length; _i++) { 939 | var observer = fullscreenToggleObservers_1[_i]; 940 | observer(isFullscreen); 941 | } 942 | }, false); 943 | } 944 | document.addEventListener("keydown", function (event) { 945 | if (event.keyCode === 27) { 946 | Canvas.toggleFullscreen(false); 947 | } 948 | }); 949 | function clientToRelative(clientX, clientY) { 950 | var rect = canvas.getBoundingClientRect(); 951 | return [ 952 | (clientX - rect.left) / rect.width, 953 | (clientY - rect.top) / rect.height, 954 | ]; 955 | } 956 | var Mouse; 957 | (function (Mouse) { 958 | var mousePosition = [0, 0]; 959 | var clientMousePosition = [0, 0]; 960 | var isMouseDownInternal = false; 961 | function getMousePosition() { 962 | return [mousePosition[0], mousePosition[1]]; 963 | } 964 | Mouse.getMousePosition = getMousePosition; 965 | function setMousePosition(x, y) { 966 | mousePosition[0] = x; 967 | mousePosition[1] = y; 968 | } 969 | Mouse.setMousePosition = setMousePosition; 970 | function isMouseDown() { 971 | return isMouseDownInternal; 972 | } 973 | Mouse.isMouseDown = isMouseDown; 974 | function mouseDown(clientX, clientY) { 975 | var pos = clientToRelative(clientX, clientY); 976 | setMousePosition(pos[0], pos[1]); 977 | isMouseDownInternal = true; 978 | for (var _i = 0, mouseDownObservers_1 = mouseDownObservers; _i < mouseDownObservers_1.length; _i++) { 979 | var observer = mouseDownObservers_1[_i]; 980 | observer(); 981 | } 982 | } 983 | Mouse.mouseDown = mouseDown; 984 | function mouseUp() { 985 | if (isMouseDownInternal) { 986 | isMouseDownInternal = false; 987 | for (var _i = 0, mouseUpObservers_1 = mouseUpObservers; _i < mouseUpObservers_1.length; _i++) { 988 | var observer = mouseUpObservers_1[_i]; 989 | observer(); 990 | } 991 | } 992 | } 993 | Mouse.mouseUp = mouseUp; 994 | function mouseMove(clientX, clientY) { 995 | clientMousePosition[0] = clientX; 996 | clientMousePosition[1] = clientY; 997 | var newPos = clientToRelative(clientX, clientY); 998 | var dX = newPos[0] - mousePosition[0]; 999 | var dY = newPos[1] - mousePosition[1]; 1000 | // Update the mousePosition before calling the observers, 1001 | // because they might call getMousePosition() and it needs to be up to date. 1002 | mousePosition[0] = newPos[0]; 1003 | mousePosition[1] = newPos[1]; 1004 | if (isMouseDownInternal) { 1005 | for (var _i = 0, mouseDragObservers_1 = mouseDragObservers; _i < mouseDragObservers_1.length; _i++) { 1006 | var observer = mouseDragObservers_1[_i]; 1007 | observer(dX, dY); 1008 | } 1009 | } 1010 | for (var _a = 0, mouseMoveObservers_1 = mouseMoveObservers; _a < mouseMoveObservers_1.length; _a++) { 1011 | var observer = mouseMoveObservers_1[_a]; 1012 | observer(newPos[0], newPos[1]); 1013 | } 1014 | } 1015 | Mouse.mouseMove = mouseMove; 1016 | if (canvas) { 1017 | canvas.addEventListener("mousedown", function (event) { 1018 | if (event.button === 0) { 1019 | mouseDown(event.clientX, event.clientY); 1020 | } 1021 | }, false); 1022 | canvas.addEventListener("mouseenter", function () { 1023 | for (var _i = 0, mouseEnterObservers_1 = mouseEnterObservers; _i < mouseEnterObservers_1.length; _i++) { 1024 | var observer = mouseEnterObservers_1[_i]; 1025 | observer(); 1026 | } 1027 | }, false); 1028 | canvas.addEventListener("mouseleave", function () { 1029 | for (var _i = 0, mouseLeaveObservers_1 = mouseLeaveObservers; _i < mouseLeaveObservers_1.length; _i++) { 1030 | var observer = mouseLeaveObservers_1[_i]; 1031 | observer(); 1032 | } 1033 | }, false); 1034 | canvas.addEventListener("wheel", function (event) { 1035 | if (mouseWheelObservers.length > 0) { 1036 | var delta = (event.deltaY > 0) ? 1 : -1; 1037 | for (var _i = 0, mouseWheelObservers_1 = mouseWheelObservers; _i < mouseWheelObservers_1.length; _i++) { 1038 | var observer = mouseWheelObservers_1[_i]; 1039 | observer(delta, mousePosition); 1040 | } 1041 | event.preventDefault(); 1042 | return false; 1043 | } 1044 | return true; 1045 | }, false); 1046 | window.addEventListener("mousemove", function (event) { 1047 | mouseMove(event.clientX, event.clientY); 1048 | }); 1049 | window.addEventListener("mouseup", function (event) { 1050 | if (event.button === 0) { 1051 | mouseUp(); 1052 | } 1053 | }); 1054 | canvasResizeObservers.push(function () { 1055 | mouseMove(clientMousePosition[0], clientMousePosition[1]); 1056 | }); 1057 | } 1058 | })(Mouse || (Mouse = {})); 1059 | (function Touch() { 1060 | var currentTouches = []; 1061 | var currentDistance = 0; // for pinching management 1062 | function computeDistance(firstTouch, secondTouch) { 1063 | var dX = firstTouch.clientX - secondTouch.clientX; 1064 | var dY = firstTouch.clientY - secondTouch.clientY; 1065 | return Math.sqrt(dX * dX + dY * dY); 1066 | } 1067 | function handleTouchStart(event) { 1068 | var isFirstTouch = (currentTouches.length === 0); 1069 | var changedTouches = Page.Helpers.Utils.touchArray(event.changedTouches); 1070 | for (var _i = 0, changedTouches_1 = changedTouches; _i < changedTouches_1.length; _i++) { 1071 | var touch = changedTouches_1[_i]; 1072 | var alreadyRegistered = false; 1073 | for (var _a = 0, currentTouches_1 = currentTouches; _a < currentTouches_1.length; _a++) { 1074 | var knownTouch = currentTouches_1[_a]; 1075 | if (touch.identifier === knownTouch.id) { 1076 | alreadyRegistered = true; 1077 | break; 1078 | } 1079 | } 1080 | if (!alreadyRegistered) { 1081 | currentTouches.push({ 1082 | id: touch.identifier, 1083 | clientX: touch.clientX, 1084 | clientY: touch.clientY, 1085 | }); 1086 | } 1087 | } 1088 | if (isFirstTouch && currentTouches.length > 0) { 1089 | var currentTouch = currentTouches[0]; 1090 | Mouse.mouseDown(currentTouch.clientX, currentTouch.clientY); 1091 | } 1092 | else if (currentTouches.length === 2) { 1093 | currentDistance = computeDistance(currentTouches[0], currentTouches[1]); 1094 | } 1095 | } 1096 | function handleTouchEnd(event) { 1097 | var knewAtLeastOneTouch = (currentTouches.length > 0); 1098 | var changedTouches = Page.Helpers.Utils.touchArray(event.changedTouches); 1099 | for (var _i = 0, changedTouches_2 = changedTouches; _i < changedTouches_2.length; _i++) { 1100 | var touch = changedTouches_2[_i]; 1101 | for (var iC = 0; iC < currentTouches.length; ++iC) { 1102 | if (touch.identifier === currentTouches[iC].id) { 1103 | currentTouches.splice(iC, 1); 1104 | iC--; 1105 | } 1106 | } 1107 | } 1108 | if (currentTouches.length === 1) { 1109 | var firstTouch = currentTouches[0]; 1110 | var newPos = clientToRelative(firstTouch.clientX, firstTouch.clientY); 1111 | Mouse.setMousePosition(newPos[0], newPos[1]); 1112 | } 1113 | else if (knewAtLeastOneTouch && currentTouches.length === 0) { 1114 | Mouse.mouseUp(); 1115 | } 1116 | } 1117 | function handleTouchMove(event) { 1118 | var touches = Page.Helpers.Utils.touchArray(event.changedTouches); 1119 | for (var _i = 0, touches_1 = touches; _i < touches_1.length; _i++) { 1120 | var touch = touches_1[_i]; 1121 | for (var _a = 0, currentTouches_2 = currentTouches; _a < currentTouches_2.length; _a++) { 1122 | var knownTouch = currentTouches_2[_a]; 1123 | if (touch.identifier === knownTouch.id) { 1124 | knownTouch.clientX = touch.clientX; 1125 | knownTouch.clientY = touch.clientY; 1126 | } 1127 | } 1128 | } 1129 | var nbObservers = mouseMoveObservers.length + mouseDragObservers.length; 1130 | if (Mouse.isMouseDown() && nbObservers > 0) { 1131 | event.preventDefault(); 1132 | } 1133 | if (currentTouches.length === 1) { 1134 | var firstTouch = currentTouches[0]; 1135 | Mouse.mouseMove(firstTouch.clientX, firstTouch.clientY); 1136 | } 1137 | else if (currentTouches.length === 2) { 1138 | var firstTouch = currentTouches[0]; 1139 | var secondTouch = currentTouches[1]; 1140 | var newDistance = computeDistance(firstTouch, secondTouch); 1141 | var deltaDistance = (currentDistance - newDistance); 1142 | var zoomFactor = deltaDistance / currentDistance; 1143 | currentDistance = newDistance; 1144 | var zoomCenterXClient = 0.5 * (firstTouch.clientX + secondTouch.clientX); 1145 | var zoomCenterYClient = 0.5 * (firstTouch.clientY + secondTouch.clientY); 1146 | var zoomCenter = clientToRelative(zoomCenterXClient, zoomCenterYClient); 1147 | for (var _b = 0, mouseWheelObservers_2 = mouseWheelObservers; _b < mouseWheelObservers_2.length; _b++) { 1148 | var observer = mouseWheelObservers_2[_b]; 1149 | observer(5 * zoomFactor, zoomCenter); 1150 | } 1151 | } 1152 | } 1153 | if (canvas) { 1154 | canvas.addEventListener("touchstart", handleTouchStart, false); 1155 | window.addEventListener("touchend", handleTouchEnd); 1156 | window.addEventListener("touchmove", handleTouchMove, { passive: false }); 1157 | } 1158 | })(); 1159 | var Indicators; 1160 | (function (Indicators) { 1161 | var indicatorSpansCache = {}; 1162 | var suffix = "-indicator-id"; 1163 | function getIndicator(id) { 1164 | var element = getElementBySelector("#" + id + suffix); 1165 | if (!element) { 1166 | throw new Error("Could not find indicator '".concat(id, "'.")); 1167 | } 1168 | return element; 1169 | } 1170 | Indicators.getIndicator = getIndicator; 1171 | function getIndicatorSpan(id) { 1172 | if (!indicatorSpansCache[id]) { // not yet in cache 1173 | var fullId = id + suffix; 1174 | var element = getElementBySelector("#" + fullId + " span"); 1175 | if (!element) { 1176 | throw new Error("Could not find indicator span '".concat(id, "'.")); 1177 | } 1178 | indicatorSpansCache[id] = element; 1179 | } 1180 | return indicatorSpansCache[id]; 1181 | } 1182 | Indicators.getIndicatorSpan = getIndicatorSpan; 1183 | })(Indicators || (Indicators = {})); 1184 | var Storage; 1185 | (function (Storage) { 1186 | var PREFIX = "canvas"; 1187 | var FULLSCREEN_PARAMETER = "fullscreen"; 1188 | var SIDE_PANE_PARAMETER = "sidepane"; 1189 | var TRUE = "true"; 1190 | var FALSE = "false"; 1191 | function updateBooleanParameter(name, checked) { 1192 | var value = checked ? TRUE : FALSE; 1193 | Page.Helpers.URL.setQueryParameter(PREFIX, name, value); 1194 | } 1195 | function attachStorageEvents() { 1196 | if (fullscreenCheckbox) { 1197 | fullscreenCheckbox.addEventListener("change", function () { 1198 | updateBooleanParameter(FULLSCREEN_PARAMETER, fullscreenCheckbox.checked); 1199 | Page.Helpers.URL.removeQueryParameter(PREFIX, SIDE_PANE_PARAMETER); 1200 | }); 1201 | } 1202 | if (sidePaneCheckbox) { 1203 | sidePaneCheckbox.addEventListener("change", function () { 1204 | updateBooleanParameter(SIDE_PANE_PARAMETER, sidePaneCheckbox.checked); 1205 | }); 1206 | } 1207 | } 1208 | Storage.attachStorageEvents = attachStorageEvents; 1209 | function applyStoredState() { 1210 | Page.Helpers.URL.loopOnParameters(PREFIX, function (name, value) { 1211 | if (name === FULLSCREEN_PARAMETER && (value === TRUE || value === FALSE)) { 1212 | if (fullscreenCheckbox) { 1213 | fullscreenCheckbox.checked = (value === TRUE); 1214 | } 1215 | } 1216 | else if (name === SIDE_PANE_PARAMETER && (value === TRUE || value === FALSE)) { 1217 | if (sidePaneCheckbox) { 1218 | sidePaneCheckbox.checked = (value === TRUE); 1219 | } 1220 | } 1221 | else { 1222 | console.log("Removing invalid query parameter '" + name + "=" + value + "'."); 1223 | Page.Helpers.URL.removeQueryParameter(PREFIX, name); 1224 | } 1225 | }); 1226 | } 1227 | Storage.applyStoredState = applyStoredState; 1228 | })(Storage || (Storage = {})); 1229 | Storage.applyStoredState(); 1230 | Storage.attachStorageEvents(); 1231 | Canvas.Observers = Object.freeze({ 1232 | canvasResize: canvasResizeObservers, 1233 | fullscreenToggle: fullscreenToggleObservers, 1234 | mouseDown: mouseDownObservers, 1235 | mouseDrag: mouseDragObservers, 1236 | mouseEnter: mouseEnterObservers, 1237 | mouseLeave: mouseLeaveObservers, 1238 | mouseMove: mouseMoveObservers, 1239 | mouseWheel: mouseWheelObservers, 1240 | mouseUp: mouseUpObservers, 1241 | }); 1242 | function getAspectRatio() { 1243 | var size = getCanvasSize(); 1244 | return size[0] / size[1]; 1245 | } 1246 | Canvas.getAspectRatio = getAspectRatio; 1247 | function getCanvas() { 1248 | return canvas; 1249 | } 1250 | Canvas.getCanvas = getCanvas; 1251 | function getCanvasContainer() { 1252 | return canvasContainer; 1253 | } 1254 | Canvas.getCanvasContainer = getCanvasContainer; 1255 | function getSize() { 1256 | return getCanvasSize(); 1257 | } 1258 | Canvas.getSize = getSize; 1259 | function getMousePosition() { 1260 | return Mouse.getMousePosition(); 1261 | } 1262 | Canvas.getMousePosition = getMousePosition; 1263 | function isFullScreen() { 1264 | return fullscreenCheckbox && fullscreenCheckbox.checked; 1265 | } 1266 | Canvas.isFullScreen = isFullScreen; 1267 | function isMouseDown() { 1268 | return Mouse.isMouseDown(); 1269 | } 1270 | Canvas.isMouseDown = isMouseDown; 1271 | function setIndicatorText(id, text) { 1272 | var indicator = Indicators.getIndicatorSpan(id); 1273 | if (indicator) { 1274 | indicator.innerText = text; 1275 | } 1276 | } 1277 | Canvas.setIndicatorText = setIndicatorText; 1278 | function setIndicatorVisibility(id, visible) { 1279 | var indicator = Indicators.getIndicator(id); 1280 | if (indicator) { 1281 | indicator.style.display = visible ? "" : "none"; 1282 | } 1283 | } 1284 | Canvas.setIndicatorVisibility = setIndicatorVisibility; 1285 | function setIndicatorsVisibility(visible) { 1286 | var indicators = document.getElementById("indicators"); 1287 | indicators.style.display = visible ? "" : "none"; 1288 | } 1289 | Canvas.setIndicatorsVisibility = setIndicatorsVisibility; 1290 | function setMaxSize(newMaxWidth, newMaxHeight) { 1291 | maxWidth = newMaxWidth; 1292 | maxHeight = newMaxHeight; 1293 | updateCanvasSize(); 1294 | } 1295 | Canvas.setMaxSize = setMaxSize; 1296 | function setResizable(resizable) { 1297 | buttonsColumn.style.display = resizable ? "" : "none"; 1298 | } 1299 | Canvas.setResizable = setResizable; 1300 | function setLoaderText(text) { 1301 | if (loader) { 1302 | loader.querySelector("span").innerText = text; 1303 | } 1304 | } 1305 | Canvas.setLoaderText = setLoaderText; 1306 | function showLoader(show) { 1307 | if (loader) { 1308 | loader.style.display = (show) ? "block" : ""; 1309 | } 1310 | } 1311 | Canvas.showLoader = showLoader; 1312 | function toggleFullscreen(fullscreen) { 1313 | if (fullscreenCheckbox) { 1314 | var needToUpdate = fullscreen !== fullscreenCheckbox.checked; 1315 | if (needToUpdate) { 1316 | fullscreenCheckbox.checked = fullscreen; 1317 | if (typeof window.CustomEvent === "function") { 1318 | fullscreenCheckbox.dispatchEvent(new CustomEvent("change")); 1319 | } 1320 | else if (typeof CustomEvent.prototype.initCustomEvent === "function") { 1321 | var changeEvent = document.createEvent("CustomEvent"); 1322 | changeEvent.initCustomEvent("change", false, false, undefined); 1323 | fullscreenCheckbox.dispatchEvent(changeEvent); 1324 | } 1325 | } 1326 | } 1327 | } 1328 | Canvas.toggleFullscreen = toggleFullscreen; 1329 | })(Canvas = Page.Canvas || (Page.Canvas = {})); 1330 | })(Page || (Page = {})); 1331 | 1332 | Page.Canvas.setMaxSize(512,512); --------------------------------------------------------------------------------