diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..70c476a --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,38 @@ +# git tag reminder : +# version tag look like : v1.0.0-rc0 +# add a tag locally : git tag tagName +# push it : git push origin tagName +# remove a tag locally : git tag -d tagName +# remove it on remote : git push --delete origin tagName + +name: Deploy +on: + push: + tags: + - v* + +jobs: + deploy_job: + name: Deploy Job + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v1 + - name: build + run: | + mkdir .public + mv * .public/ + mv .public public + rm public/LICENSE.txt public/README.md + - name: SSH setup + env: + DEPLOY_KEY: ${{ secrets.deploy_key }} + #KNOWN_HOSTS: ${{ secrets.known_hosts }} + run: | + mkdir -p ~/.ssh + echo "${DEPLOY_KEY}" > ~/.ssh/my_rsync_key + echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config + echo "IdentityFile ~/.ssh/my_rsync_key" >> ~/.ssh/config + chmod -R 700 ~/.ssh + - name: Rsync deployment + run: | + rsync -az -e ssh --delete public/ gammanu@1000i100.fr:~/lo0p.it diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b770ab9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +.generated* +generated* \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..ab05dc9 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,43 @@ +stages: + - build + - preview + - prod + +build: + stage: build + image: node:current-alpine + script: + - mkdir .public + - mv * .public/ + - mv .public public + - rm public/LICENSE.txt public/README.md + artifacts: + paths: + - public + expire_in: 1 week + +pages: + stage: preview + script: + - ls public + artifacts: + name: loopy + paths: + - public + +OK_publish: + #when: manual + stage: prod + image: liaohuqiu/rsync + before_script: + - eval $(ssh-agent -s) + - ssh-add <(echo "$SSH_PRIVATE_KEY") + - mkdir -p ~/.ssh + - >- + [[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > + ~/.ssh/config + script: + - rsync -az -e ssh ./public/ gammanu@1000i100.fr:~/lo0p.it + only: + - tags + diff --git a/v1.1/css/balloon.css b/2/css/balloon.css similarity index 99% rename from v1.1/css/balloon.css rename to 2/css/balloon.css index 17d00ed..9283e6c 100755 --- a/v1.1/css/balloon.css +++ b/2/css/balloon.css @@ -1,3 +1,4 @@ +/* from https://kazzkiq.github.io/balloon.css/ */ button[data-balloon] { overflow: visible; } diff --git a/splash/css/cursors/drag.png b/2/css/cursors/drag.png similarity index 100% rename from splash/css/cursors/drag.png rename to 2/css/cursors/drag.png diff --git a/splash/css/cursors/erase.png b/2/css/cursors/erase.png similarity index 100% rename from splash/css/cursors/erase.png rename to 2/css/cursors/erase.png diff --git a/splash/css/cursors/ink.png b/2/css/cursors/ink.png similarity index 100% rename from splash/css/cursors/ink.png rename to 2/css/cursors/ink.png diff --git a/splash/css/cursors/label.png b/2/css/cursors/label.png similarity index 100% rename from splash/css/cursors/label.png rename to 2/css/cursors/label.png diff --git a/splash/css/icons/controls.png b/2/css/icons/controls.png similarity index 100% rename from splash/css/icons/controls.png rename to 2/css/icons/controls.png diff --git a/2/css/icons/controls2.png b/2/css/icons/controls2.png new file mode 100644 index 0000000..1a05b94 Binary files /dev/null and b/2/css/icons/controls2.png differ diff --git a/splash/css/icons/drag.png b/2/css/icons/drag.png similarity index 100% rename from splash/css/icons/drag.png rename to 2/css/icons/drag.png diff --git a/splash/css/icons/erase.png b/2/css/icons/erase.png similarity index 100% rename from splash/css/icons/erase.png rename to 2/css/icons/erase.png diff --git a/favicon.png b/2/css/icons/favicon.png similarity index 100% rename from favicon.png rename to 2/css/icons/favicon.png diff --git a/splash/css/icons/ink.png b/2/css/icons/ink.png similarity index 100% rename from splash/css/icons/ink.png rename to 2/css/icons/ink.png diff --git a/splash/css/icons/label.png b/2/css/icons/label.png similarity index 100% rename from splash/css/icons/label.png rename to 2/css/icons/label.png diff --git a/splash/css/icons/speed_fast.png b/2/css/icons/speed_fast.png similarity index 100% rename from splash/css/icons/speed_fast.png rename to 2/css/icons/speed_fast.png diff --git a/splash/css/icons/speed_slow.png b/2/css/icons/speed_slow.png similarity index 100% rename from splash/css/icons/speed_slow.png rename to 2/css/icons/speed_slow.png diff --git a/v1/css/loopy.css b/2/css/loopy.css similarity index 59% rename from v1/css/loopy.css rename to 2/css/loopy.css index 0f70fa7..920f3c9 100644 --- a/v1/css/loopy.css +++ b/2/css/loopy.css @@ -15,8 +15,7 @@ canvas{ #canvasses{ position:absolute; - top:0; left:0; - width: calc(100% - 300px); + top:0; left:0; right: 300px; height: 100%; } #canvasses[fullscreen=yes]{ @@ -137,8 +136,25 @@ div[big=yes] .play_button_label{ SIDEBAR **********/ +#sidebarSwitch{ + position: absolute; + top:0; right: 300px; + padding: 10px; + padding-top: 8px; + padding-bottom: 12px; + line-height: 30px; + width: 30px; + text-align: center; + vertical-align: middle; + background-color: #eaeaea; + cursor: pointer; +} +#sidebarSwitch:hover{ + background-color: #ccc; +} #sidebar{ position: absolute; + overflow: auto; top:0; right:0; width: 300px; height: 100%; @@ -152,8 +168,11 @@ div[big=yes] .play_button_label{ -ms-user-select: none; user-select: none; } +.globalLoopyFirstItem{margin-top: -20px;} #sidebar > div{ margin: 25px; + margin-right: 15px; + padding-top: 20px; } #sidebar > div > div{ margin-bottom: 20px; @@ -181,16 +200,25 @@ div[big=yes] .play_button_label{ #sidebar[mode=play] .mini_button:hover{ background: #666; } +#sidebar[mode=play] .not_in_play_mode{ + display: none; +} #sidebar a{ color: #777; } #sidebar a:hover{ color: #999; } #sidebar hr{ border: none; border-bottom: 2px solid rgba(150,150,150,0.5); - width: 300px; - position: relative; - left:-25px; - margin: 10px 0; + margin: 30px -25px; + margin-right: -15px; +} +#sidebar .combineWithNext{ + margin: 0; } +#sidebar .combineWithNext+div .component_label, +#sidebar .combineWithNext+div .docLink{ +display: none +} + .component_input{ border: none; @@ -222,18 +250,50 @@ div[big=yes] .play_button_label{ width: 250px; height: 52px; } -.component_slider_graphic{ +.component_slider_graphic, +.component_slider_clickCatcher, +.component_slider_graphic_activeAtLeft, +.component_slider_graphic_activeAtRight, +.component_slider_graphic_activeOption +{ position: absolute; + background-size: cover; + background-repeat: no-repeat; width: 250px; height: 40px; cursor: pointer; } +.component_slider_graphic_activeAtLeft{ left: 0; background-position: left center;} +.component_slider_graphic_activeAtRight{ right: 0; background-position: right center;} .component_slider_pointer{ position: absolute; top: 42px; width: 15px; height: 10px; -} + background-image: url("sliders/slider_pointer_up.png"); + background-size: cover; +} +.combineWithNext .component_slider_graphic, +.combineWithNext .component_slider_graphic_activeOption, +.combineWithNext .component_slider_graphic_activeAtLeft, +.combineWithNext .component_slider_graphic_activeAtRight {top: 12px;} +.combineWithNext .component_slider_pointer{top: 0px; background-image: url("sliders/slider_pointer_down.png");} +.component_slider_clickCatcher{height: 52px} + +#sidebar > .compact{padding-top: 5px;} +#sidebar .compact hr{margin: 5px -25px;} +#sidebar > .compact > div{margin-bottom: 0;} +.compact .component_slider{height: 40px;} +.compact .component_slider_pointer{top: 30px;background-image: url("sliders/slider_high_contrast_pointer_up.png");} +.compact .combineWithNext .component_slider_graphic, +.compact .combineWithNext .component_slider_graphic_activeOption, +.compact .combineWithNext .component_slider_graphic_activeAtLeft, +.compact .combineWithNext .component_slider_graphic_activeAtRight {top: 0;} +.compact .combineWithNext .component_slider_pointer{top: 0; background-image: url("sliders/slider_high_contrast_pointer_down.png");} +.compact .component_slider_clickCatcher{height: 40px} +.compact .component_label{margin-bottom: 0;} +.compact .component_button{font-size: 20px;padding: 2px 10px;} + .component_label{ margin-bottom: 5px; } @@ -255,28 +315,73 @@ div[big=yes] .play_button_label{ } .component_button[header=yes]{ position: absolute; - top:0; left:0; - width: 280px; + top:0; left:0; right: 0; background: #222; - border-radius: 0px; + border-radius: 0; color: #888; } .component_button[header=yes]:hover{ background: #333; } - - +.advanced .simpleOnly, +.simpleOnly.inactive +{ + display: none; +} +.adv, .colorLogic, +.advanced .adv.colorLogic +{ + display: none; +} +.advanced .adv, +.advanced.colorLogicMode .adv.colorLogic +{ + display: block; +} +.adv_disclaimer, .colorLogic_disclaimer { + display: none; + color: #b84; + font-size: 80%; +} +.simple .adv.active, +.colorAestheticMode .adv.colorLogic.active +{ + display: block; + border: 1px dashed #b84; + background-image: linear-gradient(to bottom,#fed 0,#fed 20px,#ddd 20px); + margin: -9px; + padding: 8px; + padding-top: 2px; +} +.colorAestheticMode .adv.colorLogic.active { + border: 1px dashed #b44; + background: #fdd; /*linear-gradient(to bottom,#fdd 0,#fdd 20px,#ddd 20px);*/ +} +.colorLogic_disclaimer {color: #b44;} +.simple .adv.active>.adv_disclaimer, +.colorAestheticMode .adv.colorLogic.active>.colorLogic_disclaimer +{display: block;} /********** MODAL **********/ +.docLink{ + float:right; + font-size: 70%; + text-decoration: none; + cursor: pointer; + padding: 10px 5px; + margin: -10px 0; +} +.docLink:hover{transform: scale(2);} #modal_container{ display: none; position: absolute; width: 100%; height: 100%; + z-index: 1; -webkit-user-select: none; -moz-user-select: none; diff --git a/2/css/sliders/aggregationLatency.png b/2/css/sliders/aggregationLatency.png new file mode 100644 index 0000000..8751460 Binary files /dev/null and b/2/css/sliders/aggregationLatency.png differ diff --git a/2/css/sliders/cameraMode.png b/2/css/sliders/cameraMode.png new file mode 100644 index 0000000..c17878c Binary files /dev/null and b/2/css/sliders/cameraMode.png differ diff --git a/2/css/sliders/colorLogic.png b/2/css/sliders/colorLogic.png new file mode 100644 index 0000000..d674b99 Binary files /dev/null and b/2/css/sliders/colorLogic.png differ diff --git a/2/css/sliders/edgeFilterColor.png b/2/css/sliders/edgeFilterColor.png new file mode 100644 index 0000000..90fda1b Binary files /dev/null and b/2/css/sliders/edgeFilterColor.png differ diff --git a/2/css/sliders/edgeTargetColor.png b/2/css/sliders/edgeTargetColor.png new file mode 100644 index 0000000..36cc65d Binary files /dev/null and b/2/css/sliders/edgeTargetColor.png differ diff --git a/2/css/sliders/explode.png b/2/css/sliders/explode.png new file mode 100644 index 0000000..58ab8a5 Binary files /dev/null and b/2/css/sliders/explode.png differ diff --git a/2/css/sliders/filter.png b/2/css/sliders/filter.png new file mode 100644 index 0000000..af088e6 Binary files /dev/null and b/2/css/sliders/filter.png differ diff --git a/2/css/sliders/foreignColor.png b/2/css/sliders/foreignColor.png new file mode 100644 index 0000000..0bd21a9 Binary files /dev/null and b/2/css/sliders/foreignColor.png differ diff --git a/splash/css/sliders/color.png b/2/css/sliders/hue.png similarity index 100% rename from splash/css/sliders/color.png rename to 2/css/sliders/hue.png diff --git a/splash/css/sliders/initial.png b/2/css/sliders/init.png similarity index 100% rename from splash/css/sliders/initial.png rename to 2/css/sliders/init.png diff --git a/2/css/sliders/loopyMode.png b/2/css/sliders/loopyMode.png new file mode 100644 index 0000000..46029d8 Binary files /dev/null and b/2/css/sliders/loopyMode.png differ diff --git a/2/css/sliders/overflow.png b/2/css/sliders/overflow.png new file mode 100644 index 0000000..59ac7b8 Binary files /dev/null and b/2/css/sliders/overflow.png differ diff --git a/2/css/sliders/overflow_activeAtRight.png b/2/css/sliders/overflow_activeAtRight.png new file mode 100644 index 0000000..8b5bc0f Binary files /dev/null and b/2/css/sliders/overflow_activeAtRight.png differ diff --git a/2/css/sliders/quantitative.png b/2/css/sliders/quantitative.png new file mode 100644 index 0000000..8d679a9 Binary files /dev/null and b/2/css/sliders/quantitative.png differ diff --git a/2/css/sliders/signBehavior.png b/2/css/sliders/signBehavior.png new file mode 100644 index 0000000..b13c4f2 Binary files /dev/null and b/2/css/sliders/signBehavior.png differ diff --git a/2/css/sliders/size.png b/2/css/sliders/size.png new file mode 100644 index 0000000..bc08a72 Binary files /dev/null and b/2/css/sliders/size.png differ diff --git a/2/css/sliders/slider_high_contrast_pointer_down.png b/2/css/sliders/slider_high_contrast_pointer_down.png new file mode 100644 index 0000000..36d1d54 Binary files /dev/null and b/2/css/sliders/slider_high_contrast_pointer_down.png differ diff --git a/2/css/sliders/slider_high_contrast_pointer_up.png b/2/css/sliders/slider_high_contrast_pointer_up.png new file mode 100644 index 0000000..8f2c0dd Binary files /dev/null and b/2/css/sliders/slider_high_contrast_pointer_up.png differ diff --git a/2/css/sliders/slider_pointer_down.png b/2/css/sliders/slider_pointer_down.png new file mode 100644 index 0000000..3915203 Binary files /dev/null and b/2/css/sliders/slider_pointer_down.png differ diff --git a/splash/css/sliders/slider_pointer.png b/2/css/sliders/slider_pointer_up.png similarity index 100% rename from splash/css/sliders/slider_pointer.png rename to 2/css/sliders/slider_pointer_up.png diff --git a/splash/css/sliders/strength.png b/2/css/sliders/strength.png similarity index 100% rename from splash/css/sliders/strength.png rename to 2/css/sliders/strength.png diff --git a/2/css/sliders/textColor.png b/2/css/sliders/textColor.png new file mode 100644 index 0000000..58ddc77 Binary files /dev/null and b/2/css/sliders/textColor.png differ diff --git a/2/css/sliders/transmissionBehavior.png b/2/css/sliders/transmissionBehavior.png new file mode 100644 index 0000000..d131a13 Binary files /dev/null and b/2/css/sliders/transmissionBehavior.png differ diff --git a/2/css/sliders/underflow.png b/2/css/sliders/underflow.png new file mode 100644 index 0000000..d73b4af Binary files /dev/null and b/2/css/sliders/underflow.png differ diff --git a/2/css/sliders/underflow_activeAtLeft.png b/2/css/sliders/underflow_activeAtLeft.png new file mode 100644 index 0000000..2147aa6 Binary files /dev/null and b/2/css/sliders/underflow_activeAtLeft.png differ diff --git a/2/css/sliders/visibility.png b/2/css/sliders/visibility.png new file mode 100644 index 0000000..3f82505 Binary files /dev/null and b/2/css/sliders/visibility.png differ diff --git a/2/index.html b/2/index.html new file mode 100644 index 0000000..25b44d2 --- /dev/null +++ b/2/index.html @@ -0,0 +1,103 @@ + + + + + + LOOPY (v2.0) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/2/js/BitArray.js b/2/js/BitArray.js new file mode 100644 index 0000000..a8e73af --- /dev/null +++ b/2/js/BitArray.js @@ -0,0 +1,289 @@ +function BitArray(arrayBufferOrBitSize){ + const self = this; + if(typeof arrayBufferOrBitSize === "number") self.rawData = new Uint8Array(Math.ceil(arrayBufferOrBitSize/8)); + else self.rawData = new Uint8Array(arrayBufferOrBitSize.buffer?arrayBufferOrBitSize.buffer:arrayBufferOrBitSize); + const bitByBlock = 8*self.rawData.BYTES_PER_ELEMENT; + self.offset=0; + self.maxOffset=0; + + self.set = (value,bitSize=-1,offset= -1)=>{ + if(bitSize===0) return self; + if(typeof value !== "number") { + if(bitSize === -1) bitSize = value.rawData.byteLength*8; + } + if(bitSize === -1) bitSize = Math.max(1,Math.ceil(Math.log2(value+1))); + if(offset === -1){ + offset = self.offset; + self.setOffset(self.offset+bitSize); + } + + const currentBlock = Math.floor(offset/bitByBlock); + const availableBits = bitByBlock - offset%bitByBlock; + if(bitSize<=availableBits){ + // get block; + const blockOriginalContent = self.rawData[currentBlock]; + let newBlockContent = blockOriginalContent; + // clean the writing area + newBlockContent = newBlockContent>>>availableBits; + newBlockContent = newBlockContent<>>availableBits; + newBlockContent = newBlockContent<>>bitSize-availableBits; + else readyToInsert = value.get(availableBits); + newBlockContent = newBlockContent | readyToInsert; + // replace the block content; + self.rawData[currentBlock] = newBlockContent; + // write remaining part + if(typeof value === "number") self.set(value%Math.pow(2,bitSize-availableBits),bitSize-availableBits,offset+availableBits); + else self.set(value,bitSize-availableBits,offset+availableBits); + } + return self; + }; + self.append = (value,bitSize=-1)=>{ + self.set(value,bitSize); + return self; + }; + function shouldBeCompressed(bitSize){ + const bitForDataSize = Math.ceil(Math.log2(bitSize)); + const refBitSize = Math.floor(bitForDataSize/2); + const zMinSize = 1 // origin line + +1 // ref line + +1+bitForDataSize+refBitSize // ref size + ;//+1+bitForDataSize; // original content in ref line + return bitSize>=zMinSize*3; // if small bitSize it will be more often bigger than smaller so dont compress + } + self.zPush = (value,bitSize,zAreaStartOffset=0)=>{ + if(!shouldBeCompressed(bitSize)) return self.append(value,bitSize); + if(zAreaStartOffset===self.offset) return self.append(value,bitSize); + if(typeof value !== "object") value = (new BitArray(bitSize)).append(value,bitSize).resetOffset(); + + fillZCache(bitSize,zAreaStartOffset); + + const bitForDataSize = Math.ceil(Math.log2(bitSize)); + const refBitSize = Math.floor(bitForDataSize/2); + const minFragmentSize = 1+bitForDataSize+refBitSize+1+bitForDataSize; + + const zc = zCache[zAreaStartOffset]; + const maxPrevious = Math.min(zc.length,Math.pow(2,refBitSize)); + + const pushCandidates = []; + let partialCandidate = new BitArray(bitSize+1); + let srcOffsetPartial = 0; + + finish: for (let offset=0;offsetminFragmentSize;patternSize--){ + for(let i = 0;ia.offset<=c.offset?a:c, + (new BitArray(bitSize+1)).append(0,1).append(value.resetOffset(),bitSize));// default vanilla line + return self.append(smallest.resetOffset(),smallest.maxOffset); + }; + self.resetOffset = ()=>self.setOffset(0); + self.setOffset = (offset)=>{ + self.maxOffset = Math.max(self.maxOffset,self.offset,offset); + self.offset = offset; + return self; + }; + self.equalSequence = (bitSize, selfStartOffset, bitArray,itStartOffset)=>{ + const a = self.export(bitSize,selfStartOffset); + const b = bitArray.export(bitSize,itStartOffset); + let equal = true; + for(let i =0; i{ + const concatenated = new BitArray(self.offset+bitArray.offset); + const backupSelfOffset = self.offset; + const backupBitArrayOffset = bitArray.offset; + self.resetOffset(); + bitArray.resetOffset(); + concatenated.append(self,backupSelfOffset); + concatenated.append(bitArray,backupBitArrayOffset); + self.setOffset(backupSelfOffset); + bitArray.setOffset(backupBitArrayOffset); + return concatenated; + }; + const zCache = []; + function fillZCache(bitSize,zAreaStartOffset,cacheToLine=false){ + const bitForDataSize = Math.ceil(Math.log2(bitSize)); + const refBitSize = Math.floor(bitForDataSize/2); + + if(!zCache[zAreaStartOffset]) zCache[zAreaStartOffset] = []; + const zc = zCache[zAreaStartOffset]; + if(!zc[0])zc[0]={offset:zAreaStartOffset,zSize:bitSize,content:self.export(bitSize,zAreaStartOffset)}; + let cacheOffset = zc[zc.length-1].offset+zc[zc.length-1].zSize; + for(let i = zc.length;true;i++){ + if(cacheToLine===false && cacheOffset>=self.offset) break; + if(cacheToLine===true && cacheOffset>=self.offset+1) { + self.setOffset(cacheOffset) ; + break; + } + if(typeof cacheToLine === "number" && cacheToLine{ + if(!shouldBeCompressed(bitSize)){ + if(isFinite(zLine) && zLine>=0) return self.get(bitSize,zAreaStartOffset+zLine*bitSize); + else if(isFinite(zLine) && zLine<0) return self.get(bitSize,self.offset-zLine*bitSize); + else return self.get(bitSize); + } + if(isNaN(zLine)) fillZCache(bitSize,zAreaStartOffset,true); + else if(isFinite(zLine) && zLine>0) fillZCache(bitSize,zAreaStartOffset,zLine); + else fillZCache(bitSize,zAreaStartOffset); + + const zc = zCache[zAreaStartOffset]; + if(isFinite(zLine) && zLine>=0) return zc[zLine].content; + else if(isFinite(zLine) && zLine<0) return zc[zc.length+zLine].content; + else return zc[zc.length-1].content; + }; + self.get=(bitSize,offset = -1)=>{ + if(offset === -1){ + offset = self.offset; + self.setOffset(self.offset+bitSize); + } + const currentBlock = Math.floor(offset/bitByBlock); + const availableBits = bitByBlock - offset%bitByBlock; + //console.log(`offset:${offset}, ${bitSize}bits -> in block ${currentBlock}, read from bit ${bitByBlock-availableBits} to bit ${bitByBlock-availableBits+bitSize}`); + if(bitSize<=availableBits){ + // get block; + let result = self.rawData[currentBlock]; + // floor to availableBits + result = result%Math.pow(2,availableBits); + // shift to bitSize + result = result>>>(availableBits-bitSize); + return result; + }else { + // get block; + let result = self.rawData[currentBlock]; + // floor to availableBits + result = result%Math.pow(2,availableBits); + + const remainingBitSize = bitSize - availableBits; + // shift left up to to remaining bitSize + result = result<{ + if(offset === -1){ + offset = self.offset; + self.setOffset(self.offset+bitSize); + } + const currentBlock = Math.floor(offset/bitByBlock); + const availableBits = bitByBlock - offset%bitByBlock; + //console.log(`offset:${offset}, ${bitSize}bits -> in block ${currentBlock}, read from bit ${bitByBlock-availableBits} to bit ${bitByBlock-availableBits+bitSize}`); + if(bitSize<=availableBits){ + // get block; + let result = self.rawData[currentBlock]; + // floor to availableBits + result = result%Math.pow(2,availableBits); + // shift to bitSize + result = result>>>(availableBits-bitSize); + const bitArrayResult = new BitArray(bitSize); + bitArrayResult.append(result,bitSize); + return bitArrayResult; + }else { + // get block; + let result = self.rawData[currentBlock]; + // floor to availableBits + result = result % Math.pow(2, availableBits); + + const bitArrayResult = new BitArray(bitSize); + bitArrayResult.append(result,availableBits); + + const remainingBitSize = bitSize - availableBits; + const remaining = self.export(remainingBitSize, offset + availableBits); + bitArrayResult.append(remaining.resetOffset(),remainingBitSize); + + return bitArrayResult; + } + + }; + self.rotate = (bitByLine, lineCount, startOffset= -1)=>{ + if( !bitByLine || !lineCount) return self; + if(startOffset === -1){ + startOffset = self.offset; + self.setOffset(self.offset+bitByLine*lineCount); + } + const workSpace = new BitArray(bitByLine*lineCount); + for(let b=0 ; binitBitArray().get(4,0)); +testEqual(`get 14 for the first 8bit`, 14, async ()=>initBitArray().get(8,0)); +testEqual(`get 14 for the 4bit at offset 4`, 14, async ()=>initBitArray().get(4,4)); +testEqual(`get 7 for the 3bit at offset 4`, 7, async ()=>initBitArray().get(3,4)); +testEqual(`get 7 for the 3bit at offset 9`, 7, async ()=>initBitArray().get(3,9)); +testEqual(`get 4 for the 3bit at offset 6`, 4, async ()=>initBitArray().get(3,6)); +testEqual(`get 59185 for the 16bit at offset 4`, 59185, async ()=>initBitArray().get(16,4)); + +testEqual(`set 1 on 1bit at offset 35`, 1, async ()=>initBitArray().set(1,1,35).get(1,35)); +testEqual(`set 1 on 1bit at offset 25 (and verify previous and next data are preserved)`, + 7, async ()=>initBitArray().set(1,1,25).get(3,24)); +testEqual(`set 42 on 6bit at offset 37`, 42, async ()=>initBitArray().set(42,6,37).get(6,37)); +testEqual(`set 42 on 7bit at offset 20 (and verify previous and next data are preserved)`, + 341, async ()=>initBitArray().set(42,7,20).get(9,19)); + +testEqual(`append 1,0,1,42,0 (on 10bits)`, + 724, async ()=>initBitArray().append(1).append(0).append(1).append(42).append(0).get(10,0)); +testEqual(`set bitArray`, + '00001110 01110011 00010000 11100111 00110001 10011011 00100000 [0:8b] 00001110 01110011 00011001 10110010 [0:32b]', + async ()=>binView(initBitArray(16*8).set(initBitArray(),32,64).set(initBitArray(),32,20).rawData.buffer)); +testEqual(`export partial bitArray`, + '10101000', + async ()=>binView((new BitArray(16)).append(42,10).export(6,4).rawData.buffer)); + +testEqual(`equalSequence pass if equal`, + true, + async ()=>(new BitArray(15)).append(42,15).equalSequence(6,9,(new BitArray(10)).append(42,9),3)); +testEqual(`equalSequence fail if not equal`, + false, + async ()=>(new BitArray(15)).append(43,15).equalSequence(6,9,(new BitArray(10)).append(42,9),3)); +testEqual(`rotateArea 111 000 000 000 became 1000 1000 1000`, + '10001000 10000000', + async ()=>binView((new BitArray(12)).append(7,3).rotate(3,4,0).rawData.buffer)); +testEqual(`rotate and rotate back give same as with no rotate at all`, + '11100000 [0:8b]', + async ()=>binView((new BitArray(12)).append(7,3).rotate(3,4,0).rotate(4,3,0).rawData.buffer)); + + +{ + testEqual(`zGet first identical 30bit`, + '[0:24b] 10101000', + async ()=>binView((new BitArray(64)).append(42,30).append(parseInt('1111110001111110',2),16).zGet(30,0,0).rawData.buffer)); + testEqual(`zGet 2nd identical 30bit`, + '[0:24b] 10101000', + async ()=>binView((new BitArray(64)).append(42,30).append(parseInt('1111110001111110',2),16).zGet(30,0,1).rawData.buffer)); + + + testEqual(`zPush some identical data on 30bit each`, + {offset:48/*90*/, bitView:'[0:24b] 10101011 11110001 11111000 [0:80b]'}, + async ()=>{ + const res = initBitArray(128).zPush(42,30).zPush(42,30).zPush(42,30); + return {offset:res.offset,bitView:binView(res.rawData.buffer)}; + }); + testEqual(`zPush some similar data on 30bit each`, + {offset:55/*90*/, bitView:'[0:24b] 10101011 11101000 00001111 11110010 [0:72b]'}, + async ()=>{ + const res = initBitArray(128).zPush(42,30).zPush(43,30).zPush(42,30); + return {offset:res.offset,bitView:binView(res.rawData.buffer)}; + }); + testEqual(`zPush some similar data on 100bit each`, + {offset:136/*200*/, bitView:'[0:88b] 00101010 00001101 01000000 00000101 10101101 10111000 [0:72b]'}, + async ()=>{ + const line0 = (new BitArray(100)).set(42,6,90); + const line1 = (new BitArray(100)).set(42,6,90).set(42,6,40); + const res = (new BitArray(202)).zPush(line0,100).zPush(line1,100); + return {offset:res.offset,bitView:binView(res.rawData.buffer)}; + }); + testEqual(`zPush some similar data on 100bit each (and don't compress when it's not effective)`, + {offset:149/*200*/, bitView:'[0:88b] 00101010 00001101 01000000 00000101 10101101 10010000 00000101 10000000 [0:56b]'}, + async ()=>{ + const line0 = (new BitArray(100)).set(42,6,90); + const line1 = (new BitArray(100)).set(43,6,90).set(42,6,40); + const res = (new BitArray(202)).zPush(line0,100).zPush(line1,100); + return {offset:res.offset,bitView:binView(res.rawData.buffer)}; + }); +} diff --git a/v1.1/js/Dragger.js b/2/js/Dragger.js similarity index 55% rename from v1.1/js/Dragger.js rename to 2/js/Dragger.js index 17d35d8..cb90f86 100644 --- a/v1.1/js/Dragger.js +++ b/2/js/Dragger.js @@ -6,7 +6,7 @@ DRAGGER function Dragger(loopy){ - var self = this; + const self = this; self.loopy = loopy; // Dragging anything? @@ -15,13 +15,18 @@ function Dragger(loopy){ self.offsetY = 0; subscribe("mousedown",function(){ + if(self.loopy.mode===Loopy.MODE_PLAY && loopy.cameraMode===2) { + self.dragging = {_CLASS_:"Scene"}; + self.offsetX = Mouse.x; + self.offsetY = Mouse.y; + } // ONLY WHEN EDITING w DRAG - if(self.loopy.mode!=Loopy.MODE_EDIT) return; - if(self.loopy.tool!=Loopy.TOOL_DRAG) return; + if(self.loopy.mode!==Loopy.MODE_EDIT) return; + if(self.loopy.tool!==Loopy.TOOL_DRAG) return; // Any node under here? If so, start dragging! - var dragNode = loopy.model.getNodeByPoint(Mouse.x, Mouse.y); + const dragNode = loopy.model.getNodeByPoint(Mouse.x, Mouse.y); if(dragNode){ self.dragging = dragNode; self.offsetX = Mouse.x - dragNode.x; @@ -31,7 +36,7 @@ function Dragger(loopy){ } // Any label under here? If so, start dragging! - var dragLabel = loopy.model.getLabelByPoint(Mouse.x, Mouse.y); + const dragLabel = loopy.model.getLabelByPoint(Mouse.x, Mouse.y); if(dragLabel){ self.dragging = dragLabel; self.offsetX = Mouse.x - dragLabel.x; @@ -41,7 +46,7 @@ function Dragger(loopy){ } // Any edge under here? If so, start dragging! - var dragEdge = loopy.model.getEdgeByPoint(Mouse.x, Mouse.y); + const dragEdge = loopy.model.getEdgeByPoint(Mouse.x, Mouse.y); if(dragEdge){ self.dragging = dragEdge; self.offsetX = Mouse.x - dragEdge.labelX; @@ -50,20 +55,33 @@ function Dragger(loopy){ return; } + self.dragging = {_CLASS_:"Scene"}; + self.offsetX = Mouse.x; + self.offsetY = Mouse.y; + }); subscribe("mousemove",function(){ + if(self.loopy.mode===Loopy.MODE_PLAY && self.dragging && self.dragging._CLASS_==="Scene") { + loopy.offsetX += (Mouse.x - self.offsetX); + loopy.offsetY += (Mouse.y - self.offsetY); + } // ONLY WHEN EDITING w DRAG - if(self.loopy.mode!=Loopy.MODE_EDIT) return; - if(self.loopy.tool!=Loopy.TOOL_DRAG) return; + if(self.loopy.mode!==Loopy.MODE_EDIT) return; + if(self.loopy.tool!==Loopy.TOOL_DRAG) return; + // moving scene/zoom + if(self.dragging && self.dragging._CLASS_==="Scene"){ + loopy.offsetX += (Mouse.x - self.offsetX); + loopy.offsetY += (Mouse.y - self.offsetY); + } // If you're dragging a NODE, move it around! - if(self.dragging && self.dragging._CLASS_=="Node"){ + if(self.dragging && self.dragging._CLASS_==="Node"){ // Model's been changed! publish("model/changed"); - var node = self.dragging; + const node = self.dragging; node.x = Mouse.x - self.offsetX; node.y = Mouse.y - self.offsetY; @@ -73,27 +91,27 @@ function Dragger(loopy){ } // If you're dragging an EDGE, move it around! - if(self.dragging && self.dragging._CLASS_=="Edge"){ + if(self.dragging && self.dragging._CLASS_==="Edge"){ // Model's been changed! publish("model/changed"); - var edge = self.dragging; - var labelX = Mouse.x - self.offsetX; - var labelY = Mouse.y - self.offsetY; + const edge = self.dragging; + const labelX = Mouse.x - self.offsetX; + const labelY = Mouse.y - self.offsetY; - if(edge.from!=edge.to){ + if(edge.from!==edge.to){ // The Arc: whatever label *Y* is, relative to angle & first node's pos - var fx=edge.from.x, fy=edge.from.y, tx=edge.to.x, ty=edge.to.y; - var dx=tx-fx, dy=ty-fy; - var a = Math.atan2(dy,dx); + const fx=edge.from.x, fy=edge.from.y, tx=edge.to.x, ty=edge.to.y; + const dx=tx-fx, dy=ty-fy; + const a = Math.atan2(dy,dx); // Calculate arc - var points = [[labelX,labelY]]; - var translated = _translatePoints(points, -fx, -fy); - var rotated = _rotatePoints(translated, -a); - var newLabelPoint = rotated[0]; + const points = [[labelX,labelY]]; + const translated = _translatePoints(points, -fx, -fy); + const rotated = _rotatePoints(translated, -a); + const newLabelPoint = rotated[0]; // ooookay. edge.arc = -newLabelPoint[1]; // WHY NEGATIVE? I DON'T KNOW. @@ -101,13 +119,13 @@ function Dragger(loopy){ }else{ // For SELF-ARROWS: just get angle & mag for label. - var dx = labelX - edge.from.x, + const dx = labelX - edge.from.x, dy = labelY - edge.from.y; - var a = Math.atan2(dy,dx); - var mag = Math.sqrt(dx*dx + dy*dy); + const a = Math.atan2(dy,dx); + let mag = Math.sqrt(dx*dx + dy*dy); // Minimum mag - var minimum = edge.from.radius+25; + const minimum = edge.from.radius+25; if(mag died +}; + +Edge.allSignals = []; +Edge.MAX_SIGNALS = 100; +Edge.MAX_SIGNALS_PER_EDGE = 10; +Edge._CLASS_ = "Edge"; + +function Edge(model, config){ + + const self = this; + self._CLASS_ = "Edge"; + + // Mah Parents! + self.loopy = model.loopy; + self.model = model; + self.config = config; + + // Default values... + const defaultProperties = { + from: _makeErrorFunc("CAN'T LEAVE 'FROM' BLANK"), + to: _makeErrorFunc("CAN'T LEAVE 'TO' BLANK"), + arc: 100, + rotation: 0, + }; + injectedDefaultProps(defaultProperties,objTypeToTypeIndex("edge")); + _configureProperties(self, config, defaultProperties); + + // Get my NODES + self.from = model.getNode(self.from); + self.to = model.getNode(self.to); + + // We have signals! + self.signals = []; + self.signalSpeed = 0; + self.addSignal = function(signal){ + + if(self.to.died && !canTransmitLife(self)) return; + + const edge = self; + if(loopy.colorLogic===1){ + if(edge.edgeTargetColor=== -2) { // choose random color from possible colors + let candidateColors = {}; + const outputEdges = loopy.model.getEdgesByStartNode(edge.to); + outputEdges.forEach((toEdge) => candidateColors[toEdge.edgeFilterColor] = 1); + if (candidateColors[-1]) candidateColors = [0, 1, 2, 3, 4, 5]; + else candidateColors = Object.keys(candidateColors).map((v)=>parseInt(v)); + signal.finalColor = candidateColors[Math.floor(Math.random() * candidateColors.length)]; + } else if(edge.edgeTargetColor<0)signal.finalColor = signal.color; // keep initial color + else signal.finalColor = edge.edgeTargetColor; // change to color + } else signal.finalColor = edge.to.hue; // no logic just aesthetic ! + + // IF ALREADY TOO MANY, FORGET IT + if(Edge.allSignals.length>Edge.MAX_SIGNALS){ + return; + } + + // IF TOO MANY *ON THIS EDGE*, FORGET IT + if(self.signals.length>Edge.MAX_SIGNALS_PER_EDGE){ + return; + } + + // Re-create signal + let age; + if(signal.age===undefined){ + // age = 13; // cos divisible by 1,2,3,4 + 1 + age = 1000000; // actually just make signals last "forever". + } else age = signal.age-1; + const newSignal = { + delta: signal.delta, + position: 0, + scaleX: Math.abs(signal.delta), + scaleY: signal.vital?Math.abs(signal.delta):signal.delta, + color: signal.color, + finalColor: signal.finalColor, + vital:signal.vital, + age: age, + }; + + // If it's expired, forget it. + if(age<=0) return; + + self.signals.unshift(newSignal); // it's a queue! + + // ALL signals. + Edge.allSignals.push(newSignal); + + }; + self.updateSignals = function(){ + + // Speed? + const speed = Math.pow(2,self.loopy.signalSpeed); + self.signalSpeed = speed/self.getArrowLength(); + + // Move all signals along + for(let i=0; i=0.5){ + + // Multiply by this edge's strength! + signal.delta *= self.strength; + + } + + // And also TWEEN the scale. + var gotoScaleX = Math.abs(signal.delta); + var gotoScaleY = signal.delta; + signal.scaleX = signal.scaleX*0.8 + gotoScaleX*0.2; + signal.scaleY = signal.scaleY*0.8 + gotoScaleY*0.2; + */ + + } + + // If any signals reach >=1, pass 'em along + let lastSignal = self.signals[self.signals.length-1]; + while(lastSignal && lastSignal.position>=1){ + + // Actually pass it along + if(loopy.loopyMode===0 && self.signBehavior===0){ + lastSignal.delta *= self.strength; + }else { + if(!lastSignal.vital || (self.filter !== 0 && self.filter !== 5) || self.quantitative===2) switch (self.signBehavior) { + case 0:break; + case 1: lastSignal.delta = - lastSignal.delta;break; + case 4: lastSignal.delta = - Math.abs(lastSignal.delta);break; + case 5: lastSignal.delta = Math.abs(lastSignal.delta);break; + } + } + + lastSignal.color = lastSignal.finalColor; + self.to.takeSignal(lastSignal, self); + + // Pop it, move on down + self.removeSignal(lastSignal); + lastSignal = self.signals[self.signals.length-1]; + + } + + }; + self.removeSignal = function(signal){ + self.signals.splice( self.signals.indexOf(signal), 1 ); + Edge.allSignals.splice( Edge.allSignals.indexOf(signal), 1 ); + }; + self.getSignalBoundingBox = function(signal){ + const size = 40*Math.max(Math.abs(signal.scaleX),Math.abs(signal.scaleY)); + return { + left:signal.x-size/2, + right:signal.x+size/2, + top:signal.y-size/2, + bottom:signal.y+size/2, + cx:signal.x, + cy:signal.y, + weight:1 + } + } + self.drawSignals = function(ctx){ + + // Draw each one + for(let i=0; i0) + || (loopy.loopyMode===1 && self.signBehavior===5 && signal.delta<0) + ){ + if((self.filter !== 0 && self.filter !== 5) || !signal.vital || self.quantitative===2) { + // sin/cos-animate it for niceness. + const flip = Math.cos(blend*Math.PI); // (0,1) -> (1,-1) + ctx.scale(1, flip); + vitalFlip=true; + if(signal.vital) ctx.scale(1, flip); + } + } + + // Signal's age = alpha. + if(signal.age===2){ + ctx.globalAlpha = 0.5; + }else if(signal.age===1){ + ctx.globalAlpha = 0.25; + } + + if(signal.vital){ + if( (signal.delta<0 && (!vitalFlip || blend<0.5) ) + || (signal.delta>0 && vitalFlip && blend>=0.5) + ) drawDeath(ctx); + else if( (signal.delta>0 && (!vitalFlip || blend<0.5)) + || (signal.delta<0 && vitalFlip && blend>=0.5) + ) drawLife(ctx); + + } else if(self.quantitative===1) drawAmountArrow(ctx, signalColor); // draw weight + else drawTendencyArrow(ctx, signalColor); + + // Restore + ctx.restore(); + + } + + }; + const _listenerReset = subscribe("model/reset", function(){ + self.signals = []; + Edge.allSignals = []; + }); + + + ////////////////////////////////////// + // UPDATE & DRAW ///////////////////// + ////////////////////////////////////// + + // Update! + self.labelX = 0; + self.labelY = 0; + let fx, fy, tx, ty, + r, dx, dy, w, a, h, + y, a2, + arrowBuffer, arrowDistance, arrowAngle, beginDistance, beginAngle, + startAngle, endAngle, + y2, begin, end, + arrowLength, ax, ay, aa, + //labelAngle, + lx, ly, labelBuffer; // BECAUSE I'VE LOST CONTROL OF MY LIFE. + //self.update = function(speed){ + self.update = function(){ + + //////////////////////////////////////////////// + // PRE-CALCULATE THE MATH (for retina canvas) // + //////////////////////////////////////////////// + + if(loopy.sidebar.currentPage.target === self){ + injectPropsLabelInSideBar(loopy.sidebar.currentPage,objTypeToTypeIndex('edge')); + } + + // Edge case: if arc is EXACTLY zero, whatever, add 0.1 to it. + if(self.arc===0) self.arc=0.1; + + // Mathy calculations: (all retina, btw) + fx=self.from.x*2; + fy=self.from.y*2; + tx=self.to.x*2; + ty=self.to.y*2; + if(self.from===self.to){ + let rotation = self.rotation; + rotation *= Math.TAU/360; + tx += Math.cos(rotation); + ty += Math.sin(rotation); + } + dx = tx-fx; + dy = ty-fy; + w = Math.sqrt(dx*dx+dy*dy); + a = Math.atan2(dy,dx); + h = Math.abs(self.arc*2); + + // From: http://www.mathopenref.com/arcradius.html + r = (h/2) + ((w*w)/(8*h)); + y = r-h; // the circle's y-pos is radius - given height. + a2 = Math.acos((w/2)/r); // angle from x axis, arc-cosine of half-width & radius + + // Arrow buffer... + arrowBuffer = 15; + arrowDistance = (self.to.radius+arrowBuffer)*2; + arrowAngle = arrowDistance/r; // (distance/circumference)*TAU, close enough. + beginDistance = (self.from.radius+arrowBuffer)*2; + beginAngle = beginDistance/r; + + // Arc it! + startAngle = a2 - Math.TAU/2; + endAngle = -a2; + if(h>r){ + startAngle *= -1; + endAngle *= -1; + } + if(self.arc>0){ + y2 = y; + begin = startAngle+beginAngle; + end = endAngle-arrowAngle; + }else{ + y2 = -y; + begin = -startAngle-beginAngle; + end = -endAngle+arrowAngle; + } + + // Arrow HEAD! + arrowLength = 10*2; + ax = w/2 + Math.cos(end)*r; + ay = y2 + Math.sin(end)*r; + aa = end + Math.TAU/4; + + // My label is... + const s = self.strength; + let l; + if(loopy.loopyMode===0){ + if(s>=3) l="+++"; + else if(s>=2) l="++"; + else if(s>=1) l="+"; + else if(s===0) l="?"; + else if(s>=-1) l="–"; // EM dash, not hyphen. + else if(s>=-2) l="– –"; + else l="– – –"; + } else { + const signBehavior = ['=','⤭','F–','F+','|–|','|+|']; + const filter = ['','⬍','🕱','❀','❀🕱','🎲']; + if(self.filter && !self.signBehavior) l=filter[self.filter]; + else l=`${filter[self.filter]}${signBehavior[self.signBehavior]}`; + } + // + if(self.customLabel) l=self.customLabel; + self.label = l; + + // Label position + const labelPosition = self.getPositionAlongArrow(0.5); + lx = labelPosition.x; + ly = labelPosition.y; + + // ACTUAL label position, for grabbing purposes + self.labelX = (fx + Math.cos(a)*lx - Math.sin(a)*ly)/2; // un-retina + self.labelY = (fy + Math.sin(a)*lx + Math.cos(a)*ly)/2; // un-retina + + // ...add offset to label + labelBuffer = 18*2; // retina + if(self.arc<0) labelBuffer*=-1; + ly += labelBuffer; + + /////////////////////////////////////// + // AND THEN UPDATE OTHER STUFF AFTER // + // THE CALCULATIONS ARE DONE I GUESS // + /////////////////////////////////////// + + // When actually playing the simulation... + /*if(self.loopy.mode==Loopy.MODE_PLAY){ + self.to.nextValue += self.from.value * self.strength * speed; + }*/ + + // Update signals + self.updateSignals(); + + }; + + // Get position along arrow, on what parameter? + self.getArrowLength = function(){ + let angle; + if(self.from===self.to){ + // angle = Math.TAU; + return r*Math.TAU - 2*self.from.radius; + }else{ + //debugger; + if(y<0){ + // arc's center is above the horizon + if(self.arc<0){ // ccw + angle = Math.TAU + begin - end; + }else{ // cw + angle = Math.TAU + end - begin; + } + }else{ + // arc's center is below the horizon + angle = Math.abs(end-begin); + } + } + return r*angle; + }; + self.getPositionAlongArrow = function(param){ + + param = -0.05 + param*1.1; // (0,1) --> (-0.05, 1.05) + + // If the arc's circle is actually BELOW the line... + let begin2 = begin; + if(y<0){ + // DON'T KNOW WHY THIS WORKS, BUT IT DOES. + if(begin2>0){ + begin2-=Math.TAU; + }else{ + begin2+=Math.TAU; + } + } + + // Get angle! + const angle = begin2 + (end-begin2)*param; + + // return x & y + return{ + x: w/2 + Math.cos(angle)*r, + y: y2 + Math.sin(angle)*r + }; + + }; + + // Draw + self.draw = function(ctx){ + + + + // Width & Color + if(self.edgeTargetColor===-3) { + ctx.lineWidth = 4; + } else ctx.lineWidth = 4*Math.abs(self.strength)-2; + const gradient = ctx.createLinearGradient(0,0,ax,ay); + gradient.addColorStop(0.4,Edge.COLORS[self.edgeFilterColor]); + if(self.edgeTargetColor===-2) { + for(let x = 0;x<6;x++) gradient.addColorStop(0.5+x/10,Edge.COLORS[x]); + } else if(self.edgeTargetColor===-1) gradient.addColorStop(1,Edge.COLORS[self.edgeFilterColor]); + else gradient.addColorStop(1,Edge.COLORS[self.edgeTargetColor]); + ctx.strokeStyle = gradient; + + // Translate & Rotate! + ctx.save(); + ctx.translate(fx, fy); + ctx.rotate(a); + + let drawMe = true; + if(self.loopy.mode===Loopy.MODE_PLAY && self.from.label === "autoplay") drawMe = false; + if(self.from.died && self.filter === 1) drawMe = false; + if(self.to.died && !canTransmitLife(self)) drawMe = false; + + if(drawMe){ + // Highlight! + if(self.loopy.sidebar.currentPage.target === self){ + ctx.save(); + ctx.translate(lx, ly); + ctx.rotate(-a); + ctx.beginPath(); + ctx.arc(0, 5, 60, 0, Math.TAU, false); + ctx.fillStyle = HIGHLIGHT_COLOR; + ctx.fill(); + ctx.restore(); + } + + // Arc it! + function drawArc(ctx,arc,w,y2,r,startAngle,end) { + ctx.save(); + ctx.beginPath(); + if(self.arc>0){ + ctx.arc(w/2, y2, r, startAngle, end, false); + }else{ + ctx.arc(w/2, y2, r, -startAngle, end, true); + } + ctx.stroke(); + ctx.restore(); + } + let baseOffset =0; + if(self.quantitative===1){ + baseOffset=4; + drawArc(ctx,self.arc,w,y2,r-4,startAngle,end); + drawArc(ctx,self.arc,w,y2,r+4,startAngle,end); + + }else drawArc(ctx,self.arc,w,y2,r,startAngle,end); + + // Arrow HEAD! + ctx.save(); + ctx.translate(ax, ay); + if(self.arc<0) ctx.scale(-1,-1); + ctx.rotate(aa); + drawArrow(ctx,arrowLength,1,baseOffset); + if(self.edgeTargetColor===-3) drawArrow(ctx,arrowLength,1,12+baseOffset,1); + if(self.quantitative===2) { + drawArrow(ctx,arrowLength,1,-2.5*arrowLength+baseOffset); + drawArrow(ctx,arrowLength,1,-2.5*arrowLength+4+baseOffset); + //drawArrow(ctx,arrowLength,1,4+baseOffset); + drawArrow(ctx,arrowLength,-1,-2.5*arrowLength+baseOffset); + drawArrow(ctx,arrowLength,-1,-2.5*arrowLength+4+baseOffset); + } + ctx.restore(); + + // Stroke! + ctx.stroke(); + + // Draw label + ctx.font = "100 60px sans-serif"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.save(); + ctx.translate(lx, ly); + ctx.rotate(-a); + ctx.fillStyle = "#999"; + ctx.fillText(self.label, 0, 0); + ctx.restore(); + } + // DRAW SIGNALS + self.drawSignals(ctx); + + // Restore + ctx.restore(); + + }; + + ////////////////////////////////////// + // KILL EDGE ///////////////////////// + ////////////////////////////////////// + + self.kill = function(){ + + // Kill Listeners! + unsubscribe("model/reset",_listenerReset); + + // Remove from parent! + model.removeEdge(self); + + // Killed! + publish("kill",[self]); + + }; + + ////////////////////////////////////// + // HELPER METHODS //////////////////// + ////////////////////////////////////// + + self.isPointOnLabel = function(x, y){ + // TOTAL HACK: radius based on TOOL BEING USED. + let radius; + if(self.loopy.tool===Loopy.TOOL_DRAG || self.loopy.tool===Loopy.TOOL_INK) radius=40; // selecting, wide radius! + else if(self.loopy.tool===Loopy.TOOL_ERASE) radius=25; // no accidental erase + else radius = 15; // you wanna label close to edges + return _isPointInCircle(x, y, self.labelX, self.labelY, radius); + }; + + self.getBoundingBox = function(){ + + // SPECIAL CASE: SELF-ARC + if(self.from===self.to){ + + const perpendicular = a-Math.TAU/4; + let cx = fx + Math.cos(perpendicular)*-y2; + let cy = fy + Math.sin(perpendicular)*-y2; + cx = cx/2; // un-retina + cy = cy/2; // un-retina + + const _radius = r/2; // un-retina + + return { + left: cx - _radius, + top: cy - _radius, + right: cx + _radius, + bottom: cy + _radius + }; + + } + + // THREE POINTS: start, end, and perpendicular with r + const from = {x:self.from.x, y:self.from.y}; + const to = {x:self.to.x, y:self.to.y}; + const mid = { + x:(from.x+to.x)/2, + y:(from.y+to.y)/2 + }; + + const perpendicular = a-Math.TAU/4; + mid.x += Math.cos(perpendicular)*self.arc; + mid.y += Math.sin(perpendicular)*self.arc; + + // TEST ALL POINTS + + let left = Infinity; + let top = Infinity; + let right = -Infinity; + let bottom = -Infinity; + const points = [from, to, mid]; + for(let i=0; ix) left=x; + if(top>y) top=y; + if(rightMath.abs(bounds.bottom)) edgeConfig.arc = -bounds.top; @@ -164,7 +155,7 @@ function Ink(loopy){ // Add the edge! if(edgeConfig){ - var newEdge = loopy.model.addEdge(edgeConfig); + const newEdge = loopy.model.addEdge(edgeConfig); loopy.sidebar.edit(newEdge); } @@ -174,10 +165,10 @@ function Ink(loopy){ if(!startNode){ // Just roughly make a circle the size of the bounds of the circle - var bounds = _getBounds(self.strokeData); - var x = (bounds.left+bounds.right)/2; - var y = (bounds.top+bounds.bottom)/2; - var r = ((bounds.width/2)+(bounds.height/2))/2; + const bounds = _getBounds(self.strokeData); + const x = (bounds.left+bounds.right)/2; + const y = (bounds.top+bounds.bottom)/2; + let r = ((bounds.width/2)+(bounds.height/2))/2; // Circle can't be TOO smol if(r>15){ @@ -190,7 +181,7 @@ function Ink(loopy){ r = Ink.MINIMUM_RADIUS; // Make that node! - var newNode = loopy.model.addNode({ + const newNode = loopy.model.addNode({ x:x, y:y, radius:r @@ -208,14 +199,9 @@ function Ink(loopy){ }); subscribe("mouseclick",function(){ - - // ONLY WHEN EDITING w INK - if(self.loopy.mode!=Loopy.MODE_EDIT) return; - if(self.loopy.tool!=Loopy.TOOL_INK) return; - - // Reset + if(!areWeInkEditing()) return; self.reset(); - }); + const areWeInkEditing = () => self.loopy.mode===Loopy.MODE_EDIT && self.loopy.tool===Loopy.TOOL_INK; } \ No newline at end of file diff --git a/v1.1/js/Key.js b/2/js/Key.js similarity index 74% rename from v1.1/js/Key.js rename to 2/js/Key.js index 17a184f..b2e81bc 100644 --- a/v1.1/js/Key.js +++ b/2/js/Key.js @@ -1,15 +1,16 @@ (function(exports){ // Singleton - var Key = {}; + const Key = {}; exports.Key = Key; // Keycodes to words mapping - var KEY_CODES = { + const KEY_CODES = { 17: "control", 91: "control", // macs 13: "enter", // enter + 46: "delete", // TODO: Standardize the NAMING across files?!?! 78: "ink", // Pe(n)cil @@ -24,19 +25,24 @@ // TODO: cursors stay when click button? orrrrr switch over to fake-cursor. Key.onKeyDown = function(event){ if(window.loopy && loopy.modal && loopy.modal.isShowing) return; - var code = KEY_CODES[event.keyCode]; + // noinspection JSDeprecatedSymbols + const code = KEY_CODES[event.keyCode]; + //console.log(event.keyCode, event.code, event.key,event.charCode); + if(!code) return; Key[code] = true; publish("key/"+code); event.stopPropagation(); event.preventDefault(); - } + }; Key.onKeyUp = function(event){ if(window.loopy && loopy.modal && loopy.modal.isShowing) return; - var code = KEY_CODES[event.keyCode]; + // noinspection JSDeprecatedSymbols + const code = KEY_CODES[event.keyCode]; + if(!code) return; Key[code] = false; event.stopPropagation(); event.preventDefault(); - } + }; window.addEventListener("keydown",Key.onKeyDown,false); window.addEventListener("keyup",Key.onKeyUp,false); diff --git a/v1/js/Label.js b/2/js/Label.js similarity index 51% rename from v1/js/Label.js rename to 2/js/Label.js index a2cf176..1d51894 100644 --- a/v1/js/Label.js +++ b/2/js/Label.js @@ -3,12 +3,21 @@ LABEL! **********************************/ - +Label.COLORS = { + "-1":"#000000", // black + 0: "#880000", // red + 1: "#885533", // orange + 2: "#888800", // yellow + 3: "#558800", // green + 4: "#446688", // blue + 5: "#664488", // purple +}; Label.FONTSIZE = 40; +Label._CLASS_ = "Label"; function Label(model, config){ - var self = this; + const self = this; self._CLASS_ = "Label"; // Mah Parents! @@ -17,23 +26,27 @@ function Label(model, config){ self.config = config; // Default values... - _configureProperties(self, config, { + const defaultProperties = { x: 0, - y: 0, - text: "..." - }); + y: 0, + }; + injectedDefaultProps(defaultProperties,objTypeToTypeIndex("label")); + _configureProperties(self, config, defaultProperties); // Draw - var _circleRadius = 0; self.draw = function(ctx){ + if(self.visibility===1 && self.loopy.mode===Loopy.MODE_PLAY) return; + // cursor: pointer if clickable + if(self.loopy.mode===Loopy.MODE_PLAY && self.href && self.isPointInLabel(Mouse.x, Mouse.y)) Mouse.showCursor("pointer"); + // Retina - var x = self.x*2; - var y = self.y*2; + const x = self.x*2; + const y = self.y*2; // DRAW HIGHLIGHT??? - if(self.loopy.sidebar.currentPage.target == self){ - var bounds = self.getBounds(); + if(self.loopy.sidebar.currentPage.target === self){ + const bounds = self.getBounds(); ctx.save(); ctx.scale(2,2); // RETINA ctx.beginPath(); @@ -51,13 +64,13 @@ function Label(model, config){ ctx.font = "100 "+Label.FONTSIZE+"px sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; - ctx.fillStyle = "#000"; + ctx.fillStyle = Label.COLORS[self.textColor]; // ugh new lines are a PAIN. - var lines = self.breakText(); + const lines = self.breakText(); ctx.translate(0, -(Label.FONTSIZE*lines.length)/2); - for(var i=0; in.label==="autoplay"||n.label==="autostart"); + for(let node of autoplayNodes){ + node.takeSignal({ + delta: 0.33, + color:node.hue + }); + } }else{ publish("model/reset"); } // Edit mode! - if(mode==Loopy.MODE_EDIT){ + if(mode===Loopy.MODE_EDIT){ self.showPlayTutorial = false; // donezo self.wobbleControls = -1; // donezo self.sidebar.showPage("Edit"); @@ -140,8 +149,8 @@ function Loopy(config){ }); subscribe("export/file", function(){ - var element = document.createElement('a'); - element.setAttribute('href', 'data:text/plain;charset=utf-8,' + self.model.serialize()); + const element = document.createElement('a'); + element.setAttribute('href', 'data:application/octet-stream;base64,' + binToB64(serializeToBinary())); element.setAttribute('download', "system_model.loopy"); element.style.display = 'none'; @@ -151,18 +160,28 @@ function Loopy(config){ document.body.removeChild(element); }); + subscribe("export/json", function(){ + const element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + serializeToHumanReadableJson()); + element.setAttribute('download', "system_model.loopy.json"); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + }); subscribe("import/file", function(){ let input = document.createElement('input'); input.type = 'file'; input.onchange = e => { - var file = e.target.files[0]; - var reader = new FileReader(); - reader.readAsText(file,'UTF-8'); - reader.onload = readerEvent => { - var content = readerEvent.target.result; - self.model.deserialize(content); - } + // noinspection JSUnresolvedVariable + const file = e.target.files[0]; + const reader = new FileReader(); + reader.readAsArrayBuffer(file); + reader.onload = readerEvent => loopy.model.importModel(deserializeFromArrayBuffer(readerEvent.target.result)); }; input.click(); }); @@ -170,34 +189,39 @@ function Loopy(config){ self.saveToURL = function(embed){ // Create link - var dataString = self.model.serialize(); - var uri = dataString; // encodeURIComponent(dataString); - var base = window.location.origin + window.location.pathname; - var historyLink = base+"?data="+uri; - var link; - if(embed){ - link = base+"?embed=1&data="+uri; - }else{ - link = historyLink; - } + const uri = serializeToUrl(embed); + const base = window.location.origin + window.location.pathname; + let historyLink = base+"?"+uri; // NO LONGER DIRTY! self.dirty = false; // PUSH TO HISTORY + try{ window.history.replaceState(null, null, historyLink); + } catch(e){ + location.hash = uri; + historyLink = base+"#"+uri; + } - return link; + return historyLink; }; // "BLANK START" DATA: - var _blankData = "[[[1,403,223,1,%22something%22,4],[2,405,382,1,%22something%2520else%22,5]],[[2,1,94,-1,0],[1,2,89,1,0]],[[609,311,%22need%2520ideas%2520on%2520what%2520to%250Asimulate%253F%2520how%2520about%253A%250A%250A%25E3%2583%25BBtechnology%250A%25E3%2583%25BBenvironment%250A%25E3%2583%25BBeconomics%250A%25E3%2583%25BBbusiness%250A%25E3%2583%25BBpolitics%250A%25E3%2583%25BBculture%250A%25E3%2583%25BBpsychology%250A%250Aor%2520better%2520yet%252C%2520a%250A*combination*%2520of%250Athose%2520systems.%250Ahappy%2520modeling!%22]],2%5D"; + const _blankData = "[[[1,403,223,1,%22something%22,4],[2,405,382,1,%22something%2520else%22,5]],[[2,1,94,-1,0],[1,2,89,1,0]],[[609,311,%22need%2520ideas%2520on%2520what%2520to%250Asimulate%253F%2520how%2520about%253A%250A%250A%25E3%2583%25BBtechnology%250A%25E3%2583%25BBenvironment%250A%25E3%2583%25BBeconomics%250A%25E3%2583%25BBbusiness%250A%25E3%2583%25BBpolitics%250A%25E3%2583%25BBculture%250A%25E3%2583%25BBpsychology%250A%250Aor%2520better%2520yet%252C%2520a%250A*combination*%2520of%250Athose%2520systems.%250Ahappy%2520modeling!%22]],2%5D"; self.loadFromURL = function(){ - var data = _getParameterByName("data"); - if(!data) data=decodeURIComponent(_blankData); - self.model.deserialize(data); - }; + let remoteDataUrl = _getParameterByName("url"); + if(remoteDataUrl){ + fetch(remoteDataUrl).then(r=>r.arrayBuffer()).then(aB=>loopy.model.importModel(deserializeFromArrayBuffer(aB))); + } else { + let data = _getParameterByName("data"); + if(!data) data=location.href.split("?")[1]; + if(!data) data=location.href.split("#")[1]; + if(!data) data=decodeURIComponent(_blankData); + loopy.model.importModel(deserializeFromUrl(data)); + } + }; /////////////////////////// @@ -206,14 +230,19 @@ function Loopy(config){ self.init(); + // Play Controls + self.playbar = new PlayControls(self); + self.playbar.showPage("Editor"); // start here + if(self.embedded){ // Hide all that UI self.toolbar.dom.style.display = "none"; self.sidebar.dom.style.display = "none"; + document.getElementById("sidebarSwitch").style.display = "none"; // If *NO UI AT ALL* - var noUI = !!parseInt(_getParameterByName("no_ui")); // force to Boolean + const noUI = !!parseInt(_getParameterByName("no_ui")); // force to Boolean if(noUI){ _PADDING_BOTTOM = _PADDING; self.playbar.dom.style.display = "none"; @@ -234,52 +263,16 @@ function Loopy(config){ self.setMode(Loopy.MODE_PLAY); // Also, HACK: auto signal - var signal = _getParameterByName("signal"); + let signal = _getParameterByName("signal"); if(signal){ signal = JSON.parse(signal); - var node = self.model.getNode(signal[0]); + const node = self.model.getNode(signal[0]); node.takeSignal({ - delta: signal[1]*0.33 + delta: signal[1]*0.33, + color:node.hue }); } - }else{ - - // Center all the nodes & labels - - // If no nodes & no labels, forget it. - if(self.model.nodes.length>0 || self.model.labels.length>0){ - - // Get bounds of ALL objects... - var bounds = self.model.getBounds(); - var left = bounds.left; - var top = bounds.top; - var right = bounds.right; - var bottom = bounds.bottom; - - // Re-center! - var canvasses = document.getElementById("canvasses"); - var cx = (left+right)/2; - var cy = (top+bottom)/2; - var offsetX = (canvasses.clientWidth+50)/2 - cx; - var offsetY = (canvasses.clientHeight-80)/2 - cy; - - // MOVE ALL NODES - for(var i=0;i died + 7: "rgba(0,0,0,.3)" // node settings +}; + + +LoopyNode.DEFAULT_RADIUS = 60; +LoopyNode._CLASS_ = "Node"; + +function LoopyNode(model, config){ + + const self = this; + self._CLASS_ = "Node"; + + // Mah Parents! + self.loopy = model.loopy; + self.model = model; + self.config = config; + + // Default values... + const defaultProperties = { + radius: LoopyNode.DEFAULT_RADIUS, + }; + injectedDefaultProps(defaultProperties,objTypeToTypeIndex("node")); + _configureProperties(self, config, defaultProperties); + // Value: from 0 to 1 + self.value = self.init; + // TODO: ACTUALLY VISUALIZE AN INFINITE RANGE + self.bound = function(){ // bound ONLY when changing value. + /*var buffer = 1.2; + if(self.value<-buffer) self.value=-buffer; + if(self.value>1+buffer) self.value=1+buffer;*/ + }; + + // MOUSE. + let _controlsVisible = false; + let _controlsAlpha = 0; + let _controlsDirection = 0; + let _controlsSelected = false; + let _controlsPressed = false; + const _listenerMouseMove = subscribe("mousemove", function(){ + + // ONLY WHEN PLAYING + if(self.loopy.mode!==Loopy.MODE_PLAY) return; + + // If moused over this, show it, or not. + _controlsSelected = self.isPointInNode(Mouse.x, Mouse.y); + if(_controlsSelected){ + _controlsVisible = true; + self.loopy.showPlayTutorial = false; + _controlsDirection = (Mouse.y0?0.33:-0.33, + color: signal.color, + age: signal.age, + vital:signal.vital, + }; + + // random half filtering + if(edge.filter===5 && Math.random()<.5) continue; + // vital filtering + if(edge.filter===1 && signal.vital) continue; + if(edge.filter===2 && !signal.vital) continue; + if(edge.filter===2 && signal.delta>0) continue; + if(edge.filter===3 && !signal.vital) continue; + if(edge.filter===3 && signal.delta<0) continue; + if(edge.filter===4 && !signal.vital) continue; + if(edge.filter>=2 && edge.filter<=4) newSignal.vital = false; + // vital emitting + if(edge.quantitative===2 || newSignal.vital) { + newSignal.vital=true; + edge.addSignal(newSignal); + continue; + } + if(edge.to.died) continue; + + // Sign constraint filtering + if(edge.signBehavior===2 && signal.delta>0) continue; + if(edge.signBehavior===3 && signal.delta<0) continue; + + // vital banalized emitting + if(edge.filter>=2 && edge.filter<=4) { + edge.addSignal(newSignal); + continue; + } + + // Color constraint filtering + if(loopy.colorLogic===1 && edge.edgeFilterColor!== -1 && edge.edgeFilterColor !== signal.color) continue; + + if(loopy.colorLogic===1 && self.hue !== signal.color) { + if(edge.quantitative===0) edge.addSignal(newSignal); + if(edge.quantitative===1) quantitativeAcceptingEdges.push(edge); + continue; + } + + // Threshold filtering + if(self.value0) continue; + if(self.value>self.underflow && signal.delta<0) continue; + + if(edge.quantitative===0) edge.addSignal(newSignal); + if(edge.quantitative===1) quantitativeAcceptingEdges.push(edge); + } + // Quantitative handling + + if(quantitativeAcceptingEdges.length){ + let delta; + if( (loopy.colorLogic===1 && self.hue !== signal.color) + || (self.value>=self.overflow && self.value<=self.underflow) + ) delta = signal.delta; + if(loopy.colorLogic===0 || self.hue === signal.color){ + if(self.value>self.overflow) delta = (self.value-self.overflow)*self.size; + else if(self.value1) self.value = 1; + }; + + self.takeSignal = function(signal,fromEdge=undefined){ + if(loopy.colorLogic && self.foreignColor && signal.color!==self.hue) return; // drop signal + if(signal.vital && signal.delta>0) return self.live(signal); + if(signal.vital && signal.delta<0) return self.die(signal); + if(self.died) return; + if(loopy.colorLogic && fromEdge && fromEdge.edgeTargetColor===-3) return self.hue = signal.color; + if(!self.deltaPool) self.deltaPool=0; + if(!self.aggregate) self.aggregate = 0; + + if(self.hue === signal.color || loopy.colorLogic===0){ + self.value += signal.delta/self.size; + self.deltaPool += signal.delta/self.size; + // Animation + // _offsetVel += 0.08 * (signal.delta/Math.abs(signal.delta)); + if(signal.delta>0) _offsetVel -= 6 ; + if(signal.delta<0) _offsetVel += 6 ; + + // Implode ? + if((self.explode === -1 || self.explode === 2) && self.value<0){ + self.value = 0; + return self.die(signal); + } + // Explode ? + if((self.explode === 1 || self.explode === 2) && self.value>1) { + self.value = 1; + return self.die(signal); + } + + if(self.aggregate) return; + } + self.lastSignalAge = signal.age; + self.reseted = false; + + if(loopy.colorLogic===0 || self.hue === signal.color){ + self.valueBeforeAggregationPool = self.value - signal.delta/self.size; + } + if(loopy.colorLogic===1 && self.hue !== signal.color){ + const newSignal = {delta:signal.delta,age:signal.age,color:signal.color,vital:signal.vital}; + return self.sendSignal(newSignal); + } + + const signalSpeedRatio = 8 / Math.pow(2,self.loopy.signalSpeed); + const aggregateFunc = () => { + if(self.loopy.mode===Loopy.MODE_PLAY && !self.reseted){ + const newSignal = { + delta:self.deltaPool*self.size, + age:self.lastSignalAge, + color:signal.color, + vital:signal.vital + }; + self.sendSignal(newSignal); + } + self.aggregate=false; + self.deltaPool=0; + }; + + if(self.aggregationLatency){ + self.aggregate = setTimeout( aggregateFunc,1000 * self.aggregationLatency * signalSpeedRatio); + self.aggregateStartTime = Date.now(); + } else aggregateFunc(); + }; + + + ////////////////////////////////////// + // UPDATE & DRAW ///////////////////// + ////////////////////////////////////// + + // Update! + let _offset = 0; + let _offsetGoto = 0; + let _offsetVel = 0; + let _offsetAcc = 0; + let _offsetDamp = 0.3; + let _offsetHookes = 0.8; + self.update = function(){ //(speed) + + // When actually playing the simulation... + const _isPlaying = (self.loopy.mode===Loopy.MODE_PLAY); + + // Otherwise, value = initValue exactly + if(self.loopy.mode===Loopy.MODE_EDIT){ + self.value = self.init; + } + + // Cursor! + if(_controlsSelected) Mouse.showCursor("pointer"); + + // Keep value within bounds! + self.bound(); + + // Visually & vertically bump the node + const gotoAlpha = (_controlsVisible || self.loopy.showPlayTutorial) ? 1 : 0; + _controlsAlpha = _controlsAlpha*0.5 + gotoAlpha*0.5; + if(_isPlaying && _controlsPressed){ + _offsetGoto = -_controlsDirection*20; // by 20 pixels + // _offsetGoto = _controlsDirection*0.2; // by scale +/- 0.1 + }else{ + _offsetGoto = 0; + } + _offset += _offsetVel; + if(_offset>40) _offset=40; + if(_offset<-40) _offset=-40; + _offsetVel += _offsetAcc; + _offsetVel *= _offsetDamp; + _offsetAcc = (_offsetGoto-_offset)*_offsetHookes; + + }; + + // Draw + let _circleRadius = 0; + self.draw = function(ctx){ + + if(self.loopy.mode===Loopy.MODE_PLAY && self.label === "autoplay") return; + + // Retina + const x = self.x*2; + const y = self.y*2; + const r = self.radius*2; + const color = LoopyNode.COLORS[self.hue]; + + // Translate! + ctx.save(); + ctx.translate(x,y+_offset); + + // DRAW HIGHLIGHT??? + if(self.loopy.sidebar.currentPage.target === self){ + ctx.beginPath(); + ctx.arc(0, 0, r+40, 0, Math.TAU, false); + ctx.fillStyle = HIGHLIGHT_COLOR; + ctx.fill(); + } + + // White-gray bubble with colored border + if(self.foreignColor){ + ctx.beginPath(); + ctx.arc(0, 0, r+8, 0, Math.TAU, false); + ctx.fillStyle = "#fff"; + ctx.fill(); + ctx.lineWidth = 6; + ctx.strokeStyle = color; + ctx.stroke(); + } + ctx.beginPath(); + ctx.arc(0, 0, r-2, 0, Math.TAU, false); + ctx.fillStyle = "#fff"; + ctx.fill(); + ctx.lineWidth = 6; + ctx.strokeStyle = color; + ctx.stroke(); + + // Circle radius + // var _circleRadiusGoto = r*(self.value+1); + // _circleRadius = _circleRadius*0.75 + _circleRadiusGoto*0.25; + + // RADIUS IS (ATAN) of VALUE?!?!?! + /* + let _r = Math.atan(self.value*5); + _r = _r/(Math.PI/2); + _r = (_r+1)/2; + */ + + // INFINITE RANGE FOR RADIUS + // linear from 0 to 1, asymptotic otherwise. + let _value; + if(self.value>=0 && self.value<=1){ + // (0,1) -> (0.1, 0.9) + _value = 0.1 + 0.8*self.value; + }else{ + if(self.value<0){ + // asymptotically approach 0, starting at 0.1 + _value = (1/(Math.abs(self.value)+1))*0.1; + } + if(self.value>1){ + // asymptotically approach 1, starting at 0.9 + _value = 1 - (1/self.value)*0.1; + } + } + + // Colored bubble + ctx.beginPath(); + const _circleRadiusGoto = r*_value; // radius + _circleRadius = _circleRadius*0.8 + _circleRadiusGoto*0.2; + ctx.arc(0, 0, _circleRadius, 0, Math.TAU, false); + ctx.fillStyle = color; + ctx.fill(); + + if(self.overflow>0){ + const arrow = (angle)=>{ + ctx.save(); + ctx.beginPath(); + const oR = r*self.overflow; + const size = 1; + radialMove(ctx,angle,oR,size,-0.05,0); + radialLine(ctx,angle,oR,size,0,0.05); + radialLine(ctx,angle,oR,size,0.05,0); + ctx.lineWidth = 2; + ctx.strokeStyle = Label.COLORS[self.hue];//LoopyNode.COLORS[7]; + ctx.stroke(); + ctx.restore(); + } + for(let a = 0; a < 2*Math.PI;a+=Math.PI*0.125) arrow(a); + } + if(self.underflow<1){ + const arrow = (angle)=>{ + ctx.save(); + ctx.beginPath(); + const oR = r*self.underflow; + const size = 1; + radialMove(ctx,angle,oR,size,-0.05,0); + radialLine(ctx,angle,oR,size,0,-0.05); + radialLine(ctx,angle,oR,size,0.05,0); + ctx.lineWidth = 2; + ctx.strokeStyle = Label.COLORS[self.hue];//LoopyNode.COLORS[7]; + ctx.stroke(); + ctx.restore(); + } + for(let a = -Math.PI*0.125/2; a < 2*Math.PI;a+=Math.PI*0.125) arrow(a); + } + if(self.aggregationLatency>0){ + // show aggregationLatency visual + ctx.save(); + ctx.beginPath(); + const size = 1.8; + ctx.moveTo(0,-r); + ctx.lineTo(-0.05*r*size,-r*(1-0.01*size)); + ctx.lineTo(-0.05*r*size,-r*(1+0.05*size)); + ctx.lineTo(-0.1*r*size,-r*(1+0.05*size)); + ctx.lineTo(-0.1*r*size,-r*(1+0.1*size)); + ctx.lineTo(0.1*r*size,-r*(1+0.1*size)); + ctx.lineTo(0.1*r*size,-r*(1+0.05*size)); + ctx.lineTo(0.05*r*size,-r*(1+0.05*size)); + ctx.lineTo(0.05*r*size,-r*(1-0.01*size)); + ctx.fillStyle = color; + ctx.fill(); + + const chronoPart = (baseAngle,size)=>{ + ctx.beginPath(); + const line = (rx,ry)=>radialLine(ctx,baseAngle,r,size,rx,ry); + ctx.moveTo(Math.cos(baseAngle)*r,Math.sin(baseAngle)*r); + const xMax = .075; + line(-xMax,0) + line(-xMax,.05) + line(-(xMax-.025),.075) + line((xMax-.025),.075) + line(xMax,.05) + line(xMax,0) + ctx.fillStyle = color; + ctx.fill(); + } + chronoPart(Math.PI*-0.25,size*0.75); + chronoPart(Math.PI*-0.75,size*0.75); + + ctx.beginPath(); + // actual aggregation timer + const signalSpeedRatio = 8 / Math.pow(2,self.loopy.signalSpeed); + const timerLength = r*4/5; + const timeRatio = self.aggregate?(Date.now()-self.aggregateStartTime)/(1000 * self.aggregationLatency * signalSpeedRatio):0; + const timerAngle = Math.PI*(-0.5+2*timeRatio); + ctx.moveTo(Math.cos(timerAngle)*timerLength,Math.sin(timerAngle)*timerLength); + ctx.lineTo(0,0); + // config aggregation + const directions = { + 0.1:Math.PI*-0.44, + 0.2:Math.PI*-0.33, + 0.4:Math.PI*-0.11, + 0.8:Math.PI*0.11, + 1.6:Math.PI*0.33, + 3.2:Math.PI*0.7, + 6.4:Math.PI*1.25, + }; + const clockHandLength = r*2/3; + const clockHandAngle = directions[self.aggregationLatency]; + ctx.lineTo(Math.cos(clockHandAngle)*clockHandLength,Math.sin(clockHandAngle)*clockHandLength); + + ctx.lineWidth = 6; + ctx.strokeStyle = Label.COLORS[self.hue];//LoopyNode.COLORS[7]; + ctx.stroke(); + ctx.restore(); + + } + if(self.explode === -1 || self.explode === 2){ + // show this node can implode + const line = (angle)=>{ + ctx.save(); + ctx.beginPath(); + const size = 1; + radialMove(ctx,angle,r*0.1,size,0,0); + radialLine(ctx,angle,r*0.33,size,0,0); + ctx.lineWidth = 16; + ctx.strokeStyle = Label.COLORS[self.hue];//LoopyNode.COLORS[7]; + ctx.stroke(); + ctx.restore(); + } + for(let a = 0; a < 2*Math.PI;a+=Math.PI*0.5) line(a); + } + if(self.explode === 1 || self.explode === 2){ + // show this node can explode + const line = (angle)=>{ + ctx.save(); + ctx.beginPath(); + const size = 1; + radialMove(ctx,angle,r*1.25,size,0,0); + radialLine(ctx,angle,r*1.5,size,0,0); + ctx.lineWidth = 8; + ctx.strokeStyle = Label.COLORS[self.hue];//LoopyNode.COLORS[7]; + ctx.stroke(); + ctx.restore(); + } + for(let a = 0; a < 2*Math.PI;a+=Math.PI*0.25) line(a); + } + + // Text! + if(self.label){ + let fontsize = 40; + ctx.font = "normal "+fontsize+"px sans-serif"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillStyle = "#000"; + let width = ctx.measureText(self.label).width; + + while(width > r*2 - 30){// - 30){ // -30 for buffer. HACK: HARD-CODED. + fontsize -= 1; + ctx.font = "normal "+fontsize+"px sans-serif"; + width = ctx.measureText(self.label).width; + } + ctx.fillStyle = "rgba(100%,100%,100%,.15)"; + const padding = 3; + for(let x=-padding;x<=padding;x++)for(let y=-padding;y<=padding;y++) ctx.fillText(self.label, x, y); + ctx.fillStyle = "#000"; + ctx.fillText(self.label, 0, 0); + } + + // WOBBLE CONTROLS + const cl = 40; + let cy = 0; + if(self.loopy.showPlayTutorial && self.loopy.wobbleControls>0){ + const wobble = self.loopy.wobbleControls*(Math.TAU/30); + cy = Math.abs(Math.sin(wobble))*10; + } + + // Controls! + ctx.globalAlpha = _controlsAlpha; + ctx.strokeStyle = "rgba(0,0,0,0.8)"; + // top arrow + ctx.beginPath(); + ctx.moveTo(-cl,-cy-cl); + ctx.lineTo(0,-cy-cl*2); + ctx.lineTo(cl,-cy-cl); + ctx.lineWidth = (_controlsDirection>0) ? 10: 3; + if(self.loopy.showPlayTutorial) ctx.lineWidth=6; + ctx.stroke(); + // bottom arrow + ctx.beginPath(); + ctx.moveTo(-cl,cy+cl); + ctx.lineTo(0,cy+cl*2); + ctx.lineTo(cl,cy+cl); + ctx.lineWidth = (_controlsDirection<0) ? 10: 3; + if(self.loopy.showPlayTutorial) ctx.lineWidth=6; + ctx.stroke(); + + // Restore + ctx.restore(); + + }; + + ////////////////////////////////////// + // KILL NODE ///////////////////////// + ////////////////////////////////////// + + self.kill = function(){ + + // Kill Listeners! + unsubscribe("mousemove",_listenerMouseMove); + unsubscribe("mousedown",_listenerMouseDown); + unsubscribe("mouseup",_listenerMouseUp); + unsubscribe("model/reset",_listenerReset); + + // Remove from parent! + model.removeNode(self); + + // Killed! + publish("kill",[self]); + + }; + + ////////////////////////////////////// + // NODE DIE ////////////////////////// + ////////////////////////////////////// + + self.die = function(signal){ + if(!self.died && self.loopy.mode===Loopy.MODE_PLAY) self.sendSignal({delta:-.33,color:signal?signal.color:self.hue,vital:true}); + self.died=true; + if(self.hue!==6) self.oldhue = self.hue; + self.hue=6; + publish("died",[self]); + }; + self.live = function(signal){ + if(self.died && self.loopy.mode===Loopy.MODE_PLAY) self.sendSignal({delta:.33,color:signal?signal.color:self.hue,vital:true}); + self.died=false; + self.hue = typeof self.oldhue !== 'undefined'?self.oldhue:self.hue; + publish("live",[self]); + }; + + ////////////////////////////////////// + // HELPER METHODS //////////////////// + ////////////////////////////////////// + + self.isPointInNode = function(x, y, buffer){ + buffer = buffer || 0; + return _isPointInCircle(x, y, self.x, self.y, self.radius+buffer); + }; + + self.getBoundingBox = function(){ + return { + left: self.x - self.radius, + top: self.y - self.radius, + right: self.x + self.radius, + bottom: self.y + self.radius + }; + }; +} +function radialLine (ctx,baseAngle,baseR,size,rx,ry){ + ctx.lineTo(Math.cos(baseAngle+rx*size)*baseR*(1+ry*size),Math.sin(baseAngle+rx*size)*baseR*(1+ry*size)); +} +function radialMove (ctx,baseAngle,baseR,size,rx,ry){ + ctx.moveTo(Math.cos(baseAngle+rx*size)*baseR*(1+ry*size),Math.sin(baseAngle+rx*size)*baseR*(1+ry*size)); +} diff --git a/v1.1/js/Modal.js b/2/js/Modal.js similarity index 62% rename from v1.1/js/Modal.js rename to 2/js/Modal.js index 0467d33..1c20ef9 100644 --- a/v1.1/js/Modal.js +++ b/2/js/Modal.js @@ -6,7 +6,7 @@ Use the same PAGE UI thing function Modal(loopy){ - var self = this; + const self = this; self.loopy = loopy; PageUI.call(self, document.getElementById("modal_page")); @@ -29,16 +29,16 @@ function Modal(loopy){ document.getElementById("modal_close").onclick = self.hide; // Show... what page? - subscribe("modal", function(pageName){ + subscribe("modal", function(pageName,opt=""){ self.show(); - var page = self.showPage(pageName); + const page = self.showPage(pageName); // Do something - if(page.onshow) page.onshow(); + if(page.onshow) page.onshow(opt); // Dimensions - var dom = document.getElementById("modal"); + const dom = document.getElementById("modal"); dom.style.width = self.currentPage.width+"px"; dom.style.height = self.currentPage.height+"px"; @@ -50,10 +50,10 @@ function Modal(loopy){ // Examples (function(){ - var page = new Page(); + const page = new Page(); page.width = 670; page.height = 570; - var iframe = page.addComponent(new ModalIframe({ + const iframe = page.addComponent(new ModalIframe({ page: page, src: "pages/examples/", width: 640, @@ -65,7 +65,7 @@ function Modal(loopy){ // How To (function(){ - var page = new Page(); + const page = new Page(); page.width = 530; page.height = 430; page.addComponent(new ModalIframe({ @@ -75,7 +75,7 @@ function Modal(loopy){ height: 350 })); - var label = document.createElement("div"); + const label = document.createElement("div"); label.style.fontSize = "18px"; label.style.marginTop = "6px"; label.style.color = "#777"; @@ -86,9 +86,26 @@ function Modal(loopy){ })(); + // doc + (function(){ + const page = new Page(); + page.width = 800; + page.height = 600; + page.addComponent(new ModalIframe({ + page: page, + src: "", + width: page.width-30, + height: page.height-50 + })); + page.onshow = (opt)=>{ + page.dom.querySelector("iframe").src = `pages/doc.html?${opt}`; + }; + self.addPage("doc", page); + })(); + // Credits (function(){ - var page = new Page(); + const page = new Page(); page.width = 690; page.height = 550; page.addComponent(new ModalIframe({ @@ -96,21 +113,50 @@ function Modal(loopy){ src: "pages/credits/", width: 660, height: 500 - })) + })); self.addPage("credits", page); })(); + // urlRemoteFile + (function(){ + const page = new Page(); + page.width = 500; + page.height = 155; + const desc = page.addComponent(new ComponentHTML({ + html: `Upload your .loopy.json or .loopy file into a website (with CORS header allowing ${location.host}) then add it url to this one :` + })); + desc.dom.style.fontSize = "15px"; + const output = page.addComponent(new ComponentOutput({})); + output.output(`${location.href.split('?')[0].split('#')[0]}?url=https://where_your_uploaded_file_is_located/your_file.loopy`); + + const label = document.createElement("div"); + label.style.fontSize = "15px"; + label.style.marginTop = "6px"; + const baseUrl = location.href.split('?')[0].split('#')[0]; + let path = baseUrl; + if(path[path.length-1]!=='/'){ + const pathParts = baseUrl.split('/'); + pathParts.pop(); + path = `${pathParts.join('/')}/`; + } + label.innerHTML = `Click here to view a working example.`; + page.dom.appendChild(label); + + + self.addPage("urlRemoteFile", page); + })(); + // Save as link (function(){ - var page = new Page(); + const page = new Page(); page.width = 500; page.height = 155; page.addComponent(new ComponentHTML({ html: "copy your link:" })); - var output = page.addComponent(new ComponentOutput({})); + const output = page.addComponent(new ComponentOutput({})); - var label = document.createElement("div"); + const label = document.createElement("div"); label.style.textAlign = "right"; label.style.fontSize = "15px"; label.style.marginTop = "6px"; @@ -119,7 +165,7 @@ function Modal(loopy){ page.dom.appendChild(label); // chars left... - var chars = document.createElement("div"); + const chars = document.createElement("div"); chars.style.textAlign = "right"; chars.style.fontSize = "15px"; chars.style.marginTop = "3px"; @@ -130,12 +176,12 @@ function Modal(loopy){ page.onshow = function(){ // Copy-able link - var link = loopy.saveToURL(); + const link = loopy.saveToURL(); output.output(link); output.dom.select(); // Chars left - var html = link.length+" / 2048 characters"; + let html = link.length+" / 2048 characters"; if(link.length>2048){ html += " - MAY BE TOO LONG FOR MOST BROWSERS"; } @@ -151,66 +197,75 @@ function Modal(loopy){ // Embed (function(){ - var page = new Page(); + const page = new Page(); page.width = 700; page.height = 500; // ON UPDATE DIMENSIONS - var iframeSRC; - var _onUpdate = function(){ - var embedCode = ''; + let iframeSRC; + const _onUpdate = function(){ + iframeSRC = loopy.saveToURL(true); + const embedCode = ''; output.output(embedCode); + iframe.src = iframeSRC; }; // THE SHTUFF - var sidebar = document.createElement("div"); + const sidebar = document.createElement("div"); sidebar.style.width = "150px"; sidebar.style.height = "440px"; sidebar.style.float = "left"; page.dom.appendChild(sidebar); + //FIXME: dedup // Label - var label = document.createElement("div"); - label.innerHTML = "
PREVIEW →

"; + let label = document.createElement("div"); + label.style.marginTop = "10px"; + label.style.marginBottom = "20px"; + label.innerHTML = "PREVIEW →"; sidebar.appendChild(label); + //FIXME: dedup // Label 2 - var label = document.createElement("div"); + label = document.createElement("div"); label.style.fontSize = "15px"; label.innerHTML = "what size do you want your embed to be?"; sidebar.appendChild(label); // Size! - var width = _createNumberInput(_onUpdate); + const width = _createNumberInput(_onUpdate); sidebar.appendChild(width.dom); - var label = document.createElement("div"); + //FIXME: dedup + label = document.createElement("div"); label.style.display = "inline-block"; label.style.fontSize = "15px"; label.innerHTML = " × "; sidebar.appendChild(label); - var height = _createNumberInput(_onUpdate); + const height = _createNumberInput(_onUpdate); sidebar.appendChild(height.dom); + //FIXME: dedup // Label 3 - var label = document.createElement("div"); + label = document.createElement("div"); label.style.fontSize = "15px"; label.innerHTML = "

copy this code into your website's html:"; sidebar.appendChild(label); // Output! - var output = new ComponentOutput({}); + const output = new ComponentOutput({}); output.dom.style.fontSize = "12px"; sidebar.appendChild(output.dom); + //FIXME: dedup // Label 3 - var label = document.createElement("div"); + label = document.createElement("div"); label.style.fontSize = "15px"; label.style.textAlign = "right"; label.innerHTML = "

(note: the REMIX button lets someone else, well, remix your model! don't worry, it'll just be a copy, it won't affect the original.)"; sidebar.appendChild(label); // IFRAME - var iframe = page.addComponent(new ModalIframe({ + const iframe = page.addComponent(new ModalIframe({ page: page, manual: true, src: "", @@ -243,7 +298,7 @@ function Modal(loopy){ // GIF (function(){ - var page = new Page(); + const page = new Page(); page.width = 530; page.height = 400; page.addComponent(new ModalIframe({ @@ -251,7 +306,7 @@ function Modal(loopy){ src: "pages/gif.html", width: 500, height: 350 - })) + })); self.addPage("save_gif", page); })(); @@ -259,10 +314,10 @@ function Modal(loopy){ function ModalIframe(config){ - var self = this; + const self = this; // IFRAME - var iframe = document.createElement("iframe"); + const iframe = document.createElement("iframe"); self.dom = iframe; iframe.width = config.width; iframe.height = config.height; diff --git a/2/js/Model.js b/2/js/Model.js new file mode 100644 index 0000000..b4c0463 --- /dev/null +++ b/2/js/Model.js @@ -0,0 +1,678 @@ +/********************************** + +MODEL! + +**********************************/ + +function Model(loopy){ + + const self = this; + self.loopy = loopy; + + // Properties + self.speed = 0.05; + + // Create canvas & context + const canvas = _createCanvas(); + const ctx = canvas.getContext("2d"); + self.canvas = canvas; + self.context = ctx; + + + + /////////////////// + // NODES ////////// + /////////////////// + + // Nodes + self.nodes = []; + self.getNode = function(id){ + return self.nodes[id]; + }; + + // Remove LoopyNode + self.addNode = function(config){ + + // Model's been changed! + publish("model/changed"); + + // Add LoopyNode + const node = new LoopyNode(self,config); + self.nodes.push(node); + if(!node.id || node.id !== self.nodes.length-1) node.id = self.nodes.length-1; + applyInitialPropEffects(node); + self.update(); + return node; + + }; + + // Remove LoopyNode + self.removeNode = function(node){ + + // Model's been changed! + publish("model/changed"); + + // Remove from array + self.nodes.splice(self.nodes.indexOf(node),1); + self.nodes.forEach((n,i)=>n.id = i); + + // Remove all associated TO and FROM edges + for(let i=0; i0){ + drawCountdown = drawCountdownFull; + break; + } + } + + // DRAW??????? + drawCountdown--; + if(drawCountdown<=0) return; + + // Also only draw if last updated... + if(!_canvasDirty) return; + _canvasDirty = false; + + if(self.loopy.mode===Loopy.MODE_PLAY && loopy.cameraMode===0){ + self.smoothCameraMove(self.getBounds()); // 0.1 + } + if(self.loopy.mode===Loopy.MODE_PLAY && loopy.cameraMode===1){ + const bounds = self.getSignalsBounds(); + if(bounds.strict.weight>0) { + bounds.large.cx = bounds.strict.cx; + bounds.large.cy = bounds.strict.cy; + bounds.large.weight = bounds.strict.weight; + delete bounds.strict.cx; + delete bounds.strict.cy; + delete bounds.strict.weight; + self.smoothCameraMove(bounds.large,bounds.strict); // 0.1 + } + else self.smoothCameraMove(self.getBounds()); //0.02 + } + + // Clear! + ctx.clearRect(0,0,self.canvas.width,self.canvas.height); + + // Translate + ctx.save(); + applyZoomTransform(ctx); + + // Draw labels THEN edges THEN nodes + for(let i=0;i{ + self.clear(); + for(let key in newModel.globals)loopy[key] = newModel.globals[key]; + if(loopy.embed) loopy.embedded = 1; + applyInitialPropEffects(loopy); + // refresh sidebar + const globalEditPage = loopy.sidebar.pages[3]; + injectPropsLabelInSideBar(globalEditPage,objTypeToTypeIndex("loopy")); + + // import entities data. + newModel.nodes.forEach((n)=>self.addNode(n)); + newModel.edges.forEach((n)=>self.addEdge(n)); + newModel.labels.forEach((n)=>self.addLabel(n)); + //newModel.groups.forEach((n,i)=>self.addGroup(n)); + setTimeout(()=>{ + const need = self.getBounds(); + const available = document.getElementById("canvasses"); + if(need.left<0 || need.top<0 || need.right>available.clientWidth || need.bottom>available.clientHeight) self.center(true); + else self.center(false); + },0); // do it when loopy is fully load, else it's sheety + } + + self.clear = function(){ + + // Just kill ALL nodes. + while(self.nodes.length>0){ + self.nodes[0].kill(); + } + + // Just kill ALL labels. + while(self.labels.length>0){ + self.labels[0].kill(); + } + }; + + + //////////////////// + // HELPER METHODS // + //////////////////// + + self.getNodeByPoint = function(x,y,buffer){ + //var result; + for(let i=self.nodes.length-1; i>=0; i--){ // top-down + const node = self.nodes[i]; + if(node.isPointInNode(x,y,buffer)) return node; + } + return null; + }; + + //self.getEdgeByPoint = function(x, y, wholeArrow){ + self.getEdgeByPoint = function(x, y){ + // TODO: wholeArrow option? + //var result; + for(let i=self.edges.length-1; i>=0; i--){ // top-down + const edge = self.edges[i]; + if(edge.isPointOnLabel(x,y)) return edge; + } + return null; + }; + + self.getLabelByPoint = function(x, y){ + //var result; + for(let i=self.labels.length-1; i>=0; i--){ // top-down + const label = self.labels[i]; + if(label.isPointInLabel(x,y)) return label; + } + return null; + }; + + // Click to edit! + subscribe("mouseclick",function(){ + + // ONLY WHEN EDITING (and NOT erase) + if(self.loopy.mode!==Loopy.MODE_EDIT) return; + if(self.loopy.tool===Loopy.TOOL_ERASE) return; + + // Did you click on a node? If so, edit THAT node. + const clickedNode = self.getNodeByPoint(Mouse.x, Mouse.y); + if(clickedNode){ + loopy.sidebar.edit(clickedNode); + return; + } + + // Did you click on a label? If so, edit THAT label. + const clickedLabel = self.getLabelByPoint(Mouse.x, Mouse.y); + if(clickedLabel){ + loopy.sidebar.edit(clickedLabel); + return; + } + + // Did you click on an edge label? If so, edit THAT edge. + const clickedEdge = self.getEdgeByPoint(Mouse.x, Mouse.y); + if(clickedEdge){ + loopy.sidebar.edit(clickedEdge); + return; + } + + // If the tool LABEL? If so, TRY TO CREATE LABEL. + if(self.loopy.tool===Loopy.TOOL_LABEL){ + loopy.label.tryMakingLabel(); + return; + } + + // Otherwise, go to main Edit page. + loopy.sidebar.showPage("Edit"); + + }); + subscribe("mousewheel",function(mouse){ + // ONLY WHEN EDITING (or MODE_PLAY in freeCam) + if(self.loopy.mode===Loopy.MODE_EDIT || (self.loopy.mode===Loopy.MODE_PLAY && loopy.cameraMode===2)){ + const oldOffsetScale = loopy.offsetScale; + if(mouse.wheel<0) loopy.offsetScale*=1.1; + if(mouse.wheel>0) loopy.offsetScale*=0.9; + const old_m2M = mouseToMouse(mouse.x,mouse.y,oldOffsetScale,loopy.offsetX,loopy.offsetY); + const new_m2M = mouseToMouse(mouse.x,mouse.y,loopy.offsetScale,loopy.offsetX,loopy.offsetY); + loopy.offsetX += (new_m2M.x - old_m2M.x); + loopy.offsetY += (new_m2M.y - old_m2M.y); + } + }); + // Centering & Scaling + self.getSignalsBounds = function(includeInstant=true){ + let strictBounds = {}; + let largeBounds = {}; + for(let i=0; istrictBounds = mergeBounds(strictBounds,edge.getSignalBoundingBox(s))); + } + return {strict:strictBounds,large:largeBounds}; + }; + // Centering & Scaling + self.getBounds = function(visible=true){ + // If no nodes & no labels, forget it. + if(self.nodes.length===0 && self.labels.length===0) return; + + let bounds = {}; + // Get bounds of ALL objects... + const _testObjects = function(objects){ + for(let i=0; isize || bounds.right-bounds.left>size){ + addX -= bounds.left; + ratio = Math.min(ratio,size/(bounds.right-bounds.left)); + } + if(bounds.bottom>size || bounds.bottom-bounds.top>size){ + addY -= bounds.top; + ratio = Math.min(ratio,size/(bounds.bottom-bounds.top)); + } + self.nodes.forEach(n=>{n.x = (n.x+addX)*ratio;n.y = (n.y+addY)*ratio}); + self.labels.forEach(n=>{n.x = (n.x+addX)*ratio;n.y = (n.y+addY)*ratio}); + //self.groups.forEach(n=>{n.x = (n.x+addX)*ratio;n.y = (n.y+addY)*ratio}); + }; + function offsetToBounds(offset,fitBounds){ + const calc = (side,oppositeSide,offset,scale)=>{ + const c = (fitBounds[side]+fitBounds[oppositeSide])/2; + const sideDelta = fitBounds[side]-c; + return sideDelta/scale+c-offset-_PADDING/2; + } + return { + left: calc("left","right",offset.offsetX,offset.offsetScale), + right: calc("right","left",offset.offsetX,offset.offsetScale), + top: calc("top","bottom",offset.offsetY,offset.offsetScale), + bottom: calc("bottom","top",offset.offsetY,offset.offsetScale), + } + } + + self.smoothCameraMove = function(targetBounds,mustKeepInRangeBounds={},speedSetting = new SpeedSettings(),extraPadding=0){ + const old = {offsetScale:loopy.offsetScale, offsetX:loopy.offsetX,offsetY:loopy.offsetY}; + if(!self.olderOffset) self.olderOffset = old; + const fitBounds = fitToBounds(); + /** + * on tente de centrer sur le cx/cy du target + * si le targetbounds n'est pas entièrement inclu dans ce cadrage, on ajoute ce qui manque en maintenant le centre. + * Avec ça, on a notre cadrage idéal au quel on applique le ratio de vitesse. + * + * avec le cadrage pondéré vitesse, on regarde s'il y a un centre strict. + * Si oui, on recentre et on étend au cadrage strict à partir de l'actuel, en maintenant le centre. + * Si non, on étend au cadrage strict si nécessaire + * //NOP enfin, on élargie le cadrage d'extraPadding (en % si entre 0 et 1, en absolu si >1) + * + * C'est bon, adjugé ! + */ + + let targetOffset; + if(targetBounds.weight) targetOffset = scaleTo(targetBounds,fitBounds,true); + else targetOffset = scaleTo(targetBounds,fitBounds); + + const calcDelta = (offsetType,speedType)=>{ + let oldDelta = old[offsetType]-self.olderOffset[offsetType]; + const fullDelta = targetOffset[offsetType]-old[offsetType]; + if(offsetType=== "offsetScale" && Math.abs(fullDelta)<0.001) return 0; + if(offsetType=== "offsetScale" && Math.abs(oldDelta)<0.001) oldDelta = fullDelta>0?0.001:-0.001; + const idealDelta = fullDelta*speedType.speedMaxPercent; + let delta = idealDelta; + delta = oldDelta*speedType.inertia + delta*(1-speedType.inertia); + if(oldDelta>0){ + if(oldDelta+oldDelta*speedType.accelerationMaxdelta) delta = oldDelta-oldDelta*speedType.accelerationMax; + } else { + if(oldDelta-oldDelta*speedType.accelerationMaxdelta) delta = oldDelta+oldDelta*speedType.accelerationMax; + } + if(offsetType!== "offsetScale" && Math.abs(idealDelta)>1 && Math.abs(delta)<1) delta = idealDelta>0?1:-1; + if(offsetType!== "offsetScale" && Math.abs(fullDelta)<_PADDING/4) delta = 0; + return delta; + } + let instantTargetOffset = { + offsetX: old.offsetX + calcDelta("offsetX",speedSetting.translate), + offsetY: old.offsetY + calcDelta("offsetY",speedSetting.translate), + offsetScale: Math.abs(old.offsetScale + calcDelta("offsetScale",targetOffset.offsetScale>old.offsetScale?speedSetting.zoomIn:speedSetting.zoomOut)), + } + const instantTarget = offsetToBounds(instantTargetOffset, fitBounds); + // Does this include strict bounds ? + // if not, include them + let strictOffset; + if(mustKeepInRangeBounds.weight) strictOffset = scaleTo(mustKeepInRangeBounds,fitBounds,true); + else strictOffset = scaleTo(mustKeepInRangeBounds,fitBounds); + const strictBounds = offsetToBounds(strictOffset,fitBounds); + const mergedBounds = mergeBounds(strictBounds,instantTarget); + let mergedOffset; + if(mustKeepInRangeBounds.weight){ + mergedBounds.cx = mustKeepInRangeBounds.cx; + mergedBounds.cy = mustKeepInRangeBounds.cy; + mergedBounds.weight = mustKeepInRangeBounds.weight; + mergedOffset = scaleTo(mergedBounds,fitBounds,true); + } else mergedOffset = scaleTo(mergedBounds,fitBounds); + + loopy.offsetX = mergedOffset.offsetX; + loopy.offsetY = mergedOffset.offsetY; + loopy.offsetScale = mergedOffset.offsetScale; + self.olderOffset = old; + } + self.center = function(andScale){ + + // If no nodes & no labels, forget it. + if(self.nodes.length===0 && self.labels.length===0) return; + + // Get bounds of ALL objects... + const bounds = self.getBounds(); + const left = bounds.left; + const top = bounds.top; + const right = bounds.right; + const bottom = bounds.bottom; + + // Re-center! + const canvasses = document.getElementById("canvasses"); + const fitWidth = canvasses.clientWidth - _PADDING - _PADDING; + const fitHeight = canvasses.clientHeight - _PADDING_BOTTOM - _PADDING; + const cx = (left+right)/2; + const cy = (top+bottom)/2; + loopy.offsetX = (_PADDING+fitWidth)/2 - cx; + loopy.offsetY = (_PADDING+fitHeight)/2 - cy; + + // SCALE. + if(andScale){ + + const w = right-left; + const h = bottom-top; + + // Wider or taller than screen? + const modelRatio = w/h; + const screenRatio = fitWidth/fitHeight; + let scaleRatio; + if(modelRatio > screenRatio){ + // wider... + scaleRatio = fitWidth/w; + }else{ + // taller... + scaleRatio = fitHeight/h; + } + + // Loopy, then! + loopy.offsetScale = scaleRatio; + + } + + }; + +} +function offsetToRealOffset(scale,offsetX,offsetY) { + const canvasses = document.getElementById("canvasses"); + const CW = canvasses.clientWidth - _PADDING - _PADDING; + const CH = canvasses.clientHeight - _PADDING_BOTTOM - _PADDING; + //const tx = offsetX*2*scale + canvasses.clientWidth*(1 - scale) - _PADDING*(2 + scale) + let translateX = offsetX*2; + let translateY = offsetY*2; + translateX -= CW+_PADDING; + translateY -= CH+_PADDING; + translateX = scale*translateX; + translateY = scale*translateY; + translateX += CW+_PADDING; + translateY += CH+_PADDING; + if(loopy.embedded){ + translateX += _PADDING; // dunno why but this is needed + translateY += _PADDING; // dunno why but this is needed + } + return {scale,translateX,translateY}; +} +function applyZoomTransform(ctx){ + // Translate to center, (translate, scale, translate) to expand to size + const real = offsetToRealOffset(loopy.offsetScale,loopy.offsetX,loopy.offsetY); + //console.log(tx, ty); + ctx.setTransform(real.scale, 0, 0, real.scale, real.translateX, real.translateY); + +} +// camera misc +function fitToBounds() { + const canvasses = document.getElementById("canvasses"); + const fitWidth = canvasses.clientWidth - _PADDING - _PADDING; + const fitHeight = canvasses.clientHeight - _PADDING_BOTTOM - _PADDING; + return { + left: _PADDING, + top: _PADDING, + right: fitWidth+_PADDING, + bottom:fitHeight+_PADDING, + cx: (_PADDING+fitWidth)/2, + cy: (_PADDING+fitHeight)/2, + } +} +function calcWidthHeight(bounds) { + bounds.width = bounds.right-bounds.left; + bounds.height = bounds.bottom-bounds.top; +} +function calcCenteredWidthHeight(bounds) { + if(typeof bounds.cx === "undefined" || typeof bounds.cy === "undefined"){ + bounds.cx = (bounds.right+bounds.left)/2; + bounds.cy = (bounds.bottom+bounds.top)/2; + } + bounds.width = 2*Math.max(bounds.right-bounds.cx,bounds.cx-bounds.left); + bounds.height = 2*Math.max(bounds.bottom-bounds.cy,bounds.cy-bounds.top); +} +function scaleTo(makeTheseBounds,fitInTheseBounds,alignCenter=false){ + let scaleRatio; + calcWidthHeight(fitInTheseBounds); + if(alignCenter) calcCenteredWidthHeight(makeTheseBounds); + else calcWidthHeight(makeTheseBounds); + scaleRatio = Math.min(fitInTheseBounds.width/makeTheseBounds.width,fitInTheseBounds.height/makeTheseBounds.height); + const cx = (makeTheseBounds.right+makeTheseBounds.left)/2; + const cy = (makeTheseBounds.bottom+makeTheseBounds.top)/2; + return { + offsetX: fitInTheseBounds.cx - cx, + offsetY: fitInTheseBounds.cy - cy, + offsetScale: scaleRatio + } +} +function SpeedSettings(){ + const self = this; + self.translate = { + inertia:0.8, + speedMaxPercent:0.05, + accelerationMax: 2, + }; + self.zoomIn = { + inertia:0.8, + speedMaxPercent:0.03, + accelerationMax: 0.2, + }; + self.zoomOut = { + inertia:0.8, + speedMaxPercent:0.05, + accelerationMax: 2.5, + }; + return self; +} \ No newline at end of file diff --git a/2/js/Mouse.js b/2/js/Mouse.js new file mode 100644 index 0000000..c903f83 --- /dev/null +++ b/2/js/Mouse.js @@ -0,0 +1,78 @@ +window.Mouse = {}; +Mouse.init = function(target){ + + // Events! + const _onmousedown = function(){ + Mouse.moved = false; + Mouse.pressed = true; + Mouse.startedOnTarget = true; + publish("mousedown"); + }; + const _onmousewheel = function(event){ + publish("mousewheel",[event]); + }; + const _onmousemove = function(event){ + + const m = mouseToMouse(event.x,event.y,loopy.offsetScale,loopy.offsetX,loopy.offsetY); + // Mouse! + Mouse.x = m.x; + Mouse.y = m.y; + + Mouse.moved = true; + publish("mousemove"); + + }; + const _onmouseup = function(){ + Mouse.pressed = false; + if(Mouse.startedOnTarget){ + publish("mouseup"); + if(!Mouse.moved) publish("mouseclick"); + } + Mouse.moved = false; + Mouse.startedOnTarget = false; + }; + + // Add mouse & touch events! + _addMouseEvents(target, _onmousedown, _onmousemove, _onmouseup,_onmousewheel); + + // Cursor & Update + Mouse.target = target; + Mouse.showCursor = function(cursor){ + Mouse.target.style.cursor = cursor; + }; + Mouse.update = function(){ + Mouse.showCursor(""); + }; + +}; +function mouseToMouse(mx,my,scale,offsetX,offsetY){ + // DO THE INVERSE + const canvasses = document.getElementById("canvasses"); + let tx = 0; + let ty = 0; + const s = 1/scale; + const CW = canvasses.clientWidth - _PADDING - _PADDING; + const CH = canvasses.clientHeight - _PADDING_BOTTOM - _PADDING; + + if(loopy.embedded){ + tx -= _PADDING/2; // dunno why but this is needed + ty -= _PADDING/2; // dunno why but this is needed + } + + tx -= (CW+_PADDING)/2; + ty -= (CH+_PADDING)/2; + + tx = s*tx; + ty = s*ty; + + tx += (CW+_PADDING)/2; + ty += (CH+_PADDING)/2; + + tx -= offsetX; + ty -= offsetY; + + // Mutliply by Mouse vector + const x = mx*s + tx; + const y = my*s + ty; + return {x,y}; +} \ No newline at end of file diff --git a/v1.1/js/PageUI.js b/2/js/PageUI.js similarity index 84% rename from v1.1/js/PageUI.js rename to 2/js/PageUI.js index de5de69..cfc7ab4 100644 --- a/v1.1/js/PageUI.js +++ b/2/js/PageUI.js @@ -6,7 +6,7 @@ PAGE UI: to extend to Sidebar, Play Controls, Modal. function PageUI(dom){ - var self = this; + const self = this; self.dom = dom; self.pages = []; @@ -17,10 +17,10 @@ function PageUI(dom){ }; self.currentPage = null; self.showPage = function(id){ - var shownPage = null; - for(var i=0; ipublish("model/reset") }); + _addButton("right", {label: "Remix", icon: 3, onclick: ()=>window.open(loopy.saveToURL(),'_blank') }); }else{ - // Stop | Reset - - // STOP BUTTON - var buttonDOM = page.addComponent(new PlayButton({ - icon: 1, - label: "Stop", - onclick: function(){ - loopy.setMode(Loopy.MODE_EDIT); - } - })).dom; - buttonDOM.style.width = "100px"; - buttonDOM.style.left = "0px"; - buttonDOM.style.top = "0px"; - - // RESET BUTTON - var buttonDOM = page.addComponent(new PlayButton({ - icon: 2, - label: "Reset", - onclick: function(){ - publish("model/reset"); - } - })).dom; - buttonDOM.style.width = "100px"; - buttonDOM.style.right = "0px"; - buttonDOM.style.top = "0px"; - + _addButton("left", {label: "Stop", icon: 1, onclick: ()=>loopy.setMode(Loopy.MODE_EDIT) }); + _addButton("right", {label: "Reset", icon: 2, onclick: ()=>publish("model/reset") }); } // SPEED SLIDER - var speedSlider = page.addComponent(new PlaySlider({ + const speedSlider = page.addComponent(new PlaySlider({ value: loopy.signalSpeed, min:0, max:6, step:0.2, oninput: function(value){ @@ -125,9 +83,9 @@ function PlayControls(loopy){ function PlayButton(config){ - var self = this; + const self = this; - var label = "
" + const label = "
" + "
"+config.label+"
"; self.dom = _createButton(label, function(){ @@ -143,7 +101,7 @@ function PlayButton(config){ } function PlaySlider(config){ - var self = this; + const self = this; self.dom = document.createElement("div"); self.dom.style.bottom = "0px"; self.dom.style.position = "absolute"; @@ -151,27 +109,13 @@ function PlaySlider(config){ self.dom.style.height = "20px"; // Input - var input = document.createElement("input"); + const input = document.createElement("input"); input.setAttribute("class","play_slider"); self.dom.appendChild(input); // Slow & Fast Icons - var img = new Image(); - img.src = "css/icons/speed_slow.png"; - img.width = 20; - img.height = 15; - img.style.position = "absolute"; - img.style.left = "5px"; - img.style.top = "-2px"; - self.dom.appendChild(img); - var img = new Image(); - img.src = "css/icons/speed_fast.png"; - img.width = 20; - img.height = 15; - img.style.position = "absolute"; - img.style.right = "5px"; - img.style.top = "-2px"; - self.dom.appendChild(img); + self.dom.appendChild(domSpeedImg("css/icons/speed_slow.png","left")); + self.dom.appendChild(domSpeedImg("css/icons/speed_fast.png","right")); // Properties input.type = "range"; @@ -179,8 +123,19 @@ function PlaySlider(config){ input.step = config.step; input.min = config.min; input.max = config.max; - input.oninput = function(event){ + input.oninput = function(){ // (event) config.oninput(input.value); }; +} +function domSpeedImg(src,side) { + const img = new Image(); + img.src = src; + img.width = 20; + img.height = 15; + img.style.position = "absolute"; + img.style[side] = "5px"; + img.style.top = "-2px"; + return img; + } \ No newline at end of file diff --git a/2/js/Sidebar.js b/2/js/Sidebar.js new file mode 100644 index 0000000..168669b --- /dev/null +++ b/2/js/Sidebar.js @@ -0,0 +1,499 @@ +/********************************** + +SIDEBAR CODE + +**********************************/ + +function Sidebar(loopy){ + + const self = this; + PageUI.call(self, document.getElementById("sidebar")); + + const sideBarSwitch = document.createElement("div"); + sideBarSwitch.id = "sidebarSwitch"; + sideBarSwitch.innerHTML = '❯'; + sideBarSwitch.onclick = function(){ + const sidebar = self.dom; + const canvasses = document.getElementById("canvasses"); + let visible= false; + if(!sidebar.style.right || sidebar.style.right==="0px") visible = true; + if(visible) { + sidebar.style.right = '-300px'; + sideBarSwitch.innerHTML = '❮'; + canvasses.style.right = '0px'; + sideBarSwitch.style.right = '0px'; + } else { + sidebar.style.right = '0px'; + sideBarSwitch.innerHTML = '❯'; + canvasses.style.right = '300px'; + sideBarSwitch.style.right = '300px'; + } + publish("resize"); + } + self.dom.parentNode.appendChild(sideBarSwitch); + + // Edit + self.edit = function(object){ + self.showPage(object._CLASS_); + self.currentPage.edit(object); + }; + + // Go back to main when the thing you're editing is killed + subscribe("kill",function(object){ + if(self.currentPage.target===object){ + self.showPage("Edit"); + } + }); + + //////////////////////////////////////////////////////////////////////////////////////////// + // ACTUAL PAGES //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////// + + // LoopyNode! + (function(){ + const page = new SidebarPage(); + backToTopButton(self, page); + injectPropsInSideBar(page,objTypeToTypeIndex("node")); + page.onshow = ()=> page.getComponent("label").select(); // Focus on the label field + page.onedit = ()=>injectPropsLabelInSideBar(page,objTypeToTypeIndex("node")); + deleteMeButton(self, page, "delete node"); + self.addPage("Node", page); + })(); + + // Edge! + (function(){ + const page = new SidebarPage(); + backToTopButton(self, page); + injectPropsInSideBar(page,objTypeToTypeIndex("edge")); + page.onshow = ()=> page.getComponent("customLabel").select(); // Focus on the label field + deleteMeButton(self, page, "delete arrow"); + page.onedit = ()=>injectPropsLabelInSideBar(page,objTypeToTypeIndex("edge")); + self.addPage("Edge", page); + })(); + + // Label! + (function(){ + const page = new SidebarPage(); + backToTopButton(self, page); + injectPropsInSideBar(page,objTypeToTypeIndex("label")); + page.onshow = ()=> page.getComponent("text").select(); // Focus on the text field + page.onhide = function(){ + // If you'd just edited it... + const label = page.target; + if(!page.target) return; + // If text is "" or all spaces, DELETE. + const text = label.text; + if(/^\s*$/.test(text)){ + // that was all whitespace, KILL. + page.target = null; + label.kill(); + } + }; + deleteMeButton(self, page, "delete label"); + page.onedit = ()=>injectPropsLabelInSideBar(page,objTypeToTypeIndex("label")); + self.addPage("Label", page); + })(); + + // Edit + (function(){ + const page = new SidebarPage(); + page.target = loopy; + injectPropsInSideBar(page,objTypeToTypeIndex("loopy")); + page.onedit = ()=>injectPropsLabelInSideBar(page,objTypeToTypeIndex("loopy")); + self.addPage("Edit", page); + })(); + + // Ctrl-S to SAVE + subscribe("key/save",function(){ + if(Key.control){ // Ctrl-S or ⌘-S + publish("modal",["save_link"]); + } + }); + +} +function backToTopButton(sidebar, page){ + page.addComponent(new ComponentButton({ + header: true, + label: "back to top", + onclick: function(){ + sidebar.showPage("Edit"); + } + })); +} +function deleteMeButton(sidebar, page, label){ + page.addComponent(new ComponentButton({ + label: label, + onclick: function(me){ + me.kill(); + sidebar.showPage("Edit"); + } + })); +} + +function SidebarPage(){ + + // TODO: be able to focus on next component with an "Enter". + + const self = this; + self.target = null; + + // DOM + self.dom = document.createElement("div"); + self.hide = function(){ self.dom.style.display="none"; self.onhide(); }; + self.show = function(){ + self.dom.style.display="block"; + //self.dom.classList.remove("compact"); + //if(self.dom.offsetHeight>innerHeight) self.dom.classList.add("compact"); + self.onshow(); + }; + + // Components + self.components = []; + self.componentsByID = {}; + self.addComponent = function(propName, component){ + + // One or two args + if(!component){ + component = propName; + propName = ""; + } + + component.page = self; // tie to self + component.propName = propName; // tie to propName + self.dom.appendChild(component.dom); // add to DOM + + // remember component + self.components.push(component); + self.componentsByID[propName] = component; + + // return! + return component; + + }; + self.getComponent = function(propName){ + return self.componentsByID[propName]; + }; + + // Edit + self.edit = function(object){ + + // New target to edit! + self.target = object; + + // Show each property with its component + for(let i=0;i{ + if(event.code === "Delete" && !input.value){ + self.page.target.kill(); + } + }); + input.oninput = function(){ + self.setValue(input.value); + updateClassActiveDefault(self,config.defaultValue); + injectPropsUpdateDefault(self,self.getValue()); + // Callback! (if any) + if(config.oninput){ + config.oninput(self,self.getValue()); + } + }; + self.dom.appendChild(label); + self.dom.appendChild(input); + + // Show + self.show = function(){ + input.value = self.getValue(); + updateClassActiveDefault(self,config.defaultValue); + updateDocLink(self); + }; + + // Select + self.select = function(){ + setTimeout(function(){ input.select(); },10); + }; + +} +function advancedConditionalDisplay(self) { + self.dom.classList.add('adv'); + const adv = document.createElement("div"); + adv.innerHTML = "Advanced feature in use : "; + adv.setAttribute("class","adv_disclaimer"); + self.dom.appendChild(adv); +} +function colorLogicConditionalDisplay(self) { + self.dom.classList.add('colorLogic'); + const adv = document.createElement("div"); + adv.innerHTML = "This feature need activated colorLogic !"; + adv.setAttribute("class","colorLogic_disclaimer"); + self.dom.appendChild(adv); +} +function simpleOnlyConditionalDisplay(self) { + self.dom.classList.add('simpleOnly'); +} +function updateClassActiveDefault(self, defaultValue) { + if(self.getValue() === defaultValue)self.dom.classList.remove("active"); + else self.dom.classList.add("active"); + + if(self.page.dom.querySelector('.adv.active')){ + const simpleOnly = self.page.dom.querySelectorAll('.simpleOnly'); + for(let so of simpleOnly) so.classList.add("inactive"); + } else { + const simpleOnly = self.page.dom.querySelectorAll('.simpleOnly'); + for(let so of simpleOnly) so.classList.remove("inactive"); + } +} +function addBgImage(sliderDOM, className, imgName='', fileExtension='png') { + const img = document.createElement("div"); + img.draggable = false; + if(imgName) img.style.backgroundImage = `url(css/sliders/${imgName}.${fileExtension})`; + img.classList.add(className); + sliderDOM.appendChild(img); + return img; +} +function addDynamicUIbgImage(sliderDOM,imgName, myClass, fileExtension='png') { + return addBgImage(sliderDOM,`component_slider_graphic_${myClass}`,`${imgName}_${myClass}`,fileExtension); +} +function ComponentSlider(config){ + + // Inherit + const self = this; + Component.apply(self); + + // TODO: control with + / -, alt keys?? + + // DOM: label + slider + self.dom = document.createElement("div"); + self.dom.classList.add('not_in_play_mode'); + if(config.combineWithNext) self.dom.classList.add('combineWithNext'); + if(config.advanced) advancedConditionalDisplay(self); + if(config.colorLogic) colorLogicConditionalDisplay(self); + if(config.simpleOnly) simpleOnlyConditionalDisplay(self); + const label = _createLabel(config.label); + self.dom.appendChild(label); + const sliderDOM = document.createElement("div"); + sliderDOM.classList.add("component_slider"); + self.dom.appendChild(sliderDOM); + + // Slider DOM: graphic + pointer + + const sliderBG = addBgImage(sliderDOM,'component_slider_graphic'); + const slider = addBgImage(sliderDOM,'component_slider_graphic',config.bg); + //TODO: implement the following + /*if(config.mergedPointer){ + sliderDOM.classList.add("pointerIncluded"); + }*/ + if(config.activeAtLeft) config.activeAtLeft = addDynamicUIbgImage(sliderDOM,config.bg,"activeAtLeft"); + if(config.activeAtRight) config.activeAtRight = addDynamicUIbgImage(sliderDOM,config.bg,"activeAtRight"); + if(config.activeOption) config.activeOption = addDynamicUIbgImage(sliderDOM,config.bg,"activeOption"); + /*if(config.hover){ + sliderDOM.appendChild(addDynamicUIbgImage(sliderDOM,config.bg,"hoverOption","gif")); + }*/ + const pointer = addBgImage(sliderDOM,'component_slider_pointer'); + const clickCatcher = addBgImage(sliderDOM,'component_slider_clickCatcher'); + + const movePointer = function() { + const value = self.getValue(); + const optionIndex = config.options.indexOf(value); + const x = (optionIndex + 0.5) * (250 / config.options.length); + pointer.style.left = (x - 7.5) + "px"; + let active = 0; + if (config.activeOption) { + active=1; + const x = - optionIndex * (250 / config.options.length); + const left = optionIndex * (250 / config.options.length); + config.activeOption.style.left = `${left}px`; + config.activeOption.style.width = `${250 / config.options.length}px`; + config.activeOption.style.backgroundPosition = `${x}px 0`; + } + if (config.activeAtLeft) { + const x = (optionIndex + 1 - active) * (250 / config.options.length); + config.activeAtLeft.style.width = `${x}px`; + slider.style.right = `0px`; + slider.style.backgroundPosition = `${- (optionIndex + 1) * (250 / config.options.length)}px` + slider.style.width = `${250 - (optionIndex + 1) * (250 / config.options.length)}px` + } + if (config.activeAtRight) { + const x = - (optionIndex + active) * (250 / config.options.length); + const size = 250 - (optionIndex + active) * (250 / config.options.length); + config.activeAtRight.style.width = `${size}px`; + config.activeAtRight.style.backgroundPosition = `${x}px 0`; + slider.style.width = `${optionIndex * (250 / config.options.length)}px` + } + }; + // On click... (or on drag) + let isDragging = false; + const onmousedown = function(event){ + isDragging = true; + sliderInput(event); + }; + const onmouseup = function(){ + isDragging = false; + }; + const onmousemove = function(event){ + if(isDragging) sliderInput(event); + }; + const sliderInput = function(event){ + + // What's the option? + const index = event.x/250; + const optionIndex = Math.floor(index*config.options.length); + const option = config.options[optionIndex]; + if(option===undefined) return; + self.setValue(option); + + updateClassActiveDefault(self,config.defaultValue); + + // Callback! (if any) + injectPropsUpdateDefault(self,option); + if(config.oninput){ + config.oninput(self,option); + } + + // Move pointer there. + movePointer(); + + }; + _addMouseEvents(sliderDOM, onmousedown, onmousemove, onmouseup); + + // Show + self.show = function(){ + updateClassActiveDefault(self,config.defaultValue); + updateDocLink(self); + movePointer(); + }; + + // BG Color! + self.setBGColor = function(color){ + sliderBG.style.backgroundColor = color; + }; + +} + +function ComponentButton(config){ + + // Inherit + const self = this; + Component.apply(self); + + // DOM: just a button + self.dom = document.createElement("div"); + if(config.advanced) advancedConditionalDisplay(self); + if(config.colorLogic) colorLogicConditionalDisplay(self); + if(config.simpleOnly) simpleOnlyConditionalDisplay(self); + const button = _createButton(config.label, function(){ + config.onclick(self.page.target); + }); + self.dom.appendChild(button); + + // Unless it's a HEADER button! + if(config.header){ + button.setAttribute("header","yes"); + } + +} + +function ComponentHTML(config){ + + // Inherit + const self = this; + Component.apply(self); + + // just a div + self.dom = document.createElement("div"); + if(config.advanced) advancedConditionalDisplay(self); + if(config.colorLogic) colorLogicConditionalDisplay(self); + if(config.simpleOnly) simpleOnlyConditionalDisplay(self); + self.dom.innerHTML = config.html; + +} + +function ComponentOutput(config){ + + // Inherit + const self = this; + Component.apply(self); + + // DOM: just a readonly input that selects all when clicked + self.dom = _createInput("component_output"); + if(config.advanced) advancedConditionalDisplay(self); + if(config.colorLogic) colorLogicConditionalDisplay(self); + if(config.simpleOnly) simpleOnlyConditionalDisplay(self); + self.dom.setAttribute("readonly", "true"); + self.dom.onclick = function(){ + self.dom.select(); + }; + + // Output the string! + self.output = function(string){ + self.dom.value = string; + }; + +} \ No newline at end of file diff --git a/v1.1/js/Toolbar.js b/2/js/Toolbar.js similarity index 87% rename from v1.1/js/Toolbar.js rename to 2/js/Toolbar.js index 64d86f0..cc083e1 100644 --- a/v1.1/js/Toolbar.js +++ b/2/js/Toolbar.js @@ -6,20 +6,20 @@ TOOLBAR CODE function Toolbar(loopy){ - var self = this; + const self = this; // Tools & Buttons - var buttons = []; - var buttonsByID = {}; + const buttons = []; + const buttonsByID = {}; self.dom = document.getElementById("toolbar"); self.addButton = function(options){ - var id = options.id; - var tooltip = options.tooltip; - var callback = options.callback; + const id = options.id; + const tooltip = options.tooltip; + const callback = options.callback; // Add the button - var button = new ToolbarButton(self,{ + const button = new ToolbarButton(self,{ id: id, icon: "css/icons/"+id+".png", tooltip: tooltip, @@ -41,7 +41,7 @@ function Toolbar(loopy){ // Select button self.selectButton = function(button){ - for(var i=0;ib; +injectProperty("edge", "from",{persist:{index:0,binFunc:{bit:bitToRefAnything,encode:(v)=>v.id,decode:(v)=>v},serializeFunc:(v)=>v.id}}); +injectProperty("edge", "to",{persist:{index:1,binFunc:{bit:bitToRefAnything,encode:(v)=>v.id,decode:(v)=>v},serializeFunc:(v)=>v.id}}); +injectProperty("edge", "arc",{persist:{index:2,binFunc:factoryRatio(10,2048,true),serializeFunc:v=>Math.round(v)}}); +injectProperty("edge", "rotation",{persist:{index:4,binFunc:factoryRatio(4,360),serializeFunc:v=>Math.round(v)}}); +injectProperty("edge", "strength",{ + defaultValue:1, + persist:3, + sideBar:{ + index: 1, + options: [1,-1], + labelFunc: (v)=>`Relationship : ${v===1?'same':'invert'} effect`, + simpleOnly: true + } +}); + +injectProperty("edge", "signBehavior",{ + defaultValue:0, + persist:5, + sideBar:{ + index: 2, + options: [0,1,2,3,4,5], + labelFunc: (v,obj)=>`${obj.strength>0?'+':'–'} Valency : ${[ + "preserved", + "inverted", + "allow only negative",// remove (invert can do it) + "allow only positive", + "convert to negative", // remove (invert can do it) + "convert to positive", + ][v]}`, + advanced: true + } +}); + +injectProperty("edge", "filter",{ + defaultValue:0, + persist:10, + sideBar:{ + index: 3, + options: [0, 1, 2, 3, 4, 5], + labelFunc: (v)=>[ + "Allow : any signal", + "Allow : only arrow signal", // ⬍ ⮃ + "Allow : only death signal", // ☠ 💀 🕱 + "Allow : only life signal", // ❀ 🏵 + "Allow : death & life signal", + "Allow : randomly some signal", // 🎲 + ][v], + advanced: true + } +}); +injectProperty("edge", "quantitative",{ + defaultValue:0, + persist:9, + sideBar:{ + index: 4, + options: [0, 1, 2], + labelFunc: (v)=>[ + "Signal : input as tendency", + "Signal : input as quantity", + "Signal : output as vital change" + ][v], + advanced: true + } +}); + +const COLORS_NAME = ["red", "orange", "yellow", "green", "blue", "purple"]; +injectProperty("edge", "edgeFilterColor",{ + defaultValue:-1, + persist:6, + sideBar:{ + index: 5, + options: [-1,0,1,2,3,4,5], + labelFunc: (v)=>{ + if(loopy.colorLogic===1){ + if(parseInt(v)=== -1) return "Color filter : all color signal pass"; + else return `Color filter : ${COLORS_NAME[v]} signal only`; + } else return "Start color : "; + }, + advanced: true + } +}); +injectProperty("edge", "edgeTargetColor",{ + defaultValue:-1, + persist:7, + sideBar:{ + index: 6, + options: [-1,0,1,2,3,4,5,-2,-3], + labelFunc: (v)=>{ + if(loopy.colorLogic===1){ + if(parseInt(v)=== -1) return "Color conversion : none"; + if(parseInt(v)=== -2) return "Color conversion : to random"; + if(parseInt(v)=== -3) return "Signal color to node color"; + return `Color conversion : to ${COLORS_NAME[v]}`; + // convert to random color (from target output allowed color) (colorLogic only) + // convert node to signal color (colorLogic only) + } else{ + if(parseInt(v)=== -1) return "End color : auto from start color"; + if(parseInt(v)=== -2) return "End color : rainbow"; + if(parseInt(v)=== -3) return "End color : black double arrow"; + return `End color : ${COLORS_NAME[v]}`; + } + }, + advanced: true, + colorLogic: true + } +}); +injectProperty("edge", "customLabel",{ + defaultValue:"", + persist:{ + index:8, + deserializeFunc:decodeURIComponent + }, + sideBar:{ + index: 90, + label: "Custom name :", // ⚙ + advanced: true + } +}); +injectProperty("edge", "lengthInfoDetail",{ + sideBar:{ + index: 99, + simpleOnly: true, + html:`(to make a stronger relationship, draw multiple arrows!) +

(to make a delayed relationship, draw longer arrow : )`, + labelFunc: (v,edge)=>`${(edge.getArrowLength()/240).toPrecision(2)}s`, + } +}); +injectProperty("edge", "lengthInfoEssential",{ + sideBar:{ + index: 98, + advanced: true, + html:`Stronger link : multiple arrows +
Delay signal : longer arrow +
Signal thru this arrow : `, + labelFunc: (v,edge)=>`${(edge.getArrowLength()/240).toPrecision(3)}s`, + } +}); diff --git a/2/js/featureInjector.js b/2/js/featureInjector.js new file mode 100644 index 0000000..5d0c6a5 --- /dev/null +++ b/2/js/featureInjector.js @@ -0,0 +1,148 @@ +// A loopy implementation of SOLID Open–closed principle +const PERSIST_MODEL = []; +const EDIT_MODEL = []; + +/* +onNodeInit +onNodeTakeSignal +onNodeSendSignal +onEdgeAddSignal +onLoopyInit +onPlayReset + + */ + +/** + * + * @param objType : Object class like LoopyNode, Edge or Loopy + * @param propertyName: String, name of the property + * @param config : Object with optional : defaultValue, immutableDefault, persist*1, sideBar*2 + * *1 : if no persist Object given => don't persist this property + * persist = {index, [serializeFunc], [deserializeFunc]} + * *2 : + * sideBar = { + * index, + * options: slider enum, + * label: string || func, + * advanced: boolean + * } + * + */ +function injectProperty(objType,propertyName,config={}) { + let typeIndex = objTypeToTypeIndex(objType); + objType = get_PERSIST_TYPE_array()[typeIndex]; + + if(!objType.default) objType.default = {}; + if(typeof config.defaultValue === "undefined") config.defaultValue = 0; + if(typeof objType.default[propertyName] !== "undefined") throw `objType.default[propertyName] collision with ${propertyName}`; + objType.default[propertyName] = config.defaultValue; + if(!EDIT_MODEL[typeIndex]) EDIT_MODEL[typeIndex] = []; + if(config.sideBar){ + if(EDIT_MODEL[typeIndex][config.sideBar.index]) throw `sideBar position collision : ${config.sideBar.index} ${propertyName}`; + + const sideBarData = config.sideBar; + sideBarData.name = propertyName; + sideBarData.defaultValue = config.defaultValue; + EDIT_MODEL[typeIndex][config.sideBar.index] = sideBarData; + + //noinspection JSUnresolvedVariable + if(!config.immutableDefault) EDIT_MODEL[typeIndex][config.sideBar.index].updateDefault = (value)=> objType.default[propertyName] = value; + } + + + if(typeof config.persist !== "undefined"){ + if(typeof config.persist !== "object" && isFinite(parseInt(config.persist))) config.persist = {index:config.persist}; + if(isNaN(parseInt(config.persist.index))) throw `in injectProperty, if config.persist, config.persist.index is required`; + if(!PERSIST_MODEL[typeIndex]) PERSIST_MODEL[typeIndex] = []; + if(PERSIST_MODEL[typeIndex][config.persist.index]) throw `config.persist.index collision : ${JSON.stringify(PERSIST_MODEL[typeIndex][config.persist.index])}`; + const persist = {name:propertyName,jsonOnly:config.persist.jsonOnly}; + + persist.serializeFunc = config.persist.serializeFunc?config.persist.serializeFunc:(v)=>v; + persist.deserializeFunc = config.persist.deserializeFunc?config.persist.deserializeFunc:(v)=>v; + persist.defaultValue = config.defaultValue; + persist.bit = config.persist.bit?config.persist.bit: + config.persist.binFunc?config.persist.binFunc.bit: + config.sideBar?config.sideBar.options?Math.ceil(Math.log2(config.sideBar.options.length)):0:0; + if(!persist.jsonOnly){ + persist.encode = config.persist.binFunc? config.persist.binFunc.encode:(v)=>config.sideBar.options.indexOf(v); + persist.decode = config.persist.binFunc? config.persist.binFunc.decode:(v)=>config.sideBar.options[v]; + } + PERSIST_MODEL[typeIndex][config.persist.index] = persist; + } + +} +function injectPropsInSideBar(page,typeIndex){ + for(let i in EDIT_MODEL[typeIndex]) if(EDIT_MODEL[typeIndex].hasOwnProperty(i)){ + const feat = EDIT_MODEL[typeIndex][i]; + const componentConfig = feat; + componentConfig.bg = feat.name; + if(feat.options) page.addComponent(feat.name, new ComponentSlider(componentConfig)); + else if(feat.html) page.addComponent(feat.name, new ComponentHTML(componentConfig)); + else page.addComponent(feat.name, new ComponentInput(componentConfig)); + } +} +function injectPropsUpdateDefault(component, value){ + const typeIndex = objTypeToTypeIndex(component.page.id); + for(let i in EDIT_MODEL[typeIndex]) if(EDIT_MODEL[typeIndex].hasOwnProperty(i)){ + const feat = EDIT_MODEL[typeIndex][i]; + if(feat.name === component.propName && feat.updateDefault) feat.updateDefault(value) + } +} +function injectPropsLabelInSideBar(page,typeIndex){ + for(let i in EDIT_MODEL[typeIndex]) if(EDIT_MODEL[typeIndex].hasOwnProperty(i)){ + const feat = EDIT_MODEL[typeIndex][i]; + const component = page.getComponent(feat.name); + if(parseInt(typeIndex)===3){ //if loopy/global, init + if(feat.oninput) feat.oninput(page.target, page.target[feat.name]); + component.show(); + } + + if(parseInt(typeIndex)===0){ //if node, set BG Color + component.setBGColor(LoopyNode.COLORS[page.target.hue]); + } + if(feat.labelFunc) component.dom.querySelector('.component_label').innerHTML = feat.labelFunc(page.target[feat.name],page.target); + } +} +function injectedDefaultProps(targetConfig,typeIndex) { + const objType = get_PERSIST_TYPE_array()[typeIndex]; + for(let i in objType.default) if(objType.default.hasOwnProperty(i)){ + targetConfig[i]=objType.default[i]; + } +} + +function applyInitialPropEffects(element) { + const typeIndex = objTypeToTypeIndex(element); + for(let i in EDIT_MODEL[typeIndex]) { + if(EDIT_MODEL[typeIndex][i].oninput) EDIT_MODEL[typeIndex][i].oninput({page:{target:element}},element[i]); + } +} +function get_PERSIST_TYPE_array() { + return [ + LoopyNode, + Edge, + Label, + Loopy, + //Group, + //GroupPair, + ]; +} +function objTypeToTypeIndex(objType) { + if(typeof objType === "object") objType = objType._CLASS_; + const PERSIST_TYPE = get_PERSIST_TYPE_array(); + for(let i in PERSIST_TYPE) if(PERSIST_TYPE.hasOwnProperty(i)){ + if( + objType===i + || objType===PERSIST_TYPE[i] + || objType===PERSIST_TYPE[i].name + || objType===PERSIST_TYPE[i].name+'s' + || objType===PERSIST_TYPE[i].name.toLowerCase() + || objType===PERSIST_TYPE[i].name.toLowerCase()+'s' + || objType===PERSIST_TYPE[i]._CLASS_ + || objType===PERSIST_TYPE[i]._CLASS_+'s' + || objType===PERSIST_TYPE[i]._CLASS_.toLowerCase() + || objType===PERSIST_TYPE[i]._CLASS_.toLowerCase()+'s' + ) return parseInt(i); + } + return 3; // default : Loopy global state + //throw `${objType} unknown`; +} diff --git a/2/js/featureInjector.test.js b/2/js/featureInjector.test.js new file mode 100644 index 0000000..e69de29 diff --git a/2/js/globalFeatures.js b/2/js/globalFeatures.js new file mode 100644 index 0000000..b28cc1d --- /dev/null +++ b/2/js/globalFeatures.js @@ -0,0 +1,108 @@ +// Loopy global features +/*injectProperty("loopy", "LoopyNode._UID",{ + defaultValue:0, // bool + persist:0, // reserved +});*/ +injectProperty("loopy", "loopyMode",{ + defaultValue:0, + persist:1, + sideBar:{ + index: 1, + options: [ 0, 1], // Simple || Advanced + label: "LOOPY v2 mode :", + oninput: factorySwitchMode("simple","advanced") + } +}); +injectProperty("loopy", "colorLogic",{ + defaultValue:0, + persist:2, + sideBar:{ + index: 2, + options: [ 0, 1], + labelFunc: (v)=>v?"Color : significant for logic":"Color : only aesthetic", + advanced: true, + oninput: factorySwitchMode("colorAestheticMode","colorLogicMode") + } +}); +injectProperty("loopy", "cameraMode",{ + defaultValue:0, + persist:3, + sideBar:{ + index: 3, + options: [0,1,2], + labelFunc: (v)=>`Camera : ${[ + "resize to scene", // scene cam + "follow signals", // signal cam + "user controllable", // free cam + ][v]}`, + advanced: true + } +}); +/*injectProperty("loopy", "embed",{ + defaultValue:0, // bool + persist:3, // reserved +});*/ +injectProperty("loopy", "beforeAll",{ + sideBar:{ + index: 0, + html:`
LOOPY (v2.0) +
a tool for thinking in systems +
+
see examples + how to + credits +

` + } +}); +injectProperty("loopy", "afterAll",{ + sideBar:{ + index: 99, + html: `
+ save as link + embed in your blog +
+
save as file + load from file +
+
+
json export + load from url +
+
make a GIF using LICEcap +
+
+
+
+ LOOPY is made by nicky case + with your support on patreon <3 +
+
P.S: go read Thinking In Systems, thx +
+
+
+ LOOPY v2 reworked by 1000i100 +
+
Discover all the new features : +
- experiment advanced mode, +
- click any ? for tips & examples. +
+
Unleash your creativity ! +
+
Had fun ? Share it ! + ` + } +}); +function factorySwitchMode(disabledClass,activatedClass){ + return function(self, value){ + let apply; + if(value) apply = function(page){ + page.dom.classList.add(activatedClass); + page.dom.classList.remove(disabledClass); + }; + else apply = function(page){ + page.dom.classList.add(disabledClass); + page.dom.classList.remove(activatedClass); + }; + loopy.sidebar.pages.forEach(apply); + } +} diff --git a/2/js/groupFeatures.js b/2/js/groupFeatures.js new file mode 100644 index 0000000..604eada --- /dev/null +++ b/2/js/groupFeatures.js @@ -0,0 +1,21 @@ + +injectProperty("group", "name",{ +}); +injectProperty("group", "contentVisibility",{ + /** + * always visible + * always hidden in play mode + * only visible when including an active signal + * hidden until an incoming signal reach it (then still visible) + + when a group is hidden all his content is hidden with it. + if an element is in more than one group, it will be shown if at least one of these group is in visible state. + */ +}); +injectProperty("group", "bgVisibility",{ + // default: when visible (contentVisibility) + // only in edit mode + // always but scene cam don't resize to it +}); +injectProperty("group", "bgColor",{ +}); diff --git a/2/js/helpers.js b/2/js/helpers.js new file mode 100644 index 0000000..08105d7 --- /dev/null +++ b/2/js/helpers.js @@ -0,0 +1,409 @@ +/***************************** + +A miscellaneous collection of reuseable helper methods +that I couldn't be arsed to put into separate classes + +*****************************/ + +Math.TAU = Math.PI*2; + +HIGHLIGHT_COLOR = "rgba(193, 220, 255, 0.6)"; + +if(typeof navigator === "undefined") navigator = {platform:""}; +const isMacLike = !!navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i); + +const _PADDING = 25; +let _PADDING_BOTTOM = 110; + +onresize = function(){ + publish("resize"); +}; + +onbeforeunload = function(e) { + if(loopy.dirty){ + const dialogText = "Are you sure you want to leave without saving your changes?"; + e.returnValue = dialogText; + return dialogText; + } +}; + +function _createCanvas(){ + + const canvasses = document.getElementById("canvasses"); + const canvas = document.createElement("canvas"); + + // Dimensions + const _onResize = function(){ + const width = canvasses.clientWidth; + const height = canvasses.clientHeight; + canvas.width = width*2; // retina + canvas.style.width = width+"px"; + canvas.height = height*2; // retina + canvas.style.height = height+"px"; + }; + _onResize(); + + // Add to body! + canvasses.appendChild(canvas); + + // subscribe to RESIZE + subscribe("resize",function(){ + _onResize(); + }); + + // Gimme + return canvas; + +} + +function _createLabel(message){ + const wrapper = document.createElement("div"); + + const label = document.createElement("div"); + label.setAttribute("class","component_label"); + label.innerHTML = message; + const doc = document.createElement("a"); + doc.classList.add("docLink"); + doc.title='Know more about this feature'; + doc.innerHTML = '?'; // (O.ô) *help *doc *what? *more *know more *how? || how? + wrapper.appendChild(doc); + wrapper.appendChild(label); + return wrapper; +} + +function _createButton(label, onclick){ + const button = document.createElement("div"); + button.innerHTML = label; + button.onclick = onclick; + button.setAttribute("class","component_button"); + return button; +} + +function _createInput(className, textarea){ + const input = textarea ? document.createElement("textarea") : document.createElement("input"); + input.setAttribute("class",className); + input.addEventListener("keydown",function(event){ + event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true); + },false); // STOP IT FROM TRIGGERING KEY.js + return input; +} + +function _createNumberInput(onUpdate){ + + const self = {}; + + // dom! + self.dom = document.createElement("input"); + self.dom.style.border = "none"; + self.dom.style.width = "40px"; + self.dom.style.padding = "5px"; + + self.dom.addEventListener("keydown",function(event){ + event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true); + },false); // STOP IT FROM TRIGGERING KEY.js + + // on update + self.dom.onchange = function(){ + let value = parseInt(self.getValue()); + if(isNaN(value)) value=0; + self.setValue(value); + onUpdate(value); + }; + + // select on click, yo + self.dom.onclick = function(){ + self.dom.select(); + }; + + // set & get value + self.getValue = function(){ + return self.dom.value; + }; + self.setValue = function(num){ + self.dom.value = num; + }; + + // return an OBJECT. + return self; + +} + +function _blank(){ + // just a blank function to toss in. +} + +function _getTotalOffset(target){ + const bounds = target.getBoundingClientRect(); + return { + left: bounds.left, + top: bounds.top + }; +} + +function _addMouseEvents(target, onmousedown, onmousemove, onmouseup, onmousewheel){ + + // WRAP THEM CALLBACKS + const _onmousedown = function(event){ + const _fakeEvent = _onmousemove(event); + onmousedown(_fakeEvent); + }; + const _onmousewheel = function(event){ + const _fakeEvent = _onmousemove(event); + onmousewheel(_fakeEvent); + }; + const _onmousemove = function(event){ + + // Mouse position + const _fakeEvent = {}; + if(event.changedTouches){ + // Touch + const offset = _getTotalOffset(target); + _fakeEvent.x = event.changedTouches[0].clientX - offset.left; + _fakeEvent.y = event.changedTouches[0].clientY - offset.top; + event.preventDefault(); + }else{ + // Not Touch + _fakeEvent.x = event.offsetX; + _fakeEvent.y = event.offsetY; + _fakeEvent.wheel = event.deltaY>0?1:event.deltaY<0?-1:0; + } + + // Mousemove callback + onmousemove(_fakeEvent); + return _fakeEvent; + + }; + const _onmouseup = function(){ + const _fakeEvent = {}; + onmouseup(_fakeEvent); + }; + + // Add events! + target.addEventListener("mousedown", _onmousedown); + target.addEventListener("mousemove", _onmousemove); + document.body.addEventListener("mouseup", _onmouseup); + target.addEventListener("wheel", _onmousewheel); + + + // TOUCH. + target.addEventListener("touchstart",_onmousedown,false); + target.addEventListener("touchmove",_onmousemove,false); + document.body.addEventListener("touchend",_onmouseup,false); + +} + +function _getBounds(points){ + + // Bounds + let left=Infinity, top=Infinity, right=-Infinity, bottom=-Infinity; + for(let i=0;i box.x + box.width + || y < box.y + || y > box.y + box.height); +} + +// TODO: Make more use of this??? +function _makeErrorFunc(msg){ + return function(){ + throw Error(msg); + }; +} + +function _getParameterByName(name){ + const url = location.href; + name = name.replace(/[\[\]]/g, "\\$&"); + const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); +} + + +function _blendColors(hex1, hex2, blend){ + + let color = "#"; + for(let i=0; i<3; i++) { + + // Into numbers... + const sub1 = hex1.substring(1+2*i, 3+2*i); + const sub2 = hex2.substring(1+2*i, 3+2*i); + const num1 = parseInt(sub1, 16); + const num2 = parseInt(sub2, 16); + + // Blended number & sub + const num = Math.floor( num1*(1-blend) + num2*blend ); + const sub = num.toString(16).toUpperCase(); + const paddedSub = ('0'+sub).slice(-2); // in case it's only one digit long + + // Add that babe + color += paddedSub; + + } + + return color; + +} + +function _shiftArray(array, shiftIndex){ + const moveThisAround = array.splice(-shiftIndex); + return moveThisAround.concat(array); +} +function statArray(arr){ + const stat = {}; + arr.forEach(e=>stat[e]?stat[e]++:stat[e]=1); + return stat; +} +/* +function MonitoredStruct(inertStruct, eventNameSuffix, topAncestor) { + this.topAncestor = topAncestor ? topAncestor : this; + const closuredTopAncestor = this.topAncestor; + const handler = { + 'set': (obj, key, value) => { + if(JSON.stringify(obj[key]) === JSON.stringify(value) ) return true; + if (typeof value === 'object') obj[key] = new MonitoredStruct(value, eventNameSuffix+'.'+key, closuredTopAncestor); + else obj[key] = value; + throw ['monitoredStruct', 'change', eventNameSuffix+'.'+key,closuredTopAncestor.smartObj]; + return true; + }, + 'deleteProperty': function (obj, key) { + throw ['monitoredStruct', 'delete', eventNameSuffix+'.'+key,closuredTopAncestor.smartObj]; + return delete obj[key]; + } + }; + let goSmart = JSON.parse(JSON.stringify(inertStruct)); + for (let key in inertStruct) { + if (typeof inertStruct[key] === 'object') { + goSmart[key] = new MonitoredStruct(inertStruct[key], eventNameSuffix+'.'+key, this.topAncestor); + } else goSmart[key] = inertStruct[key]; + } + if(typeof goSmart === 'object' && goSmart !== null) this.smartObj = new Proxy(goSmart, handler); + else this.smartObj = goSmart; + return this.smartObj; +} +*/ + +function mergeBounds(...bounds){ + // Get bounds of ALL objects... + return bounds.reduce((acc,cur)=>{ + //if(isFinite(cur.left)) drawBounds(cur, `#${Math.floor(Math.random()*256).toString(16)}${Math.floor(Math.random()*256).toString(16)}${Math.floor(Math.random()*256).toString(16)}`) + if(typeof cur.left === "undefined"){ + cur.left = Infinity; + cur.right = -Infinity; + cur.top = Infinity; + cur.bottom = -Infinity; + } + if(typeof cur.weight === "undefined"){ + cur.cx = (cur.left+cur.right)/2; + cur.cy = (cur.top+cur.bottom)/2; + cur.weight = 1; + } + if(isNaN(cur.cx) || isNaN(cur.cy)) cur.cx = cur.cy = cur.weight = 0; + if(acc.left>cur.left) acc.left=cur.left; + if(acc.top>cur.top) acc.top=cur.top; + if(acc.rightMath.round(v)}}); +injectProperty("label", "y",{persist:{index:1,binFunc:factoryRatioForXY(),serializeFunc:v=>Math.round(v)}}); +injectProperty("label", "textColor",{ + defaultValue:-1, + persist:4, + sideBar:{ + index: 1, + options: [-1,0,1,2,3,4,5], + label: "Text color :", + advanced: true + } +}); +injectProperty("label", "visibility",{ + defaultValue:0, + persist:3, + sideBar:{ + index: 2, + options: [0,1], + labelFunc: (v)=>`Show : ${v===1?'only in edit mode':'always'}`, + advanced: true + } +}); +injectProperty("label", "text",{ + defaultValue:"...", + immutableDefault:true, + persist:{ + index:2, + deserializeFunc:decodeURIComponent + }, + sideBar:{ + index: 3, + label: "Label :", + textarea:true + } +}); +injectProperty("label", "href",{ + defaultValue:"", + immutableDefault:true, + persist:{ + index:5, + deserializeFunc:decodeURIComponent + }, + sideBar:{ + index: 4, + label: "Clickable ? Add an Url :", + advanced: true + } +}); diff --git a/2/js/loopyNodeFeatures.js b/2/js/loopyNodeFeatures.js new file mode 100644 index 0000000..d7da0b0 --- /dev/null +++ b/2/js/loopyNodeFeatures.js @@ -0,0 +1,134 @@ +// LoopyNode features +function updateNodeSize (self,v){ + // LoopyNode size + const selectedNode = self.page.target; + if(selectedNode.size===5) selectedNode.radius=1.2*LoopyNode.DEFAULT_RADIUS; + if(selectedNode.size===100) selectedNode.radius=1.5*LoopyNode.DEFAULT_RADIUS; + if(selectedNode.size===1) selectedNode.radius=LoopyNode.DEFAULT_RADIUS; + if(selectedNode.size<1) selectedNode.radius=0.5*LoopyNode.DEFAULT_RADIUS; + if(!selectedNode.label) selectedNode.radius=0.1*LoopyNode.DEFAULT_RADIUS; +} +injectProperty("node", "id",{persist:{index:0,jsonOnly:true}}); +injectProperty("node", "x",{persist:{index:1,binFunc:factoryRatioForXY(),serializeFunc:v=>Math.round(v)}}); +injectProperty("node", "y",{persist:{index:2,binFunc:factoryRatioForXY(),serializeFunc:v=>Math.round(v)}}); +/** + * toggleable visibility in play mode (visible, hidden in play mode, hidden when dead) + * + * toggleable interactivity in play mode (read-only, user interactive) + * + * custom image url (to load an image for the circle + * + * borderless switch (if borderless, scale it up a bit and remove borders) + */ +injectProperty("node", "label",{ + defaultValue:"?", + immutableDefault:true, + persist:{ + index:4, + jsonOnly:true, // string have special store in binary mode + deserializeFunc:decodeURIComponent + }, + sideBar:{ + index: 1, + label: "Name :", + oninput: updateNodeSize, + } +}); +injectProperty("node", "hue",{ + defaultValue:0, + persist:5, + sideBar:{ + index: 2, + options: [0,1,2,3,4,5], + label: "Color :" + } +}); +injectProperty("node", "size",{ + defaultValue:1, + persist:6, + sideBar:{ + index: 3, + options: [0.0001, 1, 5, 100], + labelFunc: (v)=>{ + const cases = []; + cases[0.0001] ="Size : tiny → boolean capacity"; + cases[1]="Size : normal → x1 capacity"; + cases[5] ="Size : big → x5 capacity"; + cases[100] ="Size : huge → x100 capacity"; + return cases[parseFloat(v)]; + }, + oninput: updateNodeSize, + advanced: true + } +}); +injectProperty("node", "init",{ + defaultValue:0.5, + persist:3, + sideBar:{ + index: 4, + options: [0, 0.16, 0.33, 0.50, 0.66, 0.83, 1], + //options: [0, 1/6, 2/6, 3/6, 4/6, 5/6, 1], + label: "Start Amount :" + } +}); +injectProperty("node", "overflow",{ + defaultValue:0, + persist:8, + sideBar:{ + index: 5, + options: [0, 0.16, 0.33, 0.50, 0.66, 0.83, 1], + label: "Overflow threshold :", + advanced: true, + combineWithNext:true, + activeAtRight:true, + } +}); +injectProperty("node", "underflow",{ + defaultValue:1, + persist:9, + sideBar:{ + index: 6, + options: [0, 0.16, 0.33, 0.50, 0.66, 0.83, 1], + label: "Negative overflow threshold :", + advanced: true, + activeAtLeft:true, + } +}); +injectProperty("node", "aggregationLatency",{ + defaultValue:0, + persist:7, + sideBar:{ + index: 7, + options: [ 0, 0.1, 0.2, 0.4, 0.8, 1.6, 3.2, 6.4], + labelFunc: (v)=>`Aggregation latency : ${v?v+'s':'none'}`, + advanced: true + } +}); +injectProperty("node", "explode",{ + defaultValue:0, + persist:10, + sideBar:{ + index: 8, + options: [ 0, -1, 1, 2], + labelFunc: (v)=>{ + const cases = []; + cases[0] ="Explode : never"; + cases[-1]="Explode : if empty"; + cases[1] ="Explode : if full"; + cases[2] ="Explode : if empty or full"; + return cases[parseInt(v)]; + }, + advanced: true + } +}); +injectProperty("node", "foreignColor",{ + defaultValue:0, + persist:11, + sideBar:{ + index: 9, + options: [ 0, 1], + labelFunc: (v)=>`Foreign color signal : ${v?'drop':'forward'}`, // delete, deny / transmit + advanced: true, + colorLogic:true + } +}); diff --git a/2/js/persist.js b/2/js/persist.js new file mode 100644 index 0000000..d545af0 --- /dev/null +++ b/2/js/persist.js @@ -0,0 +1,392 @@ +// noinspection JSUnusedGlobalSymbols +function log(bitArray) { + console.log(bitArray.maxOffset,binView(bitArray.rawData.buffer)); +} +function saveToBinary(bitArray,objToPersist,typeIndex,entityBitSize,bitToRefAnyEntity){ + const toSave = []; + for(let i in PERSIST_MODEL[typeIndex]) { + const prop = PERSIST_MODEL[typeIndex][i]; + if(prop.bit) { + let bitSize = prop.bit; + if(typeof prop.bit === "function") bitSize = prop.bit(bitToRefAnyEntity); + if(typeof toSave[i] !== "undefined") throw `collision : ${typeIndex} ${prop.name}`; + toSave[i] = {value:prop.encode(objToPersist[prop.name]),bit:bitSize}; + } + } + const tmpBitArray = new BitArray(entityBitSize); + toSave.forEach((e)=>tmpBitArray.append(e.value,e.bit)); + tmpBitArray.offset = 0; + bitArray.append(tmpBitArray,entityBitSize); +} +function loadFromBinary(bitArray,typeIndex,bitSize,entitiesCount,bitToRefAnyEntity) { + const entity = {}; + const startOffset = bitArray.offset; + for(let i in PERSIST_MODEL[typeIndex]) { + const prop = PERSIST_MODEL[typeIndex][i]; + if(prop.bit) { + let propBitSize = prop.bit; + if(typeof prop.bit === "function") propBitSize = prop.bit(bitToRefAnyEntity); + if(bitArray.offset+propBitSize>startOffset+bitSize) break; + const rawValue = bitArray.get(propBitSize) + entity[prop.name] = prop.decode(rawValue); + } + } + bitArray.setOffset(startOffset+bitSize); + return entity; +} +function humanReadableJsonPersistProps(objToPersist) { + const typeIndex = objTypeToTypeIndex(objToPersist); + const persist = {}; + for(let i in PERSIST_MODEL[typeIndex]) persist[PERSIST_MODEL[typeIndex][i].name] = PERSIST_MODEL[typeIndex][i].serializeFunc( objToPersist[PERSIST_MODEL[typeIndex][i].name]); + return persist; +} +function legacyJsonPersistProps(objToPersist) { + const typeIndex = objTypeToTypeIndex(objToPersist); + const persistArray = []; + for(let i in PERSIST_MODEL[typeIndex]) persistArray[i] = PERSIST_MODEL[typeIndex][i].serializeFunc( objToPersist[PERSIST_MODEL[typeIndex][i].name]); + return persistArray; +} +function legacyJsonRestoreProps(srcArray, targetConfig, typeIndex) { + // include in targetConfig + for(let i in PERSIST_MODEL[typeIndex]) { + //if(typeof targetConfig[PERSIST_MODEL[typeIndex][i].name] !== "undefined" && parseInt(typeIndex)!==3) throw `collision : ${typeIndex} ${PERSIST_MODEL[typeIndex][i].name}`; // except for loopy globals + if(typeof srcArray[i] !== "undefined" && srcArray[i] !== null) + targetConfig[PERSIST_MODEL[typeIndex][i].name] = PERSIST_MODEL[typeIndex][i].deserializeFunc( srcArray[i] ); + } + return targetConfig; +} +function legacyIdFix(newModel){ + newModel.nodes.forEach((n,i)=>{ + if(n.id && n.id !== i){ + const oldId = n.id; + const newId = i; + n.id = newId; + newModel.edges.forEach(edge=>{ + if(edge.from===oldId) edge.from=newId; + if(edge.to===oldId) edge.to=newId; + }); + } + }); +} + +function serializeToUrl (embed){ + const alternatives = [] + alternatives.push(stdB64ToUrl(binToB64(serializeToBinary(embed,false,false)))); + alternatives.push(stdB64ToUrl(binToB64(serializeToBinary(embed,true,false)))); + alternatives.push(stdB64ToUrl(binToB64(serializeToBinary(embed,false,true)))); + alternatives.push(stdB64ToUrl(binToB64(serializeToBinary(embed,true,true)))); + + const minSized = alternatives.reduce((acc,cur)=>{return cur.length>acc.size?acc:{size:cur.length,content:cur};},{size:+Infinity,content:''}); + return minSized.content; +} +function deserializeFromUrl (dataString){ + if(dataString[0]==='[') return deserializeFromLegacyJson(dataString); + if(dataString[0]==='{') return deserializeFromHumanReadableJson(dataString); + return deserializeFromBinary(b64ToBin(urlToStdB64(dataString)).map((v)=>v>128?v-256:v)); +} +function deserializeFromArrayBuffer(dataInArrayBuffer){ + const enc = new TextDecoder("utf-8"); + let content = enc.decode(dataInArrayBuffer); + if(content[0]==='[') return deserializeFromLegacyJson(content); + if(content[0]==='{') return deserializeFromHumanReadableJson(content); + return deserializeFromBinary(new Uint8Array(dataInArrayBuffer)); +} + +function listStringFields() { + const stringFields = []; + for(let typeIndex in EDIT_MODEL) if(EDIT_MODEL.hasOwnProperty(typeIndex)) + for(let i in EDIT_MODEL[typeIndex]) if(EDIT_MODEL[typeIndex].hasOwnProperty(i)) { + const field = EDIT_MODEL[typeIndex][i]; + if(!field.options && !field.html) stringFields.push({type:typeIndex,fieldName:field.name}); + } + return stringFields; +} +function externalizeStrings(){ + const stringFields = listStringFields(); + const strings = []; + for(let stringField of stringFields){ + const typeName = get_PERSIST_TYPE_array()[stringField["type"]]._CLASS_.toLowerCase(); + loopy.model[`${typeName}s`].forEach((item)=>strings.push(item[stringField["fieldName"]])); + } + const utf8string = strings.join('`'); + // noinspection UnnecessaryLocalVariableJS + const stringUint8Array = (new StringView(utf8string)).rawData; + return stringUint8Array; +} +function restoreStrings(bitArray, newModel) { + const areaStart = bitArray.offset/8; + const areaEnd = bitArray.rawData.buffer.byteLength; + const bin = new Uint8Array(areaEnd-areaStart); + bin.set(new Uint8Array(bitArray.rawData.buffer,areaStart,areaEnd-areaStart), 0); + const utf8string = (new StringView(bin)).toString(); + const strings = utf8string.split('`'); + const stringFields = listStringFields(); + for(let stringField of stringFields){ + const typeName = get_PERSIST_TYPE_array()[stringField["type"]]._CLASS_.toLowerCase(); + newModel[`${typeName}s`].forEach((item)=>item[stringField["fieldName"]] = strings.shift()); + } +} +function appendArea (bitArray,typeStr,entitiesSizes,entitiesCountVolume,bitToRefAnyEntity,bytesAlignSection=true){ + const areaStart = bytesAlignSection?Math.ceil(bitArray.maxOffset/8)*8:bitArray.maxOffset; + bitArray.setOffset(areaStart); + loopy.model[typeStr].forEach((n)=>saveToBinary(bitArray,n,objTypeToTypeIndex(typeStr),entitiesSizes[typeStr],bitToRefAnyEntity)); + bitArray.setOffset(areaStart); + bitArray.rotate(entitiesSizes[typeStr],entitiesCountVolume[typeStr]); +} +function extractArea (bitArray,typeStr,entitiesSizes,entitiesCount,entitiesCountVolume,bitToRefAnyEntity,bytesAlignSection=true){ + const areaStart = bytesAlignSection?Math.ceil(bitArray.maxOffset/8)*8:bitArray.maxOffset; + bitArray.setOffset(areaStart); + const typeIndex = objTypeToTypeIndex(typeStr); + if(!entitiesCount[typeIndex]) return []; + bitArray.rotate(entitiesCountVolume[typeIndex],entitiesSizes[typeIndex],areaStart); + bitArray.setOffset(areaStart); + const entities = []; + for(let i=0;iv<0?v+256:v); + + if(bin.buffer.byteLength < compressedBin.length) return bin; + return compressedBin; +} +function deserializeFromBinary (dataUint8Array){ + const newModel = {globals:{},nodes:[],edges:[],labels:[]}; + let bin = dataUint8Array; + if(bin[0]===93) bin = LZMA.decompress(dataUint8Array); + bin = new BitArray(bin); + if(bin.get(1)!==0) console.warn("hazardous unknown version"); // Version number (This Version Start With 0, on 1bit, to allow evolution starting with 1) + const bytesEntitiesCount = bin.get(1); + const embed = bin.get(1); + const entitiesKindsCount = bin.get(4); + const bitToRefAnyEntity = bin.get(4); + const knownEntities = get_PERSIST_TYPE_array().map(e=>e._CLASS_.toLowerCase()+'s'); + const entitiesCount = []; + for(let i=0;iMath.ceil(n/8)*8):entitiesCount; + const entitiesSizes = []; + for (let i=0;ilegacyJsonPersistProps(n))); // 0 - nodes + data.push(loopy.model.edges.map(n=>legacyJsonPersistProps(n))); // 1 - edges + data.push(loopy.model.labels.map(n=>legacyJsonPersistProps(n))); // 2 - labels + loopy.embed = embed; + data.push(legacyJsonPersistProps(loopy)); // 3 - globalState (including UID) + delete loopy.embed; + return JSON.stringify(data); +} +function deserializeFromLegacyJson (dataString){ + const data = JSON.parse(dataString); + const newModel = {globals:{},nodes:[],edges:[],labels:[]}; + // Get from array! + const nodes = data[0]; + const edges = data[1]; + const labels = data[2]; + const globalState = data[3]; + for(let i=0;ihumanReadableJsonPersistProps(n)), + edges:loopy.model.edges.map(n=>humanReadableJsonPersistProps(n)), + labels:loopy.model.labels.map(n=>humanReadableJsonPersistProps(n)) + }; + if(embed) json.globals.embed=true; + return JSON.stringify(json); +} +function deserializeFromHumanReadableJson (dataString){ + return JSON.parse(dataString); +} + + +const RECURRENT_LZMA_SCHEME = "XQAAAAISAAAAAAAAAAAt"; +function setCharAt(str,index,chr) { + if(index > str.length-1) return str; + return str.substr(0,index) + chr + str.substr(index+1); +} +function urlToStdB64(urlStr) { + let b64 = urlStr; + const parts = urlStr.split("/"); + if(parts.length===2){ + b64 = RECURRENT_LZMA_SCHEME; + let lastDifferencePos = 0; + for(let i = 0; i< parts[0].length;i+=2) { + const position = parseInt(parts[0][i])+lastDifferencePos; + b64 = setCharAt(b64,position,parts[0][i+1]); + lastDifferencePos=position; + } + b64 += parts[1]; + } + return b64.split('_').join('+').split('-').join('/').split('.').join('='); +} +function stdB64ToUrl(b64){ + b64 = b64.split('+').join('_').split('/').join('-').split('=').join('.').replace(/[^-_.a-zA-Z0-9]/g,''); + let start = ''; + let lastDifferencePos = 0; + for(let i =0; i9){ + start+=`${9}${b64[lastDifferencePos+9]}`; + lastDifferencePos += 9; + pos = i-lastDifferencePos; + } + lastDifferencePos = i; + start+=`${pos}${b64[i]}`; + } + } + const diffStartVersion =`${start}/${b64.substr(RECURRENT_LZMA_SCHEME.length)}`; + if(diffStartVersion.length Math.min(Math.pow(2, bitNumber)-1, Math.max(0, Math.round(Math.pow(2, bitNumber-1) * v / ratioRef) + Math.pow(2, bitNumber-1))), + decode: (v) => Math.round((v - Math.pow(2, bitNumber-1)) * ratioRef / Math.pow(2, bitNumber-1)) + }; + else return { + bit: bitNumber, + encode: (v) => Math.min(Math.pow(2, bitNumber)-1, Math.max(0, Math.round(Math.pow(2, bitNumber) * v / ratioRef))), + decode: (v) => Math.round(v * ratioRef / Math.pow(2, bitNumber)) + }; +} +function factoryRatioForXY(){ + let bitNumber; + let ratioRef; + return { + bit: (refBitSize)=>{ + bitNumber = getBitForXY(refBitSize); + ratioRef = getSceneSize(bitNumber); + return bitNumber; + }, + encode: (v) => Math.min(Math.pow(2, bitNumber) - 1, Math.max(0, Math.round(Math.pow(2, bitNumber) * v / ratioRef))), + decode: (v) => Math.round(v * ratioRef / Math.pow(2, bitNumber)) + } +} +function countEntities(ceil8=false){ + const types = get_PERSIST_TYPE_array().map(t=>`${t._CLASS_.toLowerCase()}s`); + const entities = {}; + types.filter(t=>loopy.model[t]).forEach(t=>entities[t]=loopy.model[t].length); + if(ceil8) for(let i in entities)entities[i]=Math.ceil(entities[i]/8)*8; + return entities; +} +function entityRefBitSize(){ + const entitiesCount = countEntities(); + const maxEntities = Object.values(entitiesCount).reduce((acc,cur)=>Math.max(acc,cur),1); + return Math.ceil(Math.log2(maxEntities)); +} +function entitiesSize(bitToRefAnyEntity,ceil8=false){ + const types = get_PERSIST_TYPE_array().map(t=>`${t._CLASS_.toLowerCase()}s`); + const entities = {}; + for(let i in PERSIST_MODEL) + PERSIST_MODEL.forEach((t,i)=>entities[types[i]]=t.reduce((acc,cur)=>acc+(typeof cur.bit==="function"?cur.bit(bitToRefAnyEntity):cur.bit)||acc,0)); + if(ceil8) for(let i in entities)entities[i]=Math.ceil(entities[i]/8)*8; + return entities; +} +function binView(buffer,compactness=true){ + const view = new Uint8Array(buffer); + let str = ""; + for(let i in view){ + const bin = view[i].toString(2); + let pad = ''; + for (let i=8;i>bin.length;i--) pad = `0${pad}`; + str = `${str}${pad}${bin} `; + } + // compactView + if(!compactness) return str.trim(); + else { + let compactStr = ''; + const emptyBytes = str.split('00000000 '); + let empty = 0; + let i=0; + for(i in emptyBytes){ + if(i>0) empty+=8; + if(emptyBytes[i]!==''){ + if(empty>0) compactStr=`${compactStr}[0:${empty}b] ${emptyBytes[i]}`; + else compactStr=`${compactStr}${emptyBytes[i]}`; + empty=0; + } + } + if(empty>0) compactStr=`${compactStr}[0:${empty}b] ${emptyBytes[i]}`; + return compactStr.trim(); + } +} +function getBitForXY(refBitSize){ + return Math.ceil(refBitSize)+4; +} +function getSceneSize(bitForXY) { + return Math.pow(2,4+bitForXY); +} \ No newline at end of file diff --git a/2/js/persist.test.js b/2/js/persist.test.js new file mode 100644 index 0000000..9506e7e --- /dev/null +++ b/2/js/persist.test.js @@ -0,0 +1,134 @@ +/** stdB64ToUrl and urlToStdB64 */ { +testEqual(`more url friendly chr (especially + => _)`, "-_.", async ()=>stdB64ToUrl("/+=") ); +testEqual(`compact usual LZMA header`, "/plop", async ()=>stdB64ToUrl("XQAAAAISAAAAAAAAAAAtplop") ); +testEqual(`compact usual LZMA header with changes`, "9A6B/plop", async ()=>stdB64ToUrl("XQAAAAISAAAAAAABAAAtplop") ); +testEqual(`b64 url conversion`, "XQAAAAISAAAAAAAAAAAkplop/+==", async ()=>urlToStdB64(stdB64ToUrl("XQAAAAISAAAAAAAAAAAkplop/+==")) ); +} +/** factoryRatio encode decode */ { +testEqual(`factoryRatio encode decode back as close as possible to the original`, 42, async ()=>{ + const bits = 6; + const data = 42 + const f = factoryRatio(bits,100); + return f.decode((new BitArray(bits)).append(f.encode(data),bits).get(bits,0)); +} ); +testEqual(`factoryRatio encode decode back as close as possible to the original`, 42, async ()=>{ + const bits = 7; + const data = 42 + const f = factoryRatio(bits,100, true); + return f.decode((new BitArray(bits)).append(f.encode(data),bits).get(bits,0)); +} ); +testEqual(`factoryRatio encode decode back as close as possible to the original`, -42, async ()=>{ + const bits = 7; + const data = -42; + const f = factoryRatio(bits,100, true); + return f.decode((new BitArray(bits)).append(f.encode(data),bits).get(bits,0)); +} ); +testEqual(`factoryRatio encode decode like it should ?`, 403, async ()=>{ + const bits = 10; + const data = 403; + const f = factoryRatio(bits,1920); + return f.decode((new BitArray(bits)).append(f.encode(data),bits).get(bits,0)); +} ); +testEqual(`factoryRatio encode decode like it should ?`, 223, async ()=>{ + const bits = 10; + const data = 223; + const f = factoryRatio(bits,610); + return f.decode((new BitArray(bits)).append(f.encode(data),bits).get(bits,0)); +} ); +} +/** saveToBinary and loadFromBinary */{ + function initPersist(){ + PERSIST_MODEL[0] = []; + PERSIST_MODEL[0][1] = {name:"something",bit:3,encode:(v)=>v,decode:(v)=>v}; + PERSIST_MODEL[0][5] = {name:"thing",bit:3,encode:(v)=>v,decode:(v)=>v}; + PERSIST_MODEL[0][2] = {name:"otherThing",bit:6,encode:(v)=>v,decode:(v)=>v}; + } + testEqual(`saveToBinary store in order`, '11110101 00010110 00000001', async ()=>{ + initPersist(); + const bitArray = new BitArray(24); + saveToBinary(bitArray,{something:7,thing:1,otherThing:42},0,12) + saveToBinary(bitArray,{something:3,thing:1,otherThing:0},0,12) + return binView(bitArray.rawData.buffer); + } ); + testEqual(`loadFromBinary restore in order`, `[{"something":7,"otherThing":42,"thing":1},{"something":3,"otherThing":0,"thing":1}]`, async ()=>{ + initPersist(); + const bitArray = new BitArray(24); + bitArray.append(parseInt('111101010001011000000001',2),24); + bitArray.resetOffset(); + const res = []; + res.push(loadFromBinary(bitArray,0,12,[2])); + res.push(loadFromBinary(bitArray,0,12,[2])); + return JSON.stringify(res); + } ); + testEqual(`loadFromBinary partial restore if partial data`, `[{"something":7,"otherThing":42},{"something":3,"otherThing":0}]`, async ()=>{ + initPersist(); // with 12 bit by entity (3+6+3) + const bitArray = new BitArray(18); + bitArray.append(parseInt('111101010011000000',2),18); + bitArray.resetOffset(); + const res = []; + res.push(loadFromBinary(bitArray,0,9,[2])); + res.push(loadFromBinary(bitArray,0,9,[2])); + return JSON.stringify(res); + } ); + +} +/** appendArea and extractArea */{ + function extraInitPersist(){ + initPersist(); + + if(typeof LoopyNode === "undefined") {LoopyNode = ()=> {};LoopyNode._CLASS_ = LoopyNode.name= 'Node';} + if(typeof Edge === "undefined") {Edge = ()=> {};Edge._CLASS_ = Edge.name= 'Edge';} + if(typeof Label === "undefined") {Label = ()=> {};Label._CLASS_ = Label.name= 'Label';} + if(typeof Loopy === "undefined") {Loopy = ()=> {};Loopy._CLASS_ = Loopy.name= 'Loopy';} + + loopy = {model:{nodes:[ + {something:7,thing:1,otherThing:42}, + {something:3,thing:1,otherThing:0} + ]}} + } + testEqual(`appendArea store adjusted`, + '10000000 11000000 11000000 10000000 [0:8b] 10000000 [0:8b] 10000000 [0:24b] 11000000 01000000' + /* before rotate : + 111 101010 001 + 011 000000 001' */, + async ()=>{ + extraInitPersist(); + const bitArray = new BitArray(12*8+1); + appendArea(bitArray,"nodes",{nodes:12},{nodes:8}); + bitArray.append(1,2); + return binView(bitArray.rawData.buffer); + } ); + testEqual(`appendArea store 8padded`, + '10000000 11000000 11000000 10000000 [0:8b] 10000000 [0:8b] 10000000 [0:24b] 11000000 [0:32b] 01000000' + /* before rotate : + 111 101010 001 + 011 000000 001' */, + async ()=>{ + extraInitPersist(); + const bitArray = new BitArray(16*8+1); + appendArea(bitArray,"nodes",{nodes:16},{nodes:8}); + bitArray.append(1,2); + return binView(bitArray.rawData.buffer); + } ); + testEqual(`extractArea restore adjusted`,`[{"something":7,"otherThing":42,"thing":1},{"something":3,"otherThing":0,"thing":1}]`, + async ()=>{ + extraInitPersist(); + const writeBitArray = new BitArray(12*8+1); + appendArea(writeBitArray,"nodes",{nodes:12},{nodes:8}); + writeBitArray.append(1,2); + const readBitArray = new BitArray(writeBitArray.rawData.buffer); + const res = extractArea(readBitArray,"nodes",[12],[2]); + return JSON.stringify(res); + } ); + testEqual(`extractArea restore 8padded`,`[{"something":7,"otherThing":42,"thing":1},{"something":3,"otherThing":0,"thing":1}]`, + async ()=>{ + extraInitPersist(); + const writeBitArray = new BitArray(16*8+1); + appendArea(writeBitArray,"nodes",{nodes:16},{nodes:8}); + writeBitArray.append(1,2); + const readBitArray = new BitArray(writeBitArray.rawData.buffer); + const res = extractArea(readBitArray,"nodes",[16],[2]); + return JSON.stringify(res); + } ); + +} \ No newline at end of file diff --git a/2/js/test.html b/2/js/test.html new file mode 100644 index 0000000..6a76a35 --- /dev/null +++ b/2/js/test.html @@ -0,0 +1,22 @@ + + + + Test JS + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/2/js/testCli.node.cjs b/2/js/testCli.node.cjs new file mode 100644 index 0000000..e5dcd31 --- /dev/null +++ b/2/js/testCli.node.cjs @@ -0,0 +1,12 @@ +#!/usr/bin/env node +const fs = require('fs'); +const testHtmlLauncher = fs.readFileSync("test.html","utf8"); +let jsToRun = ""; +testHtmlLauncher.replace(new RegExp(`((?!)[\\s\\S]+)?`,'g'), + (found,optionalPattern,url,jsContent,chrNumber,wholeDocument)=>{ + if(url) jsToRun+= fs.readFileSync(url,"utf8"); + if(jsContent) jsToRun+=jsContent; + }); +fs.writeFileSync('.generated.toRun.js',jsToRun); +require('./.generated.toRun.js'); +fs.unlinkSync('.generated.toRun.js'); \ No newline at end of file diff --git a/2/js/testLib.js b/2/js/testLib.js new file mode 100644 index 0000000..5f7619a --- /dev/null +++ b/2/js/testLib.js @@ -0,0 +1,42 @@ +let testOk = 0; +let skippedTest = 0; +const testList = []; +const xtest = xtestEqual = ()=>skippedTest++; +function testEqual(message,pendingResult,testFunc){ + testList.push({name:message,func:async ()=>{ + try{ + const res = await testFunc(); + const strRes = JSON.stringify(res); + const strPending = JSON.stringify(pendingResult); + if(strRes===strPending)testOk++; + else { + console.error(`${strRes} should be ${strPending} in test : ${message}`); + console.log(strRes, "received"); + console.log(strPending,"control"); + } + } catch (e) { + console.error(message,e); + } + }}); +} +function test(message,testFunc){ + testList.push({name:message,func:async ()=>{ + try{ + if(await testFunc())testOk++; + else console.error(message); + } catch (e) { + console.error(message,e); + } + }}); +} +async function runAllTest(verbose=false){ + for(let t of testList) { + if(verbose) console.log(t.name); + await t.func(); + } + let skipped = ''; + if(skippedTest) skipped = `, skipped : ${skippedTest}`; + let failed = ''; + if(testOk -9007199254740992 && nVal < 9007199254740992 && Math.floor(nVal) === nVal; + }; +} + +/*\ +|*| +|*| StringView - Mozilla Developer Network +|*| +|*| Revision #12, March 21st, 2017 +|*| +|*| https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView +|*| https://developer.mozilla.org/en-US/docs/User:fusionchess +|*| https://github.com/madmurphy/stringview.js +|*| +|*| This framework is released under the GNU Lesser General Public License, version 3 or later. +|*| http://www.gnu.org/licenses/lgpl-3.0.html +|*| +\*/ + +function StringView (vInput, sEncoding /* optional (default: UTF-8) */, nOffset /* optional */, nLength /* optional */) { + + var fTAView, aWhole, aRaw, fPutOutptCode, fGetOutptChrSize, nInptLen, nStartIdx = isFinite(nOffset) ? nOffset : 0, nTranscrType = 15; + + if (sEncoding) { this.encoding = sEncoding.toString(); } + + encSwitch: switch (this.encoding) { + case "UTF-8": + fPutOutptCode = StringView.putUTF8CharCode; + fGetOutptChrSize = StringView.getUTF8CharLength; + fTAView = Uint8Array; + break encSwitch; + case "UTF-16": + fPutOutptCode = StringView.putUTF16CharCode; + fGetOutptChrSize = StringView.getUTF16CharLength; + fTAView = Uint16Array; + break encSwitch; + case "UTF-32": + fTAView = Uint32Array; + nTranscrType &= 14; + break encSwitch; + default: + /* case "ASCII", or case "BinaryString" or unknown cases */ + fTAView = Uint8Array; + nTranscrType &= 14; + } + + typeSwitch: switch (typeof vInput) { + case "string": + /* the input argument is a primitive string: a new buffer will be created. */ + nTranscrType &= 7; + break typeSwitch; + case "object": + classSwitch: switch (vInput.constructor) { + case StringView: + /* the input argument is a stringView: a new buffer will be created. */ + nTranscrType &= 3; + break typeSwitch; + case String: + /* the input argument is an objectified string: a new buffer will be created. */ + nTranscrType &= 7; + break typeSwitch; + case ArrayBuffer: + /* the input argument is an arrayBuffer: the buffer will be shared. */ + aWhole = new fTAView(vInput); + nInptLen = this.encoding === "UTF-32" ? + vInput.byteLength >>> 2 + : this.encoding === "UTF-16" ? + vInput.byteLength >>> 1 + : + vInput.byteLength; + aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? + aWhole + : new fTAView(vInput, nStartIdx, !isFinite(nLength) ? nInptLen - nStartIdx : nLength); + + break typeSwitch; + case Uint32Array: + case Uint16Array: + case Uint8Array: + /* the input argument is a typedArray: the buffer, and possibly the array itself, will be shared. */ + fTAView = vInput.constructor; + nInptLen = vInput.length; + aWhole = vInput.byteOffset === 0 && vInput.length === ( + fTAView === Uint32Array ? + vInput.buffer.byteLength >>> 2 + : fTAView === Uint16Array ? + vInput.buffer.byteLength >>> 1 + : + vInput.buffer.byteLength + ) ? vInput : new fTAView(vInput.buffer); + aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? + vInput + : vInput.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen); + + break typeSwitch; + default: + /* the input argument is an array or another serializable object: a new typedArray will be created. */ + aWhole = new fTAView(vInput); + nInptLen = aWhole.length; + aRaw = nStartIdx === 0 && (!isFinite(nLength) || nLength === nInptLen) ? + aWhole + : aWhole.subarray(nStartIdx, isFinite(nLength) ? nStartIdx + nLength : nInptLen); + } + break typeSwitch; + default: + /* the input argument is a number, a boolean or a function: a new typedArray will be created. */ + aWhole = aRaw = new fTAView(Number(vInput) || 0); + + } + + if (nTranscrType < 8) { + + var vSource, nOutptLen, nCharStart, nCharEnd, nEndIdx, fGetInptChrSize, fGetInptChrCode; + + if (nTranscrType & 4) { /* input is string */ + + vSource = vInput; + nOutptLen = nInptLen = vSource.length; + nTranscrType ^= this.encoding === "UTF-32" ? 0 : 2; + /* ...or...: nTranscrType ^= Number(this.encoding !== "UTF-32") << 1; */ + nStartIdx = nCharStart = nOffset ? Math.max((nOutptLen + nOffset) % nOutptLen, 0) : 0; + nEndIdx = nCharEnd = (Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0) + nStartIdx, nOutptLen) : nOutptLen) - 1; + + } else { /* input is stringView */ + + vSource = vInput.rawData; + nInptLen = vInput.makeIndex(); + nStartIdx = nCharStart = nOffset ? Math.max((nInptLen + nOffset) % nInptLen, 0) : 0; + nOutptLen = Number.isInteger(nLength) ? Math.min(Math.max(nLength, 0), nInptLen - nCharStart) : nInptLen; + nEndIdx = nCharEnd = nOutptLen + nCharStart; + + if (vInput.encoding === "UTF-8") { + fGetInptChrSize = StringView.getUTF8CharLength; + fGetInptChrCode = StringView.loadUTF8CharCode; + } else if (vInput.encoding === "UTF-16") { + fGetInptChrSize = StringView.getUTF16CharLength; + fGetInptChrCode = StringView.loadUTF16CharCode; + } else { + nTranscrType &= 1; + } + + } + + if (nOutptLen === 0 || nTranscrType < 4 && vSource.encoding === this.encoding && nCharStart === 0 && nOutptLen === nInptLen) { + + /* the encoding is the same, the length too and the offset is 0... or the input is empty! */ + + nTranscrType = 7; + + } + + conversionSwitch: switch (nTranscrType) { + + case 0: + + /* both the source and the new StringView have a fixed-length encoding... */ + + aWhole = new fTAView(nOutptLen); + for (var nOutptIdx = 0; nOutptIdx < nOutptLen; aWhole[nOutptIdx] = vSource[nStartIdx + nOutptIdx++]); + break conversionSwitch; + + case 1: + + /* the source has a fixed-length encoding but the new StringView has a variable-length encoding... */ + + /* mapping... */ + + nOutptLen = 0; + + for (var nInptIdx = nStartIdx; nInptIdx < nEndIdx; nInptIdx++) { + nOutptLen += fGetOutptChrSize(vSource[nInptIdx]); + } + + aWhole = new fTAView(nOutptLen); + + /* transcription of the source... */ + + for (var nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx++) { + nOutptIdx = fPutOutptCode(aWhole, vSource[nInptIdx], nOutptIdx); + } + + break conversionSwitch; + + case 2: + + /* the source has a variable-length encoding but the new StringView has a fixed-length encoding... */ + + /* mapping... */ + + nStartIdx = 0; + + var nChrCode; + + for (nChrIdx = 0; nChrIdx < nCharStart; nChrIdx++) { + nChrCode = fGetInptChrCode(vSource, nStartIdx); + nStartIdx += fGetInptChrSize(nChrCode); + } + + aWhole = new fTAView(nOutptLen); + + /* transcription of the source... */ + + for (var nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx += fGetInptChrSize(nChrCode), nOutptIdx++) { + nChrCode = fGetInptChrCode(vSource, nInptIdx); + aWhole[nOutptIdx] = nChrCode; + } + + break conversionSwitch; + + case 3: + + /* both the source and the new StringView have a variable-length encoding... */ + + /* mapping... */ + + nOutptLen = 0; + + var nChrCode; + + for (var nChrIdx = 0, nInptIdx = 0; nChrIdx < nCharEnd; nInptIdx += fGetInptChrSize(nChrCode)) { + nChrCode = fGetInptChrCode(vSource, nInptIdx); + if (nChrIdx === nCharStart) { nStartIdx = nInptIdx; } + if (++nChrIdx > nCharStart) { nOutptLen += fGetOutptChrSize(nChrCode); } + } + + aWhole = new fTAView(nOutptLen); + + /* transcription... */ + + for (var nInptIdx = nStartIdx, nOutptIdx = 0; nOutptIdx < nOutptLen; nInptIdx += fGetInptChrSize(nChrCode)) { + nChrCode = fGetInptChrCode(vSource, nInptIdx); + nOutptIdx = fPutOutptCode(aWhole, nChrCode, nOutptIdx); + } + + break conversionSwitch; + + case 4: + + /* DOMString to ASCII or BinaryString or other unknown encodings */ + + aWhole = new fTAView(nOutptLen); + + /* transcription... */ + + for (var nIdx = 0; nIdx < nOutptLen; nIdx++) { + aWhole[nIdx] = vSource.charCodeAt(nIdx) & 0xff; + } + + break conversionSwitch; + + case 5: + + /* DOMString to UTF-8 or to UTF-16 */ + + /* mapping... */ + + nOutptLen = 0; + + for (var nMapIdx = 0; nMapIdx < nInptLen; nMapIdx++) { + if (nMapIdx === nCharStart) { nStartIdx = nOutptLen; } + nOutptLen += fGetOutptChrSize(vSource.charCodeAt(nMapIdx)); + if (nMapIdx === nCharEnd) { nEndIdx = nOutptLen; } + } + + aWhole = new fTAView(nOutptLen); + + /* transcription... */ + + for (var nOutptIdx = 0, nChrIdx = 0; nOutptIdx < nOutptLen; nChrIdx++) { + nOutptIdx = fPutOutptCode(aWhole, vSource.charCodeAt(nChrIdx), nOutptIdx); + } + + break conversionSwitch; + + case 6: + + /* DOMString to UTF-32 */ + + aWhole = new fTAView(nOutptLen); + + /* transcription... */ + + for (var nIdx = 0; nIdx < nOutptLen; nIdx++) { + aWhole[nIdx] = vSource.charCodeAt(nIdx); + } + + break conversionSwitch; + + case 7: + + aWhole = new fTAView(nOutptLen ? vSource : 0); + break conversionSwitch; + + } + + aRaw = nTranscrType > 3 && (nStartIdx > 0 || nEndIdx < aWhole.length - 1) ? aWhole.subarray(nStartIdx, nEndIdx) : aWhole; + + } + + this.buffer = aWhole.buffer; + this.bufferView = aWhole; + this.rawData = aRaw; + + Object.freeze(this); + +} + +/* CONSTRUCTOR'S METHODS */ + +StringView.loadUTF8CharCode = function (aChars, nIdx) { + /* The ISO 10646 view of UTF-8 considers valid codepoints encoded by 1-6 bytes, + * while the Unicode view of UTF-8 in 2003 has limited them to 1-4 bytes in order to + * match UTF-16's codepoints. In front of a 5/6-byte sequence StringView tries to + * encode it in any case. + */ + var nLen = aChars.length, nPart = aChars[nIdx]; + return nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? + /* (nPart - 252 << 30) may be not safe in ECMAScript! So...: */ + /* six bytes */ (nPart - 252) * 1073741824 + (aChars[nIdx + 1] - 128 << 24) + (aChars[nIdx + 2] - 128 << 18) + (aChars[nIdx + 3] - 128 << 12) + (aChars[nIdx + 4] - 128 << 6) + aChars[nIdx + 5] - 128 + : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? + /* five bytes */ (nPart - 248 << 24) + (aChars[nIdx + 1] - 128 << 18) + (aChars[nIdx + 2] - 128 << 12) + (aChars[nIdx + 3] - 128 << 6) + aChars[nIdx + 4] - 128 + : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? + /* four bytes */(nPart - 240 << 18) + (aChars[nIdx + 1] - 128 << 12) + (aChars[nIdx + 2] - 128 << 6) + aChars[nIdx + 3] - 128 + : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? + /* three bytes */ (nPart - 224 << 12) + (aChars[nIdx + 1] - 128 << 6) + aChars[nIdx + 2] - 128 + : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? + /* two bytes */ (nPart - 192 << 6) + aChars[nIdx + 1] - 128 + : + /* one byte */ nPart; + +}; + +StringView.putUTF8CharCode = function (aTarget, nChar, nPutAt) { + + var nIdx = nPutAt; + + if (nChar < 0x80 /* 128 */) { + /* one byte */ + aTarget[nIdx++] = nChar; + } else if (nChar < 0x800 /* 2048 */) { + /* two bytes */ + aTarget[nIdx++] = 0xc0 /* 192 */ + (nChar >>> 6); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } else if (nChar < 0x10000 /* 65536 */) { + /* three bytes */ + aTarget[nIdx++] = 0xe0 /* 224 */ + (nChar >>> 12); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } else if (nChar < 0x200000 /* 2097152 */) { + /* four bytes */ + aTarget[nIdx++] = 0xf0 /* 240 */ + (nChar >>> 18); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } else if (nChar < 0x4000000 /* 67108864 */) { + /* five bytes */ + aTarget[nIdx++] = 0xf8 /* 248 */ + (nChar >>> 24); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 18) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } else /* if (nChar <= 0x7fffffff) */ { /* 2147483647 */ + /* six bytes */ + aTarget[nIdx++] = 0xfc /* 252 */ + /* (nChar >>> 30) may be not safe in ECMAScript! So...: */ (nChar / 1073741824); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 24) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 18) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 12) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + ((nChar >>> 6) & 0x3f /* 63 */); + aTarget[nIdx++] = 0x80 /* 128 */ + (nChar & 0x3f /* 63 */); + } + + return nIdx; + +}; + +StringView.getUTF8CharLength = function (nChar) { + return nChar < 0x80 ? 1 : nChar < 0x800 ? 2 : nChar < 0x10000 ? 3 : nChar < 0x200000 ? 4 : nChar < 0x4000000 ? 5 : 6; +}; + +StringView.loadUTF16CharCode = function (aChars, nIdx) { + + /* UTF-16 to DOMString decoding algorithm */ + var nFrstChr = aChars[nIdx]; + + return nFrstChr > 0xD7BF /* 55231 */ && nIdx + 1 < aChars.length ? + (nFrstChr - 0xD800 /* 55296 */ << 10) + aChars[nIdx + 1] + 0x2400 /* 9216 */ + : nFrstChr; + +}; + +StringView.putUTF16CharCode = function (aTarget, nChar, nPutAt) { + + var nIdx = nPutAt; + + if (nChar < 0x10000 /* 65536 */) { + /* one element */ + aTarget[nIdx++] = nChar; + } else { + /* two elements */ + aTarget[nIdx++] = 0xD7C0 /* 55232 */ + (nChar >>> 10); + aTarget[nIdx++] = 0xDC00 /* 56320 */ + (nChar & 0x3FF /* 1023 */); + } + + return nIdx; + +}; + +StringView.getUTF16CharLength = function (nChar) { + return nChar < 0x10000 ? 1 : 2; +}; + +/* Array of bytes to base64 string decoding */ + +StringView.b64ToUint6 = function (nChr) { + + return nChr > 64 && nChr < 91 ? + nChr - 65 + : nChr > 96 && nChr < 123 ? + nChr - 71 + : nChr > 47 && nChr < 58 ? + nChr + 4 + : nChr === 43 ? + 62 + : nChr === 47 ? + 63 + : + 0; + +}; + +StringView.uint6ToB64 = function (nUint6) { + + return nUint6 < 26 ? + nUint6 + 65 + : nUint6 < 52 ? + nUint6 + 71 + : nUint6 < 62 ? + nUint6 - 4 + : nUint6 === 62 ? + 43 + : nUint6 === 63 ? + 47 + : + 65; + +}; + +/* Base64 string to array encoding */ + +StringView.bytesToBase64 = function (aBytes) { + + var eqLen = (3 - (aBytes.length % 3)) % 3, sB64Enc = ""; + + for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { + nMod3 = nIdx % 3; + /* Uncomment the following line in order to split the output in lines 76-character long: */ + /* + if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } + */ + nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); + if (nMod3 === 2 || aBytes.length - nIdx === 1) { + sB64Enc += String.fromCharCode(StringView.uint6ToB64(nUint24 >>> 18 & 63), StringView.uint6ToB64(nUint24 >>> 12 & 63), StringView.uint6ToB64(nUint24 >>> 6 & 63), StringView.uint6ToB64(nUint24 & 63)); + nUint24 = 0; + } + } + + return eqLen === 0 ? + sB64Enc + : + sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "=="); + + +}; + + +StringView.base64ToBytes = function (sBase64, nBlockBytes) { + + var + sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, + nOutLen = nBlockBytes ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockBytes) * nBlockBytes : nInLen * 3 + 1 >>> 2, aBytes = new Uint8Array(nOutLen); + + for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { + nMod4 = nInIdx & 3; + nUint24 |= StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; + if (nMod4 === 3 || nInLen - nInIdx === 1) { + for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { + aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; + } + nUint24 = 0; + } + } + + return aBytes; + +}; + +StringView.makeFromBase64 = function (sB64Inpt, sEncoding, nByteOffset, nLength) { + + return new StringView(sEncoding === "UTF-16" || sEncoding === "UTF-32" ? StringView.base64ToBytes(sB64Inpt, sEncoding === "UTF-16" ? 2 : 4).buffer : StringView.base64ToBytes(sB64Inpt), sEncoding, nByteOffset, nLength); + +}; + +/* DEFAULT VALUES */ + +StringView.prototype.encoding = "UTF-8"; /* Default encoding... */ + +/* INSTANCES' METHODS */ + +StringView.prototype.makeIndex = function (nChrLength, nStartFrom) { + + var + + aTarget = this.rawData, nChrEnd, nRawLength = aTarget.length, + nStartIdx = nStartFrom || 0, nIdxEnd = nStartIdx, nStopAtChr = isNaN(nChrLength) ? Infinity : nChrLength; + + if (nChrLength + 1 > aTarget.length) { throw new RangeError("StringView.prototype.makeIndex - The offset can\'t be major than the length of the array - 1."); } + + switch (this.encoding) { + + case "UTF-8": + + var nPart; + + for (nChrEnd = 0; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) { + nPart = aTarget[nIdxEnd]; + nIdxEnd += nPart > 251 && nPart < 254 && nIdxEnd + 5 < nRawLength ? 6 + : nPart > 247 && nPart < 252 && nIdxEnd + 4 < nRawLength ? 5 + : nPart > 239 && nPart < 248 && nIdxEnd + 3 < nRawLength ? 4 + : nPart > 223 && nPart < 240 && nIdxEnd + 2 < nRawLength ? 3 + : nPart > 191 && nPart < 224 && nIdxEnd + 1 < nRawLength ? 2 + : 1; + } + + break; + + case "UTF-16": + + for (nChrEnd = nStartIdx; nIdxEnd < nRawLength && nChrEnd < nStopAtChr; nChrEnd++) { + nIdxEnd += aTarget[nIdxEnd] > 0xD7BF /* 55231 */ && nIdxEnd + 1 < aTarget.length ? 2 : 1; + } + + break; + + default: + + nIdxEnd = nChrEnd = isFinite(nChrLength) ? nChrLength : nRawLength - 1; + + } + + if (nChrLength) { return nIdxEnd; } + + return nChrEnd; + +}; + +StringView.prototype.toBase64 = function (bWholeBuffer) { + + return StringView.bytesToBase64( + bWholeBuffer ? + ( + this.bufferView.constructor === Uint8Array ? + this.bufferView + : + new Uint8Array(this.buffer) + ) + : this.rawData.constructor === Uint8Array ? + this.rawData + : + new Uint8Array(this.buffer, this.rawData.byteOffset, this.rawData.length << (this.rawData.constructor === Uint16Array ? 1 : 2)) + ); + +}; + +StringView.prototype.subview = function (nCharOffset /* optional */, nCharLength /* optional */) { + + var + + nRawSubLen, nRawSubOffset, nSubOffset, nSubLen, bVariableLen = this.encoding === "UTF-8" || this.encoding === "UTF-16", + nThisLen, nRawLen = this.rawData.length; + + if (nRawLen === 0) { + return new StringView(this.buffer, this.encoding); + } + + nThisLen = bVariableLen ? this.makeIndex() : nRawLen; + nSubOffset = nCharOffset ? nCharOffset + 1 > nThisLen ? nThisLen : Math.max((nThisLen + nCharOffset) % nThisLen, 0) : 0; + nSubLen = Number.isInteger(nCharLength) ? Math.max(nCharLength, 0) + nSubOffset > nThisLen ? nThisLen - nSubOffset : nCharLength : nThisLen - nSubOffset; + + if (nSubOffset === 0 && nSubLen === nThisLen) { return this; } + + if (bVariableLen) { + nRawSubOffset = nSubOffset < nThisLen ? this.makeIndex(nSubOffset) : nThisLen; + nRawSubLen = nSubLen ? this.makeIndex(nSubLen, nRawSubOffset) - nRawSubOffset : 0; + } else { + nRawSubOffset = nSubOffset; + nRawSubLen = nSubLen; + } + + if (this.encoding === "UTF-16") { + nRawSubOffset <<= 1; + } else if (this.encoding === "UTF-32") { + nRawSubOffset <<= 2; + } + + return new StringView(this.buffer, this.encoding, this.rawData.byteOffset + nRawSubOffset, nRawSubLen); + +}; + +StringView.prototype.forEachChar = function (fCallback, oThat, nChrOffset, nChrLen) { + + var aSource = this.rawData, nRawEnd, nRawIdx; + + if (this.encoding === "UTF-8" || this.encoding === "UTF-16") { + + var fGetInptChrSize, fGetInptChrCode; + + if (this.encoding === "UTF-8") { + fGetInptChrSize = StringView.getUTF8CharLength; + fGetInptChrCode = StringView.loadUTF8CharCode; + } else if (this.encoding === "UTF-16") { + fGetInptChrSize = StringView.getUTF16CharLength; + fGetInptChrCode = StringView.loadUTF16CharCode; + } + + nRawIdx = isFinite(nChrOffset) ? this.makeIndex(nChrOffset) : 0; + nRawEnd = isFinite(nChrLen) ? this.makeIndex(nChrLen, nRawIdx) : aSource.length; + + for (var nChrCode, nChrIdx = 0; nRawIdx < nRawEnd; nChrIdx++) { + nChrCode = fGetInptChrCode(aSource, nRawIdx); + if (!oThat) { + fCallback(nChrCode, nChrIdx, nRawIdx, aSource); + } else { + fCallback.call(oThat, nChrCode, nChrIdx, nRawIdx, aSource); + } + nRawIdx += fGetInptChrSize(nChrCode); + } + + } else { + + nRawIdx = isFinite(nChrOffset) ? nChrOffset : 0; + nRawEnd = isFinite(nChrLen) ? nChrLen + nRawIdx : aSource.length; + + for (nRawIdx; nRawIdx < nRawEnd; nRawIdx++) { + if (!oThat) { + fCallback(aSource[nRawIdx], nRawIdx, nRawIdx, aSource); + } else { + fCallback.call(oThat, aSource[nRawIdx], nRawIdx, nRawIdx, aSource); + } + } + + } + +}; + +StringView.prototype.valueOf = StringView.prototype.toString = function () { + + if (this.encoding !== "UTF-8" && this.encoding !== "UTF-16") { + /* ASCII, UTF-32 or BinaryString to DOMString */ + return String.fromCharCode.apply(null, this.rawData); + } + + var fGetCode, fGetIncr, sView = ""; + + if (this.encoding === "UTF-8") { + fGetIncr = StringView.getUTF8CharLength; + fGetCode = StringView.loadUTF8CharCode; + } else if (this.encoding === "UTF-16") { + fGetIncr = StringView.getUTF16CharLength; + fGetCode = StringView.loadUTF16CharCode; + } + + for (var nChr, nLen = this.rawData.length, nIdx = 0; nIdx < nLen; nIdx += fGetIncr(nChr)) { + nChr = fGetCode(this.rawData, nIdx); + sView += String.fromCharCode(nChr); + } + + return sView; + +}; diff --git a/2/js/vendors/lzma_worker-min.js b/2/js/vendors/lzma_worker-min.js new file mode 100644 index 0000000..3654571 --- /dev/null +++ b/2/js/vendors/lzma_worker-min.js @@ -0,0 +1,2 @@ +var e=function(){"use strict";function r(e,r){postMessage({action:xt,cbn:r,result:e})}function t(e){var r=[];return r[e-1]=void 0,r}function o(e,r){return i(e[0]+r[0],e[1]+r[1])}function n(e,r){return u(~~Math.max(Math.min(e[1]/Ot,2147483647),-2147483648)&~~Math.max(Math.min(r[1]/Ot,2147483647),-2147483648),c(e)&c(r))}function s(e,r){var t,o;return e[0]==r[0]&&e[1]==r[1]?0:(t=0>e[1],o=0>r[1],t&&!o?-1:!t&&o?1:h(e,r)[1]<0?-1:1)}function i(e,r){var t,o;for(r%=0x10000000000000000,e%=0x10000000000000000,t=r%Ot,o=Math.floor(e/Ot)*Ot,r=r-t+o,e=e-o+t;0>e;)e+=Ot,r-=Ot;for(;e>4294967295;)e-=Ot,r+=Ot;for(r%=0x10000000000000000;r>0x7fffffff00000000;)r-=0x10000000000000000;for(;-0x8000000000000000>r;)r+=0x10000000000000000;return[e,r]}function _(e,r){return e[0]==r[0]&&e[1]==r[1]}function a(e){return e>=0?[e,0]:[e+Ot,-Ot]}function c(e){return e[0]>=2147483648?~~Math.max(Math.min(e[0]-Ot,2147483647),-2147483648):~~Math.max(Math.min(e[0],2147483647),-2147483648)}function u(e,r){var t,o;return t=e*Ot,o=r,0>r&&(o+=Ot),[o,t]}function f(e){return 30>=e?1<e[1])throw Error("Neg");return s=f(r),o=e[1]*s%0x10000000000000000,n=e[0]*s,t=n-n%Ot,o+=t,n-=t,o>=0x8000000000000000&&(o-=0x10000000000000000),[n,o]}function d(e,r){var t;return r&=63,t=f(r),i(Math.floor(e[0]/t),e[1]/t)}function p(e,r){var t;return r&=63,t=d(e,r),0>e[1]&&(t=o(t,m([2,0],63-r))),t}function h(e,r){return i(e[0]-r[0],e[1]-r[1])}function P(e,r){return e.Mc=r,e.Lc=0,e.Yb=r.length,e}function l(e){return e.Lc>=e.Yb?-1:255&e.Mc[e.Lc++]}function v(e,r,t,o){return e.Lc>=e.Yb?-1:(o=Math.min(o,e.Yb-e.Lc),M(e.Mc,e.Lc,r,t,o),e.Lc+=o,o)}function B(e){return e.Mc=t(32),e.Yb=0,e}function S(e){var r=e.Mc;return r.length=e.Yb,r}function g(e,r){e.Mc[e.Yb++]=r<<24>>24}function k(e,r,t,o){M(r,t,e.Mc,e.Yb,o),e.Yb+=o}function R(e,r,t,o,n){var s;for(s=r;t>s;++s)o[n++]=e.charCodeAt(s)}function M(e,r,t,o,n){for(var s=0;n>s;++s)t[o+s]=e[r+s]}function D(e,r){Ar(r,1<a;a+=8)g(o,255&c(d(n,a)));r.yb=(_.W=0,_.oc=t,_.pc=0,Mr(_),_.d.Ab=o,Fr(_),wr(_),br(_),_.$.rb=_.n+1-2,Qr(_.$,1<<_.Y),_.i.rb=_.n+1-2,Qr(_.i,1<<_.Y),void(_.g=Gt),X({},_))}function w(e,r,t){return e.Nb=B({}),b(e,P({},r),e.Nb,a(r.length),t),e}function E(e,r,t){var o,n,s,i,_="",c=[];for(n=0;5>n;++n){if(s=l(r),-1==s)throw Error("truncated input");c[n]=s<<24>>24}if(o=ir({}),!ar(o,c))throw Error("corrupted input");for(n=0;64>n;n+=8){if(s=l(r),-1==s)throw Error("truncated input");s=s.toString(16),1==s.length&&(s="0"+s),_=s+""+_}/^0+$|^f+$/i.test(_)?e.Tb=At:(i=parseInt(_,16),e.Tb=i>4294967295?At:a(i)),e.yb=nr(o,r,t,e.Tb)}function L(e,r){return e.Nb=B({}),E(e,P({},r),e.Nb),e}function y(e,r,o,n){var s;e.Bc=r,e._b=o,s=r+o+n,(null==e.c||e.Kb!=s)&&(e.c=null,e.Kb=s,e.c=t(e.Kb)),e.H=e.Kb-o}function C(e,r){return e.c[e.f+e.o+r]}function z(e,r,t,o){var n,s;for(e.T&&e.o+r+o>e.h&&(o=e.h-(e.o+r)),++t,s=e.f+e.o+r,n=0;o>n&&e.c[s+n]==e.c[s+n-t];++n);return n}function F(e){return e.h-e.o}function I(e){var r,t,o;for(o=e.f+e.o-e.Bc,o>0&&--o,t=e.f+e.h-o,r=0;t>r;++r)e.c[r]=e.c[o+r];e.f-=o}function x(e){var r;++e.o,e.o>e.zb&&(r=e.f+e.o,r>e.H&&I(e),N(e))}function N(e){var r,t,o;if(!e.T)for(;;){if(o=-e.f+e.Kb-e.h,!o)return;if(r=v(e.cc,e.c,e.f+e.h,o),-1==r)return e.zb=e.h,t=e.f+e.zb,t>e.H&&(e.zb=e.H-e.f),void(e.T=1);e.h+=r,e.h>=e.o+e._b&&(e.zb=e.h-e._b)}}function O(e,r){e.f+=r,e.zb-=r,e.o-=r,e.h-=r}function A(e,r,o,n,s){var i,_,a;1073741567>r&&(e.Fc=16+(n>>1),a=~~((r+o+n+s)/2)+256,y(e,r+o,n+s,a),e.ob=n,i=r+1,e.p!=i&&(e.L=t(2*(e.p=i))),_=65536,e.qb&&(_=r-1,_|=_>>1,_|=_>>2,_|=_>>4,_|=_>>8,_>>=1,_|=65535,_>16777216&&(_>>=1),e.Ec=_,++_,_+=e.R),_!=e.rc&&(e.ub=t(e.rc=_)))}function H(e,r){var t,o,n,s,i,_,a,c,u,f,m,d,p,h,P,l,v,B,S,g,k;if(e.h>=e.o+e.ob)h=e.ob;else if(h=e.h-e.o,e.xb>h)return W(e),0;for(v=0,P=e.o>e.p?e.o-e.p:0,o=e.f+e.o,l=1,c=0,u=0,e.qb?(k=Tt[255&e.c[o]]^255&e.c[o+1],c=1023&k,k^=(255&e.c[o+2])<<8,u=65535&k,f=(k^Tt[255&e.c[o+3]]<<5)&e.Ec):f=255&e.c[o]^(255&e.c[o+1])<<8,n=e.ub[e.R+f]||0,e.qb&&(s=e.ub[c]||0,i=e.ub[1024+u]||0,e.ub[c]=e.o,e.ub[1024+u]=e.o,s>P&&e.c[e.f+s]==e.c[o]&&(r[v++]=l=2,r[v++]=e.o-s-1),i>P&&e.c[e.f+i]==e.c[o]&&(i==s&&(v-=2),r[v++]=l=3,r[v++]=e.o-i-1,s=i),0!=v&&s==n&&(v-=2,l=1)),e.ub[e.R+f]=e.o,S=(e.k<<1)+1,g=e.k<<1,d=p=e.w,0!=e.w&&n>P&&e.c[e.f+n+e.w]!=e.c[o+e.w]&&(r[v++]=l=e.w,r[v++]=e.o-n-1),t=e.Fc;;){if(P>=n||0==t--){e.L[S]=e.L[g]=0;break}if(a=e.o-n,_=(e.k>=a?e.k-a:e.k-a+e.p)<<1,B=e.f+n,m=p>d?d:p,e.c[B+m]==e.c[o+m]){for(;++m!=h&&e.c[B+m]==e.c[o+m];);if(m>l&&(r[v++]=l=m,r[v++]=a-1,m==h)){e.L[g]=e.L[_],e.L[S]=e.L[_+1];break}}(255&e.c[o+m])>(255&e.c[B+m])?(e.L[g]=n,g=_+1,n=e.L[g],p=m):(e.L[S]=n,S=_,n=e.L[S],d=m)}return W(e),v}function G(e){e.f=0,e.o=0,e.h=0,e.T=0,N(e),e.k=0,O(e,-1)}function W(e){var r;++e.k>=e.p&&(e.k=0),x(e),1073741823==e.o&&(r=e.o-e.p,T(e.L,2*e.p,r),T(e.ub,e.rc,r),O(e,r))}function T(e,r,t){var o,n;for(o=0;r>o;++o)n=e[o]||0,t>=n?n=0:n-=t,e[o]=n}function Z(e,r){e.qb=r>2,e.qb?(e.w=0,e.xb=4,e.R=66560):(e.w=2,e.xb=3,e.R=0)}function Y(e,r){var t,o,n,s,i,_,a,c,u,f,m,d,p,h,P,l,v;do{if(e.h>=e.o+e.ob)d=e.ob;else if(d=e.h-e.o,e.xb>d){W(e);continue}for(p=e.o>e.p?e.o-e.p:0,o=e.f+e.o,e.qb?(v=Tt[255&e.c[o]]^255&e.c[o+1],_=1023&v,e.ub[_]=e.o,v^=(255&e.c[o+2])<<8,a=65535&v,e.ub[1024+a]=e.o,c=(v^Tt[255&e.c[o+3]]<<5)&e.Ec):c=255&e.c[o]^(255&e.c[o+1])<<8,n=e.ub[e.R+c],e.ub[e.R+c]=e.o,P=(e.k<<1)+1,l=e.k<<1,f=m=e.w,t=e.Fc;;){if(p>=n||0==t--){e.L[P]=e.L[l]=0;break}if(i=e.o-n,s=(e.k>=i?e.k-i:e.k-i+e.p)<<1,h=e.f+n,u=m>f?f:m,e.c[h+u]==e.c[o+u]){for(;++u!=d&&e.c[h+u]==e.c[o+u];);if(u==d){e.L[l]=e.L[s],e.L[P]=e.L[s+1];break}}(255&e.c[o+u])>(255&e.c[h+u])?(e.L[l]=n,l=s+1,n=e.L[l],m=u):(e.L[P]=n,P=s,n=e.L[P],f=u)}W(e)}while(0!=--r)}function V(e,r,t){var o=e.o-r-1;for(0>o&&(o+=e.M);0!=t;--t)o>=e.M&&(o=0),e.Lb[e.o++]=e.Lb[o++],e.o>=e.M&&$(e)}function j(e,r){(null==e.Lb||e.M!=r)&&(e.Lb=t(r)),e.M=r,e.o=0,e.h=0}function $(e){var r=e.o-e.h;r&&(k(e.cc,e.Lb,e.h,r),e.o>=e.M&&(e.o=0),e.h=e.o)}function K(e,r){var t=e.o-r-1;return 0>t&&(t+=e.M),e.Lb[t]}function q(e,r){e.Lb[e.o++]=r,e.o>=e.M&&$(e)}function J(e){$(e),e.cc=null}function Q(e){return e-=2,4>e?e:3}function U(e){return 4>e?0:10>e?e-3:e-6}function X(e,r){return e.cb=r,e.Z=null,e.zc=1,e}function er(e,r){return e.Z=r,e.cb=null,e.zc=1,e}function rr(e){if(!e.zc)throw Error("bad state");return e.cb?or(e):tr(e),e.zc}function tr(e){var r=sr(e.Z);if(-1==r)throw Error("corrupted input");e.Pb=At,e.Pc=e.Z.g,(r||s(e.Z.Nc,Gt)>=0&&s(e.Z.g,e.Z.Nc)>=0)&&($(e.Z.B),J(e.Z.B),e.Z.e.Ab=null,e.zc=0)}function or(e){Rr(e.cb,e.cb.Xb,e.cb.uc,e.cb.Kc),e.Pb=e.cb.Xb[0],e.cb.Kc[0]&&(Or(e.cb),e.zc=0)}function nr(e,r,t,o){return e.e.Ab=r,J(e.B),e.B.cc=t,_r(e),e.U=0,e.ib=0,e.Jc=0,e.Ic=0,e.Qc=0,e.Nc=o,e.g=Gt,e.jc=0,er({},e)}function sr(e){var r,t,n,i,_,u;if(u=c(e.g)&e.Dc,vt(e.e,e.Gb,(e.U<<4)+u)){if(vt(e.e,e.Zb,e.U))n=0,vt(e.e,e.Cb,e.U)?(vt(e.e,e.Db,e.U)?(vt(e.e,e.Eb,e.U)?(t=e.Qc,e.Qc=e.Ic):t=e.Ic,e.Ic=e.Jc):t=e.Jc,e.Jc=e.ib,e.ib=t):vt(e.e,e.pb,(e.U<<4)+u)||(e.U=7>e.U?9:11,n=1),n||(n=mr(e.sb,e.e,u)+2,e.U=7>e.U?8:11);else if(e.Qc=e.Ic,e.Ic=e.Jc,e.Jc=e.ib,n=2+mr(e.Rb,e.e,u),e.U=7>e.U?7:10,_=at(e.kb[Q(n)],e.e),_>=4){if(i=(_>>1)-1,e.ib=(2|1&_)<_)e.ib+=ut(e.kc,e.ib-_-1,e.e,i);else if(e.ib+=Bt(e.e,i-4)<<4,e.ib+=ct(e.Fb,e.e),0>e.ib)return-1==e.ib?1:-1}else e.ib=_;if(s(a(e.ib),e.g)>=0||e.ib>=e.nb)return-1;V(e.B,e.ib,n),e.g=o(e.g,a(n)),e.jc=K(e.B,0)}else r=Pr(e.gb,c(e.g),e.jc),e.jc=7>e.U?vr(r,e.e):Br(r,e.e,K(e.B,e.ib)),q(e.B,e.jc),e.U=U(e.U),e.g=o(e.g,Wt);return 0}function ir(e){e.B={},e.e={},e.Gb=t(192),e.Zb=t(12),e.Cb=t(12),e.Db=t(12),e.Eb=t(12),e.pb=t(192),e.kb=t(4),e.kc=t(114),e.Fb=_t({},4),e.Rb=dr({}),e.sb=dr({}),e.gb={};for(var r=0;4>r;++r)e.kb[r]=_t({},6);return e}function _r(e){e.B.h=0,e.B.o=0,gt(e.Gb),gt(e.pb),gt(e.Zb),gt(e.Cb),gt(e.Db),gt(e.Eb),gt(e.kc),lr(e.gb);for(var r=0;4>r;++r)gt(e.kb[r].G);pr(e.Rb),pr(e.sb),gt(e.Fb.G),St(e.e)}function ar(e,r){var t,o,n,s,i,_,a;if(5>r.length)return 0;for(a=255&r[0],n=a%9,_=~~(a/9),s=_%5,i=~~(_/5),t=0,o=0;4>o;++o)t+=(255&r[1+o])<<8*o;return t>99999999||!ur(e,n,s,i)?0:cr(e,t)}function cr(e,r){return 0>r?0:(e.Ob!=r&&(e.Ob=r,e.nb=Math.max(e.Ob,1),j(e.B,Math.max(e.nb,4096))),1)}function ur(e,r,t,o){if(r>8||t>4||o>4)return 0;hr(e.gb,t,r);var n=1<e.O;++e.O)e.ec[e.O]=_t({},3),e.hc[e.O]=_t({},3)}function mr(e,r,t){if(!vt(r,e.wc,0))return at(e.ec[t],r);var o=8;return o+=vt(r,e.wc,1)?8+at(e.tc,r):at(e.hc[t],r)}function dr(e){return e.wc=t(2),e.ec=t(16),e.hc=t(16),e.tc=_t({},8),e.O=0,e}function pr(e){gt(e.wc);for(var r=0;e.O>r;++r)gt(e.ec[r].G),gt(e.hc[r].G);gt(e.tc.G)}function hr(e,r,o){var n,s;if(null==e.V||e.u!=o||e.I!=r)for(e.I=r,e.qc=(1<n;++n)e.V[n]=Sr({})}function Pr(e,r,t){return e.V[((r&e.qc)<>>8-e.u)]}function lr(e){var r,t;for(t=1<r;++r)gt(e.V[r].Ib)}function vr(e,r){var t=1;do t=t<<1|vt(r,e.Ib,t);while(256>t);return t<<24>>24}function Br(e,r,t){var o,n,s=1;do if(n=t>>7&1,t<<=1,o=vt(r,e.Ib,(1+n<<8)+s),s=s<<1|o,n!=o){for(;256>s;)s=s<<1|vt(r,e.Ib,s);break}while(256>s);return s<<24>>24}function Sr(e){return e.Ib=t(768),e}function gr(e,r){var t,o,n,s;e.jb=r,n=e.a[r].r,o=e.a[r].j;do e.a[r].t&&(st(e.a[n]),e.a[n].r=n-1,e.a[r].Ac&&(e.a[n-1].t=0,e.a[n-1].r=e.a[r].r2,e.a[n-1].j=e.a[r].j2)),s=n,t=o,o=e.a[s].j,n=e.a[s].r,e.a[s].j=t,e.a[s].r=r,r=s;while(r>0);return e.mb=e.a[0].j,e.q=e.a[0].r}function kr(e){e.l=0,e.J=0;for(var r=0;4>r;++r)e.v[r]=0}function Rr(e,r,t,n){var i,u,f,m,d,p,P,l,v,B,S,g,k,R,M;if(r[0]=Gt,t[0]=Gt,n[0]=1,e.oc&&(e.b.cc=e.oc,G(e.b),e.W=1,e.oc=null),!e.pc){if(e.pc=1,R=e.g,_(e.g,Gt)){if(!F(e.b))return void Er(e,c(e.g));xr(e),k=c(e.g)&e.y,kt(e.d,e.C,(e.l<<4)+k,0),e.l=U(e.l),f=C(e.b,-e.s),rt(Xr(e.A,c(e.g),e.J),e.d,f),e.J=f,--e.s,e.g=o(e.g,Wt)}if(!F(e.b))return void Er(e,c(e.g));for(;;){if(P=Lr(e,c(e.g)),B=e.mb,k=c(e.g)&e.y,u=(e.l<<4)+k,1==P&&-1==B)kt(e.d,e.C,u,0),f=C(e.b,-e.s),M=Xr(e.A,c(e.g),e.J),7>e.l?rt(M,e.d,f):(v=C(e.b,-e.v[0]-1-e.s),tt(M,e.d,v,f)),e.J=f,e.l=U(e.l);else{if(kt(e.d,e.C,u,1),4>B){if(kt(e.d,e.bb,e.l,1),B?(kt(e.d,e.hb,e.l,1),1==B?kt(e.d,e.Ub,e.l,0):(kt(e.d,e.Ub,e.l,1),kt(e.d,e.vc,e.l,B-2))):(kt(e.d,e.hb,e.l,0),1==P?kt(e.d,e._,u,0):kt(e.d,e._,u,1)),1==P?e.l=7>e.l?9:11:(Kr(e.i,e.d,P-2,k),e.l=7>e.l?8:11),m=e.v[B],0!=B){for(p=B;p>=1;--p)e.v[p]=e.v[p-1];e.v[0]=m}}else{for(kt(e.d,e.bb,e.l,0),e.l=7>e.l?7:10,Kr(e.$,e.d,P-2,k),B-=4,g=Tr(B),l=Q(P),mt(e.K[l],e.d,g),g>=4&&(d=(g>>1)-1,i=(2|1&g)<g?Pt(e.Sb,i-g-1,e.d,d,S):(Rt(e.d,S>>4,d-4),pt(e.S,e.d,15&S),++e.Qb)),m=B,p=3;p>=1;--p)e.v[p]=e.v[p-1];e.v[0]=m,++e.Mb}e.J=C(e.b,P-1-e.s)}if(e.s-=P,e.g=o(e.g,a(P)),!e.s){if(e.Mb>=128&&wr(e),e.Qb>=16&&br(e),r[0]=e.g,t[0]=Mt(e.d),!F(e.b))return void Er(e,c(e.g));if(s(h(e.g,R),[4096,0])>=0)return e.pc=0,void(n[0]=0)}}}}function Mr(e){var r,t;e.b||(r={},t=4,e.X||(t=2),Z(r,t),e.b=r),Ur(e.A,e.eb,e.fb),(e.ab!=e.wb||e.Hb!=e.n)&&(A(e.b,e.ab,4096,e.n,274),e.wb=e.ab,e.Hb=e.n)}function Dr(e){var r;for(e.v=t(4),e.a=[],e.d={},e.C=t(192),e.bb=t(12),e.hb=t(12),e.Ub=t(12),e.vc=t(12),e._=t(192),e.K=[],e.Sb=t(114),e.S=ft({},4),e.$=qr({}),e.i=qr({}),e.A={},e.m=[],e.P=[],e.lb=[],e.nc=t(16),e.x=t(4),e.Q=t(4),e.Xb=[Gt],e.uc=[Gt],e.Kc=[0],e.fc=t(5),e.yc=t(128),e.vb=0,e.X=1,e.D=0,e.Hb=-1,e.mb=0,r=0;4096>r;++r)e.a[r]={};for(r=0;4>r;++r)e.K[r]=ft({},6);return e}function br(e){for(var r=0;16>r;++r)e.nc[r]=ht(e.S,r);e.Qb=0}function wr(e){var r,t,o,n,s,i,_,a;for(n=4;128>n;++n)i=Tr(n),o=(i>>1)-1,r=(2|1&i)<s;++s){for(t=e.K[s],_=s<<6,i=0;e.$b>i;++i)e.P[_+i]=dt(t,i);for(i=14;e.$b>i;++i)e.P[_+i]+=(i>>1)-1-4<<6;for(a=128*s,n=0;4>n;++n)e.lb[a+n]=e.P[_+n];for(;128>n;++n)e.lb[a+n]=e.P[_+Tr(n)]+e.yc[n]}e.Mb=0}function Er(e,r){Nr(e),Wr(e,r&e.y);for(var t=0;5>t;++t)bt(e.d)}function Lr(e,r){var t,o,n,s,i,_,a,c,u,f,m,d,p,h,P,l,v,B,S,g,k,R,M,D,b,w,E,L,y,I,x,N,O,A,H,G,W,T,Z,Y,V,j,$,K,q,J,Q,X,er,rr;if(e.jb!=e.q)return p=e.a[e.q].r-e.q,e.mb=e.a[e.q].j,e.q=e.a[e.q].r,p;if(e.q=e.jb=0,e.N?(d=e.vb,e.N=0):d=xr(e),E=e.D,b=F(e.b)+1,2>b)return e.mb=-1,1;for(b>273&&(b=273),Y=0,u=0;4>u;++u)e.x[u]=e.v[u],e.Q[u]=z(e.b,-1,e.x[u],273),e.Q[u]>e.Q[Y]&&(Y=u);if(e.Q[Y]>=e.n)return e.mb=Y,p=e.Q[Y],Ir(e,p-1),p;if(d>=e.n)return e.mb=e.m[E-1]+4,Ir(e,d-1),d;if(a=C(e.b,-1),v=C(e.b,-e.v[0]-1-1),2>d&&a!=v&&2>e.Q[Y])return e.mb=-1,1;if(e.a[0].Hc=e.l,A=r&e.y,e.a[1].z=Yt[e.C[(e.l<<4)+A]>>>2]+nt(Xr(e.A,r,e.J),e.l>=7,v,a),st(e.a[1]),B=Yt[2048-e.C[(e.l<<4)+A]>>>2],Z=B+Yt[2048-e.bb[e.l]>>>2],v==a&&(V=Z+zr(e,e.l,A),e.a[1].z>V&&(e.a[1].z=V,it(e.a[1]))),m=d>=e.Q[Y]?d:e.Q[Y],2>m)return e.mb=e.a[1].j,1;e.a[1].r=0,e.a[0].bc=e.x[0],e.a[0].ac=e.x[1],e.a[0].dc=e.x[2],e.a[0].lc=e.x[3],f=m;do e.a[f--].z=268435455;while(f>=2);for(u=0;4>u;++u)if(T=e.Q[u],!(2>T)){G=Z+Cr(e,u,e.l,A);do s=G+Jr(e.i,T-2,A),x=e.a[T],x.z>s&&(x.z=s,x.r=0,x.j=u,x.t=0);while(--T>=2)}if(D=B+Yt[e.bb[e.l]>>>2],f=e.Q[0]>=2?e.Q[0]+1:2,d>=f){for(L=0;f>e.m[L];)L+=2;for(;c=e.m[L+1],s=D+yr(e,c,f,A),x=e.a[f],x.z>s&&(x.z=s,x.r=0,x.j=c+4,x.t=0),f!=e.m[L]||(L+=2,L!=E);++f);}for(t=0;;){if(++t,t==m)return gr(e,t);if(S=xr(e),E=e.D,S>=e.n)return e.vb=S,e.N=1,gr(e,t);if(++r,O=e.a[t].r,e.a[t].t?(--O,e.a[t].Ac?($=e.a[e.a[t].r2].Hc,$=4>e.a[t].j2?7>$?8:11:7>$?7:10):$=e.a[O].Hc,$=U($)):$=e.a[O].Hc,O==t-1?$=e.a[t].j?U($):7>$?9:11:(e.a[t].t&&e.a[t].Ac?(O=e.a[t].r2,N=e.a[t].j2,$=7>$?8:11):(N=e.a[t].j,$=4>N?7>$?8:11:7>$?7:10),I=e.a[O],4>N?N?1==N?(e.x[0]=I.ac,e.x[1]=I.bc,e.x[2]=I.dc,e.x[3]=I.lc):2==N?(e.x[0]=I.dc,e.x[1]=I.bc,e.x[2]=I.ac,e.x[3]=I.lc):(e.x[0]=I.lc,e.x[1]=I.bc,e.x[2]=I.ac,e.x[3]=I.dc):(e.x[0]=I.bc,e.x[1]=I.ac,e.x[2]=I.dc,e.x[3]=I.lc):(e.x[0]=N-4,e.x[1]=I.bc,e.x[2]=I.ac,e.x[3]=I.dc)),e.a[t].Hc=$,e.a[t].bc=e.x[0],e.a[t].ac=e.x[1],e.a[t].dc=e.x[2],e.a[t].lc=e.x[3],_=e.a[t].z,a=C(e.b,-1),v=C(e.b,-e.x[0]-1-1),A=r&e.y,o=_+Yt[e.C[($<<4)+A]>>>2]+nt(Xr(e.A,r,C(e.b,-2)),$>=7,v,a),R=e.a[t+1],g=0,R.z>o&&(R.z=o,R.r=t,R.j=-1,R.t=0,g=1),B=_+Yt[2048-e.C[($<<4)+A]>>>2],Z=B+Yt[2048-e.bb[$]>>>2],v!=a||t>R.r&&!R.j||(V=Z+(Yt[e.hb[$]>>>2]+Yt[e._[($<<4)+A]>>>2]),R.z>=V&&(R.z=V,R.r=t,R.j=0,R.t=0,g=1)),w=F(e.b)+1,w=w>4095-t?4095-t:w,b=w,!(2>b)){if(b>e.n&&(b=e.n),!g&&v!=a&&(q=Math.min(w-1,e.n),P=z(e.b,0,e.x[0],q),P>=2)){for(K=U($),H=r+1&e.y,M=o+Yt[2048-e.C[(K<<4)+H]>>>2]+Yt[2048-e.bb[K]>>>2],y=t+1+P;y>m;)e.a[++m].z=268435455;s=M+(J=Jr(e.i,P-2,H),J+Cr(e,0,K,H)),x=e.a[y],x.z>s&&(x.z=s,x.r=t+1,x.j=0,x.t=1,x.Ac=0)}for(j=2,W=0;4>W;++W)if(h=z(e.b,-1,e.x[W],b),!(2>h)){l=h;do{for(;t+h>m;)e.a[++m].z=268435455;s=Z+(Q=Jr(e.i,h-2,A),Q+Cr(e,W,$,A)),x=e.a[t+h],x.z>s&&(x.z=s,x.r=t,x.j=W,x.t=0)}while(--h>=2);if(h=l,W||(j=h+1),w>h&&(q=Math.min(w-1-h,e.n),P=z(e.b,h,e.x[W],q),P>=2)){for(K=7>$?8:11,H=r+h&e.y,n=Z+(X=Jr(e.i,h-2,A),X+Cr(e,W,$,A))+Yt[e.C[(K<<4)+H]>>>2]+nt(Xr(e.A,r+h,C(e.b,h-1-1)),1,C(e.b,h-1-(e.x[W]+1)),C(e.b,h-1)),K=U(K),H=r+h+1&e.y,k=n+Yt[2048-e.C[(K<<4)+H]>>>2],M=k+Yt[2048-e.bb[K]>>>2],y=h+1+P;t+y>m;)e.a[++m].z=268435455;s=M+(er=Jr(e.i,P-2,H),er+Cr(e,0,K,H)),x=e.a[t+y],x.z>s&&(x.z=s,x.r=t+h+1,x.j=0,x.t=1,x.Ac=1,x.r2=t,x.j2=W)}}if(S>b){for(S=b,E=0;S>e.m[E];E+=2);e.m[E]=S,E+=2}if(S>=j){for(D=B+Yt[e.bb[$]>>>2];t+S>m;)e.a[++m].z=268435455;for(L=0;j>e.m[L];)L+=2;for(h=j;;++h)if(i=e.m[L+1],s=D+yr(e,i,h,A),x=e.a[t+h],x.z>s&&(x.z=s,x.r=t,x.j=i+4,x.t=0),h==e.m[L]){if(w>h&&(q=Math.min(w-1-h,e.n),P=z(e.b,h,i,q),P>=2)){for(K=7>$?7:10,H=r+h&e.y,n=s+Yt[e.C[(K<<4)+H]>>>2]+nt(Xr(e.A,r+h,C(e.b,h-1-1)),1,C(e.b,h-(i+1)-1),C(e.b,h-1)),K=U(K),H=r+h+1&e.y,k=n+Yt[2048-e.C[(K<<4)+H]>>>2],M=k+Yt[2048-e.bb[K]>>>2],y=h+1+P;t+y>m;)e.a[++m].z=268435455;s=M+(rr=Jr(e.i,P-2,H),rr+Cr(e,0,K,H)),x=e.a[t+y],x.z>s&&(x.z=s,x.r=t+h+1,x.j=0,x.t=1,x.Ac=1,x.r2=t,x.j2=i+4)}if(L+=2,L==E)break}}}}}function yr(e,r,t,o){var n,s=Q(t);return n=128>r?e.lb[128*s+r]:e.P[(s<<6)+Zr(r)]+e.nc[15&r],n+Jr(e.$,t-2,o)}function Cr(e,r,t,o){var n;return r?(n=Yt[2048-e.hb[t]>>>2],1==r?n+=Yt[e.Ub[t]>>>2]:(n+=Yt[2048-e.Ub[t]>>>2],n+=wt(e.vc[t],r-2))):(n=Yt[e.hb[t]>>>2],n+=Yt[2048-e._[(t<<4)+o]>>>2]),n}function zr(e,r,t){return Yt[e.hb[r]>>>2]+Yt[e._[(r<<4)+t]>>>2]}function Fr(e){kr(e),Dt(e.d),gt(e.C),gt(e._),gt(e.bb),gt(e.hb),gt(e.Ub),gt(e.vc),gt(e.Sb),et(e.A);for(var r=0;4>r;++r)gt(e.K[r].G);jr(e.$,1<0&&(Y(e.b,r),e.s+=r)}function xr(e){var r=0;return e.D=H(e.b,e.m),e.D>0&&(r=e.m[e.D-2],r==e.n&&(r+=z(e.b,r-1,e.m[e.D-1],273-r))),++e.s,r}function Nr(e){e.b&&e.W&&(e.b.cc=null,e.W=0)}function Or(e){Nr(e),e.d.Ab=null}function Ar(e,r){e.ab=r;for(var t=0;r>1<>24;for(var t=0;4>t;++t)e.fc[1+t]=e.ab>>8*t<<24>>24;k(r,e.fc,0,5)}function Wr(e,r){if(e.Gc){kt(e.d,e.C,(e.l<<4)+r,1),kt(e.d,e.bb,e.l,0),e.l=7>e.l?7:10,Kr(e.$,e.d,0,r);var t=Q(2);mt(e.K[t],e.d,63),Rt(e.d,67108863,26),pt(e.S,e.d,15)}}function Tr(e){return 2048>e?Zt[e]:2097152>e?Zt[e>>10]+20:Zt[e>>20]+40}function Zr(e){return 131072>e?Zt[e>>6]+12:134217728>e?Zt[e>>16]+32:Zt[e>>26]+52}function Yr(e,r,t,o){8>t?(kt(r,e.db,0,0),mt(e.Vb[o],r,t)):(t-=8,kt(r,e.db,0,1),8>t?(kt(r,e.db,1,0),mt(e.Wb[o],r,t)):(kt(r,e.db,1,1),mt(e.ic,r,t-8)))}function Vr(e){e.db=t(2),e.Vb=t(16),e.Wb=t(16),e.ic=ft({},8);for(var r=0;16>r;++r)e.Vb[r]=ft({},3),e.Wb[r]=ft({},3);return e}function jr(e,r){gt(e.db);for(var t=0;r>t;++t)gt(e.Vb[t].G),gt(e.Wb[t].G);gt(e.ic.G)}function $r(e,r,t,o,n){var s,i,_,a,c;for(s=Yt[e.db[0]>>>2],i=Yt[2048-e.db[0]>>>2],_=i+Yt[e.db[1]>>>2],a=i+Yt[2048-e.db[1]>>>2],c=0,c=0;8>c;++c){if(c>=t)return;o[n+c]=s+dt(e.Vb[r],c)}for(;16>c;++c){if(c>=t)return;o[n+c]=_+dt(e.Wb[r],c-8)}for(;t>c;++c)o[n+c]=a+dt(e.ic,c-8-8)}function Kr(e,r,t,o){Yr(e,r,t,o),0==--e.sc[o]&&($r(e,o,e.rb,e.Cc,272*o),e.sc[o]=e.rb)}function qr(e){return Vr(e),e.Cc=[],e.sc=[],e}function Jr(e,r,t){return e.Cc[272*t+r]}function Qr(e,r){for(var t=0;r>t;++t)$r(e,t,e.rb,e.Cc,272*t),e.sc[t]=e.rb}function Ur(e,r,o){var n,s;if(null==e.V||e.u!=o||e.I!=r)for(e.I=r,e.qc=(1<n;++n)e.V[n]=ot({})}function Xr(e,r,t){return e.V[((r&e.qc)<>>8-e.u)]}function et(e){var r,t=1<r;++r)gt(e.V[r].tb)}function rt(e,r,t){var o,n,s=1;for(n=7;n>=0;--n)o=t>>n&1,kt(r,e.tb,s,o),s=s<<1|o}function tt(e,r,t,o){var n,s,i,_,a=1,c=1;for(s=7;s>=0;--s)n=o>>s&1,_=c,a&&(i=t>>s&1,_+=1+i<<8,a=i==n),kt(r,e.tb,_,n),c=c<<1|n}function ot(e){return e.tb=t(768),e}function nt(e,r,t,o){var n,s,i=1,_=7,a=0;if(r)for(;_>=0;--_)if(s=t>>_&1,n=o>>_&1,a+=wt(e.tb[(1+s<<8)+i],n),i=i<<1|n,s!=n){--_;break}for(;_>=0;--_)n=o>>_&1,a+=wt(e.tb[i],n),i=i<<1|n;return a}function st(e){e.j=-1,e.t=0}function it(e){e.j=0,e.t=0}function _t(e,r){return e.F=r,e.G=t(1<o;++o)t=vt(r,e.G,n),n<<=1,n+=t,s|=t<s;++s)n=vt(t,e,r+i),i<<=1,i+=n,_|=n<>>n&1,kt(r,e.G,s,o),s=s<<1|o}function dt(e,r){var t,o,n=1,s=0;for(o=e.F;0!=o;)--o,t=r>>>o&1,s+=wt(e.G[n],t),n=(n<<1)+t;return s}function pt(e,r,t){var o,n,s=1;for(n=0;e.F>n;++n)o=1&t,kt(r,e.G,s,o),s=s<<1|o,t>>=1}function ht(e,r){var t,o,n=1,s=0;for(o=e.F;0!=o;--o)t=1&r,r>>>=1,s+=wt(e.G[n],t),n=n<<1|t;return s}function Pt(e,r,t,o,n){var s,i,_=1;for(i=0;o>i;++i)s=1&n,kt(t,e,r+_,s),_=_<<1|s,n>>=1}function lt(e,r,t,o){var n,s,i=1,_=0;for(s=t;0!=s;--s)n=1&o,o>>>=1,_+=Yt[(2047&(e[r+i]-n^-n))>>>2],i=i<<1|n;return _}function vt(e,r,t){var o,n=r[t];return o=(e.E>>>11)*n,(-2147483648^o)>(-2147483648^e.Bb)?(e.E=o,r[t]=n+(2048-n>>>5)<<16>>16,-16777216&e.E||(e.Bb=e.Bb<<8|l(e.Ab),e.E<<=8),0):(e.E-=o,e.Bb-=o,r[t]=n-(n>>>5)<<16>>16,-16777216&e.E||(e.Bb=e.Bb<<8|l(e.Ab),e.E<<=8),1)}function Bt(e,r){var t,o,n=0;for(t=r;0!=t;--t)e.E>>>=1,o=e.Bb-e.E>>>31,e.Bb-=e.E&o-1,n=n<<1|1-o,-16777216&e.E||(e.Bb=e.Bb<<8|l(e.Ab),e.E<<=8);return n}function St(e){e.Bb=0,e.E=-1;for(var r=0;5>r;++r)e.Bb=e.Bb<<8|l(e.Ab)}function gt(e){for(var r=e.length-1;r>=0;--r)e[r]=1024}function kt(e,r,t,s){var i,_=r[t];i=(e.E>>>11)*_,s?(e.xc=o(e.xc,n(a(i),[4294967295,0])),e.E-=i,r[t]=_-(_>>>5)<<16>>16):(e.E=i,r[t]=_+(2048-_>>>5)<<16>>16),-16777216&e.E||(e.E<<=8,bt(e))}function Rt(e,r,t){for(var n=t-1;n>=0;--n)e.E>>>=1,1==(r>>>n&1)&&(e.xc=o(e.xc,a(e.E))),-16777216&e.E||(e.E<<=8,bt(e))}function Mt(e){return o(o(a(e.Jb),e.mc),[4,0])}function Dt(e){e.mc=Gt,e.xc=Gt,e.E=-1,e.Jb=1,e.Oc=0}function bt(e){var r,t=c(p(e.xc,32));if(0!=t||s(e.xc,[4278190080,0])<0){e.mc=o(e.mc,a(e.Jb)),r=e.Oc;do g(e.Ab,r+t),r=255;while(0!=--e.Jb);e.Oc=c(e.xc)>>>24}++e.Jb,e.xc=m(n(e.xc,[16777215,0]),8)}function wt(e,r){return Yt[(2047&(e-r^-r))>>>2]}function Et(e){for(var r,t,o,n=0,s=0,i=e.length,_=[],a=[];i>n;++n,++s){if(r=255&e[n],128&r)if(192==(224&r)){if(n+1>=i)return e;if(t=255&e[++n],128!=(192&t))return e;a[s]=(31&r)<<6|63&t}else{if(224!=(240&r))return e; + if(n+2>=i)return e;if(t=255&e[++n],128!=(192&t))return e;if(o=255&e[++n],128!=(192&o))return e;a[s]=(15&r)<<12|(63&t)<<6|63&o}else{if(!r)return e;a[s]=r}16383==s&&(_.push(String.fromCharCode.apply(String,a)),s=-1)}return s>0&&(a.length=s,_.push(String.fromCharCode.apply(String,a))),_.join("")}function Lt(e){var r,t,o,n=[],s=0,i=e.length;if("object"==typeof e)return e;for(R(e,0,i,n,0),o=0;i>o;++o)r=n[o],r>=1&&127>=r?++s:s+=!r||r>=128&&2047>=r?2:3;for(t=[],s=0,o=0;i>o;++o)r=n[o],r>=1&&127>=r?t[s++]=r<<24>>24:!r||r>=128&&2047>=r?(t[s++]=(192|r>>6&31)<<24>>24,t[s++]=(128|63&r)<<24>>24):(t[s++]=(224|r>>12&15)<<24>>24,t[s++]=(128|r>>6&63)<<24>>24,t[s++]=(128|63&r)<<24>>24);return t}function yt(e){return e[1]+e[0]}function Ct(e,t,o,n){function s(){try{for(var e,r=(new Date).getTime();rr(a.c.yb);)if(i=yt(a.c.yb.Pb)/yt(a.c.Tb),(new Date).getTime()-r>200)return n(i),Nt(s,0),0;n(1),e=S(a.c.Nb),Nt(o.bind(null,e),0)}catch(t){o(null,t)}}var i,_,a={},c=void 0===o&&void 0===n;if("function"!=typeof o&&(_=o,o=n=0),n=n||function(e){return void 0!==_?r(e,_):void 0},o=o||function(e,r){return void 0!==_?postMessage({action:Ft,cbn:_,result:e,error:r}):void 0},c){for(a.c=w({},Lt(e),Vt(t));rr(a.c.yb););return S(a.c.Nb)}try{a.c=w({},Lt(e),Vt(t)),n(0)}catch(u){return o(null,u)}Nt(s,0)}function zt(e,t,o){function n(){try{for(var e,r=0,i=(new Date).getTime();rr(c.d.yb);)if(++r%1e3==0&&(new Date).getTime()-i>200)return _&&(s=yt(c.d.yb.Z.g)/a,o(s)),Nt(n,0),0;o(1),e=Et(S(c.d.Nb)),Nt(t.bind(null,e),0)}catch(u){t(null,u)}}var s,i,_,a,c={},u=void 0===t&&void 0===o;if("function"!=typeof t&&(i=t,t=o=0),o=o||function(e){return void 0!==i?r(_?e:-1,i):void 0},t=t||function(e,r){return void 0!==i?postMessage({action:It,cbn:i,result:e,error:r}):void 0},u){for(c.d=L({},e);rr(c.d.yb););return Et(S(c.d.Nb))}try{c.d=L({},e),a=yt(c.d.Tb),_=a>-1,o(0)}catch(f){return t(null,f)}Nt(n,0)}var Ft=1,It=2,xt=3,Nt="function"==typeof setImmediate?setImmediate:setTimeout,Ot=4294967296,At=[4294967295,-Ot],Ht=[0,-0x8000000000000000],Gt=[0,0],Wt=[1,0],Tt=function(){var e,r,t,o=[];for(e=0;256>e;++e){for(t=e,r=0;8>r;++r)0!=(1&t)?t=t>>>1^-306674912:t>>>=1;o[e]=t}return o}(),Zt=function(){var e,r,t,o=2,n=[0,1];for(t=2;22>t;++t)for(r=1<<(t>>1)-1,e=0;r>e;++e,++o)n[o]=t<<24>>24;return n}(),Yt=function(){var e,r,t,o,n=[];for(r=8;r>=0;--r)for(o=1<<9-r-1,e=1<<9-r,t=o;e>t;++t)n[t]=(r<<6)+(e-t<<6>>>9-r-1);return n}(),Vt=function(){var e=[{s:16,f:64,m:0},{s:20,f:64,m:0},{s:19,f:64,m:1},{s:20,f:64,m:1},{s:21,f:128,m:1},{s:22,f:128,m:1},{s:23,f:128,m:1},{s:24,f:255,m:1},{s:25,f:255,m:1}];return function(r){return e[r-1]||e[6]}}();return"undefined"==typeof onmessage||"undefined"!=typeof window&&void 0!==window.document||!function(){onmessage=function(r){r&&r.gc&&(r.gc.action==It?e.decompress(r.gc.gc,r.gc.cbn):r.gc.action==Ft&&e.compress(r.gc.gc,r.gc.Rc,r.gc.cbn))}}(),{compress:Ct,decompress:zt}}();this.LZMA=this.LZMA_WORKER=e; \ No newline at end of file diff --git a/v1/js/minpubsub.js b/2/js/vendors/minpubsub.js similarity index 72% rename from v1/js/minpubsub.js rename to 2/js/vendors/minpubsub.js index 358008d..f0e6abc 100644 --- a/v1/js/minpubsub.js +++ b/2/js/vendors/minpubsub.js @@ -4,10 +4,11 @@ * MIT Licensed */ (function (context) { - var MinPubSub = {}; + const MinPubSub = {}; // the topic/subscription hash - var cache = context.c_ || {}; //check for 'c_' cache for unit testing + // noinspection JSUnresolvedVariable + const cache = context.c_ || {}; //check for 'c_' cache for unit testing MinPubSub.publish = function ( /* String */ topic, /* Array? */ args) { // summary: @@ -24,7 +25,7 @@ // // publish('/some/topic', ['a','b','c']); - var subs = cache[topic], + let subs = cache[topic], len = subs ? subs.length : 0; //can change loop or reverse array if the order matters @@ -65,9 +66,9 @@ // var handle = subscribe('/some/topic', function(){}); // unsubscribe(handle); - var subs = cache[callback ? handle : handle[0]], - callback = callback || handle[1], + let subs = cache[callback ? handle : handle[0]], len = subs ? subs.length : 0; + callback = callback || handle[1]; while (len--) { if (subs[len] === callback) { @@ -77,19 +78,24 @@ }; // UMD definition to allow for CommonJS, AMD and legacy window + // noinspection JSUnresolvedVariable if (typeof module === 'object' && module.exports) { // CommonJS, just export + // noinspection JSUnresolvedVariable,JSUndeclaredVariable module.exports = exports = MinPubSub; - } else if (typeof define === 'function' && define.amd) { - // AMD support - define(function () { - return MinPubSub; - }); - } else if (typeof context === 'object') { - // If no AMD and we are in the browser, attach to window - context.publish = MinPubSub.publish; - context.subscribe = MinPubSub.subscribe; - context.unsubscribe = MinPubSub.unsubscribe; + } else { // noinspection JSUnresolvedVariable + if (typeof define === 'function' && define.amd) { + // AMD support + // noinspection JSUnresolvedFunction + define(function () { + return MinPubSub; + }); + } else if (typeof context === 'object') { + // If no AMD and we are in the browser, attach to window + context.publish = MinPubSub.publish; + context.subscribe = MinPubSub.subscribe; + context.unsubscribe = MinPubSub.unsubscribe; + } } })(this.window); \ No newline at end of file diff --git a/v1.1/favicon.png b/2/pages/credits/favicon.png similarity index 100% rename from v1.1/favicon.png rename to 2/pages/credits/favicon.png diff --git a/v1.1/pages/credits/index.html b/2/pages/credits/index.html similarity index 100% rename from v1.1/pages/credits/index.html rename to 2/pages/credits/index.html diff --git a/v1.1/pages/credits/peep/aimee.png b/2/pages/credits/peep/aimee.png similarity index 100% rename from v1.1/pages/credits/peep/aimee.png rename to 2/pages/credits/peep/aimee.png diff --git a/v1.1/pages/credits/peep/buster.png b/2/pages/credits/peep/buster.png similarity index 100% rename from v1.1/pages/credits/peep/buster.png rename to 2/pages/credits/peep/buster.png diff --git a/v1.1/pages/credits/peep/chad.png b/2/pages/credits/peep/chad.png similarity index 100% rename from v1.1/pages/credits/peep/chad.png rename to 2/pages/credits/peep/chad.png diff --git a/v1.1/pages/credits/peep/jared.png b/2/pages/credits/peep/jared.png similarity index 100% rename from v1.1/pages/credits/peep/jared.png rename to 2/pages/credits/peep/jared.png diff --git a/v1.1/pages/credits/peep/ljt.png b/2/pages/credits/peep/ljt.png similarity index 100% rename from v1.1/pages/credits/peep/ljt.png rename to 2/pages/credits/peep/ljt.png diff --git a/v1.1/pages/credits/peep/mark.png b/2/pages/credits/peep/mark.png similarity index 100% rename from v1.1/pages/credits/peep/mark.png rename to 2/pages/credits/peep/mark.png diff --git a/v1.1/pages/credits/peep/matt.png b/2/pages/credits/peep/matt.png similarity index 100% rename from v1.1/pages/credits/peep/matt.png rename to 2/pages/credits/peep/matt.png diff --git a/v1.1/pages/credits/peep/michael_duke.png b/2/pages/credits/peep/michael_duke.png similarity index 100% rename from v1.1/pages/credits/peep/michael_duke.png rename to 2/pages/credits/peep/michael_duke.png diff --git a/v1.1/pages/credits/peep/michael_huff.png b/2/pages/credits/peep/michael_huff.png similarity index 100% rename from v1.1/pages/credits/peep/michael_huff.png rename to 2/pages/credits/peep/michael_huff.png diff --git a/v1.1/pages/credits/peep/natalie.png b/2/pages/credits/peep/natalie.png similarity index 100% rename from v1.1/pages/credits/peep/natalie.png rename to 2/pages/credits/peep/natalie.png diff --git a/v1.1/pages/credits/peep/nicholas.png b/2/pages/credits/peep/nicholas.png similarity index 100% rename from v1.1/pages/credits/peep/nicholas.png rename to 2/pages/credits/peep/nicholas.png diff --git a/v1.1/pages/credits/peep/noel.png b/2/pages/credits/peep/noel.png similarity index 100% rename from v1.1/pages/credits/peep/noel.png rename to 2/pages/credits/peep/noel.png diff --git a/v1.1/pages/credits/peep/phil.png b/2/pages/credits/peep/phil.png similarity index 100% rename from v1.1/pages/credits/peep/phil.png rename to 2/pages/credits/peep/phil.png diff --git a/v1.1/pages/credits/peep/philippe.png b/2/pages/credits/peep/philippe.png similarity index 100% rename from v1.1/pages/credits/peep/philippe.png rename to 2/pages/credits/peep/philippe.png diff --git a/v1.1/pages/credits/peep/thomas.png b/2/pages/credits/peep/thomas.png similarity index 100% rename from v1.1/pages/credits/peep/thomas.png rename to 2/pages/credits/peep/thomas.png diff --git a/v1.1/pages/credits/peep/travis.png b/2/pages/credits/peep/travis.png similarity index 100% rename from v1.1/pages/credits/peep/travis.png rename to 2/pages/credits/peep/travis.png diff --git a/v1.1/pages/credits/peep/yu-han.png b/2/pages/credits/peep/yu-han.png similarity index 100% rename from v1.1/pages/credits/peep/yu-han.png rename to 2/pages/credits/peep/yu-han.png diff --git a/v1.1/pages/credits/polygon/aimee.png b/2/pages/credits/polygon/aimee.png similarity index 100% rename from v1.1/pages/credits/polygon/aimee.png rename to 2/pages/credits/polygon/aimee.png diff --git a/v1.1/pages/credits/polygon/buster.png b/2/pages/credits/polygon/buster.png similarity index 100% rename from v1.1/pages/credits/polygon/buster.png rename to 2/pages/credits/polygon/buster.png diff --git a/v1.1/pages/credits/polygon/cedric.png b/2/pages/credits/polygon/cedric.png similarity index 100% rename from v1.1/pages/credits/polygon/cedric.png rename to 2/pages/credits/polygon/cedric.png diff --git a/v1.1/pages/credits/polygon/chad.png b/2/pages/credits/polygon/chad.png similarity index 100% rename from v1.1/pages/credits/polygon/chad.png rename to 2/pages/credits/polygon/chad.png diff --git a/v1.1/pages/credits/polygon/dylan.png b/2/pages/credits/polygon/dylan.png similarity index 100% rename from v1.1/pages/credits/polygon/dylan.png rename to 2/pages/credits/polygon/dylan.png diff --git a/v1.1/pages/credits/polygon/dylan_s.png b/2/pages/credits/polygon/dylan_s.png similarity index 100% rename from v1.1/pages/credits/polygon/dylan_s.png rename to 2/pages/credits/polygon/dylan_s.png diff --git a/v1.1/pages/credits/polygon/feiya.png b/2/pages/credits/polygon/feiya.png similarity index 100% rename from v1.1/pages/credits/polygon/feiya.png rename to 2/pages/credits/polygon/feiya.png diff --git a/v1.1/pages/credits/polygon/glen.png b/2/pages/credits/polygon/glen.png similarity index 100% rename from v1.1/pages/credits/polygon/glen.png rename to 2/pages/credits/polygon/glen.png diff --git a/v1.1/pages/credits/polygon/iago.png b/2/pages/credits/polygon/iago.png similarity index 100% rename from v1.1/pages/credits/polygon/iago.png rename to 2/pages/credits/polygon/iago.png diff --git a/v1.1/pages/credits/polygon/jared.png b/2/pages/credits/polygon/jared.png similarity index 100% rename from v1.1/pages/credits/polygon/jared.png rename to 2/pages/credits/polygon/jared.png diff --git a/v1.1/pages/credits/polygon/karen.png b/2/pages/credits/polygon/karen.png similarity index 100% rename from v1.1/pages/credits/polygon/karen.png rename to 2/pages/credits/polygon/karen.png diff --git a/v1.1/pages/credits/polygon/kate.png b/2/pages/credits/polygon/kate.png similarity index 100% rename from v1.1/pages/credits/polygon/kate.png rename to 2/pages/credits/polygon/kate.png diff --git a/v1.1/pages/credits/polygon/kevin.png b/2/pages/credits/polygon/kevin.png similarity index 100% rename from v1.1/pages/credits/polygon/kevin.png rename to 2/pages/credits/polygon/kevin.png diff --git a/v1.1/pages/credits/polygon/klemen.png b/2/pages/credits/polygon/klemen.png similarity index 100% rename from v1.1/pages/credits/polygon/klemen.png rename to 2/pages/credits/polygon/klemen.png diff --git a/v1.1/pages/credits/polygon/kuerqing1024.png b/2/pages/credits/polygon/kuerqing1024.png similarity index 100% rename from v1.1/pages/credits/polygon/kuerqing1024.png rename to 2/pages/credits/polygon/kuerqing1024.png diff --git a/v1.1/pages/credits/polygon/ljt.png b/2/pages/credits/polygon/ljt.png similarity index 100% rename from v1.1/pages/credits/polygon/ljt.png rename to 2/pages/credits/polygon/ljt.png diff --git a/v1.1/pages/credits/polygon/mark.png b/2/pages/credits/polygon/mark.png similarity index 100% rename from v1.1/pages/credits/polygon/mark.png rename to 2/pages/credits/polygon/mark.png diff --git a/v1.1/pages/credits/polygon/matt.png b/2/pages/credits/polygon/matt.png similarity index 100% rename from v1.1/pages/credits/polygon/matt.png rename to 2/pages/credits/polygon/matt.png diff --git a/v1.1/pages/credits/polygon/michael_duke.png b/2/pages/credits/polygon/michael_duke.png similarity index 100% rename from v1.1/pages/credits/polygon/michael_duke.png rename to 2/pages/credits/polygon/michael_duke.png diff --git a/v1.1/pages/credits/polygon/michael_huff.png b/2/pages/credits/polygon/michael_huff.png similarity index 100% rename from v1.1/pages/credits/polygon/michael_huff.png rename to 2/pages/credits/polygon/michael_huff.png diff --git a/v1.1/pages/credits/polygon/michelle.png b/2/pages/credits/polygon/michelle.png similarity index 100% rename from v1.1/pages/credits/polygon/michelle.png rename to 2/pages/credits/polygon/michelle.png diff --git a/v1.1/pages/credits/polygon/natalie.png b/2/pages/credits/polygon/natalie.png similarity index 100% rename from v1.1/pages/credits/polygon/natalie.png rename to 2/pages/credits/polygon/natalie.png diff --git a/v1.1/pages/credits/polygon/nicholas.png b/2/pages/credits/polygon/nicholas.png similarity index 100% rename from v1.1/pages/credits/polygon/nicholas.png rename to 2/pages/credits/polygon/nicholas.png diff --git a/v1.1/pages/credits/polygon/nimrod.png b/2/pages/credits/polygon/nimrod.png similarity index 100% rename from v1.1/pages/credits/polygon/nimrod.png rename to 2/pages/credits/polygon/nimrod.png diff --git a/v1.1/pages/credits/polygon/noel.png b/2/pages/credits/polygon/noel.png similarity index 100% rename from v1.1/pages/credits/polygon/noel.png rename to 2/pages/credits/polygon/noel.png diff --git a/v1.1/pages/credits/polygon/phil.png b/2/pages/credits/polygon/phil.png similarity index 100% rename from v1.1/pages/credits/polygon/phil.png rename to 2/pages/credits/polygon/phil.png diff --git a/v1.1/pages/credits/polygon/philippe.png b/2/pages/credits/polygon/philippe.png similarity index 100% rename from v1.1/pages/credits/polygon/philippe.png rename to 2/pages/credits/polygon/philippe.png diff --git a/v1.1/pages/credits/polygon/rob.png b/2/pages/credits/polygon/rob.png similarity index 100% rename from v1.1/pages/credits/polygon/rob.png rename to 2/pages/credits/polygon/rob.png diff --git a/v1.1/pages/credits/polygon/thomas.png b/2/pages/credits/polygon/thomas.png similarity index 100% rename from v1.1/pages/credits/polygon/thomas.png rename to 2/pages/credits/polygon/thomas.png diff --git a/v1.1/pages/credits/polygon/travis.png b/2/pages/credits/polygon/travis.png similarity index 100% rename from v1.1/pages/credits/polygon/travis.png rename to 2/pages/credits/polygon/travis.png diff --git a/v1.1/pages/credits/polygon/william.png b/2/pages/credits/polygon/william.png similarity index 100% rename from v1.1/pages/credits/polygon/william.png rename to 2/pages/credits/polygon/william.png diff --git a/v1.1/pages/credits/polygon/yu-han.png b/2/pages/credits/polygon/yu-han.png similarity index 100% rename from v1.1/pages/credits/polygon/yu-han.png rename to 2/pages/credits/polygon/yu-han.png diff --git a/v1.1/pages/credits/polygon/zach.png b/2/pages/credits/polygon/zach.png similarity index 100% rename from v1.1/pages/credits/polygon/zach.png rename to 2/pages/credits/polygon/zach.png diff --git a/2/pages/doc.css b/2/pages/doc.css new file mode 100644 index 0000000..eb115bc --- /dev/null +++ b/2/pages/doc.css @@ -0,0 +1 @@ +iframe{margin: 10px 0; width: 100%;height: 90vh;} \ No newline at end of file diff --git a/2/pages/doc.html b/2/pages/doc.html new file mode 100644 index 0000000..1edbf0a --- /dev/null +++ b/2/pages/doc.html @@ -0,0 +1,120 @@ + + + + Contextual help + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/2/pages/doc/default.fr.md b/2/pages/doc/default.fr.md new file mode 100644 index 0000000..6ffb159 --- /dev/null +++ b/2/pages/doc/default.fr.md @@ -0,0 +1,5 @@ +# Bienvenue dans l'aide de LOOPY + + +## Sommaire + diff --git a/2/pages/doc/default.md b/2/pages/doc/default.md new file mode 100644 index 0000000..0766773 --- /dev/null +++ b/2/pages/doc/default.md @@ -0,0 +1,54 @@ +# Default + + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur tristique ullamcorper fringilla. Aenean tortor ipsum, pharetra non lectus in, elementum tincidunt urna. Suspendisse ac ex a ante bibendum euismod eu et ipsum. Praesent sit amet placerat lorem. Vivamus dignissim blandit sagittis. Aliquam tempus tortor est, vel vulputate lorem porttitor ac. Vivamus tristique faucibus odio in ultrices. + +Phasellus sed neque leo. Vivamus tincidunt laoreet purus, id placerat magna sollicitudin eu. Cras eget erat a ante convallis ullamcorper in sit amet ante. Proin at dui vitae mi eleifend ultrices a vitae nisi. Mauris sed nunc aliquet, laoreet nunc sit amet, mattis erat. Morbi vestibulum pellentesque justo ac feugiat. Sed enim velit, feugiat quis aliquam quis, tincidunt id lacus. + +Etiam lorem tortor, ultricies et vulputate at, aliquet sit amet felis. Vivamus orci lacus, blandit nec justo pharetra, volutpat aliquam dolor. In et enim nisl. Praesent mauris odio, convallis vitae erat eget, maximus luctus risus. Etiam est libero, aliquam eu erat nec, porta porttitor enim. Integer lobortis magna eu convallis euismod. Ut varius metus vitae ligula congue viverra. Mauris scelerisque arcu elit, vel malesuada nisi maximus vitae. Cras ac elit neque. Integer sit amet odio non elit malesuada sagittis ut vitae nisl. Nulla in sagittis ipsum. Sed a dolor non justo ultricies varius. + +Fusce eleifend sapien vitae euismod fermentum. Nam ut sem facilisis, scelerisque felis quis, feugiat dolor. Curabitur sodales massa quis velit luctus, nec imperdiet libero molestie. Nunc in nisl justo. Suspendisse potenti. Vestibulum ac arcu massa. Proin quis nibh et quam imperdiet condimentum. Pellentesque fringilla feugiat eros in aliquam. Phasellus ex nulla, facilisis et leo et, malesuada consequat mi. Integer vel ornare erat. Etiam in tortor venenatis, mollis magna quis, imperdiet erat. Mauris interdum maximus dictum. Curabitur at tincidunt neque, at volutpat urna. + +Pellentesque aliquet arcu velit, nec gravida enim pulvinar non. Duis sed viverra odio. Mauris lacinia mauris et urna faucibus porta. Praesent elit arcu, malesuada a sagittis a, pharetra eu nisl. Aenean quis odio tortor. Vestibulum id rhoncus massa. Suspendisse malesuada lorem quis nibh elementum pharetra. Phasellus sit amet lacinia purus, id mattis felis. + +Phasellus sit amet dignissim odio. Vestibulum blandit nibh a dui fermentum, sed porta neque venenatis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam viverra augue sit amet odio molestie, vitae bibendum nulla luctus. Aenean arcu ipsum, gravida ut nisi eu, tempus aliquam elit. Curabitur ac dolor id quam venenatis interdum non vitae neque. Nunc commodo condimentum rutrum. Aenean suscipit consectetur erat non dictum. Phasellus gravida odio quis leo tristique, molestie vestibulum purus tincidunt. Pellentesque tristique euismod mattis. Aliquam sodales imperdiet congue. Duis ac tristique neque, a tincidunt erat. + +Aenean consequat, arcu eu sollicitudin vulputate, ante magna aliquam nibh, ac tincidunt arcu erat sed ante. Sed rhoncus arcu sagittis elit facilisis condimentum. Nullam eu sapien magna. Aliquam aliquam bibendum enim id finibus. Fusce mi dolor, sollicitudin id mi eget, pretium pellentesque ligula. Aenean molestie est non eros interdum, eu tempor elit mattis. Nullam nec nunc vulputate, tristique leo non, efficitur magna. Vivamus orci dui, vestibulum a mattis sit amet, sodales vitae nulla. + +In pellentesque eros nibh, sed sodales odio imperdiet sit amet. Aliquam auctor nec nisi ut pharetra. Aenean tincidunt consectetur magna, et consectetur risus aliquet non. Curabitur condimentum felis in porta efficitur. Vivamus id dui sodales, vulputate felis et, posuere arcu. Vestibulum placerat malesuada augue pharetra aliquet. Pellentesque eu magna nec neque egestas tempus eu in tortor. Praesent sit amet scelerisque nibh. Nulla tempor odio nisi, nec auctor enim rhoncus sit amet. Donec pharetra orci sed libero varius pharetra. Integer posuere tellus in elit faucibus, vel sollicitudin tortor viverra. Etiam vitae turpis hendrerit, rhoncus leo eget, tempor nisl. Quisque consectetur, nisl dapibus euismod hendrerit, lectus nisl tincidunt velit, vitae egestas urna nisl euismod arcu. + +Curabitur id massa dolor. Quisque nibh orci, consequat vel metus eget, fringilla condimentum lacus. Nulla ac ultrices ligula. Cras hendrerit nisl nec purus consectetur rutrum non in nulla. Phasellus viverra iaculis nibh, et congue nisl porta id. Praesent sodales semper sem id congue. Aliquam ut ipsum tristique, facilisis erat vel, accumsan dui. Ut varius justo hendrerit magna suscipit placerat. Duis consectetur massa eu semper scelerisque. Donec erat risus, aliquam porttitor semper eget, aliquam vitae neque. Fusce aliquet egestas turpis, posuere eleifend ipsum convallis in. Praesent condimentum sit amet mauris a gravida. Praesent ultrices magna non nisi faucibus, viverra aliquam magna viverra. Maecenas molestie condimentum bibendum. Sed quam eros, semper non mattis sit amet, porttitor ac felis. + +Donec posuere, arcu vel interdum mollis, ligula tortor tincidunt diam, in cursus sapien dui nec dolor. Quisque mattis massa sed eros imperdiet, vitae porta sapien dapibus. Quisque suscipit ultrices orci sed accumsan. Nam imperdiet arcu ligula, id sollicitudin dolor commodo sit amet. Sed porttitor sem interdum neque bibendum dictum. Aenean aliquet non tellus id malesuada. Maecenas ut condimentum turpis. + +# Global + +Curabitur sit amet tristique justo, vitae pretium dolor. Aenean tincidunt et eros eget pellentesque. Maecenas semper nibh a bibendum euismod. Integer tortor sem, pharetra vitae erat sit amet, ultrices lobortis erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Integer fermentum sapien eget felis dapibus elementum. Phasellus ultricies, orci quis vehicula congue, massa lectus viverra tortor, at mattis est nulla hendrerit quam. Nulla viverra laoreet orci a egestas. Praesent aliquet, augue nec bibendum sollicitudin, odio quam eleifend quam, vitae viverra elit est eu orci. In non tristique libero. Integer suscipit tellus augue, quis pellentesque elit finibus in. Praesent luctus, dolor in sollicitudin suscipit, orci purus porta arcu, sed euismod libero mauris sed lectus. Integer tristique lacus non posuere faucibus. Suspendisse elit lectus, condimentum sagittis condimentum ut, rutrum ac turpis. Etiam dictum diam neque, vitae porta libero rhoncus eget. In lacinia ex nec nulla suscipit varius. + +Donec pharetra nulla sed ligula elementum, vitae dignissim turpis lobortis. Nam nec suscipit ipsum. Donec vehicula vestibulum cursus. Proin ultrices tellus tellus, vitae aliquam ante ullamcorper ut. Nulla mattis eu leo et posuere. Nulla tellus augue, posuere vitae erat eget, interdum ornare lacus. Nam non nibh quam. Morbi rutrum metus et gravida posuere. Sed molestie vehicula dui, id eleifend diam blandit ac. Mauris luctus venenatis diam, vitae blandit odio. Ut efficitur sagittis risus sed rhoncus. Vivamus sollicitudin felis vel neque efficitur, id pellentesque lorem gravida. Phasellus in enim non tellus fermentum placerat quis sit amet nunc. Suspendisse non placerat erat, non sodales enim. + +In vestibulum sodales ante, et malesuada lacus condimentum nec. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent sit amet purus tempor, sollicitudin tellus non, consectetur nulla. Morbi neque orci, commodo ac maximus vel, mollis accumsan odio. Integer ut ultricies ipsum. Sed diam sapien, mattis ut tristique sit amet, venenatis luctus tortor. Donec fermentum a odio sit amet pretium. Donec iaculis sem ac nunc hendrerit, non consequat dolor rhoncus. + +Cras nunc erat, aliquam eget consectetur nec, pulvinar non lacus. Sed eget lacinia nunc. Duis ligula enim, hendrerit a erat ac, malesuada iaculis tortor. Nunc finibus ac ex in dapibus. Integer iaculis dignissim finibus. Nulla ornare sem ut risus ultrices, tristique hendrerit enim blandit. Nullam ullamcorper arcu at magna euismod fringilla. Sed hendrerit vitae tellus a imperdiet. Ut nec semper urna. Integer posuere semper magna, vitae eleifend ligula dapibus sed. Vestibulum aliquet turpis facilisis orci tristique dignissim. Aliquam erat volutpat. Etiam nulla leo, tincidunt sit amet ligula quis, mattis ornare elit. + +In id rhoncus risus. Ut eu mattis ante. Donec sit amet ante et libero malesuada scelerisque nec a leo. Suspendisse pellentesque ipsum eu nulla pharetra, ac fringilla justo tempus. Fusce varius tortor in dictum posuere. Nulla commodo ultricies pretium. Praesent vitae ante nec urna blandit pretium. Vivamus venenatis auctor mi. Quisque massa odio, tempor id luctus id, dapibus a tortor. Integer eget eleifend diam. + +Integer non lorem eget nisl facilisis commodo. Cras elementum rhoncus convallis. Duis ac justo interdum erat commodo porttitor. Duis auctor arcu eu faucibus sagittis. Fusce ac erat scelerisque, vehicula nisi sed, mattis risus. Donec euismod elit sit amet faucibus tincidunt. Etiam eget eleifend mauris. Duis aliquet libero a mi tristique, at luctus quam blandit. Donec consectetur neque dui, at consequat enim luctus sit amet. Fusce viverra nibh eget egestas tincidunt. Aliquam in ultrices metus, id gravida diam. Nam ut arcu ac ligula auctor ullamcorper luctus vitae velit. Nunc sit amet turpis enim. In lobortis, metus ac pulvinar commodo, tortor lectus auctor nisi, eget tempus sem metus at leo. + +Donec a dui eu leo efficitur blandit. Vivamus egestas viverra odio, eu faucibus elit scelerisque in. Sed risus eros, porttitor vel commodo ut, blandit at lacus. Nulla iaculis vehicula dui, nec tincidunt mi pellentesque aliquam. Fusce eu magna fringilla, ultrices mi eu, posuere felis. Suspendisse eu convallis turpis, a dapibus justo. Praesent rhoncus sapien ac enim facilisis, non eleifend purus lacinia. Suspendisse pulvinar erat a gravida blandit. Aliquam sed purus nulla. Aenean pretium condimentum elementum. + +Quisque iaculis ligula nec metus fermentum tincidunt. Fusce vel nisi vel dui viverra egestas. Maecenas pretium, velit non consectetur pretium, arcu sem aliquet mauris, et tempus neque lacus lobortis turpis. Nullam id sodales augue. Mauris sit amet vulputate justo. Sed quis ultrices lectus, sed tempus nisl. Mauris viverra velit quis leo dapibus, a tristique ex iaculis. Mauris at bibendum magna, id porttitor leo. Vestibulum in ex commodo, sodales neque in, aliquet metus. + +
+ +# Node + +Proin ex sem, vehicula vitae purus ac, viverra rutrum felis. Fusce vel dolor et tortor pharetra fringilla. Vestibulum vulputate tellus et diam cursus dignissim. Phasellus tempus vulputate metus sed egestas. In hac habitasse platea dictumst. Nullam id rutrum libero. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus aliquam ipsum malesuada consequat bibendum. Quisque arcu magna, consequat cursus sapien nec, vehicula dignissim velit. Nullam vitae semper lectus. Nullam faucibus laoreet turpis in molestie. + +Curabitur placerat justo a ultricies rutrum. Praesent mattis tristique sodales. Ut et ullamcorper massa. Proin tincidunt nunc ut vulputate rhoncus. Ut lacinia interdum aliquet. Nulla eget enim non dui elementum rhoncus. Mauris dictum est at enim aliquam lacinia. Morbi erat risus, sagittis congue nibh at, ultricies porttitor lacus. Ut consequat, nulla vitae aliquam dignissim, velit erat dictum turpis, id aliquet mi quam nec turpis. Mauris venenatis viverra fermentum. Mauris pharetra mattis felis, sed facilisis diam bibendum eget. + +
+ + + + diff --git a/2/pages/doc/loopyMode.md b/2/pages/doc/loopyMode.md new file mode 100644 index 0000000..8f7ff30 --- /dev/null +++ b/2/pages/doc/loopyMode.md @@ -0,0 +1 @@ +# LOOPY v2 mode diff --git a/2/pages/examples/example.loopy.json b/2/pages/examples/example.loopy.json new file mode 100644 index 0000000..1e2085e --- /dev/null +++ b/2/pages/examples/example.loopy.json @@ -0,0 +1 @@ +[[[1,742,425,0,"automation",0],[2,1047,419,0,"profits",0],[3,930,726,0,"job%20loss",4],[4,1128,757,0,"frustration",4],[5,1313,676,0,"political%20unrest",4],[6,1216,527,0,"tax%20revenue",3]],[[1,3,-88,1,0],[1,2,-150,1,0],[2,1,-137,1,0],[3,4,-27,1,0],[4,5,-32,1,0],[2,6,12,1,0]],[[892,441,"vicious%20cycle%0Aof%20automation%0Aleads%20to%20short-%0Aterm%20job%20loss%2C%0Athen%20frustration%2C%0Athen%20political%0Aunrest"],[1294,387,"CHALLENGE%3A%0Athink%20up%20and%20model%20your%0Aown%20solutions%20to%20this%20problem!%0A(hint%3A%20what%20tax-funded%20programs%0Acould%20mitigate%20job%20loss%20due%20to%0Aautomation%3F)"],[1050,627,"%3F%3F%3F%20what%20goes%20here%20%3F%3F%3F"]],6] \ No newline at end of file diff --git a/v1.1/pages/credits/favicon.png b/2/pages/examples/favicon.png similarity index 100% rename from v1.1/pages/credits/favicon.png rename to 2/pages/examples/favicon.png diff --git a/v1.1/pages/examples/index.html b/2/pages/examples/index.html similarity index 100% rename from v1.1/pages/examples/index.html rename to 2/pages/examples/index.html diff --git a/2/pages/examples/stringPart b/2/pages/examples/stringPart new file mode 100644 index 0000000..41091da --- /dev/null +++ b/2/pages/examples/stringPart @@ -0,0 +1,17 @@ +6 +074242500 +104741900 +093072604 +112875704 +131367604 +121652703 +13-08810 +12-15010 +21-13710 +34-02710 +45-03210 +26+01210 +892441 +1294387 +1050627 +automation"profits"job loss"frustration"political unrest"tax revenue"vicious cycle of automation leads to short- term job loss then frustration then political unrest"CHALLENGE think up and model your own solutions to this problem! (hint what tax-funded programs could mitigate job loss due to automation )" what goes here diff --git a/v1.1/pages/gif.html b/2/pages/gif.html similarity index 100% rename from v1.1/pages/gif.html rename to 2/pages/gif.html diff --git a/v1.1/pages/gifs.gif b/2/pages/gifs.gif similarity index 100% rename from v1.1/pages/gifs.gif rename to 2/pages/gifs.gif diff --git a/v1.1/pages/howto.html b/2/pages/howto.html similarity index 100% rename from v1.1/pages/howto.html rename to 2/pages/howto.html diff --git a/v1.1/pages/howto/1.png b/2/pages/howto/1.png similarity index 100% rename from v1.1/pages/howto/1.png rename to 2/pages/howto/1.png diff --git a/v1.1/pages/howto/2.png b/2/pages/howto/2.png similarity index 100% rename from v1.1/pages/howto/2.png rename to 2/pages/howto/2.png diff --git a/v1.1/pages/howto/3.png b/2/pages/howto/3.png similarity index 100% rename from v1.1/pages/howto/3.png rename to 2/pages/howto/3.png diff --git a/v1.1/pages/howto/4.png b/2/pages/howto/4.png similarity index 100% rename from v1.1/pages/howto/4.png rename to 2/pages/howto/4.png diff --git a/v1.1/pages/howto/5.png b/2/pages/howto/5.png similarity index 100% rename from v1.1/pages/howto/5.png rename to 2/pages/howto/5.png diff --git a/2/pages/marked.min.js b/2/pages/marked.min.js new file mode 100644 index 0000000..c7105a2 --- /dev/null +++ b/2/pages/marked.min.js @@ -0,0 +1,6 @@ +/** + * marked - a markdown parser + * Copyright (c) 2011-2020, Christopher Jeffrey. (MIT Licensed) + * https://github.com/markedjs/marked + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).marked=t()}(this,function(){"use strict";function r(e,t){for(var n=0;n"']/),l=/[&<>"']/g,a=/[<>"']|&(?!#?\w+;)/,o=/[<>"']|&(?!#?\w+;)/g,h={"&":"&","<":"<",">":">",'"':""","'":"'"};var u=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function c(e){return e.replace(u,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}var p=/(^|[^\[])\^/g;var g=/[^\w:]/g,f=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;var d={},b=/^[^:]+:\/*[^/]*$/,k=/^([^:]+:)[\s\S]*$/,m=/^([^:]+:\/*[^/]*)[\s\S]*$/;function x(e,t){d[" "+e]||(b.test(e)?d[" "+e]=e+"/":d[" "+e]=_(e,"/",!0));var n=-1===(e=d[" "+e]).indexOf(":");return"//"===t.substring(0,2)?n?t:e.replace(k,"$1")+t:"/"===t.charAt(0)?n?t:e.replace(m,"$1")+t:e+t}function _(e,t,n){var r=e.length;if(0===r)return"";for(var s=0;st)n.splice(t);else for(;n.length ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?\\?>\\n*|\\n*|\\n*|)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,nptable:Z,table:Z,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};C.def=q(C.def).replace("label",C._label).replace("title",C._title).getRegex(),C.bullet=/(?:[*+-]|\d{1,9}\.)/,C.item=/^( *)(bull) ?[^\n]*(?:\n(?!\1bull ?)[^\n]*)*/,C.item=q(C.item,"gm").replace(/bull/g,C.bullet).getRegex(),C.list=q(C.list).replace(/bull/g,C.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+C.def.source+")").getRegex(),C._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",C._comment=//,C.html=q(C.html,"i").replace("comment",C._comment).replace("tag",C._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),C.paragraph=q(C._paragraph).replace("hr",C.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",C._tag).getRegex(),C.blockquote=q(C.blockquote).replace("paragraph",C.paragraph).getRegex(),C.normal=L({},C),C.gfm=L({},C.normal,{nptable:"^ *([^|\\n ].*\\|.*)\\n *([-:]+ *\\|[-| :]*)(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)",table:"^ *\\|(.+)\\n *\\|?( *[-:]+[-| :]*)(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),C.gfm.nptable=q(C.gfm.nptable).replace("hr",C.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",C._tag).getRegex(),C.gfm.table=q(C.gfm.table).replace("hr",C.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|!--)").replace("tag",C._tag).getRegex(),C.pedantic=L({},C.normal,{html:q("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",C._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,fences:Z,paragraph:q(C.normal._paragraph).replace("hr",C.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",C.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});var O={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:Z,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,strong:/^__([^\s_])__(?!_)|^\*\*([^\s*])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/,em:/^_([^\s_])_(?!_)|^\*([^\s*<\[])\*(?!\*)|^_([^\s<][\s\S]*?[^\s_])_(?!_|[^\spunctuation])|^_([^\s_<][\s\S]*?[^\s])_(?!_|[^\spunctuation])|^\*([^\s<"][\s\S]*?[^\s\*])\*(?!\*|[^\spunctuation])|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:Z,text:/^(`+|[^`])(?:[\s\S]*?(?:(?=[\\?@\\[^_{|}~"};O.em=q(O.em).replace(/punctuation/g,O._punctuation).getRegex(),O._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,O._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,O._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,O.autolink=q(O.autolink).replace("scheme",O._scheme).replace("email",O._email).getRegex(),O._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,O.tag=q(O.tag).replace("comment",C._comment).replace("attribute",O._attribute).getRegex(),O._label=/(?:\[[^\[\]]*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,O._href=/<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*/,O._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,O.link=q(O.link).replace("label",O._label).replace("href",O._href).replace("title",O._title).getRegex(),O.reflink=q(O.reflink).replace("label",O._label).getRegex(),O.normal=L({},O),O.pedantic=L({},O.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,link:q(/^!?\[(label)\]\((.*?)\)/).replace("label",O._label).getRegex(),reflink:q(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",O._label).getRegex()}),O.gfm=L({},O.normal,{escape:q(O.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^~+(?=\S)([\s\S]*?\S)~+/,text:/^(`+|[^`])(?:[\s\S]*?(?:(?=[\\ ?/gm,""),this.token(s,t),this.tokens.push({type:"blockquote_end"});else if(s=this.rules.list.exec(e)){for(e=e.substring(s[0].length),o={type:"list_start",ordered:d=1<(i=s[2]).length,start:d?+i:"",loose:!1},this.tokens.push(o),n=!(h=[]),f=(s=s[0].match(this.rules.item)).length,p=0;p'+(n?e:N(e,!0))+"\n":"
"+(n?e:N(e,!0))+"
"},t.blockquote=function(e){return"
\n"+e+"
\n"},t.html=function(e){return e},t.heading=function(e,t,n,r){return this.options.headerIds?"'+e+"\n":""+e+"\n"},t.hr=function(){return this.options.xhtml?"
\n":"
\n"},t.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"\n"},t.listitem=function(e){return"
  • "+e+"
  • \n"},t.checkbox=function(e){return" "},t.paragraph=function(e){return"

    "+e+"

    \n"},t.table=function(e,t){return"\n\n"+e+"\n"+(t=t&&""+t+"")+"
    \n"},t.tablerow=function(e){return"\n"+e+"\n"},t.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+"\n"},t.strong=function(e){return""+e+""},t.em=function(e){return""+e+""},t.codespan=function(e){return""+e+""},t.br=function(){return this.options.xhtml?"
    ":"
    "},t.del=function(e){return""+e+""},t.link=function(e,t,n){if(null===(e=F(this.options.sanitize,this.options.baseUrl,e)))return n;var r='"},t.image=function(e,t,n){if(null===(e=F(this.options.sanitize,this.options.baseUrl,e)))return n;var r=''+n+'":">"},t.text=function(e){return e},e}(),G=function(){function e(){this.seen={}}return e.prototype.slug=function(e){var t=e.toLowerCase().trim().replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-");if(this.seen.hasOwnProperty(t))for(var n=t;this.seen[n]++,t=n+"-"+this.seen[n],this.seen.hasOwnProperty(t););return this.seen[t]=0,t},e}(),M=s.defaults,V=D.inline,H=A,J=y,K=function(){function u(e,t){if(this.options=t||M,this.links=e,this.rules=V.normal,this.options.renderer=this.options.renderer||new X,this.renderer=this.options.renderer,this.renderer.options=this.options,!this.links)throw new Error("Tokens array requires a `links` property.");this.options.pedantic?this.rules=V.pedantic:this.options.gfm&&(this.options.breaks?this.rules=V.breaks:this.rules=V.gfm)}u.output=function(e,t,n){return new u(t,n).output(e)};var e=u.prototype;return e.output=function(e){for(var t,n,r,s,i,l,a="";e;)if(i=this.rules.escape.exec(e))e=e.substring(i[0].length),a+=J(i[1]);else if(i=this.rules.tag.exec(e))!this.inLink&&/^/i.test(i[0])&&(this.inLink=!1),!this.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(i[0])?this.inRawBlock=!0:this.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(i[0])&&(this.inRawBlock=!1),e=e.substring(i[0].length),a+=this.renderer.html(this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):J(i[0]):i[0]);else if(i=this.rules.link.exec(e)){var o=H(i[2],"()");if(-1$/,"$1"),a+=this.outputLink(i,{href:u.escapes(r),title:u.escapes(s)}),this.inLink=!1}else if((i=this.rules.reflink.exec(e))||(i=this.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\s+/g," "),!(t=this.links[t.toLowerCase()])||!t.href){a+=i[0].charAt(0),e=i[0].substring(1)+e;continue}this.inLink=!0,a+=this.outputLink(i,t),this.inLink=!1}else if(i=this.rules.strong.exec(e))e=e.substring(i[0].length),a+=this.renderer.strong(this.output(i[4]||i[3]||i[2]||i[1]));else if(i=this.rules.em.exec(e))e=e.substring(i[0].length),a+=this.renderer.em(this.output(i[6]||i[5]||i[4]||i[3]||i[2]||i[1]));else if(i=this.rules.code.exec(e))e=e.substring(i[0].length),a+=this.renderer.codespan(J(i[2].trim(),!0));else if(i=this.rules.br.exec(e))e=e.substring(i[0].length),a+=this.renderer.br();else if(i=this.rules.del.exec(e))e=e.substring(i[0].length),a+=this.renderer.del(this.output(i[1]));else if(i=this.rules.autolink.exec(e))e=e.substring(i[0].length),r="@"===i[2]?"mailto:"+(n=J(this.mangle(i[1]))):n=J(i[1]),a+=this.renderer.link(r,null,n);else if(this.inLink||!(i=this.rules.url.exec(e))){if(i=this.rules.text.exec(e))e=e.substring(i[0].length),this.inRawBlock?a+=this.renderer.text(this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):J(i[0]):i[0]):a+=this.renderer.text(J(this.smartypants(i[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else{if("@"===i[2])r="mailto:"+(n=J(i[0]));else{for(;l=i[0],i[0]=this.rules._backpedal.exec(i[0])[0],l!==i[0];);n=J(i[0]),r="www."===i[1]?"http://"+n:n}e=e.substring(i[0].length),a+=this.renderer.link(r,null,n)}return a},u.escapes=function(e){return e?e.replace(u.rules._escapes,"$1"):e},e.outputLink=function(e,t){var n=t.href,r=t.title?J(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,J(e[1]))},e.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},e.mangle=function(e){if(!this.options.mangle)return e;for(var t,n=e.length,r="",s=0;sAn error occurred:

    "+se(e.message+"",!0)+"
    ";throw e}}return oe.options=oe.setOptions=function(e){return ne(oe.defaults,e),le(oe.defaults),oe},oe.getDefaults=ie,oe.defaults=ae,oe.Parser=te,oe.parser=te.parse,oe.Renderer=X,oe.TextRenderer=Q,oe.Lexer=B,oe.lexer=B.lex,oe.InlineLexer=K,oe.inlineLexer=K.output,oe.Slugger=G,oe.parse=oe}); \ No newline at end of file diff --git a/v1.1/pages/page.css b/2/pages/page.css similarity index 100% rename from v1.1/pages/page.css rename to 2/pages/page.css diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md new file mode 100644 index 0000000..fe15719 --- /dev/null +++ b/CHANGELOG.fr.md @@ -0,0 +1,162 @@ +[EN](CHANGELOG.md) | FR + +# Changelog | liste des changements +Tous les changements notables de ce projet seront documenté dans ce fichier. + +Le format est basé sur [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +et ce projet adhère au [versionnage sémantique](https://semver.org/spec/v2.0.0.html). + +## Feuille de route | développements futurs +- Groupes +- Support Multi-langue +- Écrire les pages de documentations avec exemples +- Pour plus de détails, consultez les [tickets/issues](https://github.com/1000i100/loopy/issues) + +## [Non-publié/Non-Stabilisé] (par [1000i100]) + +### Ajouté + +##### Interface d'édition +- un **mode simple** pour garder LOOPY aussi simple qu'avant (prise en main aisée / apprentissage facile). Le mode simple ne contiens que ce qui était dans la v1 (et v1.1). +- un **mode avancé** pour débrider le potentiel de créativité avec les fonctionnalités de la V2. +- ajout pour chaque fonctionnalité d'un `?` pour accéder à l'aide contextuelle avec exemple d'usage. +- un commutateur entre mode **colorAesthetic** et **colorLogic** pour permettre l'usage des couleurs comme différenciateur de types de signaux et noeuds. +- zoom avec la molette de souris (ou cliqué déplacé avec l'outil main) dans la scène pour naviguer dans les modèles les plus vastes. +- sélectionné le **mode de camera** à utiliser en mode lecture. + +##### Play mode cameras +- **scene cam.** : (resize to scene) the camera scale to show all visible elements. +- **signal cam.** : (follow signals) the camera focus and zoom on signals. Switch to scene camera when there is no signal. +- **free cam.** : (user controllable) user can zoom in/out and drag the scene even in play mode. + +##### Death / Life mechanics +- nodes can now die and reborn (by receiving vital change propagation signal or by explosion threshold settings). +- when a node die, it sends (propagate) a death signal to all arrows allowing it. +- when a dead node receives any signal except reborn, it's dropped. +- when a dead node reborn, it sends (propagate) a reborn/life signal to all arrows allowing it. +- when an alive node receives a reborn signal, it's dropped. +- when a dead node receives a death signal, it's dropped. + +##### Node advanced features +- empty the name field to resize it to a tiny internal-logic node. +- name it "autoplay" to auto send a signal on start in play-mode. +- press del key when name field is empty to remove the node. +- 4 node **sizes** with 4 different **storage capacities** (none, normal, x5 and x100) +- **Overflow threshold** : a node can store signals without forwarding them up to a threshold, and down to another threshold (store signals within a threshold window, forward them outside the window). +- **Aggregation latency** : bypass thresholds to store signals for a duration before releasing them merged into one. +- **Death trigger** : choose if a node implode (die) when empty, or explode (die) when full. + +##### Arrow advanced features (previously named Edge) +- **Valency** allows you to act on the signal valency : preserve, invert, filter to keep only positive or negative signal, convert any signal to positive or to negative. +- Arrow can be set to randomly allow/drop signals +- Arrow can allow classical signal and/or vital change signal (death/life). +- Arrow can convert signal to vital change signal +- Arrow can handle signal as tendency (legacy default) or quantity (new) + + **Tendency mechanics** + nothing new here : when a signal reach a node it add it valency to the node stock and the node forward it + by cloning it on every output arrows (if no threshold or similar new node feature change that). + + **Quantitative mechanics** + when a signal reach a node it add it valency to the node stock, like in tendency mode. + But, if at least one output arrow is in quantitative mode, + all the overflow will be split between quantitative arrows, send and deduce from the node stock. + AND, all tendency (or vital change converter) arrow will get a fixed value signal. + +- Optional custom name (replacing it behavior symbols) +- Display signal timing to go from start node to end node thru this arrow. + +##### Text advanced features +- choose the color for each text message in your model (from 7 choices). +- switch text visibility : you can hide some text in play mode to keep them only as reminder for edition. +- link field : bind your text to a web link to make it clickable. + +##### ColorLogic mechanics +- When global colorLogic switch is enable, color become significant, and extra features are unlocked. +- A node stock is only updated by color matching signals. +- All nodes behaviors (threshold, latency, death) are only triggered by matching color signals. +- Signals reaching a node with a foreign color will be forwarded except if **Foreign color** is set to drop them. +- Arrow can **filter signals by color** to only allow a specific color. +- Arrow can also **convert a signal color** from one to another. +- Arrow can even convert to a **random color** from the ones allowed by arrows starting from end node. +- A specific arrow can change its end node color : it will **fill node with signal color** + (signal is destroyed in the process, use another arrow to clone and spread it if you want). + +##### Misc ergonomic / under the hood changes +- compact the edition sidebar on small screen. +- combined sliders to be able to change 2 parameters for one main feature (used for nodes thresholds) +- alternative image in sliderWidget depending of the selected option (for better understanding the choice effect) +- dynamic re-labeling feature name depending of selected option +- keep advanced selected setting in simple mode but display warning in the UI +- keep colorLogic selected setting in colorAesthetic mode but display warning in the UI +- added `json export` to export human readable json (old `save to file` now save a compressed file like `save as link` does now) +- added `load from url` to explain how to include in link an external loopy data file. + +### Changed +- **save as link** now store data in binary with lzma compression then base64 conversion. +- **load from file** (or from url) understand the legacy json format and the new compressed one (and the new human readable json format). +- arrows polarity + / - are ignored in advanced mode to let space for the more complete sign Behavior features. +- node text now have a white halo to improve readability. + + +## [Version 1.1] - 2017-04-11 (par [Nicky Case]) +### Added +- save as file, to store your work (system model) in your computer for backup and future improvement (and for big system that don't fit in the url) +- load from file, to load local or downloaded system models. +- make a GIF page to explain how to do gif from loopy with LICEcap or Peek + +### Changed +- node amounts are now "uncapped" +- better distribution of "signals" + + +## [1.0.0] - 2017-02-23 (par [Nicky Case]) +### Added + +##### Edit mode features +- an edit mode to create your system model. +- a tool bar to pick the tool you want to use +- use ink tool to add system entities to your model +- with ink tool create Node with pencil move +- with ink tool create Arrow/Edge with pencil move between nodes +- use text tool to add some text in your model +- use hand tool to move any part of your model +- use erase tool to delete any part of your model +- use ink or hand tool to select any part of your model and edit it +- a sidebar to view and edit any entity option and display some tips +- in sidebar for nodes, edit name, color and start amount +- in sidebar for edges, switch between positive effect (signal preservation) and negative effect (sgnial inverted) +- in sidebar for texts, edit content text +- in sidebar welcome page: an intro, somes links and import/export features +- save as link, to store your work (system model) in the url and share it easily +- embed in your website, to include the live demo directly in your website +- a play button to switch in play mode + +##### Play mode user interface +- a play mode to explore system reactions with moving signals. +- a stop/remix button to switch back in edit mode +- a reset button to start again your simulation from the beginning +- in embed/embedded mode : full screen mode with no sidebar +- a no-UI mode for tiny embed use-case +- an autoplay/autoSendSignal url parameter to start with a moving signal without user action +- a replay mode to discover how to build a system model with loopy just by watching a ghost replay of it. +- auto resize the play scene in full screen / embed mode +- a speed slider to run the simulation (signal speed) slower or faster. + +##### Play mode signals features +- as user, by a click, send positive or negative signal from a node +- signals follow arrows to reach next nodes (and change their color to match nodes color) +- signals add/remove theirs values to the node amount then bounce thru arrows +- arrow length changes the delay for a signal to go from a node to another + +##### And... +- all other stuff i miss to mention here. + + +[Non-publié/Non-Stabilisé]: https://github.com/1000i100/loopy/compare/v1.1.0...HEAD + +[Version 1.1]: https://github.com/1000i100/loopy/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/1000i100/loopy/releases/tag/v0.0.1 + +[Nicky Case]: https://github.com/ncase "@ncase" +[1000i100]: https://github.com/1000i100 "@1000i100" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b0efc34 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,164 @@ +EN | [FR](CHANGELOG.fr.md) + +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Road-map +- Groups +- Multi-lang support +- Write documentation pages and examples +- For more, check the [issues](https://github.com/1000i100/loopy/issues). + + + +## [Unreleased] (by [1000i100]) + +### Added + +##### Edit mode user interface +- a **simple mode** to keep loopy as simple as it used to be (easy onboarding or learning curve). Simple mode include only v1.* features. +- an **advanced mode** to unleash the power of creativity with V2 features. +- add a help shortcut `?` for each features linked to a page with some explanations and use-case examples +- a global switch between **colorAesthetic** and **colorLogic** mode to allow the use of color to unambiguous different kind of signals and nodes. +- zoom in and out (or drag with move tool) in the scene to explore big system. +- select the **camera mode** to use in play mode. + +##### Play mode cameras +- **scene cam.** : (resize to scene) the camera scale to show all visible elements. +- **signal cam.** : (follow signals) the camera focus and zoom on signals. Switch to scene camera when there is no signal. +- **free cam.** : (user controllable) user can zoom in/out and drag the scene even in play mode. + +##### Death / Life mechanics +- nodes can now die and reborn (by receiving vital change propagation signal or by explosion threshold settings). +- when a node die, it sends (propagate) a death signal to all arrows allowing it. +- when a dead node receives any signal except reborn, it's dropped. +- when a dead node reborn, it sends (propagate) a reborn/life signal to all arrows allowing it. +- when an alive node receives a reborn signal, it's dropped. +- when a dead node receives a death signal, it's dropped. + +##### Node advanced features +- empty the name field to resize it to a tiny internal-logic node. +- name it "autoplay" to auto send a signal on start in play-mode. +- press del key when name field is empty to remove the node. +- 4 node **sizes** with 4 different **storage capacities** (none, normal, x5 and x100) +- **Overflow threshold** : a node can store signals without forwarding them up to a threshold, and down to another threshold (store signals within a threshold window, forward them outside the window). +- **Aggregation latency** : bypass thresholds to store signals for a duration before releasing them merged into one. +- **Death trigger** : choose if a node implode (die) when empty, or explode (die) when full. + +##### Arrow advanced features (previously named Edge) +- **Valency** allows you to act on the signal valency : preserve, invert, filter to keep only positive or negative signal, convert any signal to positive or to negative. +- Arrow can be set to randomly allow/drop signals +- Arrow can allow classical signal and/or vital change signal (death/life). +- Arrow can convert signal to vital change signal +- Arrow can handle signal as tendency (legacy default) or quantity (new) + + **Tendency mechanics** + nothing new here : when a signal reach a node it add it valency to the node stock and the node forward it + by cloning it on every output arrows (if no threshold or similar new node feature change that). + + **Quantitative mechanics** + when a signal reach a node it add it valency to the node stock, like in tendency mode. + But, if at least one output arrow is in quantitative mode, + all the overflow will be split between quantitative arrows, send and deduce from the node stock. + AND, all tendency (or vital change converter) arrow will get a fixed value signal. + +- Optional custom name (replacing it behavior symbols) +- Display signal timing to go from start node to end node thru this arrow. + +##### Text advanced features +- choose the color for each text message in your model (from 7 choices). +- switch text visibility : you can hide some text in play mode to keep them only as reminder for edition. +- link field : bind your text to a web link to make it clickable. + +##### ColorLogic mechanics +- When global colorLogic switch is enable, color become significant, and extra features are unlocked. +- A node stock is only updated by color matching signals. +- All nodes behaviors (threshold, latency, death) are only triggered by matching color signals. +- Signals reaching a node with a foreign color will be forwarded except if **Foreign color** is set to drop them. +- Arrow can **filter signals by color** to only allow a specific color. +- Arrow can also **convert a signal color** from one to another. +- Arrow can even convert to a **random color** from the ones allowed by arrows starting from end node. +- A specific arrow can change its end node color : it will **fill node with signal color** + (signal is destroyed in the process, use another arrow to clone and spread it if you want). + +##### Misc ergonomic / under the hood changes +- compact the edition sidebar on small screen. +- combined sliders to be able to change 2 parameters for one main feature (used for nodes thresholds) +- alternative image in sliderWidget depending of the selected option (for better understanding the choice effect) +- dynamic re-labeling feature name depending of selected option +- keep advanced selected setting in simple mode but display warning in the UI +- keep colorLogic selected setting in colorAesthetic mode but display warning in the UI +- added `json export` to export human readable json (old `save to file` now save a compressed file like `save as link` does now) +- added `load from url` to explain how to include in link an external loopy data file. + +### Changed +- **save as link** now store data in binary with lzma compression then base64 conversion. +- **load from file** (or from url) understand the legacy json format and the new compressed one (and the new human readable json format). +- arrows polarity + / - are ignored in advanced mode to let space for the more complete sign Behavior features. +- node text now have a white halo to improve readability. + + +## [Version 1.1] - 2017-04-11 (by [Nicky Case]) +### Added +- save as file, to store your work (system model) in your computer for backup and future improvement (and for big system that don't fit in the url) +- load from file, to load local or downloaded system models. +- make a GIF page to explain how to do gif from loopy with LICEcap or Peek + +### Changed +- node amounts are now "uncapped" +- better distribution of "signals" + + +## [1.0.0] - 2017-02-23 (by [Nicky Case]) +### Added + +##### Edit mode features +- an edit mode to create your system model. +- a tool bar to pick the tool you want to use +- use ink tool to add system entities to your model +- with ink tool create Node with pencil move +- with ink tool create Arrow/Edge with pencil move between nodes +- use text tool to add some text in your model +- use hand tool to move any part of your model +- use erase tool to delete any part of your model +- use ink or hand tool to select any part of your model and edit it +- a sidebar to view and edit any entity option and display some tips +- in sidebar for nodes, edit name, color and start amount +- in sidebar for edges, switch between positive effect (signal preservation) and negative effect (sgnial inverted) +- in sidebar for texts, edit content text +- in sidebar welcome page: an intro, somes links and import/export features +- save as link, to store your work (system model) in the url and share it easily +- embed in your website, to include the live demo directly in your website +- a play button to switch in play mode + +##### Play mode user interface +- a play mode to explore system reactions with moving signals. +- a stop/remix button to switch back in edit mode +- a reset button to start again your simulation from the beginning +- in embed/embedded mode : full screen mode with no sidebar +- a no-UI mode for tiny embed use-case +- an autoplay/autoSendSignal url parameter to start with a moving signal without user action +- a replay mode to discover how to build a system model with loopy just by watching a ghost replay of it. +- auto resize the play scene in full screen / embed mode +- a speed slider to run the simulation (signal speed) slower or faster. + +##### Play mode signals features +- as user, by a click, send positive or negative signal from a node +- signals follow arrows to reach next nodes (and change their color to match nodes color) +- signals add/remove theirs values to the node amount then bounce thru arrows +- arrow length changes the delay for a signal to go from a node to another + +##### And... +- all other stuff i miss to mention here. + + +[Unreleased]: https://github.com/1000i100/loopy/compare/v1.1.0...HEAD + +[Version 1.1]: https://github.com/1000i100/loopy/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/1000i100/loopy/releases/tag/v0.0.1 + +[Nicky Case]: https://github.com/ncase "@ncase" +[1000i100]: https://github.com/1000i100 "@1000i100" diff --git a/README.fr.md b/README.fr.md new file mode 100644 index 0000000..b54a634 --- /dev/null +++ b/README.fr.md @@ -0,0 +1,45 @@ +![](https://i.imgur.com/S8c7E8o.gif) + +[EN](README.md) | FR + +### [LOOPY - un outil pour penser en systèmes](https://lo0p.it/) + +Utilisez LOOPY dans sa dernière [version stable](https://lo0p.it/2/) ou testez les derniers ajouts dans la [version du jour même](https://1000i100.github.io/loopy/2/). + +Découvrez des [exemples d'usage de LOOPY créés par la communauté !](https://lo0p.it/2/pages/examples) + +- Project original par [Nicky Case](https://github.com/ncase/loopy) (juqu'à la v1.1 ~ 2017) +- Complété et maintenu par [1000i100](https://github.com/1000i100) (depuis la v2 ~ 2020) + +[Zero Droits Réservés](http://creativecommons.org/publicdomain/zero/1.0/): +LOOPY est entièrement open source / dans le domaine public. + +Pour copier/modifier/étendre LOOPY, clonez juste ce dépôt Github avec sa branche gh-pages. +([en savoir plus sur les pages Github gratuites](https://pages.github.com/)) + +### Liste des fonctionnalité + +Toutes les fonctions de LOOPY sont listé dans le [changelog](CHANGELOG.fr.md) écrit pour être compréhensible par des humains. + +Pour chaque fonction, vous pouvez trouver plus d'explications et des exemple spécifique d'usage dans la [documentation](2/pages/doc/). +La documentation est aussi disponible depuis l'aide contextuelle de LOOPY : Cliquez simplement sur le `?` associé à une fonction. + +### Contribuer + +Vous avez trouvé un bug ou vous souhaitez une nouvelle fonctionnalité ? Cherchez dans les [tickets/issues](https://github.com/1000i100/loopy/issues) pour voir si c'est déjà listé. Si ce n'est pas le cas, ajoutez le votre ! + +Vous savez le faire vous même ? Les [demande de fusion](https://github.com/1000i100/loopy/pulls) sont très bienvenue ! + +Traduire LOOPY ? Ça se passe sur [weblate] ! + +Financer tout ce travail ? +J'y ai passé environ 200 heurs jusqu'a maintenant (2020). +Je travaille aussi sur d'autre projets Libre (FOSS). +Je vie avec très peu de revenus, donc chaque don compte ! +Soutenez-moi via : +- [1forma-tic.fr (mon site pro)](https://1forma-tic.fr) +- [Tipeee](https://fr.tipeee.com/1000i100) +- [Liberapay](https://liberapay.com/1000i100/) +- [Open-Collective](https://opencollective.com/soutien-1000i100) +- [Patreon](https://www.patreon.com/1000i100) +- [Github Sponsors](https://github.com/sponsors/1000i100) diff --git a/README.md b/README.md index 70c8865..63cdd31 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ ![](https://i.imgur.com/S8c7E8o.gif) -### [LOOPY - a tool for thinking in systems](http://ncase.me/loopy/) +EN | [FR](README.fr.md) + +### [LOOPY - a tool for thinking in systems](https://lo0p.it/) + +Use LOOPY in it last [stable version](https://lo0p.it/2/) or try latest features in the [nightly version](https://1000i100.github.io/loopy/2/). + +Check out these [user-made LOOPY's!](https://lo0p.it/2/pages/examples) + +- Original project by [Nicky Case](https://github.com/ncase/loopy) (up to v1.1 ~ 2017) +- Extended and maintained by [1000i100](https://github.com/1000i100) (since v2 ~ 2020) [Zero Rights Reserved](http://creativecommons.org/publicdomain/zero/1.0/): LOOPY is entirely open source/public domain. @@ -8,17 +17,28 @@ LOOPY is entirely open source/public domain. To mirror LOOPY, just clone this Github Repo with the gh-pages branch. ([learn more about these free Github Pages](https://pages.github.com/)) -Other Peeps' Open Source Code I Used: -- [minpubsub](https://github.com/daniellmb/MinPubSub) -- [balloon.css](https://kazzkiq.github.io/balloon.css/) -- [simple sharing buttons](https://simplesharingbuttons.com/) +### Features list + +All features are listed in a human readable [changelog](CHANGELOG.md). + +For any feature, you can find more explanation and specific use-case examples in the [documentation](2/pages/doc/). +Documentation is also available from contextual help inside LOOPY : Just click on the feature associated `?`. + +### Contribute -Check out these [user-made LOOPY's!](http://ncase.me/loopy/v1.1/pages/examples) +Found a bug or wish a feature ? Search in [issues](https://github.com/1000i100/loopy/issues) if it's not listed, add a new one ! ---- +You're able to do it ? [Merge/pull request](https://github.com/1000i100/loopy/pulls) are very welcome ! -Version 1.1: -- node amounts are now "uncapped" -- better distribution of "signals" +Translate LOOPY ? Here is the [weblate] ! -Version 1.0: the whole everything. +Funding all this work ? I've spent around 200 hours on loopy for now (2020). +I also work on other FOSS projects. +I live with very few incomes, so every gift count ! +Support me with : +- [Github Sponsors](https://github.com/sponsors/1000i100) +- [1forma-tic.fr (my freelancer website)](https://1forma-tic.fr) +- [Tipeee](https://fr.tipeee.com/1000i100) +- [Liberapay](https://liberapay.com/1000i100/) +- [Open-Collective](https://opencollective.com/soutien-1000i100) +- [Patreon](https://www.patreon.com/1000i100) diff --git a/css/icons/Email.png b/about/css/icons/Email.png similarity index 100% rename from css/icons/Email.png rename to about/css/icons/Email.png diff --git a/css/icons/Facebook.png b/about/css/icons/Facebook.png similarity index 100% rename from css/icons/Facebook.png rename to about/css/icons/Facebook.png diff --git a/css/icons/Twitter.png b/about/css/icons/Twitter.png similarity index 100% rename from css/icons/Twitter.png rename to about/css/icons/Twitter.png diff --git a/css/index.css b/about/css/index.css similarity index 100% rename from css/index.css rename to about/css/index.css diff --git a/v1.1/pages/examples/favicon.png b/about/favicon.png similarity index 100% rename from v1.1/pages/examples/favicon.png rename to about/favicon.png diff --git a/img/feature_draw.gif b/about/img/feature_draw.gif similarity index 100% rename from img/feature_draw.gif rename to about/img/feature_draw.gif diff --git a/img/feature_play.gif b/about/img/feature_play.gif similarity index 100% rename from img/feature_play.gif rename to about/img/feature_play.gif diff --git a/img/feature_remix.gif b/about/img/feature_remix.gif similarity index 100% rename from img/feature_remix.gif rename to about/img/feature_remix.gif diff --git a/img/logo.png b/about/img/logo.png similarity index 100% rename from img/logo.png rename to about/img/logo.png diff --git a/img/use_1.png b/about/img/use_1.png similarity index 100% rename from img/use_1.png rename to about/img/use_1.png diff --git a/img/use_2.png b/about/img/use_2.png similarity index 100% rename from img/use_2.png rename to about/img/use_2.png diff --git a/img/use_3.png b/about/img/use_3.png similarity index 100% rename from img/use_3.png rename to about/img/use_3.png diff --git a/img/use_4.png b/about/img/use_4.png similarity index 100% rename from img/use_4.png rename to about/img/use_4.png diff --git a/img/use_5.png b/about/img/use_5.png similarity index 100% rename from img/use_5.png rename to about/img/use_5.png diff --git a/social/thumbnail.png b/about/social/thumbnail.png similarity index 100% rename from social/thumbnail.png rename to about/social/thumbnail.png diff --git a/social/thumbnail_small.png b/about/social/thumbnail_small.png similarity index 100% rename from social/thumbnail_small.png rename to about/social/thumbnail_small.png diff --git a/splash/css/balloon.css b/about/splash/css/balloon.css similarity index 100% rename from splash/css/balloon.css rename to about/splash/css/balloon.css diff --git a/v1.1/css/cursors/drag.png b/about/splash/css/cursors/drag.png similarity index 100% rename from v1.1/css/cursors/drag.png rename to about/splash/css/cursors/drag.png diff --git a/v1.1/css/cursors/erase.png b/about/splash/css/cursors/erase.png similarity index 100% rename from v1.1/css/cursors/erase.png rename to about/splash/css/cursors/erase.png diff --git a/v1.1/css/cursors/ink.png b/about/splash/css/cursors/ink.png similarity index 100% rename from v1.1/css/cursors/ink.png rename to about/splash/css/cursors/ink.png diff --git a/v1.1/css/cursors/label.png b/about/splash/css/cursors/label.png similarity index 100% rename from v1.1/css/cursors/label.png rename to about/splash/css/cursors/label.png diff --git a/v1.1/css/icons/controls.png b/about/splash/css/icons/controls.png similarity index 100% rename from v1.1/css/icons/controls.png rename to about/splash/css/icons/controls.png diff --git a/v1.1/css/icons/drag.png b/about/splash/css/icons/drag.png similarity index 100% rename from v1.1/css/icons/drag.png rename to about/splash/css/icons/drag.png diff --git a/v1.1/css/icons/erase.png b/about/splash/css/icons/erase.png similarity index 100% rename from v1.1/css/icons/erase.png rename to about/splash/css/icons/erase.png diff --git a/v1.1/css/icons/ink.png b/about/splash/css/icons/ink.png similarity index 100% rename from v1.1/css/icons/ink.png rename to about/splash/css/icons/ink.png diff --git a/v1.1/css/icons/label.png b/about/splash/css/icons/label.png similarity index 100% rename from v1.1/css/icons/label.png rename to about/splash/css/icons/label.png diff --git a/v1.1/css/icons/speed_fast.png b/about/splash/css/icons/speed_fast.png similarity index 100% rename from v1.1/css/icons/speed_fast.png rename to about/splash/css/icons/speed_fast.png diff --git a/v1.1/css/icons/speed_slow.png b/about/splash/css/icons/speed_slow.png similarity index 100% rename from v1.1/css/icons/speed_slow.png rename to about/splash/css/icons/speed_slow.png diff --git a/splash/css/loopy.css b/about/splash/css/loopy.css similarity index 100% rename from splash/css/loopy.css rename to about/splash/css/loopy.css diff --git a/v1.1/css/sliders/color.png b/about/splash/css/sliders/color.png similarity index 100% rename from v1.1/css/sliders/color.png rename to about/splash/css/sliders/color.png diff --git a/v1.1/css/sliders/initial.png b/about/splash/css/sliders/initial.png similarity index 100% rename from v1.1/css/sliders/initial.png rename to about/splash/css/sliders/initial.png diff --git a/v1.1/css/sliders/slider_pointer.png b/about/splash/css/sliders/slider_pointer.png similarity index 100% rename from v1.1/css/sliders/slider_pointer.png rename to about/splash/css/sliders/slider_pointer.png diff --git a/v1.1/css/sliders/strength.png b/about/splash/css/sliders/strength.png similarity index 100% rename from v1.1/css/sliders/strength.png rename to about/splash/css/sliders/strength.png diff --git a/splash/css/sliders/strength_original.png b/about/splash/css/sliders/strength_original.png similarity index 100% rename from splash/css/sliders/strength_original.png rename to about/splash/css/sliders/strength_original.png diff --git a/splash/index.html b/about/splash/index.html similarity index 100% rename from splash/index.html rename to about/splash/index.html diff --git a/splash/js/Dragger.js b/about/splash/js/Dragger.js similarity index 100% rename from splash/js/Dragger.js rename to about/splash/js/Dragger.js diff --git a/splash/js/Edge.js b/about/splash/js/Edge.js similarity index 100% rename from splash/js/Edge.js rename to about/splash/js/Edge.js diff --git a/splash/js/Eraser.js b/about/splash/js/Eraser.js similarity index 100% rename from splash/js/Eraser.js rename to about/splash/js/Eraser.js diff --git a/splash/js/Ink.js b/about/splash/js/Ink.js similarity index 100% rename from splash/js/Ink.js rename to about/splash/js/Ink.js diff --git a/splash/js/Key.js b/about/splash/js/Key.js similarity index 100% rename from splash/js/Key.js rename to about/splash/js/Key.js diff --git a/splash/js/Label.js b/about/splash/js/Label.js similarity index 100% rename from splash/js/Label.js rename to about/splash/js/Label.js diff --git a/splash/js/Labeller.js b/about/splash/js/Labeller.js similarity index 100% rename from splash/js/Labeller.js rename to about/splash/js/Labeller.js diff --git a/splash/js/Loopy.js b/about/splash/js/Loopy.js similarity index 100% rename from splash/js/Loopy.js rename to about/splash/js/Loopy.js diff --git a/splash/js/Modal.js b/about/splash/js/Modal.js similarity index 100% rename from splash/js/Modal.js rename to about/splash/js/Modal.js diff --git a/splash/js/Model.js b/about/splash/js/Model.js similarity index 100% rename from splash/js/Model.js rename to about/splash/js/Model.js diff --git a/splash/js/Mouse.js b/about/splash/js/Mouse.js similarity index 100% rename from splash/js/Mouse.js rename to about/splash/js/Mouse.js diff --git a/splash/js/Node.js b/about/splash/js/Node.js similarity index 100% rename from splash/js/Node.js rename to about/splash/js/Node.js diff --git a/splash/js/PageUI.js b/about/splash/js/PageUI.js similarity index 100% rename from splash/js/PageUI.js rename to about/splash/js/PageUI.js diff --git a/splash/js/PlayControls.js b/about/splash/js/PlayControls.js similarity index 100% rename from splash/js/PlayControls.js rename to about/splash/js/PlayControls.js diff --git a/splash/js/Sidebar.js b/about/splash/js/Sidebar.js similarity index 99% rename from splash/js/Sidebar.js rename to about/splash/js/Sidebar.js index 02a5322..cdda86a 100644 --- a/splash/js/Sidebar.js +++ b/about/splash/js/Sidebar.js @@ -173,7 +173,7 @@ function Sidebar(loopy){ "

    "+ - "
    LOOPY is "+ + "LOOPY is "+ "made by nicky case "+ "with your support on patreon <3" diff --git a/splash/js/Toolbar.js b/about/splash/js/Toolbar.js similarity index 100% rename from splash/js/Toolbar.js rename to about/splash/js/Toolbar.js diff --git a/splash/js/helpers.js b/about/splash/js/helpers.js similarity index 100% rename from splash/js/helpers.js rename to about/splash/js/helpers.js diff --git a/splash/js/minpubsub.js b/about/splash/js/minpubsub.js similarity index 100% rename from splash/js/minpubsub.js rename to about/splash/js/minpubsub.js diff --git a/imgSrc/aggregationLatency.svg b/imgSrc/aggregationLatency.svg new file mode 100644 index 0000000..98e8836 --- /dev/null +++ b/imgSrc/aggregationLatency.svg @@ -0,0 +1,2382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/cameraMode.svg b/imgSrc/cameraMode.svg new file mode 100644 index 0000000..0d8176e --- /dev/null +++ b/imgSrc/cameraMode.svg @@ -0,0 +1,1315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/colorLogic.svg b/imgSrc/colorLogic.svg new file mode 100644 index 0000000..15dc7e4 --- /dev/null +++ b/imgSrc/colorLogic.svg @@ -0,0 +1,1407 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + Aesthetic + + Aesthetic + Type logic + Type logic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/edgeTargetColor.svg b/imgSrc/edgeTargetColor.svg new file mode 100644 index 0000000..4408fde --- /dev/null +++ b/imgSrc/edgeTargetColor.svg @@ -0,0 +1,1228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + 🎲 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/explode.svg b/imgSrc/explode.svg new file mode 100644 index 0000000..6b1d8b4 --- /dev/null +++ b/imgSrc/explode.svg @@ -0,0 +1,1370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/filter.svg b/imgSrc/filter.svg new file mode 100644 index 0000000..d2b154d --- /dev/null +++ b/imgSrc/filter.svg @@ -0,0 +1,1717 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 🎲 + + + + + + + + + + + + + + + diff --git a/imgSrc/foreignColor.svg b/imgSrc/foreignColor.svg new file mode 100644 index 0000000..805c948 --- /dev/null +++ b/imgSrc/foreignColor.svg @@ -0,0 +1,1155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/loopyMode.svg b/imgSrc/loopyMode.svg new file mode 100644 index 0000000..3fe7418 --- /dev/null +++ b/imgSrc/loopyMode.svg @@ -0,0 +1,1036 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + Simple + Advanced + + diff --git a/imgSrc/overflow.svg b/imgSrc/overflow.svg new file mode 100644 index 0000000..e23ca91 --- /dev/null +++ b/imgSrc/overflow.svg @@ -0,0 +1,1870 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/quantitative.svg b/imgSrc/quantitative.svg new file mode 100644 index 0000000..063f514 --- /dev/null +++ b/imgSrc/quantitative.svg @@ -0,0 +1,1462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/signBehavior.svg b/imgSrc/signBehavior.svg new file mode 100644 index 0000000..055c152 --- /dev/null +++ b/imgSrc/signBehavior.svg @@ -0,0 +1,1385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/size.svg b/imgSrc/size.svg new file mode 100644 index 0000000..3f346a1 --- /dev/null +++ b/imgSrc/size.svg @@ -0,0 +1,1096 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/strengthLinkBehavior.svg b/imgSrc/strengthLinkBehavior.svg new file mode 100644 index 0000000..2d98e9b --- /dev/null +++ b/imgSrc/strengthLinkBehavior.svg @@ -0,0 +1,2860 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/textColor.svg b/imgSrc/textColor.svg new file mode 100644 index 0000000..526f3ec --- /dev/null +++ b/imgSrc/textColor.svg @@ -0,0 +1,1129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/transmissionBehavior.svg b/imgSrc/transmissionBehavior.svg new file mode 100644 index 0000000..2dfbcb7 --- /dev/null +++ b/imgSrc/transmissionBehavior.svg @@ -0,0 +1,800 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/imgSrc/visibility.svg b/imgSrc/visibility.svg new file mode 100644 index 0000000..d2be77a --- /dev/null +++ b/imgSrc/visibility.svg @@ -0,0 +1,1115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + & + + + diff --git a/index.html b/index.html index 96de043..1fc39b6 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ - + @@ -25,12 +25,12 @@ - + - + @@ -38,7 +38,7 @@
    - +
    @@ -56,7 +56,7 @@
    - +
    play with simulations
    @@ -74,11 +74,11 @@ like a wee baby
    - +
    - +
    remix others' simulations
    @@ -98,9 +98,9 @@ NOW PLAY WITH AN EXAMPLE:
    - +
    - +
    OR, MAKE A MODEL FROM SCRATCH →
    @@ -111,7 +111,7 @@ Like duct tape, you can use LOOPY for all sorts of things:
    - +
    However you choose to use LOOPY, hopefully it can give you not just @@ -119,7 +119,7 @@ the complex systems of the world around us. It's a hot mess out there.

    - +
    TRY OUT LOOPY →

    @@ -140,28 +140,28 @@ LOOPY is also open source and public domain, meaning it's free for coders, educators, and just about anybody to re-use and re-mix LOOPY as they see fit. - (Get the source code on Github!) + (Get the source code on Github!)
    LOOPY is made by Nicky Case, (my wobsite | my tweeter)
    thanks to my generous supporters on Patreon! - (see them all here) + (see them all here)

    And if you like what I make, feel free to toss coins at me on Patreon. <3
    - +
    share:
    diff --git a/v1.1/css/loopy.css b/v1.1/css/loopy.css deleted file mode 100644 index 0f70fa7..0000000 --- a/v1.1/css/loopy.css +++ /dev/null @@ -1,321 +0,0 @@ -body{ - margin:0; - background: #fff; - - font-family: Helvetica, Arial, sans-serif; - font-weight: normal; - font-size: 20px; - - overflow: hidden; -} -canvas{ - position:absolute; - top:0; left:0; -} - -#canvasses{ - position:absolute; - top:0; left:0; - width: calc(100% - 300px); - height: 100%; -} -#canvasses[fullscreen=yes]{ - width: 100%; -} -#canvasses[cursor=ink]{ - cursor: url('cursors/ink.png') 0 30, auto; -} -#canvasses[cursor=drag]{ - cursor: url('cursors/drag.png') 15 15, auto; -} -#canvasses[cursor=erase]{ - cursor: url('cursors/erase.png') 5 25, auto; -} -#canvasses[cursor=label]{ - cursor: url('cursors/label.png') 15 15, auto; -} - - - -/********** - PLAY CONTROLS -**********/ - -#playbar{ - position: absolute; - bottom: 15px; - left: calc(50% - 275px); /* 50% - half of (playbar width + sidebar width)*/ - - width: 250px; - height: 75px; - - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -#playbar[fullscreen=yes]{ - left: calc(50% - 125px); /* 50% - half of (playbar width)*/ -} -#playbar .component_button{ - position: absolute; - bottom: 0; - width: 230px; - height: 25px; - background: #444; -} -#playbar .component_button:hover{ - background: #555; -} -#playbar .play_slider{ - width: 190px; - position: absolute; - left: 30px; - margin: 0; -} -.play_button_icon{ - width: 25px; - height: 25px; - background-image: url(icons/controls.png); - display: inline-block; - background-size: 400% 100%; -} -.play_button_icon[icon="0"]{ background-position: 0 0; } -.play_button_icon[icon="1"]{ background-position: -25px 0; } -.play_button_icon[icon="2"]{ background-position: -50px 0; } -.play_button_icon[icon="3"]{ background-position: -75px 0; } -.play_button_label{ - display: inline-block; - position: relative; - top: -5px; -} -div[big=yes] .play_button_icon{ - width: 30px; - height: 30px; -} -div[big=yes] .play_button_label{ - top: -3px; -} - - - -/********** - TOOLBAR -**********/ - -#toolbar{ - position: absolute; - left: 0px; - top: calc(33% - 142.5px); /* 33% - half of toolbar height */ - - width:75px; height:280px; - padding: 5px; - background-color: #ddd; - - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -#toolbar[mode=play]{ - display: none; -} - -.toolbar_button{ - width:60px; height:60px; - margin: 5px; - border: 2px solid #ddd; - cursor: pointer; - background-size: 100% 100%; -} -.toolbar_button[selected=yes]{ - border: 2px solid #888; - background-color: #999; -} - -/********** - SIDEBAR -**********/ - -#sidebar{ - position: absolute; - top:0; right:0; - width: 300px; - height: 100%; - background: #ddd; - - font-weight: 100; - font-size: 17px; - - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -#sidebar > div{ - margin: 25px; -} -#sidebar > div > div{ - margin-bottom: 20px; -} -#sidebar[mode=play]{ - background: #222; - color: #777; -} -#sidebar .mini_button{ - background: #999; - color: #fff; - padding: 3px 5px; - border-radius: 2px; - font-size: 16px; - - cursor: pointer; -} -#sidebar .mini_button:hover{ - background: #aaa; -} -#sidebar[mode=play] .mini_button{ - background: #555; - color: #111; -} -#sidebar[mode=play] .mini_button:hover{ - background: #666; -} -#sidebar a{ color: #777; } -#sidebar a:hover{ color: #999; } -#sidebar hr{ - border: none; - border-bottom: 2px solid rgba(150,150,150,0.5); - width: 300px; - position: relative; - left:-25px; - margin: 10px 0; -} -.component_input{ - border: none; - - width:calc(100% - 10px); - height:22px; - padding: 5px; - - font-size: 20px; - font-weight: 100; -} -.component_textarea{ - border: none; - width:calc(100% - 10px); - height: 100px; - padding: 5px; - font-size: 16px; - font-weight: 100; -} -.component_output{ - border: 1px solid #aaa; - width:100%; - background: #ccc; - color: #666; - font-size: 15px; - font-weight: 100; -} -.component_slider{ - position: relative; - width: 250px; - height: 52px; -} -.component_slider_graphic{ - position: absolute; - width: 250px; - height: 40px; - cursor: pointer; -} -.component_slider_pointer{ - position: absolute; - top: 42px; - width: 15px; - height: 10px; -} -.component_label{ - margin-bottom: 5px; -} -.component_button{ - - width: calc(100% - 20px); - background: #888; - color: #fff; - font-size: 20px; - font-weight: 100; - text-align: center; - padding: 10px; - border-radius: 5px; - - cursor: pointer; -} -.component_button:hover{ - background: #999; -} -.component_button[header=yes]{ - position: absolute; - top:0; left:0; - width: 280px; - background: #222; - border-radius: 0px; - color: #888; -} -.component_button[header=yes]:hover{ - background: #333; -} - - - -/********** - -MODAL - -**********/ - -#modal_container{ - display: none; - position: absolute; - width: 100%; height: 100%; - - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -#modal_container[show=yes]{ - display: block; -} -#modal_bg{ - position: absolute; - width: 100%; height: 100%; - background: rgba(0,0,0,0.8); -} -#modal{ - - position: absolute; - top:0; left:0; right:0; bottom:0; - margin: auto; - - width: 600px; height: 300px; - padding: 10px; - background: #ddd; -} -#modal_close{ - width: 120px; - font-weight: 100; - color: #777; - cursor: pointer; -} -#modal_close:hover{ - color: #888; -} -#modal_page{ - margin: 15px; - font-size: 25px; - font-weight: 100; - color: #333; -} -#modal iframe{ - border: none; -} diff --git a/v1.1/css/sliders/strength_original.png b/v1.1/css/sliders/strength_original.png deleted file mode 100644 index f37aa97..0000000 Binary files a/v1.1/css/sliders/strength_original.png and /dev/null differ diff --git a/v1.1/index.html b/v1.1/index.html index 2bd0b12..2889e53 100644 --- a/v1.1/index.html +++ b/v1.1/index.html @@ -1,90 +1,14 @@ - - - LOOPY (v1.1) - - - - - - - - - - - - - - - - - - - - - - - - - - - - + LOOPY (v1.1) + - - - -
    - - -
    -
    - - - - - + +

    Redirecting to LOOPY 2.0 !

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/v1.1/js/Edge.js b/v1.1/js/Edge.js deleted file mode 100644 index b74ef0d..0000000 --- a/v1.1/js/Edge.js +++ /dev/null @@ -1,518 +0,0 @@ -/********************************** - -EDGE! - -**********************************/ - -Edge.allSignals = []; -Edge.MAX_SIGNALS = 100; -Edge.MAX_SIGNALS_PER_EDGE = 10; -Edge.defaultStrength = 1; - -function Edge(model, config){ - - var self = this; - self._CLASS_ = "Edge"; - - // Mah Parents! - self.loopy = model.loopy; - self.model = model; - self.config = config; - - // Default values... - _configureProperties(self, config, { - from: _makeErrorFunc("CAN'T LEAVE 'FROM' BLANK"), - to: _makeErrorFunc("CAN'T LEAVE 'TO' BLANK"), - arc: 100, - rotation: 0, - strength: Edge.defaultStrength - }); - - // Get my NODES - self.from = model.getNode(self.from); - self.to = model.getNode(self.to); - - // We have signals! - self.signals = []; - self.signalSpeed = 0; - self.addSignal = function(signal){ - - // IF ALREADY TOO MANY, FORGET IT - if(Edge.allSignals.length>Edge.MAX_SIGNALS){ - return; - } - - // IF TOO MANY *ON THIS EDGE*, FORGET IT - if(self.signals.length>Edge.MAX_SIGNALS_PER_EDGE){ - return; - } - - // Re-create signal - var delta = signal.delta; - var age; - if(signal.age===undefined){ - // age = 13; // cos divisible by 1,2,3,4 + 1 - age = 1000000; // actually just make signals last "forever". - }else{ - age = signal.age-1; - } - var newSignal = { - delta: delta, - position: 0, - scaleX: Math.abs(delta), - scaleY: delta, - age: age - }; - - // If it's expired, forget it. - if(age<=0) return; - - self.signals.unshift(newSignal); // it's a queue! - - // ALL signals. - Edge.allSignals.push(newSignal); - - }; - self.updateSignals = function(){ - - // Speed? - var speed = Math.pow(2,self.loopy.signalSpeed); - self.signalSpeed = speed/self.getArrowLength(); - - // Move all signals along - for(var i=0; i=0.5){ - - // Multiply by this edge's strength! - signal.delta *= self.strength; - - } - - // And also TWEEN the scale. - var gotoScaleX = Math.abs(signal.delta); - var gotoScaleY = signal.delta; - signal.scaleX = signal.scaleX*0.8 + gotoScaleX*0.2; - signal.scaleY = signal.scaleY*0.8 + gotoScaleY*0.2; - */ - - } - - // If any signals reach >=1, pass 'em along - var lastSignal = self.signals[self.signals.length-1]; - while(lastSignal && lastSignal.position>=1){ - - // Actually pass it along - lastSignal.delta *= self.strength; // flip at the end only! - self.to.takeSignal(lastSignal); - - // Pop it, move on down - self.removeSignal(lastSignal); - lastSignal = self.signals[self.signals.length-1]; - - } - - }; - self.removeSignal = function(signal){ - self.signals.splice( self.signals.indexOf(signal), 1 ); - Edge.allSignals.splice( Edge.allSignals.indexOf(signal), 1 ); - }; - self.drawSignals = function(ctx){ - - // Draw each one - for(var i=0; i (1,-1) - ctx.scale(1, flip); - } - - // Signal's age = alpha. - if(signal.age==2){ - ctx.globalAlpha = 0.5; - }else if(signal.age==1){ - ctx.globalAlpha = 0.25; - } - - // Draw an arrow - ctx.beginPath(); - ctx.moveTo(-2,0); - ctx.lineTo(0,-2); - ctx.lineTo(2,0); - ctx.lineTo(1,0); - ctx.lineTo(1,2); - ctx.lineTo(-1,2); - ctx.lineTo(-1,0); - ctx.fillStyle = signalColor; - ctx.fill(); - - // Restore - ctx.restore(); - - } - - }; - var _listenerReset = subscribe("model/reset", function(){ - self.signals = []; - Edge.allSignals = []; - }); - - - ////////////////////////////////////// - // UPDATE & DRAW ///////////////////// - ////////////////////////////////////// - - // Update! - self.labelX = 0; - self.labelY = 0; - var fx, fy, tx, ty, - r, dx, dy, w, a, h, - y, a2, - arrowBuffer, arrowDistance, arrowAngle, beginDistance, beginAngle, - startAngle, endAngle, - y2, begin, end, - arrowLength, ax, ay, aa, - labelAngle, lx, ly, labelBuffer; // BECAUSE I'VE LOST CONTROL OF MY LIFE. - self.update = function(speed){ - - //////////////////////////////////////////////// - // PRE-CALCULATE THE MATH (for retina canvas) // - //////////////////////////////////////////////// - - // Edge case: if arc is EXACTLY zero, whatever, add 0.1 to it. - if(self.arc==0) self.arc=0.1; - - // Mathy calculations: (all retina, btw) - fx=self.from.x*2; - fy=self.from.y*2; - tx=self.to.x*2; - ty=self.to.y*2; - if(self.from==self.to){ - var rotation = self.rotation; - rotation *= Math.TAU/360; - tx += Math.cos(rotation); - ty += Math.sin(rotation); - } - dx = tx-fx; - dy = ty-fy; - w = Math.sqrt(dx*dx+dy*dy); - a = Math.atan2(dy,dx); - h = Math.abs(self.arc*2); - - // From: http://www.mathopenref.com/arcradius.html - r = (h/2) + ((w*w)/(8*h)); - y = r-h; // the circle's y-pos is radius - given height. - a2 = Math.acos((w/2)/r); // angle from x axis, arc-cosine of half-width & radius - - // Arrow buffer... - arrowBuffer = 15; - arrowDistance = (self.to.radius+arrowBuffer)*2; - arrowAngle = arrowDistance/r; // (distance/circumference)*TAU, close enough. - beginDistance = (self.from.radius+arrowBuffer)*2; - beginAngle = beginDistance/r; - - // Arc it! - startAngle = a2 - Math.TAU/2; - endAngle = -a2; - if(h>r){ - startAngle *= -1; - endAngle *= -1; - } - if(self.arc>0){ - y2 = y; - begin = startAngle+beginAngle; - end = endAngle-arrowAngle; - }else{ - y2 = -y; - begin = -startAngle-beginAngle; - end = -endAngle+arrowAngle; - } - - // Arrow HEAD! - arrowLength = 10*2; - ax = w/2 + Math.cos(end)*r; - ay = y2 + Math.sin(end)*r; - aa = end + Math.TAU/4; - - // My label is... - var s = self.strength; - var l; - if(s>=3) l="+++"; - else if(s>=2) l="++"; - else if(s>=1) l="+"; - else if(s==0) l="?"; - else if(s>=-1) l="–"; // EM dash, not hyphen. - else if(s>=-2) l="– –"; - else l="– – –"; - self.label = l; - - // Label position - var labelPosition = self.getPositionAlongArrow(0.5); - lx = labelPosition.x; - ly = labelPosition.y; - - // ACTUAL label position, for grabbing purposes - self.labelX = (fx + Math.cos(a)*lx - Math.sin(a)*ly)/2; // un-retina - self.labelY = (fy + Math.sin(a)*lx + Math.cos(a)*ly)/2; // un-retina - - // ...add offset to label - labelBuffer = 18*2; // retina - if(self.arc<0) labelBuffer*=-1; - ly += labelBuffer; - - /////////////////////////////////////// - // AND THEN UPDATE OTHER STUFF AFTER // - // THE CALCULATIONS ARE DONE I GUESS // - /////////////////////////////////////// - - // When actually playing the simulation... - /*if(self.loopy.mode==Loopy.MODE_PLAY){ - self.to.nextValue += self.from.value * self.strength * speed; - }*/ - - // Update signals - self.updateSignals(); - - }; - - // Get position along arrow, on what parameter? - self.getArrowLength = function(){ - var angle; - if(self.from==self.to){ - // angle = Math.TAU; - return r*Math.TAU - 2*self.from.radius; - }else{ - //debugger; - if(y<0){ - // arc's center is above the horizon - if(self.arc<0){ // ccw - angle = Math.TAU + begin - end; - }else{ // cw - angle = Math.TAU + end - begin; - } - }else{ - // arc's center is below the horizon - angle = Math.abs(end-begin); - } - } - return r*angle; - }; - self.getPositionAlongArrow = function(param){ - - param = -0.05 + param*1.1; // (0,1) --> (-0.05, 1.05) - - // If the arc's circle is actually BELOW the line... - var begin2 = begin; - if(y<0){ - // DON'T KNOW WHY THIS WORKS, BUT IT DOES. - if(begin2>0){ - begin2-=Math.TAU; - }else{ - begin2+=Math.TAU; - } - } - - // Get angle! - var angle = begin2 + (end-begin2)*param; - - // return x & y - return{ - x: w/2 + Math.cos(angle)*r, - y: y2 + Math.sin(angle)*r - }; - - }; - - // Draw - self.draw = function(ctx){ - - // Width & Color - ctx.lineWidth = 4*Math.abs(self.strength)-2; - ctx.strokeStyle = "#666"; - - // Translate & Rotate! - ctx.save(); - ctx.translate(fx, fy); - ctx.rotate(a); - - // Highlight! - if(self.loopy.sidebar.currentPage.target == self){ - ctx.save(); - ctx.translate(lx, ly); - ctx.rotate(-a); - ctx.beginPath(); - ctx.arc(0, 5, 60, 0, Math.TAU, false); - ctx.fillStyle = HIGHLIGHT_COLOR; - ctx.fill(); - ctx.restore(); - } - - // Arc it! - ctx.beginPath(); - if(self.arc>0){ - ctx.arc(w/2, y2, r, startAngle, end, false); - }else{ - ctx.arc(w/2, y2, r, -startAngle, end, true); - } - - // Arrow HEAD! - ctx.save(); - ctx.translate(ax, ay); - if(self.arc<0) ctx.scale(-1,-1); - ctx.rotate(aa); - ctx.moveTo(-arrowLength, -arrowLength); - ctx.lineTo(0,0); - ctx.lineTo(-arrowLength, arrowLength); - ctx.restore(); - - // Stroke! - ctx.stroke(); - - // Draw label - ctx.font = "100 60px sans-serif"; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.save(); - ctx.translate(lx, ly); - ctx.rotate(-a); - ctx.fillStyle = "#999"; - ctx.fillText(self.label, 0, 0); - ctx.restore(); - - // DRAW SIGNALS - self.drawSignals(ctx); - - // Restore - ctx.restore(); - - }; - - ////////////////////////////////////// - // KILL EDGE ///////////////////////// - ////////////////////////////////////// - - self.kill = function(){ - - // Kill Listeners! - unsubscribe("model/reset",_listenerReset); - - // Remove from parent! - model.removeEdge(self); - - // Killed! - publish("kill",[self]); - - }; - - ////////////////////////////////////// - // HELPER METHODS //////////////////// - ////////////////////////////////////// - - self.isPointOnLabel = function(x, y){ - // TOTAL HACK: radius based on TOOL BEING USED. - var radius; - if(self.loopy.tool==Loopy.TOOL_DRAG || self.loopy.tool==Loopy.TOOL_INK) radius=40; // selecting, wide radius! - else if(self.loopy.tool==Loopy.TOOL_ERASE) radius=25; // no accidental erase - else radius = 15; // you wanna label close to edges - return _isPointInCircle(x, y, self.labelX, self.labelY, radius); - }; - - self.getBoundingBox = function(){ - - // SPECIAL CASE: SELF-ARC - if(self.from==self.to){ - - var perpendicular = a-Math.TAU/4; - var cx = fx + Math.cos(perpendicular)*-y2; - var cy = fy + Math.sin(perpendicular)*-y2; - cx = cx/2; // un-retina - cy = cy/2; // un-retina - - var _radius = r/2; // un-retina - - return { - left: cx - _radius, - top: cy - _radius, - right: cx + _radius, - bottom: cy + _radius - }; - - } - - // THREE POINTS: start, end, and perpendicular with r - var from = {x:self.from.x, y:self.from.y}; - var to = {x:self.to.x, y:self.to.y}; - var mid = { - x:(from.x+to.x)/2, - y:(from.y+to.y)/2 - }; - - var perpendicular = a-Math.TAU/4; - mid.x += Math.cos(perpendicular)*self.arc; - mid.y += Math.sin(perpendicular)*self.arc; - - // TEST ALL POINTS - - var left = Infinity; - var top = Infinity; - var right = -Infinity; - var bottom = -Infinity; - var points = [from, to, mid]; - for(var i=0; ix) left=x; - if(top>y) top=y; - if(rightMath.abs(bounds.bottom)) edgeConfig.arc = -bounds.top; - else edgeConfig.arc = -bounds.bottom; - - } - - // Add the edge! - if(edgeConfig){ - var newEdge = loopy.model.addEdge(edgeConfig); - loopy.sidebar.edit(newEdge); - } - - } - - // NODE: did NOT start in a node. - if(!startNode){ - - // Just roughly make a circle the size of the bounds of the circle - var bounds = _getBounds(self.strokeData); - var x = (bounds.left+bounds.right)/2; - var y = (bounds.top+bounds.bottom)/2; - var r = ((bounds.width/2)+(bounds.height/2))/2; - - // Circle can't be TOO smol - if(r>15){ - - // Snap to radius - /*r = Math.round(r/Ink.SNAP_TO_RADIUS)*Ink.SNAP_TO_RADIUS; - if(r0){ - drawCountdown = drawCountdownFull; - break; - } - } - - // DRAW??????? - drawCountdown--; - if(drawCountdown<=0) return; - - // Also only draw if last updated... - if(!_canvasDirty) return; - _canvasDirty = false; - - // Clear! - ctx.clearRect(0,0,self.canvas.width,self.canvas.height); - - // Translate - ctx.save(); - - // Translate to center, (translate, scale, translate) to expand to size - var canvasses = document.getElementById("canvasses"); - var CW = canvasses.clientWidth - _PADDING - _PADDING; - var CH = canvasses.clientHeight - _PADDING_BOTTOM - _PADDING; - var tx = loopy.offsetX*2; - var ty = loopy.offsetY*2; - tx -= CW+_PADDING; - ty -= CH+_PADDING; - var s = loopy.offsetScale; - tx = s*tx; - ty = s*ty; - tx += CW+_PADDING; - ty += CH+_PADDING; - if(loopy.embedded){ - tx += _PADDING; // dunno why but this is needed - ty += _PADDING; // dunno why but this is needed - } - ctx.setTransform(s, 0, 0, s, tx, ty); - - // Draw labels THEN edges THEN nodes - for(var i=0;i0){ - self.nodes[0].kill(); - } - - // Just kill ALL labels. - while(self.labels.length>0){ - self.labels[0].kill(); - } - }; - - - - //////////////////// - // HELPER METHODS // - //////////////////// - - self.getNodeByPoint = function(x,y,buffer){ - var result; - for(var i=self.nodes.length-1; i>=0; i--){ // top-down - var node = self.nodes[i]; - if(node.isPointInNode(x,y,buffer)) return node; - } - return null; - }; - - self.getEdgeByPoint = function(x, y, wholeArrow){ - // TODO: wholeArrow option? - var result; - for(var i=self.edges.length-1; i>=0; i--){ // top-down - var edge = self.edges[i]; - if(edge.isPointOnLabel(x,y)) return edge; - } - return null; - }; - - self.getLabelByPoint = function(x, y){ - var result; - for(var i=self.labels.length-1; i>=0; i--){ // top-down - var label = self.labels[i]; - if(label.isPointInLabel(x,y)) return label; - } - return null; - }; - - // Click to edit! - subscribe("mouseclick",function(){ - - // ONLY WHEN EDITING (and NOT erase) - if(self.loopy.mode!=Loopy.MODE_EDIT) return; - if(self.loopy.tool==Loopy.TOOL_ERASE) return; - - // Did you click on a node? If so, edit THAT node. - var clickedNode = self.getNodeByPoint(Mouse.x, Mouse.y); - if(clickedNode){ - loopy.sidebar.edit(clickedNode); - return; - } - - // Did you click on a label? If so, edit THAT label. - var clickedLabel = self.getLabelByPoint(Mouse.x, Mouse.y); - if(clickedLabel){ - loopy.sidebar.edit(clickedLabel); - return; - } - - // Did you click on an edge label? If so, edit THAT edge. - var clickedEdge = self.getEdgeByPoint(Mouse.x, Mouse.y); - if(clickedEdge){ - loopy.sidebar.edit(clickedEdge); - return; - } - - // If the tool LABEL? If so, TRY TO CREATE LABEL. - if(self.loopy.tool==Loopy.TOOL_LABEL){ - loopy.label.tryMakingLabel(); - return; - } - - // Otherwise, go to main Edit page. - loopy.sidebar.showPage("Edit"); - - }); - - // Centering & Scaling - self.getBounds = function(){ - - // If no nodes & no labels, forget it. - if(self.nodes.length==0 && self.labels.length==0) return; - - // Get bounds of ALL objects... - var left = Infinity; - var top = Infinity; - var right = -Infinity; - var bottom = -Infinity; - var _testObjects = function(objects){ - for(var i=0; ibounds.left) left=bounds.left; - if(top>bounds.top) top=bounds.top; - if(right screenRatio){ - // wider... - scaleRatio = fitWidth/w; - }else{ - // taller... - scaleRatio = fitHeight/h; - } - - // Loopy, then! - loopy.offsetScale = scaleRatio; - - } - - }; - -} \ No newline at end of file diff --git a/v1.1/js/Mouse.js b/v1.1/js/Mouse.js deleted file mode 100644 index a9917a2..0000000 --- a/v1.1/js/Mouse.js +++ /dev/null @@ -1,72 +0,0 @@ -window.Mouse = {}; -Mouse.init = function(target){ - - // Events! - var _onmousedown = function(event){ - Mouse.moved = false; - Mouse.pressed = true; - Mouse.startedOnTarget = true; - publish("mousedown"); - }; - var _onmousemove = function(event){ - - // DO THE INVERSE - var canvasses = document.getElementById("canvasses"); - var tx = 0; - var ty = 0; - var s = 1/loopy.offsetScale; - var CW = canvasses.clientWidth - _PADDING - _PADDING; - var CH = canvasses.clientHeight - _PADDING_BOTTOM - _PADDING; - - if(loopy.embedded){ - tx -= _PADDING/2; // dunno why but this is needed - ty -= _PADDING/2; // dunno why but this is needed - } - - tx -= (CW+_PADDING)/2; - ty -= (CH+_PADDING)/2; - - tx = s*tx; - ty = s*ty; - - tx += (CW+_PADDING)/2; - ty += (CH+_PADDING)/2; - - tx -= loopy.offsetX; - ty -= loopy.offsetY; - - // Mutliply by Mouse vector - var mx = event.x*s + tx; - var my = event.y*s + ty; - - // Mouse! - Mouse.x = mx; - Mouse.y = my; - - Mouse.moved = true; - publish("mousemove"); - - }; - var _onmouseup = function(){ - Mouse.pressed = false; - if(Mouse.startedOnTarget){ - publish("mouseup"); - if(!Mouse.moved) publish("mouseclick"); - } - Mouse.moved = false; - Mouse.startedOnTarget = false; - }; - - // Add mouse & touch events! - _addMouseEvents(target, _onmousedown, _onmousemove, _onmouseup); - - // Cursor & Update - Mouse.target = target; - Mouse.showCursor = function(cursor){ - Mouse.target.style.cursor = cursor; - }; - Mouse.update = function(){ - Mouse.showCursor(""); - }; - -}; \ No newline at end of file diff --git a/v1.1/js/Node.js b/v1.1/js/Node.js deleted file mode 100644 index c76bb04..0000000 --- a/v1.1/js/Node.js +++ /dev/null @@ -1,337 +0,0 @@ -/********************************** - -NODE! - -**********************************/ - -Node.COLORS = { - 0: "#EA3E3E", // red - 1: "#EA9D51", // orange - 2: "#FEEE43", // yellow - 3: "#BFEE3F", // green - 4: "#7FD4FF", // blue - 5: "#A97FFF" // purple -}; - -Node.defaultValue = 0.5; -Node.defaultHue = 0; - -Node.DEFAULT_RADIUS = 60; - -function Node(model, config){ - - var self = this; - self._CLASS_ = "Node"; - - // Mah Parents! - self.loopy = model.loopy; - self.model = model; - self.config = config; - - // Default values... - _configureProperties(self, config, { - id: Node._getUID, - x: 0, - y: 0, - init: Node.defaultValue, // initial value! - label: "?", - hue: Node.defaultHue, - radius: Node.DEFAULT_RADIUS - }); - - // Value: from 0 to 1 - self.value = self.init; - // TODO: ACTUALLY VISUALIZE AN INFINITE RANGE - self.bound = function(){ // bound ONLY when changing value. - /*var buffer = 1.2; - if(self.value<-buffer) self.value=-buffer; - if(self.value>1+buffer) self.value=1+buffer;*/ - }; - - // MOUSE. - var _controlsVisible = false; - var _controlsAlpha = 0; - var _controlsDirection = 0; - var _controlsSelected = false; - var _controlsPressed = false; - var _listenerMouseMove = subscribe("mousemove", function(){ - - // ONLY WHEN PLAYING - if(self.loopy.mode!=Loopy.MODE_PLAY) return; - - // If moused over this, show it, or not. - _controlsSelected = self.isPointInNode(Mouse.x, Mouse.y); - if(_controlsSelected){ - _controlsVisible = true; - self.loopy.showPlayTutorial = false; - _controlsDirection = (Mouse.y40) _offset=40 - if(_offset<-40) _offset=-40; - _offsetVel += _offsetAcc; - _offsetVel *= _offsetDamp; - _offsetAcc = (_offsetGoto-_offset)*_offsetHookes; - - }; - - // Draw - var _circleRadius = 0; - self.draw = function(ctx){ - - // Retina - var x = self.x*2; - var y = self.y*2; - var r = self.radius*2; - var color = Node.COLORS[self.hue]; - - // Translate! - ctx.save(); - ctx.translate(x,y+_offset); - - // DRAW HIGHLIGHT??? - if(self.loopy.sidebar.currentPage.target == self){ - ctx.beginPath(); - ctx.arc(0, 0, r+40, 0, Math.TAU, false); - ctx.fillStyle = HIGHLIGHT_COLOR; - ctx.fill(); - } - - // White-gray bubble with colored border - ctx.beginPath(); - ctx.arc(0, 0, r-2, 0, Math.TAU, false); - ctx.fillStyle = "#fff"; - ctx.fill(); - ctx.lineWidth = 6; - ctx.strokeStyle = color; - ctx.stroke(); - - // Circle radius - // var _circleRadiusGoto = r*(self.value+1); - // _circleRadius = _circleRadius*0.75 + _circleRadiusGoto*0.25; - - // RADIUS IS (ATAN) of VALUE?!?!?! - var _r = Math.atan(self.value*5); - _r = _r/(Math.PI/2); - _r = (_r+1)/2; - - // INFINITE RANGE FOR RADIUS - // linear from 0 to 1, asymptotic otherwise. - var _value; - if(self.value>=0 && self.value<=1){ - // (0,1) -> (0.1, 0.9) - _value = 0.1 + 0.8*self.value; - }else{ - if(self.value<0){ - // asymptotically approach 0, starting at 0.1 - _value = (1/(Math.abs(self.value)+1))*0.1; - } - if(self.value>1){ - // asymptotically approach 1, starting at 0.9 - _value = 1 - (1/self.value)*0.1; - } - } - - // Colored bubble - ctx.beginPath(); - var _circleRadiusGoto = r*_value; // radius - _circleRadius = _circleRadius*0.8 + _circleRadiusGoto*0.2; - ctx.arc(0, 0, _circleRadius, 0, Math.TAU, false); - ctx.fillStyle = color; - ctx.fill(); - - // Text! - var fontsize = 40; - ctx.font = "normal "+fontsize+"px sans-serif"; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillStyle = "#000"; - var width = ctx.measureText(self.label).width; - while(width > r*2 - 30){ // -30 for buffer. HACK: HARD-CODED. - fontsize -= 1; - ctx.font = "normal "+fontsize+"px sans-serif"; - width = ctx.measureText(self.label).width; - } - ctx.fillText(self.label, 0, 0); - - // WOBBLE CONTROLS - var cl = 40; - var cy = 0; - if(self.loopy.showPlayTutorial && self.loopy.wobbleControls>0){ - var wobble = self.loopy.wobbleControls*(Math.TAU/30); - cy = Math.abs(Math.sin(wobble))*10; - } - - // Controls! - ctx.globalAlpha = _controlsAlpha; - ctx.strokeStyle = "rgba(0,0,0,0.8)"; - // top arrow - ctx.beginPath(); - ctx.moveTo(-cl,-cy-cl); - ctx.lineTo(0,-cy-cl*2); - ctx.lineTo(cl,-cy-cl); - ctx.lineWidth = (_controlsDirection>0) ? 10: 3; - if(self.loopy.showPlayTutorial) ctx.lineWidth=6; - ctx.stroke(); - // bottom arrow - ctx.beginPath(); - ctx.moveTo(-cl,cy+cl); - ctx.lineTo(0,cy+cl*2); - ctx.lineTo(cl,cy+cl); - ctx.lineWidth = (_controlsDirection<0) ? 10: 3; - if(self.loopy.showPlayTutorial) ctx.lineWidth=6; - ctx.stroke(); - - // Restore - ctx.restore(); - - }; - - ////////////////////////////////////// - // KILL NODE ///////////////////////// - ////////////////////////////////////// - - self.kill = function(){ - - // Kill Listeners! - unsubscribe("mousemove",_listenerMouseMove); - unsubscribe("mousedown",_listenerMouseDown); - unsubscribe("mouseup",_listenerMouseUp); - unsubscribe("model/reset",_listenerReset); - - // Remove from parent! - model.removeNode(self); - - // Killed! - publish("kill",[self]); - - }; - - ////////////////////////////////////// - // HELPER METHODS //////////////////// - ////////////////////////////////////// - - self.isPointInNode = function(x, y, buffer){ - buffer = buffer || 0; - return _isPointInCircle(x, y, self.x, self.y, self.radius+buffer); - }; - - self.getBoundingBox = function(){ - return { - left: self.x - self.radius, - top: self.y - self.radius, - right: self.x + self.radius, - bottom: self.y + self.radius - }; - }; - -} - -//////////////////////////// -// Unique ID identifiers! // -//////////////////////////// - -Node._UID = 0; -Node._getUID = function(){ - Node._UID++; - return Node._UID; -}; diff --git a/v1.1/js/PlayControls.js b/v1.1/js/PlayControls.js deleted file mode 100644 index 66adac7..0000000 --- a/v1.1/js/PlayControls.js +++ /dev/null @@ -1,186 +0,0 @@ -/********************************** - -PLAY CONTROLS CODE: -- play -- pause/reset/speed - -**********************************/ - -function PlayControls(loopy){ - - var self = this; - PageUI.call(self, document.getElementById("playbar")); - - self.loopy = loopy; - - // PAGES & BUTTONS - - // PLAY BUTTON's keyboard shortcut - // TODO: Toggle back & forth?????? - subscribe("key/enter",function(){ - if(Key.control){ // Ctrl-Enter or ⌘-Enter - loopy.setMode(Loopy.MODE_PLAY); - } - }); - - // During the Editor - (function(){ - var page = new Page(); - - // PLAY BUTTON - var buttonDOM = page.addComponent(new PlayButton({ - icon: 0, - label: "Play", - tooltip: isMacLike ? "⌘-Enter" : "control-enter", - onclick: function(){ - loopy.setMode(Loopy.MODE_PLAY); - //self.showPage("Edit"); - } - })).dom; - buttonDOM.setAttribute("big","yes"); - buttonDOM.style.fontSize = "28px"; - buttonDOM.style.height = "35px"; - - self.addPage("Editor", page); - })(); - - // During the Player - (function(){ - var page = new Page(); - - if(loopy.embedded){ - - // Reset | Remix - - // RESET - var buttonDOM = page.addComponent(new PlayButton({ - icon: 2, - label: "Reset", - onclick: function(){ - publish("model/reset"); - } - })).dom; - buttonDOM.style.width = "100px"; - buttonDOM.style.left = "0px"; - buttonDOM.style.top = "0px"; - - // REMIX BUTTON - var buttonDOM = page.addComponent(new PlayButton({ - icon: 3, - label: "Remix", - onclick: function(){ - var url = loopy.saveToURL(); - window.open(url,'_blank'); - } - })).dom; - buttonDOM.style.width = "100px"; - buttonDOM.style.right = "0px"; - buttonDOM.style.top = "0px"; - - }else{ - - // Stop | Reset - - // STOP BUTTON - var buttonDOM = page.addComponent(new PlayButton({ - icon: 1, - label: "Stop", - onclick: function(){ - loopy.setMode(Loopy.MODE_EDIT); - } - })).dom; - buttonDOM.style.width = "100px"; - buttonDOM.style.left = "0px"; - buttonDOM.style.top = "0px"; - - // RESET BUTTON - var buttonDOM = page.addComponent(new PlayButton({ - icon: 2, - label: "Reset", - onclick: function(){ - publish("model/reset"); - } - })).dom; - buttonDOM.style.width = "100px"; - buttonDOM.style.right = "0px"; - buttonDOM.style.top = "0px"; - - } - - // SPEED SLIDER - var speedSlider = page.addComponent(new PlaySlider({ - value: loopy.signalSpeed, - min:0, max:6, step:0.2, - oninput: function(value){ - loopy.signalSpeed = value; - } - })).dom; - speedSlider.style.bottom = "0px"; - - self.addPage("Player", page); - - })(); - -} - -function PlayButton(config){ - - var self = this; - - var label = "
    " - + "
    "+config.label+"
    "; - - self.dom = _createButton(label, function(){ - config.onclick(); - }); - - // Tooltip! - if(config.tooltip){ - self.dom.setAttribute("data-balloon", config.tooltip); - self.dom.setAttribute("data-balloon-pos", "top"); - } - -} -function PlaySlider(config){ - - var self = this; - self.dom = document.createElement("div"); - self.dom.style.bottom = "0px"; - self.dom.style.position = "absolute"; - self.dom.style.width = "100%"; - self.dom.style.height = "20px"; - - // Input - var input = document.createElement("input"); - input.setAttribute("class","play_slider"); - self.dom.appendChild(input); - - // Slow & Fast Icons - var img = new Image(); - img.src = "css/icons/speed_slow.png"; - img.width = 20; - img.height = 15; - img.style.position = "absolute"; - img.style.left = "5px"; - img.style.top = "-2px"; - self.dom.appendChild(img); - var img = new Image(); - img.src = "css/icons/speed_fast.png"; - img.width = 20; - img.height = 15; - img.style.position = "absolute"; - img.style.right = "5px"; - img.style.top = "-2px"; - self.dom.appendChild(img); - - // Properties - input.type = "range"; - input.value = config.value; - input.step = config.step; - input.min = config.min; - input.max = config.max; - input.oninput = function(event){ - config.oninput(input.value); - }; - -} \ No newline at end of file diff --git a/v1.1/js/Sidebar.js b/v1.1/js/Sidebar.js deleted file mode 100644 index c5cf1f7..0000000 --- a/v1.1/js/Sidebar.js +++ /dev/null @@ -1,453 +0,0 @@ -/********************************** - -SIDEBAR CODE - -**********************************/ - -function Sidebar(loopy){ - - var self = this; - PageUI.call(self, document.getElementById("sidebar")); - - // Edit - self.edit = function(object){ - self.showPage(object._CLASS_); - self.currentPage.edit(object); - }; - - // Go back to main when the thing you're editing is killed - subscribe("kill",function(object){ - if(self.currentPage.target==object){ - self.showPage("Edit"); - } - }); - - //////////////////////////////////////////////////////////////////////////////////////////// - // ACTUAL PAGES //////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////// - - // Node! - (function(){ - var page = new SidebarPage(); - page.addComponent(new ComponentButton({ - header: true, - label: "back to top", - onclick: function(){ - self.showPage("Edit"); - } - })); - page.addComponent("label", new ComponentInput({ - label: "

    Name:" - //label: "Name:" - })); - page.addComponent("hue", new ComponentSlider({ - bg: "color", - label: "Color:", - options: [0,1,2,3,4,5], - oninput: function(value){ - Node.defaultHue = value; - } - })); - page.addComponent("init", new ComponentSlider({ - bg: "initial", - label: "Start Amount:", - options: [0, 0.16, 0.33, 0.50, 0.66, 0.83, 1], - //options: [0, 1/6, 2/6, 3/6, 4/6, 5/6, 1], - oninput: function(value){ - Node.defaultValue = value; - } - })); - page.onedit = function(){ - - // Set color of Slider - var node = page.target; - var color = Node.COLORS[node.hue]; - page.getComponent("init").setBGColor(color); - - // Focus on the name field IF IT'S "" or "?" - var name = node.label; - if(name=="" || name=="?") page.getComponent("label").select(); - - }; - page.addComponent(new ComponentButton({ - label: "delete node", - //label: "delete circle", - onclick: function(node){ - node.kill(); - self.showPage("Edit"); - } - })); - self.addPage("Node", page); - })(); - - // Edge! - (function(){ - var page = new SidebarPage(); - page.addComponent(new ComponentButton({ - header: true, - label: "back to top", - onclick: function(){ - self.showPage("Edit"); - } - })); - page.addComponent("strength", new ComponentSlider({ - bg: "strength", - label: "

    Relationship:", - //label: "Relationship:", - options: [1, -1], - oninput: function(value){ - Edge.defaultStrength = value; - } - })); - page.addComponent(new ComponentHTML({ - html: "(to make a stronger relationship, draw multiple arrows!)

    "+ - "(to make a delayed relationship, draw longer arrows)" - })); - page.addComponent(new ComponentButton({ - //label: "delete edge", - label: "delete arrow", - //label: "delete relationship", - onclick: function(edge){ - edge.kill(); - self.showPage("Edit"); - } - })); - self.addPage("Edge", page); - })(); - - // Label! - (function(){ - var page = new SidebarPage(); - page.addComponent(new ComponentButton({ - header: true, - label: "back to top", - onclick: function(){ - self.showPage("Edit"); - } - })); - page.addComponent("text", new ComponentInput({ - label: "

    Label:", - //label: "Label:", - textarea: true - })); - page.onshow = function(){ - // Focus on the text field - page.getComponent("text").select(); - }; - page.onhide = function(){ - - // If you'd just edited it... - var label = page.target; - if(!page.target) return; - - // If text is "" or all spaces, DELETE. - var text = label.text; - if(/^\s*$/.test(text)){ - // that was all whitespace, KILL. - page.target = null; - label.kill(); - } - - }; - page.addComponent(new ComponentButton({ - label: "delete label", - onclick: function(label){ - label.kill(); - self.showPage("Edit"); - } - })); - self.addPage("Label", page); - })(); - - // Edit - (function(){ - var page = new SidebarPage(); - page.addComponent(new ComponentHTML({ - html: ""+ - - "LOOPY (v1.1)
    a tool for thinking in systems

    "+ - - "see examples "+ - "how to "+ - "credits

    "+ - - "

    "+ - - "save as link

    "+ - "save as file "+ - "load from file

    "+ - "embed in your website

    "+ - "make a GIF using LICEcap

    "+ - - "

    "+ - - "LOOPY is "+ - "made by nicky case "+ - "with your support on patreon <3

    "+ - "P.S: go read Thinking In Systems, thx" - - })); - self.addPage("Edit", page); - })(); - - // Ctrl-S to SAVE - subscribe("key/save",function(){ - if(Key.control){ // Ctrl-S or ⌘-S - publish("modal",["save_link"]); - } - }); - -} - -function SidebarPage(){ - - // TODO: be able to focus on next component with an "Enter". - - var self = this; - self.target = null; - - // DOM - self.dom = document.createElement("div"); - self.show = function(){ self.dom.style.display="block"; self.onshow(); }; - self.hide = function(){ self.dom.style.display="none"; self.onhide(); }; - - // Components - self.components = []; - self.componentsByID = {}; - self.addComponent = function(propName, component){ - - // One or two args - if(!component){ - component = propName; - propName = ""; - } - - component.page = self; // tie to self - component.propName = propName; // tie to propName - self.dom.appendChild(component.dom); // add to DOM - - // remember component - self.components.push(component); - self.componentsByID[propName] = component; - - // return! - return component; - - }; - self.getComponent = function(propName){ - return self.componentsByID[propName]; - }; - - // Edit - self.edit = function(object){ - - // New target to edit! - self.target = object; - - // Show each property with its component - for(var i=0;ibox.x+box.width) return false; - if(ybox.y+box.height) return false; - - return true; - -} - -// TODO: Make more use of this??? -function _makeErrorFunc(msg){ - return function(){ - throw Error(msg); - }; -} - -function _getParameterByName(name){ - var url = window.location.href; - name = name.replace(/[\[\]]/g, "\\$&"); - var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), - results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ''; - return decodeURIComponent(results[2].replace(/\+/g, " ")); -}; - - -function _blendColors(hex1, hex2, blend){ - - var color = "#"; - for(var i=0; i<3; i++) { - - // Into numbers... - var sub1 = hex1.substring(1+2*i, 3+2*i); - var sub2 = hex2.substring(1+2*i, 3+2*i); - var num1 = parseInt(sub1, 16); - var num2 = parseInt(sub2, 16); - - // Blended number & sub - var num = Math.floor( num1*(1-blend) + num2*blend ); - var sub = num.toString(16).toUpperCase(); - var paddedSub = ('0'+sub).slice(-2); // in case it's only one digit long - - // Add that babe - color += paddedSub; - - } - - return color; - -} - -function _shiftArray(array, shiftIndex){ - var moveThisAround = array.splice(-shiftIndex); - var shifted = moveThisAround.concat(array); - return shifted; -} - - diff --git a/v1.1/js/minpubsub.js b/v1.1/js/minpubsub.js deleted file mode 100644 index 358008d..0000000 --- a/v1.1/js/minpubsub.js +++ /dev/null @@ -1,95 +0,0 @@ -/*! - * MinPubSub - * Copyright(c) 2011 Daniel Lamb - * MIT Licensed - */ -(function (context) { - var MinPubSub = {}; - - // the topic/subscription hash - var cache = context.c_ || {}; //check for 'c_' cache for unit testing - - MinPubSub.publish = function ( /* String */ topic, /* Array? */ args) { - // summary: - // Publish some data on a named topic. - // topic: String - // The channel to publish on - // args: Array? - // The data to publish. Each array item is converted into an ordered - // arguments on the subscribed functions. - // - // example: - // Publish stuff on '/some/topic'. Anything subscribed will be called - // with a function signature like: function(a,b,c){ ... } - // - // publish('/some/topic', ['a','b','c']); - - var subs = cache[topic], - len = subs ? subs.length : 0; - - //can change loop or reverse array if the order matters - while (len--) { - subs[len].apply(context, args || []); - } - }; - - MinPubSub.subscribe = function ( /* String */ topic, /* Function */ callback) { - // summary: - // Register a callback on a named topic. - // topic: String - // The channel to subscribe to - // callback: Function - // The handler event. Anytime something is publish'ed on a - // subscribed channel, the callback will be called with the - // published array as ordered arguments. - // - // returns: Array - // A handle which can be used to unsubscribe this particular subscription. - // - // example: - // subscribe('/some/topic', function(a, b, c){ /* handle data */ }); - - if (!cache[topic]) { - cache[topic] = []; - } - cache[topic].push(callback); - return [topic, callback]; // Array - }; - - MinPubSub.unsubscribe = function ( /* Array */ handle, /* Function? */ callback) { - // summary: - // Disconnect a subscribed function for a topic. - // handle: Array - // The return value from a subscribe call. - // example: - // var handle = subscribe('/some/topic', function(){}); - // unsubscribe(handle); - - var subs = cache[callback ? handle : handle[0]], - callback = callback || handle[1], - len = subs ? subs.length : 0; - - while (len--) { - if (subs[len] === callback) { - subs.splice(len, 1); - } - } - }; - - // UMD definition to allow for CommonJS, AMD and legacy window - if (typeof module === 'object' && module.exports) { - // CommonJS, just export - module.exports = exports = MinPubSub; - } else if (typeof define === 'function' && define.amd) { - // AMD support - define(function () { - return MinPubSub; - }); - } else if (typeof context === 'object') { - // If no AMD and we are in the browser, attach to window - context.publish = MinPubSub.publish; - context.subscribe = MinPubSub.subscribe; - context.unsubscribe = MinPubSub.unsubscribe; - } - -})(this.window); \ No newline at end of file diff --git a/v1/css/balloon.css b/v1/css/balloon.css deleted file mode 100755 index 17d00ed..0000000 --- a/v1/css/balloon.css +++ /dev/null @@ -1,173 +0,0 @@ -button[data-balloon] { - overflow: visible; -} -[data-balloon] { - position: relative; -} -[data-balloon]:before, -[data-balloon]:after { - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; - filter: alpha(opacity=0); - -khtml-opacity: 0; - -moz-opacity: 0; - opacity: 0; - pointer-events: none; - -webkit-transition: all 0.1s ease-out 0.1s; - transition: all 0.1s ease-out 0.1s; - bottom: 100%; - left: 50%; - position: absolute; - z-index: 10; - -webkit-transform: translate(-50%, 10px); - -ms-transform: translate(-50%, 10px); - transform: translate(-50%, 10px); - -webkit-transform-origin: top; - -ms-transform-origin: top; - transform-origin: top; -} -[data-balloon]:after { - background: rgba(17, 17, 17, 0.9); - border-radius: 4px; - color: #fff; - content: attr(data-balloon); - font-size: 18px; - padding: .5em 1em; - white-space: nowrap; - margin-bottom: 11px; -} -[data-balloon]:before { - background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2236px%22%20height%3D%2212px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%280%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E') no-repeat; - background-size: 100% auto; - height: 6px; - width: 18px; - content: ""; - margin-bottom: 5px; -} -[data-balloon]:hover:before, -[data-balloon][data-balloon-visible]:before, -[data-balloon]:hover:after, -[data-balloon][data-balloon-visible]:after { - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; - filter: alpha(opacity=100); - -khtml-opacity: 1; - -moz-opacity: 1; - opacity: 1; - pointer-events: auto; - -webkit-transform: translate(-50%, 0); - -ms-transform: translate(-50%, 0); - transform: translate(-50%, 0); -} -[data-balloon].font-awesome:after { - font-family: FontAwesome; -} -[data-balloon][data-balloon-break]:after { - white-space: pre; -} -[data-balloon-pos="down"]:before, -[data-balloon-pos="down"]:after { - bottom: auto; - left: 50%; - top: 100%; - -webkit-transform: translate(-50%, -10px); - -ms-transform: translate(-50%, -10px); - transform: translate(-50%, -10px); -} -[data-balloon-pos="down"]:after { - margin-top: 11px; -} -[data-balloon-pos="down"]:before { - background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2236px%22%20height%3D%2212px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%28180%2018%206%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E') no-repeat; - background-size: 100% auto; - height: 6px; - width: 18px; - margin-top: 5px; - margin-bottom: 0; -} -[data-balloon-pos="down"]:hover:before, -[data-balloon-pos="down"][data-balloon-visible]:before, -[data-balloon-pos="down"]:hover:after, -[data-balloon-pos="down"][data-balloon-visible]:after { - -webkit-transform: translate(-50%, 0); - -ms-transform: translate(-50%, 0); - transform: translate(-50%, 0); -} -[data-balloon-pos="left"]:before, -[data-balloon-pos="left"]:after { - bottom: auto; - left: auto; - right: 100%; - top: 50%; - -webkit-transform: translate(10px, -50%); - -ms-transform: translate(10px, -50%); - transform: translate(10px, -50%); -} -[data-balloon-pos="left"]:after { - margin-right: 11px; -} -[data-balloon-pos="left"]:before { - background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%28-90%2018%2018%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E') no-repeat; - background-size: 100% auto; - height: 18px; - width: 6px; - margin-right: 5px; - margin-bottom: 0; -} -[data-balloon-pos="left"]:hover:before, -[data-balloon-pos="left"][data-balloon-visible]:before, -[data-balloon-pos="left"]:hover:after, -[data-balloon-pos="left"][data-balloon-visible]:after { - -webkit-transform: translate(0, -50%); - -ms-transform: translate(0, -50%); - transform: translate(0, -50%); -} -[data-balloon-pos="right"]:before, -[data-balloon-pos="right"]:after { - bottom: auto; - left: 100%; - top: 50%; - -webkit-transform: translate(-10px, -50%); - -ms-transform: translate(-10px, -50%); - transform: translate(-10px, -50%); -} -[data-balloon-pos="right"]:after { - margin-left: 11px; -} -[data-balloon-pos="right"]:before { - background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2817,%2017,%2017,%200.9%29%22%20transform%3D%22rotate%2890%206%206%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E') no-repeat; - background-size: 100% auto; - height: 18px; - width: 6px; - margin-bottom: 0; - margin-left: 5px; -} -[data-balloon-pos="right"]:hover:before, -[data-balloon-pos="right"][data-balloon-visible]:before, -[data-balloon-pos="right"]:hover:after, -[data-balloon-pos="right"][data-balloon-visible]:after { - -webkit-transform: translate(0, -50%); - -ms-transform: translate(0, -50%); - transform: translate(0, -50%); -} -[data-balloon-length]:after { - white-space: normal; -} -[data-balloon-length="small"]:after { - width: 80px; -} -[data-balloon-length="medium"]:after { - width: 150px; -} -[data-balloon-length="large"]:after { - width: 260px; -} -[data-balloon-length="xlarge"]:after { - width: 90vw; -} -@media screen and (min-width: 768px) { - [data-balloon-length="xlarge"]:after { - width: 380px; - } -} -[data-balloon-length="fit"]:after { - width: 100%; -} diff --git a/v1/css/cursors/drag.png b/v1/css/cursors/drag.png deleted file mode 100644 index 40693d8..0000000 Binary files a/v1/css/cursors/drag.png and /dev/null differ diff --git a/v1/css/cursors/erase.png b/v1/css/cursors/erase.png deleted file mode 100644 index 5c2aee1..0000000 Binary files a/v1/css/cursors/erase.png and /dev/null differ diff --git a/v1/css/cursors/ink.png b/v1/css/cursors/ink.png deleted file mode 100644 index ce6f1b4..0000000 Binary files a/v1/css/cursors/ink.png and /dev/null differ diff --git a/v1/css/cursors/label.png b/v1/css/cursors/label.png deleted file mode 100644 index 19dc921..0000000 Binary files a/v1/css/cursors/label.png and /dev/null differ diff --git a/v1/css/icons/controls.png b/v1/css/icons/controls.png deleted file mode 100644 index ce17db8..0000000 Binary files a/v1/css/icons/controls.png and /dev/null differ diff --git a/v1/css/icons/drag.png b/v1/css/icons/drag.png deleted file mode 100644 index 3431b30..0000000 Binary files a/v1/css/icons/drag.png and /dev/null differ diff --git a/v1/css/icons/erase.png b/v1/css/icons/erase.png deleted file mode 100644 index fdbe3d5..0000000 Binary files a/v1/css/icons/erase.png and /dev/null differ diff --git a/v1/css/icons/ink.png b/v1/css/icons/ink.png deleted file mode 100644 index fd49ed4..0000000 Binary files a/v1/css/icons/ink.png and /dev/null differ diff --git a/v1/css/icons/label.png b/v1/css/icons/label.png deleted file mode 100644 index 4f39873..0000000 Binary files a/v1/css/icons/label.png and /dev/null differ diff --git a/v1/css/icons/speed_fast.png b/v1/css/icons/speed_fast.png deleted file mode 100644 index ac6fca9..0000000 Binary files a/v1/css/icons/speed_fast.png and /dev/null differ diff --git a/v1/css/icons/speed_slow.png b/v1/css/icons/speed_slow.png deleted file mode 100644 index 408f4e1..0000000 Binary files a/v1/css/icons/speed_slow.png and /dev/null differ diff --git a/v1/css/sliders/color.png b/v1/css/sliders/color.png deleted file mode 100644 index d304565..0000000 Binary files a/v1/css/sliders/color.png and /dev/null differ diff --git a/v1/css/sliders/initial.png b/v1/css/sliders/initial.png deleted file mode 100644 index 0a5cf56..0000000 Binary files a/v1/css/sliders/initial.png and /dev/null differ diff --git a/v1/css/sliders/slider_pointer.png b/v1/css/sliders/slider_pointer.png deleted file mode 100644 index 697a033..0000000 Binary files a/v1/css/sliders/slider_pointer.png and /dev/null differ diff --git a/v1/css/sliders/strength.png b/v1/css/sliders/strength.png deleted file mode 100644 index 5ae21fd..0000000 Binary files a/v1/css/sliders/strength.png and /dev/null differ diff --git a/v1/css/sliders/strength_original.png b/v1/css/sliders/strength_original.png deleted file mode 100644 index f37aa97..0000000 Binary files a/v1/css/sliders/strength_original.png and /dev/null differ diff --git a/v1/favicon.png b/v1/favicon.png deleted file mode 100644 index a1b03e2..0000000 Binary files a/v1/favicon.png and /dev/null differ diff --git a/v1/index.html b/v1/index.html index 08f7b25..2c9398f 100644 --- a/v1/index.html +++ b/v1/index.html @@ -1,90 +1,14 @@ - - - LOOPY (v1.0) - - - - - - - - - - - - - - - - - - - - - - - - - - - - + LOOPY (v1.0) + - - - -
    - - -
    -
    - - - - - + +

    Redirecting to LOOPY 2.0 !

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/v1/js/Dragger.js b/v1/js/Dragger.js deleted file mode 100644 index 17d35d8..0000000 --- a/v1/js/Dragger.js +++ /dev/null @@ -1,153 +0,0 @@ -/********************************** - -DRAGGER - -**********************************/ - -function Dragger(loopy){ - - var self = this; - self.loopy = loopy; - - // Dragging anything? - self.dragging = null; - self.offsetX = 0; - self.offsetY = 0; - - subscribe("mousedown",function(){ - - // ONLY WHEN EDITING w DRAG - if(self.loopy.mode!=Loopy.MODE_EDIT) return; - if(self.loopy.tool!=Loopy.TOOL_DRAG) return; - - // Any node under here? If so, start dragging! - var dragNode = loopy.model.getNodeByPoint(Mouse.x, Mouse.y); - if(dragNode){ - self.dragging = dragNode; - self.offsetX = Mouse.x - dragNode.x; - self.offsetY = Mouse.y - dragNode.y; - loopy.sidebar.edit(dragNode); // and edit! - return; - } - - // Any label under here? If so, start dragging! - var dragLabel = loopy.model.getLabelByPoint(Mouse.x, Mouse.y); - if(dragLabel){ - self.dragging = dragLabel; - self.offsetX = Mouse.x - dragLabel.x; - self.offsetY = Mouse.y - dragLabel.y; - loopy.sidebar.edit(dragLabel); // and edit! - return; - } - - // Any edge under here? If so, start dragging! - var dragEdge = loopy.model.getEdgeByPoint(Mouse.x, Mouse.y); - if(dragEdge){ - self.dragging = dragEdge; - self.offsetX = Mouse.x - dragEdge.labelX; - self.offsetY = Mouse.y - dragEdge.labelY; - loopy.sidebar.edit(dragEdge); // and edit! - return; - } - - }); - subscribe("mousemove",function(){ - - // ONLY WHEN EDITING w DRAG - if(self.loopy.mode!=Loopy.MODE_EDIT) return; - if(self.loopy.tool!=Loopy.TOOL_DRAG) return; - - // If you're dragging a NODE, move it around! - if(self.dragging && self.dragging._CLASS_=="Node"){ - - // Model's been changed! - publish("model/changed"); - - var node = self.dragging; - node.x = Mouse.x - self.offsetX; - node.y = Mouse.y - self.offsetY; - - // update coz visual glitches - loopy.model.update(); - - } - - // If you're dragging an EDGE, move it around! - if(self.dragging && self.dragging._CLASS_=="Edge"){ - - // Model's been changed! - publish("model/changed"); - - var edge = self.dragging; - var labelX = Mouse.x - self.offsetX; - var labelY = Mouse.y - self.offsetY; - - if(edge.from!=edge.to){ - - // The Arc: whatever label *Y* is, relative to angle & first node's pos - var fx=edge.from.x, fy=edge.from.y, tx=edge.to.x, ty=edge.to.y; - var dx=tx-fx, dy=ty-fy; - var a = Math.atan2(dy,dx); - - // Calculate arc - var points = [[labelX,labelY]]; - var translated = _translatePoints(points, -fx, -fy); - var rotated = _rotatePoints(translated, -a); - var newLabelPoint = rotated[0]; - - // ooookay. - edge.arc = -newLabelPoint[1]; // WHY NEGATIVE? I DON'T KNOW. - - }else{ - - // For SELF-ARROWS: just get angle & mag for label. - var dx = labelX - edge.from.x, - dy = labelY - edge.from.y; - var a = Math.atan2(dy,dx); - var mag = Math.sqrt(dx*dx + dy*dy); - - // Minimum mag - var minimum = edge.from.radius+25; - if(magEdge.MAX_SIGNALS){ - return; - } - - // Re-create signal - var delta = signal.delta; - var age; - if(signal.age===undefined){ - // age = 13; // cos divisible by 1,2,3,4 + 1 - age = 1000000; // actually just make signals last "forever". - }else{ - age = signal.age-1; - } - var newSignal = { - delta: delta, - position: 0, - scaleX: Math.abs(delta), - scaleY: delta, - age: age - }; - - // If it's expired, forget it. - if(age<=0) return; - - self.signals.unshift(newSignal); // it's a queue! - - // ALL signals. - Edge.allSignals.push(newSignal); - - }; - self.updateSignals = function(){ - - // Speed? - var speed = Math.pow(2,self.loopy.signalSpeed); - self.signalSpeed = speed/self.getArrowLength(); - - // Move all signals along - for(var i=0; i=0.5){ - - // Multiply by this edge's strength! - signal.delta *= self.strength; - - } - - // And also TWEEN the scale. - var gotoScaleX = Math.abs(signal.delta); - var gotoScaleY = signal.delta; - signal.scaleX = signal.scaleX*0.8 + gotoScaleX*0.2; - signal.scaleY = signal.scaleY*0.8 + gotoScaleY*0.2; - */ - - } - - // If any signals reach >=1, pass 'em along - var lastSignal = self.signals[self.signals.length-1]; - while(lastSignal && lastSignal.position>=1){ - - // Actually pass it along - lastSignal.delta *= self.strength; // flip at the end only! - self.to.takeSignal(lastSignal); - - // Pop it, move on down - self.removeSignal(lastSignal); - lastSignal = self.signals[self.signals.length-1]; - - } - - }; - self.removeSignal = function(signal){ - self.signals.splice( self.signals.indexOf(signal), 1 ); - Edge.allSignals.splice( Edge.allSignals.indexOf(signal), 1 ); - }; - self.drawSignals = function(ctx){ - - // Draw each one - for(var i=0; i (1,-1) - ctx.scale(1, flip); - } - - // Signal's age = alpha. - if(signal.age==2){ - ctx.globalAlpha = 0.5; - }else if(signal.age==1){ - ctx.globalAlpha = 0.25; - } - - // Draw an arrow - ctx.beginPath(); - ctx.moveTo(-2,0); - ctx.lineTo(0,-2); - ctx.lineTo(2,0); - ctx.lineTo(1,0); - ctx.lineTo(1,2); - ctx.lineTo(-1,2); - ctx.lineTo(-1,0); - ctx.fillStyle = signalColor; - ctx.fill(); - - // Restore - ctx.restore(); - - } - - }; - var _listenerReset = subscribe("model/reset", function(){ - self.signals = []; - Edge.allSignals = []; - }); - - - ////////////////////////////////////// - // UPDATE & DRAW ///////////////////// - ////////////////////////////////////// - - // Update! - self.labelX = 0; - self.labelY = 0; - var fx, fy, tx, ty, - r, dx, dy, w, a, h, - y, a2, - arrowBuffer, arrowDistance, arrowAngle, beginDistance, beginAngle, - startAngle, endAngle, - y2, begin, end, - arrowLength, ax, ay, aa, - labelAngle, lx, ly, labelBuffer; // BECAUSE I'VE LOST CONTROL OF MY LIFE. - self.update = function(speed){ - - //////////////////////////////////////////////// - // PRE-CALCULATE THE MATH (for retina canvas) // - //////////////////////////////////////////////// - - // Edge case: if arc is EXACTLY zero, whatever, add 0.1 to it. - if(self.arc==0) self.arc=0.1; - - // Mathy calculations: (all retina, btw) - fx=self.from.x*2; - fy=self.from.y*2; - tx=self.to.x*2; - ty=self.to.y*2; - if(self.from==self.to){ - var rotation = self.rotation; - rotation *= Math.TAU/360; - tx += Math.cos(rotation); - ty += Math.sin(rotation); - } - dx = tx-fx; - dy = ty-fy; - w = Math.sqrt(dx*dx+dy*dy); - a = Math.atan2(dy,dx); - h = Math.abs(self.arc*2); - - // From: http://www.mathopenref.com/arcradius.html - r = (h/2) + ((w*w)/(8*h)); - y = r-h; // the circle's y-pos is radius - given height. - a2 = Math.acos((w/2)/r); // angle from x axis, arc-cosine of half-width & radius - - // Arrow buffer... - arrowBuffer = 15; - arrowDistance = (self.to.radius+arrowBuffer)*2; - arrowAngle = arrowDistance/r; // (distance/circumference)*TAU, close enough. - beginDistance = (self.from.radius+arrowBuffer)*2; - beginAngle = beginDistance/r; - - // Arc it! - startAngle = a2 - Math.TAU/2; - endAngle = -a2; - if(h>r){ - startAngle *= -1; - endAngle *= -1; - } - if(self.arc>0){ - y2 = y; - begin = startAngle+beginAngle; - end = endAngle-arrowAngle; - }else{ - y2 = -y; - begin = -startAngle-beginAngle; - end = -endAngle+arrowAngle; - } - - // Arrow HEAD! - arrowLength = 10*2; - ax = w/2 + Math.cos(end)*r; - ay = y2 + Math.sin(end)*r; - aa = end + Math.TAU/4; - - // My label is... - var s = self.strength; - var l; - if(s>=3) l="+++"; - else if(s>=2) l="++"; - else if(s>=1) l="+"; - else if(s==0) l="?"; - else if(s>=-1) l="–"; // EM dash, not hyphen. - else if(s>=-2) l="– –"; - else l="– – –"; - self.label = l; - - // Label position - var labelPosition = self.getPositionAlongArrow(0.5); - lx = labelPosition.x; - ly = labelPosition.y; - - // ACTUAL label position, for grabbing purposes - self.labelX = (fx + Math.cos(a)*lx - Math.sin(a)*ly)/2; // un-retina - self.labelY = (fy + Math.sin(a)*lx + Math.cos(a)*ly)/2; // un-retina - - // ...add offset to label - labelBuffer = 18*2; // retina - if(self.arc<0) labelBuffer*=-1; - ly += labelBuffer; - - /////////////////////////////////////// - // AND THEN UPDATE OTHER STUFF AFTER // - // THE CALCULATIONS ARE DONE I GUESS // - /////////////////////////////////////// - - // When actually playing the simulation... - /*if(self.loopy.mode==Loopy.MODE_PLAY){ - self.to.nextValue += self.from.value * self.strength * speed; - }*/ - - // Update signals - self.updateSignals(); - - }; - - // Get position along arrow, on what parameter? - self.getArrowLength = function(){ - var angle; - if(self.from==self.to){ - angle = Math.TAU; - }else{ - //debugger; - if(y<0){ - // arc's center is above the horizon - if(self.arc<0){ // ccw - angle = Math.TAU + begin - end; - }else{ // cw - angle = Math.TAU + end - begin; - } - }else{ - // arc's center is below the horizon - angle = Math.abs(end-begin); - } - } - return r*angle; - }; - self.getPositionAlongArrow = function(param){ - - param = -0.05 + param*1.1; // (0,1) --> (-0.05, 1.05) - - // If the arc's circle is actually BELOW the line... - var begin2 = begin; - if(y<0){ - // DON'T KNOW WHY THIS WORKS, BUT IT DOES. - if(begin2>0){ - begin2-=Math.TAU; - }else{ - begin2+=Math.TAU; - } - } - - // Get angle! - var angle = begin2 + (end-begin2)*param; - - // return x & y - return{ - x: w/2 + Math.cos(angle)*r, - y: y2 + Math.sin(angle)*r - }; - - }; - - // Draw - self.draw = function(ctx){ - - // Width & Color - ctx.lineWidth = 4*Math.abs(self.strength)-2; - ctx.strokeStyle = "#666"; - - // Translate & Rotate! - ctx.save(); - ctx.translate(fx, fy); - ctx.rotate(a); - - // Highlight! - if(self.loopy.sidebar.currentPage.target == self){ - ctx.save(); - ctx.translate(lx, ly); - ctx.rotate(-a); - ctx.beginPath(); - ctx.arc(0, 5, 60, 0, Math.TAU, false); - ctx.fillStyle = HIGHLIGHT_COLOR; - ctx.fill(); - ctx.restore(); - } - - // Arc it! - ctx.beginPath(); - if(self.arc>0){ - ctx.arc(w/2, y2, r, startAngle, end, false); - }else{ - ctx.arc(w/2, y2, r, -startAngle, end, true); - } - - // Arrow HEAD! - ctx.save(); - ctx.translate(ax, ay); - if(self.arc<0) ctx.scale(-1,-1); - ctx.rotate(aa); - ctx.moveTo(-arrowLength, -arrowLength); - ctx.lineTo(0,0); - ctx.lineTo(-arrowLength, arrowLength); - ctx.restore(); - - // Stroke! - ctx.stroke(); - - // Draw label - ctx.font = "100 60px sans-serif"; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.save(); - ctx.translate(lx, ly); - ctx.rotate(-a); - ctx.fillStyle = "#999"; - ctx.fillText(self.label, 0, 0); - ctx.restore(); - - // DRAW SIGNALS - self.drawSignals(ctx); - - // Restore - ctx.restore(); - - }; - - ////////////////////////////////////// - // KILL EDGE ///////////////////////// - ////////////////////////////////////// - - self.kill = function(){ - - // Kill Listeners! - unsubscribe("model/reset",_listenerReset); - - // Remove from parent! - model.removeEdge(self); - - // Killed! - publish("kill",[self]); - - }; - - ////////////////////////////////////// - // HELPER METHODS //////////////////// - ////////////////////////////////////// - - self.isPointOnLabel = function(x, y){ - // TOTAL HACK: radius based on TOOL BEING USED. - var radius; - if(self.loopy.tool==Loopy.TOOL_DRAG || self.loopy.tool==Loopy.TOOL_INK) radius=40; // selecting, wide radius! - else if(self.loopy.tool==Loopy.TOOL_ERASE) radius=25; // no accidental erase - else radius = 15; // you wanna label close to edges - return _isPointInCircle(x, y, self.labelX, self.labelY, radius); - }; - - self.getBoundingBox = function(){ - - // SPECIAL CASE: SELF-ARC - if(self.from==self.to){ - - var perpendicular = a-Math.TAU/4; - var cx = fx + Math.cos(perpendicular)*-y2; - var cy = fy + Math.sin(perpendicular)*-y2; - cx = cx/2; // un-retina - cy = cy/2; // un-retina - - var _radius = r/2; // un-retina - - return { - left: cx - _radius, - top: cy - _radius, - right: cx + _radius, - bottom: cy + _radius - }; - - } - - // THREE POINTS: start, end, and perpendicular with r - var from = {x:self.from.x, y:self.from.y}; - var to = {x:self.to.x, y:self.to.y}; - var mid = { - x:(from.x+to.x)/2, - y:(from.y+to.y)/2 - }; - - var perpendicular = a-Math.TAU/4; - mid.x += Math.cos(perpendicular)*self.arc; - mid.y += Math.sin(perpendicular)*self.arc; - - // TEST ALL POINTS - - var left = Infinity; - var top = Infinity; - var right = -Infinity; - var bottom = -Infinity; - var points = [from, to, mid]; - for(var i=0; ix) left=x; - if(top>y) top=y; - if(right=0) self.wobbleControls--; // wobble - if(!self.modal.isShowing){ // modAl - self.model.update(); // modEl - } - }; - setInterval(self.update, 1000/30); // 30 FPS, why not. - - // Draw - self.draw = function(){ - if(!self.modal.isShowing){ // modAl - self.model.draw(); // modEl - } - requestAnimationFrame(self.draw); - }; - - // TODO: Smarter drawing of Ink, Edges, and Nodes - // (only Nodes need redrawing often. And only in PLAY mode.) - - ////////////////////// - // PLAY & EDIT MODE // - ////////////////////// - - self.showPlayTutorial = false; - self.wobbleControls = -1; - self.setMode = function(mode){ - - self.mode = mode; - publish("loopy/mode"); - - // Play mode! - if(mode==Loopy.MODE_PLAY){ - self.showPlayTutorial = true; // show once! - if(!self.embedded) self.wobbleControls=45; // only if NOT embedded - self.sidebar.showPage("Edit"); - self.playbar.showPage("Player"); - self.sidebar.dom.setAttribute("mode","play"); - self.toolbar.dom.setAttribute("mode","play"); - document.getElementById("canvasses").removeAttribute("cursor"); // TODO: EVENT BASED - }else{ - publish("model/reset"); - } - - // Edit mode! - if(mode==Loopy.MODE_EDIT){ - self.showPlayTutorial = false; // donezo - self.wobbleControls = -1; // donezo - self.sidebar.showPage("Edit"); - self.playbar.showPage("Editor"); - self.sidebar.dom.setAttribute("mode","edit"); - self.toolbar.dom.setAttribute("mode","edit"); - document.getElementById("canvasses").setAttribute("cursor", self.toolbar.currentTool); // TODO: EVENT BASED - } - - }; - - ///////////////// - // SAVE & LOAD // - ///////////////// - - self.dirty = false; - - // YOU'RE A DIRTY BOY - subscribe("model/changed", function(){ - if(!self.embedded) self.dirty = true; - }); - - self.saveToURL = function(embed){ - - // Create link - var dataString = self.model.serialize(); - var uri = dataString; // encodeURIComponent(dataString); - var base = window.location.origin + window.location.pathname; - var historyLink = base+"?data="+uri; - var link; - if(embed){ - link = base+"?embed=1&data="+uri; - }else{ - link = historyLink; - } - - // NO LONGER DIRTY! - self.dirty = false; - - // PUSH TO HISTORY - window.history.replaceState(null, null, historyLink); - - return link; - - }; - - // "BLANK START" DATA: - var _blankData = "[[[1,403,223,1,%22something%22,4],[2,405,382,1,%22something%2520else%22,5]],[[2,1,94,-1,0],[1,2,89,1,0]],[[609,311,%22need%2520ideas%2520on%2520what%2520to%250Asimulate%253F%2520how%2520about%253A%250A%250A%25E3%2583%25BBtechnology%250A%25E3%2583%25BBenvironment%250A%25E3%2583%25BBeconomics%250A%25E3%2583%25BBbusiness%250A%25E3%2583%25BBpolitics%250A%25E3%2583%25BBculture%250A%25E3%2583%25BBpsychology%250A%250Aor%2520better%2520yet%252C%2520a%250A*combination*%2520of%250Athose%2520systems.%250Ahappy%2520modeling!%22]],2%5D"; - - self.loadFromURL = function(){ - var data = _getParameterByName("data"); - if(!data) data=decodeURIComponent(_blankData); - self.model.deserialize(data); - }; - - - /////////////////////////// - //////// EMBEDDED? //////// - /////////////////////////// - - self.init(); - - if(self.embedded){ - - // Hide all that UI - self.toolbar.dom.style.display = "none"; - self.sidebar.dom.style.display = "none"; - - // Fullscreen canvas - document.getElementById("canvasses").setAttribute("fullscreen","yes"); - self.playbar.dom.setAttribute("fullscreen","yes"); - publish("resize"); - - // Center & SCALE The Model - self.model.center(true); - subscribe("resize",function(){ - self.model.center(true); - }); - - // Autoplay! - self.setMode(Loopy.MODE_PLAY); - - }else{ - - // Center all the nodes & labels - - // If no nodes & no labels, forget it. - if(self.model.nodes.length>0 || self.model.labels.length>0){ - - // Get bounds of ALL objects... - var bounds = self.model.getBounds(); - var left = bounds.left; - var top = bounds.top; - var right = bounds.right; - var bottom = bounds.bottom; - - // Re-center! - var canvasses = document.getElementById("canvasses"); - var cx = (left+right)/2; - var cy = (top+bottom)/2; - var offsetX = (canvasses.clientWidth+50)/2 - cx; - var offsetY = (canvasses.clientHeight-80)/2 - cy; - - // MOVE ALL NODES - for(var i=0;ithe examples!"; - page.dom.appendChild(label); - - self.addPage("howto", page); - - })(); - - // Credits - (function(){ - var page = new Page(); - page.width = 690; - page.height = 550; - page.addComponent(new ModalIframe({ - page: page, - src: "pages/credits/", - width: 660, - height: 500 - })) - self.addPage("credits", page); - })(); - - - // Save as link - (function(){ - var page = new Page(); - page.width = 500; - page.height = 155; - page.addComponent(new ComponentHTML({ - html: "copy your link:" - })); - var output = page.addComponent(new ComponentOutput({})); - - var label = document.createElement("div"); - label.style.textAlign = "right"; - label.style.fontSize = "15px"; - label.style.marginTop = "6px"; - label.style.color = "#888"; - label.innerHTML = "(this is a long URL, so you may want to use a link-shortener like bit.ly)"; - page.dom.appendChild(label); - - // chars left... - var chars = document.createElement("div"); - chars.style.textAlign = "right"; - chars.style.fontSize = "15px"; - chars.style.marginTop = "3px"; - chars.style.color = "#888"; - chars.innerHTML = "X out of 2048 characters"; - page.dom.appendChild(chars); - - page.onshow = function(){ - - // Copy-able link - var link = loopy.saveToURL(); - output.output(link); - output.dom.select(); - - // Chars left - var html = link.length+" / 2048 characters"; - if(link.length>2048){ - html += " - MAY BE TOO LONG FOR MOST BROWSERS"; - } - chars.innerHTML = html; - chars.style.fontWeight = (link.length>2048) ? "bold" : "100"; - chars.style.fontSize = (link.length>2048) ? "14px" : "15px"; - - }; - - // or, tweet it - self.addPage("save_link", page); - })(); - - // Embed - (function(){ - var page = new Page(); - page.width = 700; - page.height = 500; - - // ON UPDATE DIMENSIONS - var iframeSRC; - var _onUpdate = function(){ - var embedCode = ''; - output.output(embedCode); - }; - - // THE SHTUFF - var sidebar = document.createElement("div"); - sidebar.style.width = "150px"; - sidebar.style.height = "440px"; - sidebar.style.float = "left"; - page.dom.appendChild(sidebar); - - // Label - var label = document.createElement("div"); - label.innerHTML = "
    PREVIEW →

    "; - sidebar.appendChild(label); - - // Label 2 - var label = document.createElement("div"); - label.style.fontSize = "15px"; - label.innerHTML = "what size do you want your embed to be?"; - sidebar.appendChild(label); - - // Size! - var width = _createNumberInput(_onUpdate); - sidebar.appendChild(width.dom); - var label = document.createElement("div"); - label.style.display = "inline-block"; - label.style.fontSize = "15px"; - label.innerHTML = " × "; - sidebar.appendChild(label); - var height = _createNumberInput(_onUpdate); - sidebar.appendChild(height.dom); - - // Label 3 - var label = document.createElement("div"); - label.style.fontSize = "15px"; - label.innerHTML = "

    copy this code into your website's html:"; - sidebar.appendChild(label); - - // Output! - var output = new ComponentOutput({}); - output.dom.style.fontSize = "12px"; - sidebar.appendChild(output.dom); - - // Label 3 - var label = document.createElement("div"); - label.style.fontSize = "15px"; - label.style.textAlign = "right"; - label.innerHTML = "

    (note: the REMIX button lets someone else, well, remix your model! don't worry, it'll just be a copy, it won't affect the original.)"; - sidebar.appendChild(label); - - // IFRAME - var iframe = page.addComponent(new ModalIframe({ - page: page, - manual: true, - src: "", - width: 500, - height: 440 - })).dom; - iframe.style.float = "right"; - page.onshow = function(){ - - // Default dimensions - width.setValue(500); - height.setValue(440); - - // The iframe! - iframeSRC = loopy.saveToURL(true); - iframe.src = iframeSRC; - - // Select to copy-paste - _onUpdate(); - output.dom.select(); - - }; - page.onhide = function(){ - iframe.removeAttribute("src"); - }; - self.addPage("embed", page); - - - })(); - - // GIF - (function(){ - var page = new Page(); - page.width = 530; - page.height = 400; - page.addComponent(new ModalIframe({ - page: page, - src: "pages/gif.html", - width: 500, - height: 350 - })) - self.addPage("save_gif", page); - })(); - -} - -function ModalIframe(config){ - - var self = this; - - // IFRAME - var iframe = document.createElement("iframe"); - self.dom = iframe; - iframe.width = config.width; - iframe.height = config.height; - - // Show & Hide - if(!config.manual){ - config.page.onshow = function(){ - iframe.src = config.src; - }; - config.page.onhide = function(){ - iframe.removeAttribute("src"); - }; - } - -} \ No newline at end of file diff --git a/v1/js/Model.js b/v1/js/Model.js deleted file mode 100644 index da8959c..0000000 --- a/v1/js/Model.js +++ /dev/null @@ -1,541 +0,0 @@ -/********************************** - -MODEL! - -**********************************/ - -function Model(loopy){ - - var self = this; - self.loopy = loopy; - - // Properties - self.speed = 0.05; - - // Create canvas & context - var canvas = _createCanvas(); - var ctx = canvas.getContext("2d"); - self.canvas = canvas; - self.context = ctx; - - - - /////////////////// - // NODES ////////// - /////////////////// - - // Nodes - self.nodes = []; - self.nodeByID = {}; - self.getNode = function(id){ - return self.nodeByID[id]; - }; - - // Remove Node - self.addNode = function(config){ - - // Model's been changed! - publish("model/changed"); - - // Add Node - var node = new Node(self,config); - self.nodeByID[node.id] = node; - self.nodes.push(node); - self.update(); - return node; - - }; - - // Remove Node - self.removeNode = function(node){ - - // Model's been changed! - publish("model/changed"); - - // Remove from array - self.nodes.splice(self.nodes.indexOf(node),1); - - // Remove from object - delete self.nodeByID[node.id]; - - // Remove all associated TO and FROM edges - for(var i=0; i0){ - drawCountdown = drawCountdownFull; - break; - } - } - - // DRAW??????? - drawCountdown--; - if(drawCountdown<=0) return; - - // Also only draw if last updated... - if(!_canvasDirty) return; - _canvasDirty = false; - - // Clear! - ctx.clearRect(0,0,self.canvas.width,self.canvas.height); - - // Translate - ctx.save(); - - // Translate to center, (translate, scale, translate) to expand to size - var canvasses = document.getElementById("canvasses"); - var CW = canvasses.clientWidth - _PADDING - _PADDING; - var CH = canvasses.clientHeight - _PADDING_BOTTOM - _PADDING; - var tx = loopy.offsetX*2; - var ty = loopy.offsetY*2; - tx -= CW+_PADDING; - ty -= CH+_PADDING; - var s = loopy.offsetScale; - tx = s*tx; - ty = s*ty; - tx += CW+_PADDING; - ty += CH+_PADDING; - if(loopy.embedded){ - tx += _PADDING; // dunno why but this is needed - ty += _PADDING; // dunno why but this is needed - } - ctx.setTransform(s, 0, 0, s, tx, ty); - - // Draw labels THEN edges THEN nodes - for(var i=0;i0){ - self.nodes[0].kill(); - } - - }; - - - - //////////////////// - // HELPER METHODS // - //////////////////// - - self.getNodeByPoint = function(x,y,buffer){ - var result; - for(var i=self.nodes.length-1; i>=0; i--){ // top-down - var node = self.nodes[i]; - if(node.isPointInNode(x,y,buffer)) return node; - } - return null; - }; - - self.getEdgeByPoint = function(x, y, wholeArrow){ - // TODO: wholeArrow option? - var result; - for(var i=self.edges.length-1; i>=0; i--){ // top-down - var edge = self.edges[i]; - if(edge.isPointOnLabel(x,y)) return edge; - } - return null; - }; - - self.getLabelByPoint = function(x, y){ - var result; - for(var i=self.labels.length-1; i>=0; i--){ // top-down - var label = self.labels[i]; - if(label.isPointInLabel(x,y)) return label; - } - return null; - }; - - // Click to edit! - subscribe("mouseclick",function(){ - - // ONLY WHEN EDITING (and NOT erase) - if(self.loopy.mode!=Loopy.MODE_EDIT) return; - if(self.loopy.tool==Loopy.TOOL_ERASE) return; - - // Did you click on a node? If so, edit THAT node. - var clickedNode = self.getNodeByPoint(Mouse.x, Mouse.y); - if(clickedNode){ - loopy.sidebar.edit(clickedNode); - return; - } - - // Did you click on a label? If so, edit THAT label. - var clickedLabel = self.getLabelByPoint(Mouse.x, Mouse.y); - if(clickedLabel){ - loopy.sidebar.edit(clickedLabel); - return; - } - - // Did you click on an edge label? If so, edit THAT edge. - var clickedEdge = self.getEdgeByPoint(Mouse.x, Mouse.y); - if(clickedEdge){ - loopy.sidebar.edit(clickedEdge); - return; - } - - // If the tool LABEL? If so, TRY TO CREATE LABEL. - if(self.loopy.tool==Loopy.TOOL_LABEL){ - loopy.label.tryMakingLabel(); - return; - } - - // Otherwise, go to main Edit page. - loopy.sidebar.showPage("Edit"); - - }); - - // Centering & Scaling - self.getBounds = function(){ - - // If no nodes & no labels, forget it. - if(self.nodes.length==0 && self.labels.length==0) return; - - // Get bounds of ALL objects... - var left = Infinity; - var top = Infinity; - var right = -Infinity; - var bottom = -Infinity; - var _testObjects = function(objects){ - for(var i=0; ibounds.left) left=bounds.left; - if(top>bounds.top) top=bounds.top; - if(right screenRatio){ - // wider... - scaleRatio = fitWidth/w; - }else{ - // taller... - scaleRatio = fitHeight/h; - } - - // Loopy, then! - loopy.offsetScale = scaleRatio; - - } - - }; - -} \ No newline at end of file diff --git a/v1/js/Mouse.js b/v1/js/Mouse.js deleted file mode 100644 index a9917a2..0000000 --- a/v1/js/Mouse.js +++ /dev/null @@ -1,72 +0,0 @@ -window.Mouse = {}; -Mouse.init = function(target){ - - // Events! - var _onmousedown = function(event){ - Mouse.moved = false; - Mouse.pressed = true; - Mouse.startedOnTarget = true; - publish("mousedown"); - }; - var _onmousemove = function(event){ - - // DO THE INVERSE - var canvasses = document.getElementById("canvasses"); - var tx = 0; - var ty = 0; - var s = 1/loopy.offsetScale; - var CW = canvasses.clientWidth - _PADDING - _PADDING; - var CH = canvasses.clientHeight - _PADDING_BOTTOM - _PADDING; - - if(loopy.embedded){ - tx -= _PADDING/2; // dunno why but this is needed - ty -= _PADDING/2; // dunno why but this is needed - } - - tx -= (CW+_PADDING)/2; - ty -= (CH+_PADDING)/2; - - tx = s*tx; - ty = s*ty; - - tx += (CW+_PADDING)/2; - ty += (CH+_PADDING)/2; - - tx -= loopy.offsetX; - ty -= loopy.offsetY; - - // Mutliply by Mouse vector - var mx = event.x*s + tx; - var my = event.y*s + ty; - - // Mouse! - Mouse.x = mx; - Mouse.y = my; - - Mouse.moved = true; - publish("mousemove"); - - }; - var _onmouseup = function(){ - Mouse.pressed = false; - if(Mouse.startedOnTarget){ - publish("mouseup"); - if(!Mouse.moved) publish("mouseclick"); - } - Mouse.moved = false; - Mouse.startedOnTarget = false; - }; - - // Add mouse & touch events! - _addMouseEvents(target, _onmousedown, _onmousemove, _onmouseup); - - // Cursor & Update - Mouse.target = target; - Mouse.showCursor = function(cursor){ - Mouse.target.style.cursor = cursor; - }; - Mouse.update = function(){ - Mouse.showCursor(""); - }; - -}; \ No newline at end of file diff --git a/v1/js/Node.js b/v1/js/Node.js deleted file mode 100644 index 52e737a..0000000 --- a/v1/js/Node.js +++ /dev/null @@ -1,337 +0,0 @@ -/********************************** - -NODE! - -**********************************/ - -Node.COLORS = { - 0: "#EA3E3E", // red - 1: "#EA9D51", // orange - 2: "#FEEE43", // yellow - 3: "#BFEE3F", // green - 4: "#7FD4FF", // blue - 5: "#A97FFF" // purple -}; - -Node.defaultValue = 0.5; -Node.defaultHue = 0; - -Node.DEFAULT_RADIUS = 60; - -function Node(model, config){ - - var self = this; - self._CLASS_ = "Node"; - - // Mah Parents! - self.loopy = model.loopy; - self.model = model; - self.config = config; - - // Default values... - _configureProperties(self, config, { - id: Node._getUID, - x: 0, - y: 0, - init: Node.defaultValue, // initial value! - label: "?", - hue: Node.defaultHue, - radius: Node.DEFAULT_RADIUS - }); - - // Value: from 0 to 1 - self.value = self.init; - // TODO: ACTUALLY VISUALIZE AN INFINITE RANGE - self.bound = function(){ // bound ONLY when changing value. - var buffer = 1.2; - if(self.value<-buffer) self.value=-buffer; - if(self.value>1+buffer) self.value=1+buffer; - }; - - // MOUSE. - var _controlsVisible = false; - var _controlsAlpha = 0; - var _controlsDirection = 0; - var _controlsSelected = false; - var _controlsPressed = false; - var _listenerMouseMove = subscribe("mousemove", function(){ - - // ONLY WHEN PLAYING - if(self.loopy.mode!=Loopy.MODE_PLAY) return; - - // If moused over this, show it, or not. - _controlsSelected = self.isPointInNode(Mouse.x, Mouse.y); - if(_controlsSelected){ - _controlsVisible = true; - self.loopy.showPlayTutorial = false; - _controlsDirection = (Mouse.y40) _offset=40 - if(_offset<-40) _offset=-40; - _offsetVel += _offsetAcc; - _offsetVel *= _offsetDamp; - _offsetAcc = (_offsetGoto-_offset)*_offsetHookes; - - }; - - // Draw - var _circleRadius = 0; - self.draw = function(ctx){ - - // Retina - var x = self.x*2; - var y = self.y*2; - var r = self.radius*2; - var color = Node.COLORS[self.hue]; - - // Translate! - ctx.save(); - ctx.translate(x,y+_offset); - - // DRAW HIGHLIGHT??? - if(self.loopy.sidebar.currentPage.target == self){ - ctx.beginPath(); - ctx.arc(0, 0, r+40, 0, Math.TAU, false); - ctx.fillStyle = HIGHLIGHT_COLOR; - ctx.fill(); - } - - // White-gray bubble with colored border - ctx.beginPath(); - ctx.arc(0, 0, r-2, 0, Math.TAU, false); - ctx.fillStyle = "#fff"; - ctx.fill(); - ctx.lineWidth = 6; - ctx.strokeStyle = color; - ctx.stroke(); - - // Circle radius - // var _circleRadiusGoto = r*(self.value+1); - // _circleRadius = _circleRadius*0.75 + _circleRadiusGoto*0.25; - - // RADIUS IS (ATAN) of VALUE?!?!?! - var _r = Math.atan(self.value*5); - _r = _r/(Math.PI/2); - _r = (_r+1)/2; - - // INFINITE RANGE FOR RADIUS - // linear from 0 to 1, asymptotic otherwise. - var _value; - if(self.value>=0 && self.value<=1){ - // (0,1) -> (0.1, 0.9) - _value = 0.1 + 0.8*self.value; - }else{ - if(self.value<0){ - // asymptotically approach 0, starting at 0.1 - _value = (1/(Math.abs(self.value)+1))*0.1; - } - if(self.value>1){ - // asymptotically approach 1, starting at 0.9 - _value = 1 - (1/self.value)*0.1; - } - } - - // Colored bubble - ctx.beginPath(); - var _circleRadiusGoto = r*_value; // radius - _circleRadius = _circleRadius*0.8 + _circleRadiusGoto*0.2; - ctx.arc(0, 0, _circleRadius, 0, Math.TAU, false); - ctx.fillStyle = color; - ctx.fill(); - - // Text! - var fontsize = 40; - ctx.font = "normal "+fontsize+"px sans-serif"; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillStyle = "#000"; - var width = ctx.measureText(self.label).width; - while(width > r*2 - 30){ // -30 for buffer. HACK: HARD-CODED. - fontsize -= 1; - ctx.font = "normal "+fontsize+"px sans-serif"; - width = ctx.measureText(self.label).width; - } - ctx.fillText(self.label, 0, 0); - - // WOBBLE CONTROLS - var cl = 40; - var cy = 0; - if(self.loopy.showPlayTutorial && self.loopy.wobbleControls>0){ - var wobble = self.loopy.wobbleControls*(Math.TAU/30); - cy = Math.abs(Math.sin(wobble))*10; - } - - // Controls! - ctx.globalAlpha = _controlsAlpha; - ctx.strokeStyle = "rgba(0,0,0,0.8)"; - // top arrow - ctx.beginPath(); - ctx.moveTo(-cl,-cy-cl); - ctx.lineTo(0,-cy-cl*2); - ctx.lineTo(cl,-cy-cl); - ctx.lineWidth = (_controlsDirection>0) ? 10: 3; - if(self.loopy.showPlayTutorial) ctx.lineWidth=6; - ctx.stroke(); - // bottom arrow - ctx.beginPath(); - ctx.moveTo(-cl,cy+cl); - ctx.lineTo(0,cy+cl*2); - ctx.lineTo(cl,cy+cl); - ctx.lineWidth = (_controlsDirection<0) ? 10: 3; - if(self.loopy.showPlayTutorial) ctx.lineWidth=6; - ctx.stroke(); - - // Restore - ctx.restore(); - - }; - - ////////////////////////////////////// - // KILL NODE ///////////////////////// - ////////////////////////////////////// - - self.kill = function(){ - - // Kill Listeners! - unsubscribe("mousemove",_listenerMouseMove); - unsubscribe("mousedown",_listenerMouseDown); - unsubscribe("mouseup",_listenerMouseUp); - unsubscribe("model/reset",_listenerReset); - - // Remove from parent! - model.removeNode(self); - - // Killed! - publish("kill",[self]); - - }; - - ////////////////////////////////////// - // HELPER METHODS //////////////////// - ////////////////////////////////////// - - self.isPointInNode = function(x, y, buffer){ - buffer = buffer || 0; - return _isPointInCircle(x, y, self.x, self.y, self.radius+buffer); - }; - - self.getBoundingBox = function(){ - return { - left: self.x - self.radius, - top: self.y - self.radius, - right: self.x + self.radius, - bottom: self.y + self.radius - }; - }; - -} - -//////////////////////////// -// Unique ID identifiers! // -//////////////////////////// - -Node._UID = 0; -Node._getUID = function(){ - Node._UID++; - return Node._UID; -}; diff --git a/v1/js/PageUI.js b/v1/js/PageUI.js deleted file mode 100644 index de5de69..0000000 --- a/v1/js/PageUI.js +++ /dev/null @@ -1,51 +0,0 @@ -/********************************** - -PAGE UI: to extend to Sidebar, Play Controls, Modal. - -**********************************/ - -function PageUI(dom){ - - var self = this; - self.dom = dom; - - self.pages = []; - self.addPage = function(id, page){ - page.id = id; - self.dom.appendChild(page.dom); - self.pages.push(page); - }; - self.currentPage = null; - self.showPage = function(id){ - var shownPage = null; - for(var i=0; i
    Name:" - //label: "Name:" - })); - page.addComponent("hue", new ComponentSlider({ - bg: "color", - label: "Color:", - options: [0,1,2,3,4,5], - oninput: function(value){ - Node.defaultHue = value; - } - })); - page.addComponent("init", new ComponentSlider({ - bg: "initial", - label: "Start Amount:", - options: [0, 0.16, 0.33, 0.50, 0.66, 0.83, 1], - //options: [0, 1/6, 2/6, 3/6, 4/6, 5/6, 1], - oninput: function(value){ - Node.defaultValue = value; - } - })); - page.onedit = function(){ - - // Set color of Slider - var node = page.target; - var color = Node.COLORS[node.hue]; - page.getComponent("init").setBGColor(color); - - // Focus on the name field IF IT'S "" or "?" - var name = node.label; - if(name=="" || name=="?") page.getComponent("label").select(); - - }; - page.addComponent(new ComponentButton({ - label: "delete node", - //label: "delete circle", - onclick: function(node){ - node.kill(); - self.showPage("Edit"); - } - })); - self.addPage("Node", page); - })(); - - // Edge! - (function(){ - var page = new SidebarPage(); - page.addComponent(new ComponentButton({ - header: true, - label: "back to top", - onclick: function(){ - self.showPage("Edit"); - } - })); - page.addComponent("strength", new ComponentSlider({ - bg: "strength", - label: "

    Relationship:", - //label: "Relationship:", - options: [1, -1], - oninput: function(value){ - Edge.defaultStrength = value; - } - })); - page.addComponent(new ComponentHTML({ - html: "(to make a stronger relationship, draw multiple arrows!)

    "+ - "(to make a delayed relationship, draw longer arrows)" - })); - page.addComponent(new ComponentButton({ - //label: "delete edge", - label: "delete arrow", - //label: "delete relationship", - onclick: function(edge){ - edge.kill(); - self.showPage("Edit"); - } - })); - self.addPage("Edge", page); - })(); - - // Label! - (function(){ - var page = new SidebarPage(); - page.addComponent(new ComponentButton({ - header: true, - label: "back to top", - onclick: function(){ - self.showPage("Edit"); - } - })); - page.addComponent("text", new ComponentInput({ - label: "

    Label:", - //label: "Label:", - textarea: true - })); - page.onshow = function(){ - // Focus on the text field - page.getComponent("text").select(); - }; - page.onhide = function(){ - - // If you'd just edited it... - var label = page.target; - if(!page.target) return; - - // If text is "" or all spaces, DELETE. - var text = label.text; - if(/^\s*$/.test(text)){ - // that was all whitespace, KILL. - page.target = null; - label.kill(); - } - - }; - page.addComponent(new ComponentButton({ - label: "delete label", - onclick: function(label){ - label.kill(); - self.showPage("Edit"); - } - })); - self.addPage("Label", page); - })(); - - // Edit - (function(){ - var page = new SidebarPage(); - page.addComponent(new ComponentHTML({ - html: ""+ - - "LOOPY (v1.0)
    a tool for thinking in systems

    "+ - - "see examples "+ - "how to "+ - "credits

    "+ - - "

    "+ - - "save as link

    "+ - //"save as image

    "+ - "embed in your website

    "+ - "make a GIF using LICEcap

    "+ - - "

    "+ - - "LOOPY is "+ - "made by nicky case "+ - "with your support on patreon <3" - - })); - self.addPage("Edit", page); - })(); - - // Ctrl-S to SAVE - subscribe("key/save",function(){ - if(Key.control){ // Ctrl-S or ⌘-S - publish("modal",["save_link"]); - } - }); - -} - -function SidebarPage(){ - - // TODO: be able to focus on next component with an "Enter". - - var self = this; - self.target = null; - - // DOM - self.dom = document.createElement("div"); - self.show = function(){ self.dom.style.display="block"; self.onshow(); }; - self.hide = function(){ self.dom.style.display="none"; self.onhide(); }; - - // Components - self.components = []; - self.componentsByID = {}; - self.addComponent = function(propName, component){ - - // One or two args - if(!component){ - component = propName; - propName = ""; - } - - component.page = self; // tie to self - component.propName = propName; // tie to propName - self.dom.appendChild(component.dom); // add to DOM - - // remember component - self.components.push(component); - self.componentsByID[propName] = component; - - // return! - return component; - - }; - self.getComponent = function(propName){ - return self.componentsByID[propName]; - }; - - // Edit - self.edit = function(object){ - - // New target to edit! - self.target = object; - - // Show each property with its component - for(var i=0;ibox.x+box.width) return false; - if(ybox.y+box.height) return false; - - return true; - -} - -// TODO: Make more use of this??? -function _makeErrorFunc(msg){ - return function(){ - throw Error(msg); - }; -} - -function _getParameterByName(name){ - var url = window.location.href; - name = name.replace(/[\[\]]/g, "\\$&"); - var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), - results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ''; - return decodeURIComponent(results[2].replace(/\+/g, " ")); -}; - - -function _blendColors(hex1, hex2, blend){ - - var color = "#"; - for(var i=0; i<3; i++) { - - // Into numbers... - var sub1 = hex1.substring(1+2*i, 3+2*i); - var sub2 = hex2.substring(1+2*i, 3+2*i); - var num1 = parseInt(sub1, 16); - var num2 = parseInt(sub2, 16); - - // Blended number & sub - var num = Math.floor( num1*(1-blend) + num2*blend ); - var sub = num.toString(16).toUpperCase(); - var paddedSub = ('0'+sub).slice(-2); // in case it's only one digit long - - // Add that babe - color += paddedSub; - - } - - return color; - -} - -function _shiftArray(array, shiftIndex){ - var moveThisAround = array.splice(-shiftIndex); - var shifted = moveThisAround.concat(array); - return shifted; -} - - diff --git a/v1/pages/credits/favicon.png b/v1/pages/credits/favicon.png deleted file mode 100644 index a1b03e2..0000000 Binary files a/v1/pages/credits/favicon.png and /dev/null differ diff --git a/v1/pages/credits/index.html b/v1/pages/credits/index.html deleted file mode 100644 index 751ef8e..0000000 --- a/v1/pages/credits/index.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - LOOPY: Credits! - - - - - - - - - - -
    - - -
    -

    - LOOPY is made by me, Nicky Case, - with the kind, lovely support of my fans on patreon! -

    -

    - Speaking of which, here they are: -

    -
    - -
    - - -
    -
    - - - Aimee Jarboe -
    -
    - - - Buster Benson -
    -
    - - - Chad Sansing -
    -
    - - - Jared Cosulich -
    -
    - - - Louis-Jean Teitelbaum -
    -
    - - - Mark McCartney -
    -
    - - - Matt Hughes -
    -
    - - - Michael Duke -
    -
    - - - Michael Huff -
    -
    - - - Natalie Sun -
    -
    - - - Nicholas Perry -
    -
    - - - Noel Lehmann -
    -
    - - - Phil Dougherty -
    -
    - - - Philippe Vallotti -
    -
    - - - Travis Ross -
    -
    - - - Yu-Han Kuo -
    -
    - - -
    -
    - - Cedric Rossi -
    -
    - - Dylan Field -
    -
    - - Dylan Sinnott -
    -
    - - Feiya Wang -
    -
    - - Glen E. Ivey -
    -
    - - Iago Medeiros Cordeiro -
    -
    - - Karen Cooper -
    -
    - - Kate Fractal -
    -
    - - Kevin Wang -
    -
    - - Klemen Slavic -
    -
    - - kuerqing1024 -
    -
    - - Michelle Kelly -
    -
    - - Nimrod Kimhi -
    -
    - - Rob Napier -
    -
    - - Thomas de Rego -
    -
    - - William O'Hanley -
    -
    - - Zach Smith -
    -
    - - -
    - 3Blue1Brown
    - Adam M. Smith
    - Alex Dytrych
    - Andrew
    - Andy
    - Aria Minaei
    - Artemiy Solopov
    - Aschelon
    - ben fei
    - Bill
    - Billy Madison
    - Boondoggle
    - Brandon
    - Brent Werness
    - Brian Wu
    - Brie Code
    - Bruno Chagas Macedo Carvalho
    - Bruno Guerrero
    - Casey Ross
    - Charlie McIlwain
    - Christopher
    - Colin
    - Colin #2
    - Cort Stratton
    - Craig Protzel
    - Craig Steele
    - Daniel Horowitz
    - Daniel Shiffman
    - Dave Tu
    - David Smit
    - Dylan Meconis
    - Fabio Utzig
    - Fahrstuhl
    - Forrest Oliphant
    - Frank Leon Rose
    - Heather Weaver
    - Henry Reich
    - J. Hu
    - Jacob Christian Munch-Andersen
    - Jacques Frechet
    - James Hogan
    - Janusz Leidgens
    - Jason Brennan
    - Jeff Long
    - Joel
    - Johannes Wärn
    - John_Ca
    - Johnny Owens
    - Jonathan
    - Joseph Perry
    - Joshua Horowitz
    - Julia Karmo
    - Kaitlin M.
    - Karishma Bhatia
    - Kat Suricata
    - Kathryn Long
    - Keith Olson
    - Kevin
    - Lawrence
    - Linda Booth Sweeney
    - Luming Hao
    - Maic Lopez Saenz
    - Majid Iqbal
    - Matt "Kupo" Roszak
    - Matt Warren
    - May-Li Khoe
    - Mekki MacAulay
    - Micah Cowan
    - Michael Sargent
    - Michelle Brown
    - Milan Pingel
    - Monika Denes
    - Mustafa Alic
    - Nick Schrag
    - Nikita
    - Noah Swartz
    - Olivia Brode-Roger
    - Pat Mächler
    - Peter McEvoy
    - Philip Tibitoski
    - Piotr Migdal
    - Rachel Nabors
    - Raphael D'Amico
    - Richard Hackathorn
    - Roland Tanglao
    - Ryan Barker
    - Sam Anderson
    - Sam Maynard
    - Samira Nedungadi
    - Sarah Barbour
    - sarah mathys
    - SB Sigma
    - Seanny123
    - Serguei Filimonov
    - Shane-o
    - Sigpipe
    - Sina Khanifar
    - Sylvain Francis
    - Syria Carys Sirlay
    - Thembers
    - Tom
    - Tony Onodi
    - Traci Lawson
    - Woo-Kyeong Choi
    - Yona
    - Zoe Bogner -
    - -
    - - -
    -

    - and special thanks to my beta-testers, - who helped me squash the bugs and shame the kinks out of LOOPY: -

    -
    - Alex Dytrych
    - Alex Jaffe
    - Amit Patel
    - Andrea Hawksley
    - Bret Victor
    - Brian Bucklew
    - Chris Walker
    - Dan Cook
    - Dylan Jones
    - Ichiro Lambe
    - Jack Schaedler
    - Joshua Horowitz
    - M Eifler
    - Saketh Kasibatla
    - Sebastian Morr
    - Tanya Short
    - Toph Tucker
    - Vi Hart -
    -
    - -
    - - \ No newline at end of file diff --git a/v1/pages/credits/peep/aimee.png b/v1/pages/credits/peep/aimee.png deleted file mode 100644 index 09dfe08..0000000 Binary files a/v1/pages/credits/peep/aimee.png and /dev/null differ diff --git a/v1/pages/credits/peep/buster.png b/v1/pages/credits/peep/buster.png deleted file mode 100644 index ec3d9cc..0000000 Binary files a/v1/pages/credits/peep/buster.png and /dev/null differ diff --git a/v1/pages/credits/peep/chad.png b/v1/pages/credits/peep/chad.png deleted file mode 100644 index dff54ec..0000000 Binary files a/v1/pages/credits/peep/chad.png and /dev/null differ diff --git a/v1/pages/credits/peep/jared.png b/v1/pages/credits/peep/jared.png deleted file mode 100644 index b38b39b..0000000 Binary files a/v1/pages/credits/peep/jared.png and /dev/null differ diff --git a/v1/pages/credits/peep/ljt.png b/v1/pages/credits/peep/ljt.png deleted file mode 100644 index 62b8997..0000000 Binary files a/v1/pages/credits/peep/ljt.png and /dev/null differ diff --git a/v1/pages/credits/peep/mark.png b/v1/pages/credits/peep/mark.png deleted file mode 100644 index 6e7a2f8..0000000 Binary files a/v1/pages/credits/peep/mark.png and /dev/null differ diff --git a/v1/pages/credits/peep/matt.png b/v1/pages/credits/peep/matt.png deleted file mode 100644 index 7312d22..0000000 Binary files a/v1/pages/credits/peep/matt.png and /dev/null differ diff --git a/v1/pages/credits/peep/michael_duke.png b/v1/pages/credits/peep/michael_duke.png deleted file mode 100644 index 198ab95..0000000 Binary files a/v1/pages/credits/peep/michael_duke.png and /dev/null differ diff --git a/v1/pages/credits/peep/michael_huff.png b/v1/pages/credits/peep/michael_huff.png deleted file mode 100644 index 6664075..0000000 Binary files a/v1/pages/credits/peep/michael_huff.png and /dev/null differ diff --git a/v1/pages/credits/peep/natalie.png b/v1/pages/credits/peep/natalie.png deleted file mode 100644 index 2b83bc6..0000000 Binary files a/v1/pages/credits/peep/natalie.png and /dev/null differ diff --git a/v1/pages/credits/peep/nicholas.png b/v1/pages/credits/peep/nicholas.png deleted file mode 100644 index 0205c25..0000000 Binary files a/v1/pages/credits/peep/nicholas.png and /dev/null differ diff --git a/v1/pages/credits/peep/noel.png b/v1/pages/credits/peep/noel.png deleted file mode 100644 index a87779a..0000000 Binary files a/v1/pages/credits/peep/noel.png and /dev/null differ diff --git a/v1/pages/credits/peep/phil.png b/v1/pages/credits/peep/phil.png deleted file mode 100644 index af39735..0000000 Binary files a/v1/pages/credits/peep/phil.png and /dev/null differ diff --git a/v1/pages/credits/peep/philippe.png b/v1/pages/credits/peep/philippe.png deleted file mode 100644 index 50e945e..0000000 Binary files a/v1/pages/credits/peep/philippe.png and /dev/null differ diff --git a/v1/pages/credits/peep/travis.png b/v1/pages/credits/peep/travis.png deleted file mode 100644 index bfa410c..0000000 Binary files a/v1/pages/credits/peep/travis.png and /dev/null differ diff --git a/v1/pages/credits/peep/yu-han.png b/v1/pages/credits/peep/yu-han.png deleted file mode 100644 index 745f4a4..0000000 Binary files a/v1/pages/credits/peep/yu-han.png and /dev/null differ diff --git a/v1/pages/credits/polygon/aimee.png b/v1/pages/credits/polygon/aimee.png deleted file mode 100644 index d967d00..0000000 Binary files a/v1/pages/credits/polygon/aimee.png and /dev/null differ diff --git a/v1/pages/credits/polygon/buster.png b/v1/pages/credits/polygon/buster.png deleted file mode 100644 index fef4986..0000000 Binary files a/v1/pages/credits/polygon/buster.png and /dev/null differ diff --git a/v1/pages/credits/polygon/cedric.png b/v1/pages/credits/polygon/cedric.png deleted file mode 100644 index fb3d4fc..0000000 Binary files a/v1/pages/credits/polygon/cedric.png and /dev/null differ diff --git a/v1/pages/credits/polygon/chad.png b/v1/pages/credits/polygon/chad.png deleted file mode 100644 index d16710e..0000000 Binary files a/v1/pages/credits/polygon/chad.png and /dev/null differ diff --git a/v1/pages/credits/polygon/dylan.png b/v1/pages/credits/polygon/dylan.png deleted file mode 100644 index 65e10d2..0000000 Binary files a/v1/pages/credits/polygon/dylan.png and /dev/null differ diff --git a/v1/pages/credits/polygon/dylan_s.png b/v1/pages/credits/polygon/dylan_s.png deleted file mode 100644 index 8752a52..0000000 Binary files a/v1/pages/credits/polygon/dylan_s.png and /dev/null differ diff --git a/v1/pages/credits/polygon/feiya.png b/v1/pages/credits/polygon/feiya.png deleted file mode 100644 index 0f0c884..0000000 Binary files a/v1/pages/credits/polygon/feiya.png and /dev/null differ diff --git a/v1/pages/credits/polygon/glen.png b/v1/pages/credits/polygon/glen.png deleted file mode 100644 index 0ab0cb4..0000000 Binary files a/v1/pages/credits/polygon/glen.png and /dev/null differ diff --git a/v1/pages/credits/polygon/iago.png b/v1/pages/credits/polygon/iago.png deleted file mode 100644 index 6eee613..0000000 Binary files a/v1/pages/credits/polygon/iago.png and /dev/null differ diff --git a/v1/pages/credits/polygon/jared.png b/v1/pages/credits/polygon/jared.png deleted file mode 100644 index 8631cf5..0000000 Binary files a/v1/pages/credits/polygon/jared.png and /dev/null differ diff --git a/v1/pages/credits/polygon/karen.png b/v1/pages/credits/polygon/karen.png deleted file mode 100644 index 7cf01cf..0000000 Binary files a/v1/pages/credits/polygon/karen.png and /dev/null differ diff --git a/v1/pages/credits/polygon/kate.png b/v1/pages/credits/polygon/kate.png deleted file mode 100644 index 3b6aa38..0000000 Binary files a/v1/pages/credits/polygon/kate.png and /dev/null differ diff --git a/v1/pages/credits/polygon/kevin.png b/v1/pages/credits/polygon/kevin.png deleted file mode 100644 index d219dfb..0000000 Binary files a/v1/pages/credits/polygon/kevin.png and /dev/null differ diff --git a/v1/pages/credits/polygon/klemen.png b/v1/pages/credits/polygon/klemen.png deleted file mode 100644 index 8d47f13..0000000 Binary files a/v1/pages/credits/polygon/klemen.png and /dev/null differ diff --git a/v1/pages/credits/polygon/kuerqing1024.png b/v1/pages/credits/polygon/kuerqing1024.png deleted file mode 100644 index 2730587..0000000 Binary files a/v1/pages/credits/polygon/kuerqing1024.png and /dev/null differ diff --git a/v1/pages/credits/polygon/ljt.png b/v1/pages/credits/polygon/ljt.png deleted file mode 100644 index 5dc4c0d..0000000 Binary files a/v1/pages/credits/polygon/ljt.png and /dev/null differ diff --git a/v1/pages/credits/polygon/mark.png b/v1/pages/credits/polygon/mark.png deleted file mode 100644 index 50c71c0..0000000 Binary files a/v1/pages/credits/polygon/mark.png and /dev/null differ diff --git a/v1/pages/credits/polygon/matt.png b/v1/pages/credits/polygon/matt.png deleted file mode 100644 index fa5132b..0000000 Binary files a/v1/pages/credits/polygon/matt.png and /dev/null differ diff --git a/v1/pages/credits/polygon/michael_duke.png b/v1/pages/credits/polygon/michael_duke.png deleted file mode 100644 index 8bbd451..0000000 Binary files a/v1/pages/credits/polygon/michael_duke.png and /dev/null differ diff --git a/v1/pages/credits/polygon/michael_huff.png b/v1/pages/credits/polygon/michael_huff.png deleted file mode 100644 index e762929..0000000 Binary files a/v1/pages/credits/polygon/michael_huff.png and /dev/null differ diff --git a/v1/pages/credits/polygon/michelle.png b/v1/pages/credits/polygon/michelle.png deleted file mode 100644 index e2511cc..0000000 Binary files a/v1/pages/credits/polygon/michelle.png and /dev/null differ diff --git a/v1/pages/credits/polygon/natalie.png b/v1/pages/credits/polygon/natalie.png deleted file mode 100644 index 5d6c505..0000000 Binary files a/v1/pages/credits/polygon/natalie.png and /dev/null differ diff --git a/v1/pages/credits/polygon/nicholas.png b/v1/pages/credits/polygon/nicholas.png deleted file mode 100644 index 8a907cd..0000000 Binary files a/v1/pages/credits/polygon/nicholas.png and /dev/null differ diff --git a/v1/pages/credits/polygon/nimrod.png b/v1/pages/credits/polygon/nimrod.png deleted file mode 100644 index 9fb7940..0000000 Binary files a/v1/pages/credits/polygon/nimrod.png and /dev/null differ diff --git a/v1/pages/credits/polygon/noel.png b/v1/pages/credits/polygon/noel.png deleted file mode 100644 index 9d1a12a..0000000 Binary files a/v1/pages/credits/polygon/noel.png and /dev/null differ diff --git a/v1/pages/credits/polygon/phil.png b/v1/pages/credits/polygon/phil.png deleted file mode 100644 index 15be551..0000000 Binary files a/v1/pages/credits/polygon/phil.png and /dev/null differ diff --git a/v1/pages/credits/polygon/philippe.png b/v1/pages/credits/polygon/philippe.png deleted file mode 100644 index 66d8b0f..0000000 Binary files a/v1/pages/credits/polygon/philippe.png and /dev/null differ diff --git a/v1/pages/credits/polygon/rob.png b/v1/pages/credits/polygon/rob.png deleted file mode 100644 index 0776371..0000000 Binary files a/v1/pages/credits/polygon/rob.png and /dev/null differ diff --git a/v1/pages/credits/polygon/thomas.png b/v1/pages/credits/polygon/thomas.png deleted file mode 100644 index df02041..0000000 Binary files a/v1/pages/credits/polygon/thomas.png and /dev/null differ diff --git a/v1/pages/credits/polygon/travis.png b/v1/pages/credits/polygon/travis.png deleted file mode 100644 index 9e549e3..0000000 Binary files a/v1/pages/credits/polygon/travis.png and /dev/null differ diff --git a/v1/pages/credits/polygon/william.png b/v1/pages/credits/polygon/william.png deleted file mode 100644 index 4ee8b3d..0000000 Binary files a/v1/pages/credits/polygon/william.png and /dev/null differ diff --git a/v1/pages/credits/polygon/yu-han.png b/v1/pages/credits/polygon/yu-han.png deleted file mode 100644 index 3c56c1c..0000000 Binary files a/v1/pages/credits/polygon/yu-han.png and /dev/null differ diff --git a/v1/pages/credits/polygon/zach.png b/v1/pages/credits/polygon/zach.png deleted file mode 100644 index 73bff9a..0000000 Binary files a/v1/pages/credits/polygon/zach.png and /dev/null differ diff --git a/v1/pages/examples/favicon.png b/v1/pages/examples/favicon.png deleted file mode 100644 index a1b03e2..0000000 Binary files a/v1/pages/examples/favicon.png and /dev/null differ diff --git a/v1/pages/examples/index.html b/v1/pages/examples/index.html deleted file mode 100644 index 201a12a..0000000 --- a/v1/pages/examples/index.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - LOOPY: Examples! - - - - - - - - - - - - - diff --git a/v1/pages/gif.html b/v1/pages/gif.html deleted file mode 100644 index f1faf02..0000000 --- a/v1/pages/gif.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - Step 1: download LICEcap, - a free open-source tool to record GIFs (Windows/Mac only. Linux users, try - Peek) -

    - Step 2: record a GIF while playing around in LOOPY -

    - Step 3: ta-dah -

    -
    - -
    - - \ No newline at end of file diff --git a/v1/pages/gifs.gif b/v1/pages/gifs.gif deleted file mode 100644 index 2e17cc3..0000000 Binary files a/v1/pages/gifs.gif and /dev/null differ diff --git a/v1/pages/howto.html b/v1/pages/howto.html deleted file mode 100644 index 9e60b41..0000000 --- a/v1/pages/howto.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - -
    - - \ No newline at end of file diff --git a/v1/pages/howto/1.png b/v1/pages/howto/1.png deleted file mode 100644 index 59e9deb..0000000 Binary files a/v1/pages/howto/1.png and /dev/null differ diff --git a/v1/pages/howto/2.png b/v1/pages/howto/2.png deleted file mode 100644 index 35eabd0..0000000 Binary files a/v1/pages/howto/2.png and /dev/null differ diff --git a/v1/pages/howto/3.png b/v1/pages/howto/3.png deleted file mode 100644 index 69dc1ee..0000000 Binary files a/v1/pages/howto/3.png and /dev/null differ diff --git a/v1/pages/howto/4.png b/v1/pages/howto/4.png deleted file mode 100644 index 405fb9e..0000000 Binary files a/v1/pages/howto/4.png and /dev/null differ diff --git a/v1/pages/howto/5.png b/v1/pages/howto/5.png deleted file mode 100644 index 4cf4a46..0000000 Binary files a/v1/pages/howto/5.png and /dev/null differ diff --git a/v1/pages/page.css b/v1/pages/page.css deleted file mode 100644 index 34b15d2..0000000 --- a/v1/pages/page.css +++ /dev/null @@ -1,183 +0,0 @@ -body{ - background: #ddd; - font-family: Helvetica, Arial, sans-serif; - font-weight: 100; - font-size: 19px; - color: #222; - margin: 0; -} -.full{ - width:100%; - margin: 0; - display: block; -} -.shadow{ - width:100%; height: 100%; - position: fixed; - top:0; left:0; - box-shadow: inset 0 0 20px rgba(0,0,0,0.75); -} - -/********************************/ -/********************************/ - -#examples{ - width: 620px; - margin:0 auto; - overflow: hidden; -} - -#examples hr{ - border:none; - border-bottom: 2px dashed #aaa; - width: 560px; - margin: 0 auto; - margin-top: 15px; -} - -#more_examples{ - color: #333; - width: 500px; - margin: 30px auto; -} -#more_examples li{ - color: #666; - margin-bottom: 1em; - position: relative; - left: -0.8em; -} -#more_examples li a{ - color: #dd4040; -} -#more_examples li a:hover{ - color: #ee6060; -} -#more_examples li a.twitter{ - color: inherit; -} - -.example{ - display: block; - float: left; - width: 180px; - height: 180px; - - cursor: pointer; - transform: scale(1); - transition: all 0.2s ease-in-out; -} -.example:hover{ - transform: scale(1.1); -} -.example > div{ - width: 180px; - height: 180px; - background: #888; - border-radius: 100px; - float: left; - position: relative; -} -.example > div > div{ - font-size: 25px; - color: #fff; - width: 150px; - height: 55px; - position: absolute; - top:0; left:0; right:0; bottom:0; - margin: auto; - text-align: center; -} -.example:nth-child(n+2){ - margin-left: 19px; -} - -/********************************/ -/********************************/ - -#credits{ - width: 600px; - margin: 50px auto; - font-weight: 100; - color: #fff; - font-size: 20px; -} - -.credit_intro{ - font-size: 26px; -} -.credit_intro a{ - color: #dd3939; -} -.credit_intro a:hover{ - color: #ee6060; -} - -#credits hr{ - border:none; - border-bottom: 4px dashed #444; - margin-top: 30px; -} - -.credits_peeps{ - overflow: hidden; - padding-bottom: 20px; -} -.credits_peeps > div{ - position: relative; - width: 150px; - height: 210px; - float: left; - text-align: center; -} -.credits_peeps > div > img:nth-child(1){ - position: absolute; - left: 0; - width: 150px; - bottom: 30px; -} -.credits_peeps > div > img:nth-child(2){ - position: absolute; - width: 50px; - right: 5px; - bottom: 30px; -} -.credits_peeps > div > span{ - display: block; - position: absolute; - bottom:0; - width: 150px; - height: 20px; - text-transform: lowercase; -} - -.credits_polygons{ - margin-top: 15px; - overflow: hidden; - color: #ddd; - font-size: 18px; - padding-bottom: 20px; - text-align: center; -} -.credits_polygons > div{ - display: inline-block; -} -.credits_polygons > div > img{ - width: 40px; - position: relative; - top:10px; -} -.credits_polygons > div > span{ - text-transform: lowercase; -} - -.credits_names{ - margin-top: 30px; - overflow: hidden; - color: #bbb; - font-size: 16px; - padding-bottom: 20px; - text-align: center; - -webkit-columns: 150px 3; - -moz-columns: 150px 3; - columns: 150px 3; -} \ No newline at end of file