diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..5636f38 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +/build/*.js \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..1a76640 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,61 @@ +{ + "parserOptions": { + "ecmaVersion": 9, + "sourceType": "module", + "ecmaFeatures": {} + }, + "rules": { + "constructor-super": 2, + "for-direction": 2, + "getter-return": 2, + "no-case-declarations": 2, + "no-class-assign": 2, + "no-compare-neg-zero": 2, + "no-cond-assign": 2, + "no-const-assign": 2, + "no-constant-condition": 2, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-dupe-args": 2, + "no-dupe-class-members": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty-character-class": 2, + "no-empty-pattern": 2, + "no-ex-assign": 2, + "no-extra-boolean-cast": 2, + "no-extra-semi": 2, + "no-fallthrough": 2, + "no-func-assign": 2, + "no-global-assign": 2, + "no-inner-declarations": 2, + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-mixed-spaces-and-tabs": 2, + "no-new-symbol": 2, + "no-obj-calls": 2, + "no-octal": 2, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-self-assign": 2, + "no-sparse-arrays": 2, + "no-this-before-super": 2, + "no-unexpected-multiline": 2, + "no-unreachable": 2, + "no-unsafe-finally": 2, + "no-unsafe-negation": 2, + "no-unused-labels": 2, + "no-unused-vars": 2, + "require-yield": 2, + "use-isnan": 2, + "valid-typeof": 2, + "no-undefined": 2, + "no-undef": 2 + }, + "env": { + "browser": true, + "node": true, + "es6": true + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..deefaba --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/.vscode +package-lock.json \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..529887e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,18 @@ +{ + "arrowParens": "avoid", + "bracketSpacing": true, + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxBracketSameLine": true, + "jsxSingleQuote": true, + "parser": "flow", + "printWidth": 80, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false +} \ No newline at end of file diff --git a/README.md b/README.md index 6e70d3d..f34072d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Drawception ANBT [![Public domain](http://i.creativecommons.org/p/zero/1.0/88x31 A userscript to make Drawception.com better: more drawing tools, tablet support, sandbox with palettes and uploading to imgur, like all, quick menu buttons with old browser support, and other enhancements. -[**Direct script link**](https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/drawception-anbt.user.js) (use to install/update manually, or "save as...") +[**Direct script link**](https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js) (use to install/update manually, or "save as...") [New canvas with recording and playback, standalone version](http://grompe.org.ru/drawit/) @@ -22,13 +22,13 @@ A userscript to make Drawception.com better: more drawing tools, tablet support, - Opera 15+: add the script in [Violentmonkey add-on](https://addons.opera.com/en/extensions/details/violent-monkey/?display=en) - Maxthon: add the script in [Violentmonkey add-on](http://extension.maxthon.com/detail/index.php?view_id=1680) - Mobile browsers / other / single use: - - create a bookmark with the following URL: + - create a bookmark with the following URL (be careful, the "javascript:" at the beginning may not be copied with the rest in some browsers, don’t forget to check before executing the code): - `javascript:(()=>(x=new XMLHttpRequest,x.open('GET','https://raw.githubusercontent.com/EnderDragonneau/Drawception-ANBT/master/drawception-anbt.user.js'),x.onload=()=>eval(x.responseText),x.send()))()` + `javascript:(()=>(x=new XMLHttpRequest,x.open('GET','https://raw.githubusercontent.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js'),x.onload=()=>eval(x.responseText),x.send()))()` and follow it while being on drawception.com site; if that doesn't work, try pasting it in the address bar. -After installing script management add-on, just click on the [**Direct script link**](https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/drawception-anbt.user.js). +After installing script management add-on, just click on the [**Direct script link**](https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js). ## FEATURES diff --git a/build/drawception-anbt.user.js b/build/drawception-anbt.user.js new file mode 100644 index 0000000..9ad8867 --- /dev/null +++ b/build/drawception-anbt.user.js @@ -0,0 +1,2312 @@ +// ==UserScript== +// @name Drawception ANBT +// @author Grom PE +// @namespace http://grompe.org.ru/ +// @version 1.156.2019.06 +// @description Enhancement script for Drawception.com - Artists Need Better Tools +// @downloadURL https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js +// @match http://drawception.com/* +// @match https://drawception.com/* +// @grant none +// @run-at document-start +// @license Public domain +// ==/UserScript== +;(function() { + 'use strict' + + const addDarkCSS = () => + localStorage.setItem( + 'gpe_darkCSS', + ( + 'a{color:#77c0ff$}.wrapper{~#444$}#nav-drag{~#353535$}.btn-default{~#7f7f7f$;border-bottom-color:#666$;border-left-color:#666$;border-right-color:#666$;border-top-color:#666$;color:#CCC$}' + + '.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{~#757575$;border-bottom-color:#565656$;border-left-color:#565656$;border-right-color:#565656$;border-top-color:#565656$;color:#DDD$}' + + '.btn-success{~#2e2e2e$;border-bottom-color:#262626$;border-left-color:#262626$;border-right-color:#262626$;border-top-color:#262626$;color:#CCC$}' + + '.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{~#232323$;border-bottom-color:#1c1c1c$;border-left-color:#1c1c1c$;border-right-color:#1c1c1c$;border-top-color:#1c1c1c$;color:#DDD$}' + + '.btn-primary{~#213184$;border-bottom-color:#1a1a68$;border-left-color:#1a1a68$;border-right-color:#1a1a68$;border-top-color:#1a1a68$;color:#CCC$}' + + '.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{~#191964$;border-bottom-color:#141450$;border-left-color:#141450$;border-right-color:#141450$;border-top-color:#141450$;color:#DDD$}' + + '.btn-info{~#2d7787$;border-bottom-color:#236969$;border-left-color:#236969$;border-right-color:#236969$;border-top-color:#236969$;color:#CCC$}' + + '.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{~#1c5454$;border-bottom-color:#133939$;border-left-color:#133939$;border-right-color:#133939$;border-top-color:#133939$;color:#DDD$}' + + '.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{~#3b3b3b$}.navbar-toggle{~#393939$}.navbar{border-bottom:1px solid #000$}.forum-thread-starter,.breadcrumb,.regForm{~#555$}' + + '.form-control{~#666$;border:1px solid #333$;color:#EEE$}code,pre{~#656$;color:#FCC$}body{color:#EEE$}footer{~#333$;border-top:1px solid #000$}' + + '.pagination>li:not(.disabled):not(.active),.pagination>li:not(.disabled):not(.active)>a:hover,.pagination>li:not(.disabled):not(.active)>span:hover,.pagination>li:not(.disabled):not(.active)>a:focus,.pagination>li:not(.disabled):not(.active)>span:focus{~#444$}.pagination>li>a,.pagination>li>span{~#555$;border:1px solid #000$}' + + '.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{~#666$;border-top:1px solid #444$;border-bottom:1px solid #444$}' + + '.drawingForm{~#555$}.well{~#666$;border:1px solid #333$}#timeleft{color:#AAA$}legend{border-bottom:1px solid #000$}.thumbpanel{color:#EEE;~#555$}.thumbpanel img{~#fffdc9$}.panel-number,.modal-content,.profile-user-header{~#555$}' + + '#commentForm{~#555$;border:1px solid #000$}.modal-header,.nav-tabs{border-bottom:1px solid #000$}hr,.modal-footer{border-top:1px solid #000$}' + + '.store-item{background:#666$;~-moz-linear-gradient(top,#666 0,#333 100%)$;~-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(100%,#333))$;~-webkit-linear-gradient(top,#666 0,#333 100%)$;~-o-linear-gradient(top,#666 0,#333 100%)$;~-ms-linear-gradient(top,#666 0,#333 100%)$;~linear-gradient(to bottom,#666 0,#333 100%)$;border:1px solid #222$}' + + '.store-item:hover{border:1px solid #000$}.store-item-title{~#222$;color:#DDD$}.store-title-link{color:#DDD$}.profile-award{~#222$}.profile-award-unlocked{~#888$}.progress-bar{color:#CCC$;~#214565$}.progress{~#333$}' + + '.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(0,0,0,0.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(0,0,0,0.15)),color-stop(.75,rgba(0,0,0,0.15)),color-stop(.75,transparent),to(transparent))$;background-image:-webkit-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:-moz-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$}' + + '.progress-bar-success{~#363$}.progress-bar-info{~#367$}.progress-bar-warning{~#863$}.progress-bar-danger{~#733$}' + + '.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#DDD$;~#555$;border:1px solid #222$}.nav>li>a:hover,.nav>li>a:focus{~#333$;border-bottom-color:#222$;border-left-color:#111$;border-right-color:#111$;border-top-color:#111$}' + + '.nav>li.disabled>a,.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#555$}.table-striped>tbody>tr:nth-child(2n+1)>td,.table-striped>tbody>tr:nth-child(2n+1)>th{~#333$}' + + '.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{~#555$}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{border-top:1px solid #333$}.news-alert{~#555$;border:2px solid #444$}' + + '.btn-menu{~#2e2e2e$}.btn-menu:hover{~#232323$}.btn-yellow{~#8a874e$}.btn-yellow:hover{~#747034$}' + + 'a.label{color:#fff$}.text-muted,a.text-muted{color:#999$}a.wrong-order{color:#F99$}div.comment-holder:target{~#454$}' + + '.popover{~#777$}.popover-title{~#666$;border-bottom:1px solid #444$}.popover.top .arrow:after{border-top-color:#777$}.popover.right .arrow:after{border-right-color:#777$}.popover.bottom .arrow:after{border-bottom-color:#777$}.popover.left .arrow:after{border-left-color:#777$}' + + '.label-fancy{~#444$;border-color:#333$;color:#FFF$}' + + '.avatar,.profile-avatar{~#444$;border:1px solid #777$;}' + + '.bg-lifesupport{~#444$}body{~#555$}.snap-content{~#333$}' + + 'select,textarea{color:#000$}.help-block{color:#ddd$}.jumbotron{~#444$}' + + '.navbar-dropdown{~#444$}a.list-group-item{~#444$;color:#fff$;border:1px solid #222$}a.list-group-item:hover,a.list-group-item:focus{~#222$}' + + '.likebutton.btn-success{color:#050$;~#5A5$}.likebutton.btn-success:hover{~#494$}' + + ".thumbnail[style*='background-color: rgb(255, 255, 255)']{~#555$}" + + '.popup,.v--modal{~#666$;border:1px solid #222$}.btn-reaction{~#666$;border:none$;color:#AAA$}@media(min-width:625px){.create-game-wrapper{~#555$}}' + + '.profile-header{~#555$}.profile-nav > li > a{~#333$}.profile-nav>li.active>a,.profile-nav>li>a:hover{~#555$}' + + '.gsc-control-cse{~#444$;border-color:#333$}.gsc-above-wrapper-area,.gsc-result{border:none$}.gs-snippet{color:#AAA$}.gs-visibleUrl{color:#8A8$}a.gs-title b,.gs-visibleUrl b{color:#EEE$}.gsc-adBlock{display:none$}.gsc-input{~#444$;border-color:#333$;color:#EEE$}' + + '.comment-highlight{border:none$;background:#454$}#header-emotes{~#555$}#header-bar-container{border:none$}.paypal-button-tag-content{color:#EEE$}.numlikes{color:#EEE$}.gsc-input-box{~#444$;border-color:#333$}.gsc-completion-container{~#333$;border-color:#000$}.gsc-completion-selected{~#222$}.gsc-completion-container b{color:#AAA$}.alert-nice{~#4a4a4a$}.store-buy-coins{~#777$}.store-buy-coins:hover{~#666$}.store-buy-coins>h2,.store-buy-coins>h2>small{color:#EEE$}.store-package-selector{~#888$}.store-package-selector>label{color:#EEE$}.label-stat{~#444$;color:#EEE$;border:1px solid #555$}.label-stat.disabled{~#333$}.option{padding:4px 8px$;~#666$;color:#EEE$;border-color:#333$}.option.selected{border-color:#EEE$}.sleek-select{~#666$;border-color:#333$}select{color:#EEE$}.modal-note{color:#EEE$}.vue-dialog-button{~#555$;border:none$}.vue-dialog-button:hover{~#5a5a5a$}.vue-dialog-buttons{border-top:1px solid #222$}.dashboard-item{~#333$}legend{color:#EEE$}.list-group-item{~#444$;color:#EEE$;border:1px solid #222$}.alert-warning{color:#EEE$;~#555$;border-color:#555$}.btn-reaction.active{border:1px solid #EEE$}.bg-shadow-box{~#333$}.btn-gray{~#222$;border:none$}.btn-gray:hover{color:#EEE$;~#1a1a1a$}.btn-bright{~#333$;color:#EEE$}' + + '.player-name-new{color:#33b73f$}.gsc-tabsArea>div{overflow:hidden$}.gs-image-popup-box{~#333$;border-color:#222$}.gs-size{color:#6f6f6f$}.gsc-result-info{color:#EEE$}.gsc-refinementsArea{border:none$}.gsc-tabsArea{border-bottom-color:#333$}.gsc-cursor-page{color:#EEE$}.gsc-cursor-current-page{color:#AAA$}.profile-nav>.disabled>a{color:#555$;~#3a3a3a$}.profile-nav>.disabled>a:hover{~#3a3a3a$}.sleek-select:hover{border-color:#EEE$}' + + '.btn-menu{border-color:#1e1e1e$;text-shadow:0px 0px 3px #777$}.btn-menu:hover{border-color:#1e1e1e$}.pagination>.active>span{color:#EEE$}#btn-notifications{color:#EEE$}.btn-warning{color:#EEE$}.alert-nice{color:#EEE$}.emotes-popup{~#2e2e2e$}.navbar-toggle{~#2e2e2e$;border-color:#1e1e1e$}.navbar-toggle .icon-bar{~#EEE$}' + + '.gamepanel-highlight{box-shadow:0 0 20px #111$;~#222$}' + + 'a.anbt_replaypanel:hover{color:#8af$}' + + '.anbt_favedpanel{color:#d9534f$}' + + '' + ) + .replace(/~/g, 'background-color:') + .replace(/\$/g, ' !important') + ) + + const getLocalStorageItem = (name, empty) => { + const item = localStorage.getItem(name) + try { + return item ? JSON.parse(item) : empty || '' + } catch (e) { + return item ? item : empty || '' + } + } + + const setDarkMode = () => { + const settings = getLocalStorageItem('gpe_anbtSettings', {}) + if (settings.anbtDarkMode || typeof settings.anbtDarkMode === 'undefined') { + if (getLocalStorageItem('gpe_inDark', 0)) { + const css = document.createElement('style') + css.id = 'darkgraycss' + css.type = 'text/css' + css.appendChild( + document.createTextNode(getLocalStorageItem('gpe_darkCSS')) + ) + if (document.head) document.head.appendChild(css) + else { + const darkLoad = setInterval(() => { + if (!document.head) return + document.head.appendChild(css) + clearInterval(darkLoad) + }, 100) + } + } + } + } + + const options = { + enableWacom: 0, + fixTabletPluginGoingAWOL: 1, + hideCross: 0, + enterToCaption: 0, + pressureExponent: 0.5, + backup: 1, + timeoutSound: 0, + timeoutSoundBlitz: 0, + timeoutSoundVolume: 100, + newCanvas: 1, + proxyImgur: 0, + ajaxRetry: 1, + localeTimestamp: 0, + autoplay: 1, + submitConfirm: 1, + smoothening: 1, + autoBypassNSFW: 0, + colorNumberShortcuts: 1, + colorUnderCursorHint: 1, + bookmarkOwnCaptions: 0, + colorDoublePress: 0, + newCanvasCSS: '', + forumHiddenUsers: '', + maxCommentHeight: 1000, + useOldFont: true, + useOldFontSize: true, + markdownTools: 1, + anbtDarkMode: 1 + } + + const $ = (selector, array = false) => { + const elements = selector.startsWith('<') + ? [ + ...new DOMParser().parseFromString(selector, 'text/html').body + .children + ] + : [...document.querySelectorAll(selector)] + return elements.length > 1 || array ? elements : elements[0] + } + + const betterCreate = () => { + if (!options.enterToCaption) { + if ($('#prompt')) + $('#prompt').addEventListener('keydown', event => { + if (event.keyCode === 13) event.preventDefault() + }) + } + } + + const addStyle = css => { + const parent = + document.getElementsByTagName('head')[0] || document.documentElement + const style = document.createElement('style') + style.type = 'text/css' + style.appendChild(document.createTextNode(css)) + parent.appendChild(style) + } + + const globals = { + userId: getLocalStorageItem( + 'gpe_lastSeenId', + $('.player-dropdown a[href^="/player/"]') && + $('.player-dropdown a[href^="/player/"]').href.match( + /\/player\/(\d+)\// + )[1] + ), + months: [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec' + ], + alphabet: '36QtfkmuFds0UjlvCGIXZ125bEMhz48JSYgipwKn7OVHRBPoy9DLWaceqxANTr', + greetings: [ + 'Oruvaq lbh!', + "Ubcr vg'f abg envavat gbqnl.", + 'Jurer vf lbhe tbq abj?', + 'Lbh fubhyq srry 5% zber cbjreshy abj.', + 'Fhqqrayl, abguvat unccrarq!', + '^_^', + 'Guvf gnxrf fb ybat gb svavfu...', + "Jungrire lbh qb, qba'g ernq guvf grkg.", + 'Pyvpx urer sbe 1 serr KC', + 'Or cngvrag.', + "Whfg qba'g fgneg nal qenzn nobhg vg.", + '47726S6Q2069732074686520677265617465737421', + 'Cynl fzneg.', + 'Cynl avpr.', + 'Fzvyr!', + "Qba'g sbetrg gb rng.", + "V xabj jung lbh'ir qbar.", + 'Fpvrapr!', + 'Gbqnl vf n tbbq qnl.' + ] + } + + const decimalToBase62 = number => { + let result = '' + while (number !== 0) { + const quotient = number % 62 + result = globals.alphabet[quotient] + result + number = (number - quotient) / 62 + } + return result + } + + const scrambleID = number => { + if (isNaN(number)) throw new Error('Invalid panel ID') + return [...decimalToBase62(parseInt(number, 10) + 3521614606208)] + .reverse() + .join('') + } + + const linkifyDrawingPanels = img => { + if (img.parentNode.nodeName !== 'A') { + if ( + img.src.match(/\/images\/panels\//) || + img.src.match(/\/pub\/panels\//) + ) + img.outerHTML = `${img.outerHTML}` + if (img.src.match(/\/drawings\//)) + img.outerHTML = `${img.outerHTML}` + if (img.src.match(/\/panel\//)) + img.outerHTML = `${img.outerHTML}` + if (img.src.match(/\/images\/games\//) || img.src.match(/\/pub\/games\//)) + img.outerHTML = `${img.outerHTML}` + if (img.src.match(/\/display-panel.php?/)) { + const newsrc = `/panel/drawing/${scrambleID( + img.src.match(/x=(\d+)/)[1] + )}/` + img.setAttribute('src', newsrc) + img.outerHTML = `${img.outerHTML}` + } + } + } + + const linkifyNodeText = node => { + if (node.textContent.includes('://')) + node.innerHTML = node.innerHTML.replace( + /([^"]|^)(https?:\/\/(?:(?:(?:[^\s<()]*\([^\s<()]*\))+)|(?:[^\s<()]+)))/g, + '$1$2' + ) + } + + const versions = { + scriptVersion: '1.156.2019.06', + newCanvasVersion: 53, + siteVersion: 'f6b35cce', + runtimeVersion: '1ba6bf05' + } + + const setupNewCanvas = (insandbox, url) => { + const canvasHTML = localStorage.getItem('anbt_canvasHTML') + const canvasHTMLver = localStorage.getItem('anbt_canvasHTMLver') + if ( + !canvasHTML || + canvasHTMLver < versions.newCanvasVersion || + canvasHTML.length < 10000 + ) { + const request = new XMLHttpRequest() + request.open( + 'GET', + 'https://api.github.com/repos/EnderDragonneau/Drawception-ANBT/contents/build/index.html' + ) + request.setRequestHeader('Accept', 'application/vnd.github.3.raw') + request.onload = () => { + if (request.responseText.length < 10000) { + alert( + `Error: instead of new canvas code, got this response from GitHub:\n${request.responseText}` + ) + location.pathname = '/' + } else { + localStorage.setItem('anbt_canvasHTML', request.responseText) + localStorage.setItem('anbt_canvasHTMLver', versions.newCanvasVersion) + setupNewCanvas(insandbox, url) + } + } + request.onerror = () => { + alert('Error loading the new canvas code. Please try again.') + location.pathname = '/' + } + request.send() + return + } + const inforum = url.match(/forums\//) + const friendgameid = url.match(/play\/(.+)\//) + const panelid = url.match(/sandbox\/#?([^/]+)/) + const incontest = + url.match(/contests\/play\//) && document.getElementById('canvas-holder') + const vertitle = `ANBT v${versions.scriptVersion}` + if (incontest) window.onbeforeunload = () => {} + const normalurl = + insandbox && !inforum + ? `/sandbox/${panelid ? `#${panelid[1]}` : ''}` + : incontest + ? '/contests/play/' + : inforum + ? url.match(/\/forums\/?.+/) + : `/play/${friendgameid ? `${friendgameid[1]}/` : ''}` + try { + if (location.pathname + location.hash !== normalurl) + history.pushState({}, document.title, normalurl) + } catch (e) {} + const alarmSoundOgg = + 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAABnHAAAAAAAAHQUSFoBHgF2b3JiaXMAAAAAAUSsAAAAAAAAYG0AAAAAAADJAU9nZ1MAAAAAAAAAAAAAZxwAAAEAAABq35G0DxD/////////////////NQN2b3JiaXMAAAAAAAAAAAEFdm9yYmlzH0JDVgEAAAEAFGNWKWaZUpJbihlzmDFnGWPUWoolhBRCKKVzVlurKbWaWsq5xZxzzpViUilFmVJQW4oZY1IpBhlTEltpIYQUQgehcxJbaa2l2FpqObacc62VUk4ppBhTiEromFJMKaQYU4pK6Jxz0DnmnFOMSgg1lVpTyTGFlFtLKXROQgephM5SS7F0kEoHJXRQOms5lRJTKZ1jVkJquaUcU8qtpphzjIHQkFUAAAEAwEAQGrIKAFAAABCGoSiKAoSGrAIAMgAABOAojuIokiI5kmM5FhAasgoAAAIAEAAAwHAUSZEUy9EcTdIszdI8U5ZlWZZlWZZlWZZd13VdIDRkFQAAAQBAKAcZxRgQhJSyEggNWQUAIAAAAIIowxADQkNWAQAAAQAIUR4h5qGj3nvvEXIeIeYdg9577yG0XjnqoaTee++99x5777n33nvvkWFeIeehk9577xFiHBnFmXLee+8hpJwx6J2D3nvvvfeec+451957752j3kHpqdTee++Vk14x6Z2jXnvvJdUeQuqlpN5777333nvvvffee++9955777333nvvrefeau+9995777333nvvvffee++9995777333nvvgdCQVQAAEAAAYRg2iHHHpPfae2GYJ4Zp56T3nnvlqGcMegqx9557773X3nvvvffeeyA0ZBUAAAgAACGEEFJIIYUUUkghhhhiyCGHHIIIKqmkoooqqqiiiiqqLKOMMsook4wyyiyjjjrqqMPOQgoppNJKC620VFtvLdUehBBCCCGEEEIIIYQQvvceCA1ZBQCAAAAwxhhjjEEIIYQQQkgppZRiiimmmAJCQ1YBAIAAAAIAAAAsSZM0R3M8x3M8x1M8R3RER3RER5RESbRETfREUTRFVbRF3dRN3dRNXdVN27VVW7ZlXdddXddlXdZlXdd1Xdd1Xdd1Xdd1XbeB0JBVAAAIAABhkEEGGYQQQkghhZRSijHGGHPOOSA0ZBUAAAgAIAAAAEBxFEdxHMmRJMmyLM3yLM8SNVMzNVNzNVdzRVd1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' + if (inforum) { + if (document.querySelector('.v--modal-overlay')) + document.querySelector('.v--modal-overlay').outerHTML = '' + const div = document.querySelector('.wrapper').children[1] + const iframe = document.createElement('iframe') + const modalOverlay = document.createElement('div') + modalOverlay.setAttribute('class', 'v--modal-overlay') + iframe.id = 'iframe' + iframe.setAttribute('class', 'v--modal-background-click') + modalOverlay.appendChild(iframe) + div.appendChild(modalOverlay) + iframe.contentWindow.document.open() + iframe.contentWindow.anbtReady = () => { + iframe.contentWindow.inforum = inforum + iframe.contentWindow.insandbox = insandbox + iframe.contentWindow.incontest = incontest + iframe.contentWindow.options = options + iframe.contentWindow.alarmSoundOgg = alarmSoundOgg + iframe.contentWindow.vertitle = vertitle + iframe.contentWindow.getLocalStorageItem = getLocalStorageItem + iframe.contentWindow.needToGoDeeper() + } + iframe.contentWindow.document.write(canvasHTML) + iframe.contentWindow.document.close() + return + } + document.open() + window.anbtReady = () => { + if (friendgameid) window.friendgameid = friendgameid[1] + if (panelid) window.panelid = panelid[1] + window.inforum = inforum + window.insandbox = insandbox + window.incontest = incontest + window.options = options + window.alarmSoundOgg = alarmSoundOgg + window.vertitle = vertitle + window.getLocalStorageItem = getLocalStorageItem + window.needToGoDeeper() + } + document.write(canvasHTML) + document.close() + } + + const formatTimestamp = date => { + if (typeof date === 'number') date = new Date(date) + if (options.localeTimestamp) return date.toLocaleString() + return `${('0' + date.getDate()).slice(-2)} ${ + globals.months[date.getMonth()] + } ${date.getFullYear()} ${('0' + date.getHours()).slice(-2)}:${( + '0' + date.getMinutes() + ).slice(-2)}` + } + + const betterForums = () => { + $('.comment-body *', true).forEach(comment => linkifyNodeText(comment)) + $('img', true).forEach(img => linkifyDrawingPanels(img)) + if (document.location.pathname.match(/\/forums\/(\w+|-)\/.+/)) { + const hideUserIds = options.forumHiddenUsers + ? options.forumHiddenUsers.split(',') + : '' + if (hideUserIds) + addStyle( + '.anbt_hideUserPost:not(:target) {opacity: 0.4; margin-bottom: 10px}' + + '.anbt_hideUserPost:not(:target) .comment-body, .anbt_hideUserPost:not(:target) .avatar {display: none}' + + '' + ) + let lastid = 0 + $('.comment-avatar', true).forEach(({ parentNode }) => { + const commentHolder = parentNode.parentNode.parentNode + const anch = commentHolder.id || '' + commentHolder.classList.add('comment-holder') + const textMuted = commentHolder.querySelector('a.text-muted') + const vue = commentHolder.childNodes[0].__vue__ + if (vue) { + textMuted.textContent = `${textMuted.textContent.trim()}, ${formatTimestamp( + vue.comment_date * 1000 + )}` + if (vue.edit_date > 0) { + const el = textMuted.parentNode.querySelector('span[rel="tooltip"]') + const text = `${el.title}, ${formatTimestamp( + vue.edit_date * 1000 + ).replace(/ /g, '\u00A0')}` + el.setAttribute('title', text) + } + } + if (anch) { + const id = parseInt(anch.substring(1), 10) + const text = textMuted.textContent.trim() + textMuted.textContent = `${text} #${id}` + textMuted.setAttribute('title', 'Link to post') + if (id < lastid) textMuted.classList.add('wrong-order') + try { + const { href } = commentHolder.querySelector('a[href^="/player/"]') + if (href) { + const userId = href.match(/\d+/)[0] + if (hideUserIds.includes(userId)) + commentHolder.classList.add('anbt_hideUserPost') + } + } catch (e) {} + lastid = id + } + }) + if ( + $('.comment-holder') && + $('.comment-holder').length === 20 && + $('#comment-form .btn-primary') + ) + $('#comment-form .btn-primary').insertAdjacentHTML( + 'afterend', + '
Note: posting to another page
' + ) + } + if (options.proxyImgur) + $('img[src*="imgur.com/"]', true).forEach(img => + img.setAttribute( + 'src', + img.src.replace('imgur.com', 'filmot.com').replace('https', 'http') + ) + ) + const pagination = $('.pagination', true) + if (pagination.length) + $('.breadcrumb').insertAdjacentHTML( + 'afterend', + `
${pagination[0].outerHTML}
` + ) + if (document.location.pathname.match(/\/forums\/(\w+)\/$/)) { + const hiddenTopics = getLocalStorageItem('gpe_forumHiddenTopics', []) + let hidden = 0 + const tempUnhideLink = $('') + $('.forum-thread', true).forEach(thread => { + const href = thread + .querySelector('a:first-child') + .href.match(/\/forums\/\w+\/(\d+)\//) + if (!href || !href[1] || href[1] === 11830) return + const id = href[1] + if (hiddenTopics.includes(id)) { + thread.classList.add('anbt_hidden') + hidden++ + } + const hideLink = $('') + hideLink.addEventListener('click', () => { + const hiddenTopics = getLocalStorageItem('gpe_forumHiddenTopics', []) + if (hiddenTopics.includes(id)) { + if (hiddenTopics.includes(id)) + hiddenTopics.splice(hiddenTopics.indexOf(id), 1) + hiddenTopics.splice(hiddenTopics.indexOf(id), 1) + thread.classList.remove('anbt_hidden') + hidden-- + } else { + if (!hiddenTopics.includes(id)) hiddenTopics.push(id) + hiddenTopics.push(id) + thread.classList.add('anbt_hidden') + hidden++ + tempUnhideLink.style.display = '' + } + tempUnhideLink.textContent = hidden + localStorage.setItem( + 'gpe_forumHiddenTopics', + JSON.stringify(hiddenTopics) + ) + }) + thread.querySelector('p:nth-child(2)').appendChild(hideLink) + }) + tempUnhideLink.textContent = hidden + tempUnhideLink.addEventListener('click', () => { + $('#main').classList.toggle('anbt_showt') + }) + if (!hidden) tempUnhideLink.style.display = 'none' + if ($('#js-btn-toggle-thread')) + $('#js-btn-toggle-thread').parentNode.appendChild(tempUnhideLink) + } + $('.btn.btn-default', true).forEach(button => + button.addEventListener('click', () => { + if (button.textContent === 'Draw') + setupNewCanvas(true, document.location.href) + }) + ) + } + + const betterComments = () => { + const comments = [...$('#comments').nextElementSibling.children].slice(1) + comments.forEach(x => { + x.parentNode.parentNode.classList.add('comment-holder') + }) + const gamePlayers = [] + const playerdata = {} + $('.gamepanel-holder', true).forEach((gamePanel, index) => { + const detail = gamePanel.querySelector('.panel-details') + const gamepanel = gamePanel.querySelector('.gamepanel') + const playerLink = detail.querySelector('.panel-user a') + if (!playerLink) return + const id = playerLink.href.match(/\/player\/(\d+)\//)[1] + playerdata[id] = { + panel_number: index + 1, + player_anchor: playerLink, + panel_id: gamepanel.id, + drew: gamepanel.querySelector('img') !== null, + comments: 0 + } + gamePlayers.push(id) + }) + const seenComments = getLocalStorageItem('gpe_seenComments', {}) + const gameid = document.location.href.match(/game\/([^/]+)\//)[1] + if (comments) { + const hour = Math.floor(Date.now() / (1000 * 60 * 60)) + for (const tempgame in seenComments) + if (seenComments[tempgame].h + 24 * 7 < hour) + delete seenComments[tempgame] + let maxseenid = 0 + comments.forEach(holder => { + const dateElement = holder.querySelector('a.text-muted') + const vue = holder.__vue__ + if (vue) { + const text = dateElement.textContent.trim() + dateElement.textContent = `${text}, ${formatTimestamp( + vue.comment_date * 1000 + )}` + if (vue.edit_date > 0) { + const element = dateElement.parentNode.querySelector( + 'span[rel="tooltip"]' + ) + const title = `${element.title}, ${formatTimestamp( + vue.edit_date * 1000 + ).replace(/ /g, '\u00A0')}` + element.setAttribute('title', title) + } + } + const ago = dateElement.textContent + const commentid = parseInt(holder.id.slice(1), 10) + dateElement.setAttribute('title', 'Link to comment') + dateElement.textContent = `${dateElement.textContent.trim()} #${commentid}` + if (ago.match(/just now|min|hour|a day| [1-7] day/)) { + if (!(seenComments[gameid] && seenComments[gameid].id >= commentid)) { + holder.classList.add('comment-new') + if (maxseenid < commentid) maxseenid = commentid + } + } + const link = holder.querySelector('.text-bold a') + ? holder.querySelector('.text-bold a').href.match(/\/player\/(\d+)\//) + : '' + if (link) { + const id = link[1] + if (gamePlayers.includes(id)) { + const drew = playerdata[id].drew ? 'drew' : 'wrote' + dateElement.insertAdjacentHTML( + 'beforebegin', + `(${drew} #${playerdata[id].panel_number}) ` + ) + playerdata[id].comments++ + } + } + }) + if (maxseenid) + seenComments[gameid] = { + h: hour, + id: maxseenid + } + localStorage.setItem('gpe_seenComments', JSON.stringify(seenComments)) + } + for (const playerId in gamePlayers) { + const data = playerdata[playerId] + if (data && data.comments) { + const cmt2 = `Player left ${data.comments} comment${ + data.comments > 1 ? 's' : '' + }` + data.player_anchor.title = cmt2 + data.player_anchor.insertAdjacentHTML( + 'afterend', + `${data.comments}` + ) + } + } + if (options.maxCommentHeight) { + const h = options.maxCommentHeight + comments.forEach(comment => + comment.addEventListener('click', () => { + if ( + comment.clientHeight > h - 50 && + !$(location.hash).has(comment).length + ) + location.hash = `#${comment.parentNode.parentNode.id}` + }) + ) + } + } + + const waitForComments = () => { + const comments = $('#comments') + ? [...$('#comments').nextElementSibling.children].slice(1) + : '' + if (comments.length && !comments[0].classList.contains('spinner')) + betterComments() + else { + if (comments.length === 0) return + setTimeout(waitForComments, 1000) + } + } + + const checkForRecording = (url, success, retrying) => { + const request = new XMLHttpRequest() + request.open('GET', `${url}?anbt`, true) + request.responseType = 'arraybuffer' + request.onload = () => { + const buffer = request.response + const dataView = new window.DataView(buffer) + const magic = dataView.getUint32(0) + if (magic != 0x89504e47) return request.onerror() + for (let i = 8; i < buffer.byteLength; i += 4) { + const chunklen = dataView.getUint32(i) + i += 4 + const chunkname = dataView.getUint32(i) + i += 4 + if (chunkname === 0x73764762) return success() + else { + if (chunkname === 0x49454e44) break + i += chunklen + } + } + } + request.onerror = () => { + console.log( + 'checkForRecording fail (likely due to cache without CORS), retrying' + ) + if (!retrying) checkForRecording(`${url}?anbt`, success, true) + } + request.send() + } + + const addReplayButton = drawing => { + if (drawing.replayAdded) return + drawing.replayAdded = true + const { parentNode, src } = drawing + checkForRecording(src, () => { + const newid = $(`img[src='${src}']`) + .parentNode.querySelector('a[href^="/panel/"]') + .href.match(/\/panel\/[^/]+\/([^/]+)/)[1] + const id = newid.length >= 8 ? newid : scrambleID(parentNode.id.slice(6)) + const replayButton = $( + `` + ) + replayButton.addEventListener('click', event => { + if (event.which === 2) return + event.preventDefault() + setupNewCanvas(true, `/sandbox/#${id}`) + }) + parentNode.insertAdjacentHTML('beforebegin', replayButton.outerHTML) + }) + } + + const reversePanels = () => { + const element = $('.gamepanel-holder')[0].parentNode.parentNode + ;[...element.childNodes] + .reverse() + .forEach(child => element.appendChild(child)) + } + + const betterGame = () => { + if (document.title === 'Not Safe For Work (18+) Gate') { + if (options.autoBypassNSFW) window.DrawceptionPlay.bypassNsfwGate() + return + } + const drawings = $( + 'img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]' + ) + if ($('#btn-copy-url')) + $('#btn-copy-url').insertAdjacentHTML( + 'afterend', + ' Reverse' + ) + $('.reversePanels').addEventListener('click', reversePanels) + const favButton = $( + '' + ) + $('.panel-number', true).forEach(panelNumber => + panelNumber.insertAdjacentHTML('afterend', favButton.outerHTML) + ) + $('.gamepanel', true).forEach(({ parentNode }) => { + if (parentNode.querySelector('.gamepanel-tools>a:last-child') === null) + return + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + const id = parentNode + .querySelector('.gamepanel-tools>a:last-child') + .href.match(/\/panel\/[^/]+\/([^/]+)\/[^/]+\//)[1] + if (panels[id]) + parentNode + .querySelector('.anbt_favpanel') + .classList.add('anbt_favedpanel') + }) + $('.anbt_favpanel', true).forEach(favPanelButton => { + favPanelButton.addEventListener('click', () => { + if (favPanelButton.classList.contains('anbt_favedpanel')) return + const { parentNode } = favPanelButton + const id = parentNode + .querySelector('.gamepanel-tools>a:last-child') + .href.match(/\/panel\/[^/]+\/([^/]+)\/[^/]+\//)[1] + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + const panel = { + time: Date.now(), + by: parentNode.querySelector('.panel-user a').textContent + } + panel.userLink = parentNode + .querySelector('.panel-user a') + .href.match(/\/player\/[^/]+\/[^/]+\//)[0] + const img = parentNode.querySelector('.gamepanel img') + if (img) { + panel.image = img.src + panel.caption = img.alt + } else { + panel.caption = parentNode + .querySelector('.gamepanel') + .textContent.trim() + } + panels[id] = panel + localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) + favPanelButton.classList.add('anbt_favedpanel') + }) + }) + if (options.newCanvas) { + if (drawings) + drawings.forEach(drawing => + drawing.addEventListener('load', addReplayButton(drawing)) + ) + } + setTimeout(waitForComments, 200) + } + + const getCookie = name => + document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`))[2] || null + + const setCookie = (name, value, expire) => { + if (expire) { + const time = new Date() + time.setTime(time.getTime() + 24 * expire * 60 * 60 * 1e3) + expire = time.toUTCString() + } + document.cookie = `${name}=${value ? JSON.stringify(value) : ''}; expires=${ + expire ? expire : 'Thu, 01 Jan 1970 00:00:00 UTC' + }; path=/` + } + + const getPanelId = url => { + const match = url.match(/\/panel\/[^/]+\/(\w+)\//) + if (match) return match[1] + } + + const base62ToDecimal = number => { + number = number.toString() + const cachePosition = {} + let result = 0 + let pow = 1 + for (let i = number.length - 1; i >= 0; i--) { + const character = number[i] + if (typeof cachePosition[character] === 'undefined') { + cachePosition[character] = globals.alphabet.indexOf(character) + } + result += pow * cachePosition[character] + pow *= 62 + } + return result + } + + const unscrambleID = string => + base62ToDecimal([...string].reverse().join('')) - 3521614606208 + + const betterPanel = () => { + const favButton = $( + '' + ) + const gamePanel = $( + '.panel-caption-display>.flex,.gamepanel-holder>.gamepanel' + ) + if (gamePanel) gamePanel.insertAdjacentHTML('afterend', favButton.outerHTML) + const favBtn = $('.btn.btn-info') + if (favBtn) { + favBtn.addEventListener('click', event => { + event.preventDefault() + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + const panel = { + time: Date.now(), + by: $('.lead a', true)[0].textContent, + userLink: $('.lead a', true)[0].href.match( + /\/player\/[^/]+\/[^/]+\// + )[0] + } + const id = document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//)[1] + const img = $('.gamepanel img') + if (img) { + panel.image = img.src + panel.caption = img.alt + } else { + panel.caption = $('.gamepanel').textContent.trim() + } + panels[id] = panel + localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) + favBtn.setAttribute('disabled', 'disabled') + favBtn.querySelector('b').textContent = 'Favorited!' + }) + } + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + if ( + document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//) && + panels[document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//)[1]] + ) { + favBtn.setAttribute('disabled', 'disabled') + favBtn.querySelector('b').textContent = 'Favorited!' + } + const panelId = getPanelId(location.pathname) + if (options.newCanvas && panelId && unscrambleID(panelId) >= 14924553) { + const img = $('.gamepanel img') + if (img) + checkForRecording(img.src, () => { + const replayLink = $( + ` Replay ` + ) + replayLink.addEventListener('click', event => { + if (event.which === 2) return + event.preventDefault() + setupNewCanvas(true, `/sandbox/#${panelId}`) + }) + $('.gamepanel').insertAdjacentHTML('afterend', replayLink.outerHTML) + }) + } + if ( + $('.btn-primary').length > 1 && + $('.btn-primary')[1].textContent === 'Play again' + ) { + const ccButton = $( + '' + ) + ccButton.addEventListener('click', event => { + event.preventDefault() + const id = unscrambleID(panelId) + const cookie = getCookie('covercreatorids') + const ids = cookie ? JSON.parse(cookie) : [] + if (!ids.includes(id)) { + if (ids.length > 98) { + window.apprise( + 'Max cover creator drawings selected. Please remove some before adding more.' + ) + return + } else ids.push(id.toString()) + } else { + ccButton + .setAttribute('disabled', 'disabled') + .querySelector('b').textContent = 'Already added!' + return + } + setCookie('covercreatorids', JSON.stringify(ids)) + ccButton + .setAttribute('disabled', 'disabled') + .querySelector('b').textContent = 'Added!' + }) + $('.gamepanel').insertAdjacentHTML('afterend', ccButton.outerHTML) + } + } + + const rot13 = number => + [...number.toString()] + .map(character => { + character = character.charCodeAt(0) + if (character >= 97 && character <= 122) + character = ((character - 97 + 13) % 26) + 97 + if (character >= 65 && character <= 90) + character = ((character - 65 + 13) % 26) + 65 + return String.fromCharCode(character) + }) + .join('') + + const simpleHash = number => + number + .toString() + .split('') + .reduce((a, b) => { + a = (a << 5) - a + b.charCodeAt(0) + return a & a + }, 0) + + const randomGreeting = () => { + const change_every_half_day = Math.floor(Date.now() / (1000 * 60 * 60 * 12)) + const rnddata = simpleHash( + change_every_half_day + parseInt(globals.userid, 10) + 178889 + ) + return rot13(globals.greetings[rnddata % globals.greetings.length]) + } + + const addReplaySign = drawing => { + if (drawing.replayAdded) return + drawing.replayAdded = true + const panel = drawing.parentNode.parentNode + const { src } = drawing + checkForRecording(src, () => { + const newid = src.match(/(\w+).png$/)[1] + const replaySign = + newid.length >= 8 + ? $( + `` + ) + : $( + '' + ) + panel.appendChild(replaySign) + }) + } + + const fadeOut = (element, duration = 400) => { + duration = duration === 'slow' ? 600 : duration + element.style.opacity = element.style.opacity + ? parseFloat(element.style.opacity) - 0.1 + : 1 + if (parseFloat(element.style.opacity) < 0) { + element.style.opacity = 0 + element.style.display = 'none' + } else + setTimeout(() => { + fadeOut(element, duration) + }, duration / 10) + } + + const viewMyGameBookmarks = () => { + const removeButtonHTML = + '' + const games = getLocalStorageItem('gpe_gameBookmarks', {}) + const result = [] + for (let id in games) { + const extraClass = games[id].own ? ' anbt_owncaption' : '' + if (id.length > 10) { + result.push( + `

${id}${removeButtonHTML}

` + ) + const xhr = new XMLHttpRequest() + xhr.open('GET', `/play/${id}`) + xhr.onload = () => { + const { responseText, status } = xhr + if (status === 200) { + const m = + responseText.match(/Game is not private/) || + (responseText.match(/Problem loading game/) && 'del') + if (m) { + const gamename = + `${ + games[id].own + ? ` with your caption${ + games[id].caption ? ` ${games[id].caption}` : '' + }` + : '' + }${ + games[id].time + ? ` bookmarked on ${formatTimestamp(games[id].time)}` + : '' + }` || id + const status = m === 'del' ? 'Deleted' : 'Unfinished public' + $(`#${id}`).querySelector( + 'span' + ).textContent = `${status} game${gamename}` + return + } + const title = responseText.match(/(.+)<\/title>/)[1] + const [url, gameId] = responseText.match(/\/game\/([^/]+)\/[^/]+\//) + delete games[id] + games[gameId] = { + title, + url + } + $(`#${id}`).id = gameId + const spanId = $(`#${gameId}`).querySelector('span') + spanId.parentNode.replaceChild( + $(`<a href="${url}">${title}</a>`), + spanId + ) + localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) + } else { + $(`#${id}`).querySelector( + 'span' + ).textContent = `Error while retrieving game: ${responseText}` + } + } + xhr.send() + } else if (id.length === 10) + result.push( + `<p class="well${extraClass}" id="${id}"><a href="${games[id].url}">${games[id].title}</a>${removeButtonHTML}</p>` + ) + } + $('#anbt_userpage').innerHTML = result.length + ? result.join('') + : "You don't have any bookmarked games." + $('#anbt_userpage .anbt_gamedel', true).forEach(gameDelete => + gameDelete.addEventListener('click', event => { + event.preventDefault() + const { id } = gameDelete.parentNode + fadeOut($(`#${id}`)) + delete games[id] + localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) + }) + ) + } + + const viewMyPanelFavorites = () => { + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + let result = '' + let needsUpdate = false + for (const id in panels) { + if (panels[id].image && panels[id].image.match(/^\/pub\/panels\//)) { + needsUpdate = true + panels[id].image = panels[id].image.replace( + '/pub/panels/', + 'https://cdn.drawception.com/images/panels/' + ) + } + result += `<div id="${id}" style="float: left; position: relative; min-width: 150px;"><div class="thumbpanel-holder" style="overflow:hidden"><a class="anbt_paneldel" href="#" title="Remove">X</a><a href="/panel/-/${id}/-/" class="thumbpanel" rel="tooltip" title="${ + panels[id].caption + }">${ + panels[id].image + ? `<img src="${panels[id].image}" width="125" height="104" alt="${panels[id].caption}" />` + : panels[id].caption + }</a><span class="text-muted" style="white-space:nowrap">by <a href="${ + panels[id].userLink + }">${ + panels[id].by + }</a></span><br><small class="text-muted"><span class="fas fa-heart text-danger"></span> ${formatTimestamp( + panels[id].time + )}</small></div></div>` + } + if (needsUpdate) + localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) + result = result + ? `${result}<div style="clear:left"></div>` + : "You don't have any favorited panels." + $('#anbt_userpage').innerHTML = result + $('#anbt_userpage .anbt_paneldel', true).forEach(panelDelete => + panelDelete.addEventListener('click', event => { + event.preventDefault() + const { id } = panelDelete.parentNode.parentNode + fadeOut($(`#${CSS.escape(id)}`)) + delete panels[id] + localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) + }) + ) + } + + const betterPlayer = () => { + const pubinfo = $('.profile-header-info .text-muted > span:last-child') + if (pubinfo) linkifyNodeText(pubinfo.parentNode) + const loc = document.location.href + if (loc.match(new RegExp(`/player/${globals.userId}/[^/]+/(?:$|#)`))) { + const anbtSection = $('<h2>ANBT stuff: </h2>') + const panelFavoritesButton = $( + '<a class="btn btn-primary viewFavorites" href="#anbt_panelfavorites">Panel Favorites</a>' + ) + const gameBookmarks = $( + '<a class="btn btn-primary viewBookmarks" href="#anbt_gamebookmarks">Game Bookmarks</a>' + ) + anbtSection.appendChild(panelFavoritesButton) + anbtSection.appendChild(gameBookmarks) + const profilemain = $('.profile-layout-content').firstChild + profilemain.insertAdjacentHTML( + 'afterbegin', + `<h5 id="anbt_userpage">${randomGreeting()}</h5>` + ) + profilemain.insertAdjacentHTML('afterbegin', anbtSection.outerHTML) + $('.viewFavorites').addEventListener('click', event => { + event.preventDefault() + viewMyPanelFavorites() + }) + $('.viewBookmarks').addEventListener('click', event => { + event.preventDefault() + viewMyGameBookmarks() + }) + if (document.location.hash.includes('#anbt_panelfavorites')) + viewMyPanelFavorites() + if (document.location.hash.includes('#anbt_gamebookmarks')) + viewMyGameBookmarks() + if (window.date) { + const pubinfo = $('.profile-user-header>div.row>div>h1+p') + if (pubinfo) + [...pubinfo.childNodes][4].nodeValue = ` ${formatTimestamp( + window.date + )} \xa0` + } + } else { + const drawings = $( + 'img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]', + true + ) + if (options.newCanvas) + drawings.forEach(drawing => + drawing.addEventListener('load', addReplaySign(drawing)) + ) + drawings.forEach(({ src, parentNode }) => { + if (src.match(/-1\.png$/)) + parentNode.parentNode.appendChild( + $( + '<span class="pull-right" title="Draw First game"><img src="/img/icon-coins.png"></span>' + ) + ) + }) + } + if (loc.match(/player\/\d+\/[^/]+\/(posts)|(comments)\//)) { + $('.forum-thread-starter', true).forEach(threadStarter => { + const vue = threadStarter.childNodes[0].__vue__ + if (vue) { + const ts = threadStarter.querySelector('a.text-muted').firstChild + ts.textContent = `${ts.textContent.trim()}, ${formatTimestamp( + vue.comment_date * 1000 + )}` + if (vue.edit_date > 0) { + const el = ts.parentNode.parentNode.querySelector( + 'span[rel="tooltip"]' + ) + const text = `${el.title}, ${formatTimestamp( + vue.edit_date * 1000 + ).replace(/ /g, '\u00A0')}` + el.setAttribute('title', text) + } + } + const postlink = threadStarter.querySelector( + '.add-margin-top small.text-muted' + ) + const created = postlink.textContent.match(/^\s*Created/) + const commented = postlink.textContent.match(/^\s*Commented/) + const prefix = commented + ? 'Comment in the game' + : created + ? 'New thread' + : 'Reply in' + const n = $(`<h4 class="anbt_threadtitle">${prefix}: </h4>`) + const thread = postlink.querySelector('a') + n.appendChild(thread) + threadStarter.insertAdjacentHTML('afterbegin', n.outerHTML) + postlink.parentNode.parentNode.removeChild(postlink.parentNode) + }) + } + } + + const escapeHTML = value => + value + .toString() + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + + const addGroup = (name, settings) => { + const controlGroup = $('<div class="control-group"></div>') + controlGroup.appendChild($(`<label class="control-label">${name}</label>`)) + settings.forEach(setting => { + const value = options[setting[0]] + const [name, type, description] = setting + const controls = $('<div class="controls"></div>') + if (type === 'boolean') { + controls.appendChild( + $( + `<label><input type="checkbox" id="anbt_${name}" name="${name}" value="1" ${ + value ? 'checked="checked"' : '' + }"> ${description}</label>` + ) + ) + } else if (type === 'number') { + $( + `<b>${description}:</b><input class="form-control" type="text" data-subtype="number" name="${name}" value="${escapeHTML( + value || 0 + )}">` + ).forEach(node => controls.appendChild(node)) + } else if (type === 'longstr') { + $( + `<b>${description}:</b><textarea class="form-control" name="${name}">${escapeHTML( + value + )}</textarea>` + ).forEach(node => controls.appendChild(node)) + } else { + $( + `<b>${description}:</b><input class="form-control" type="text" name="${name}" value="${escapeHTML( + value + )}">` + ).forEach(node => controls.appendChild(node)) + } + controlGroup.appendChild(controls) + }) + return controlGroup + } + + const fadeIn = (element, duration = 400) => { + element.style.display = 'inline' + duration = duration === 'slow' ? 600 : duration + element.style.opacity = element.style.opacity + ? parseFloat(element.style.opacity) + 0.1 + : 0.2 + if (parseFloat(element.style.opacity) > 1) element.style.opacity = 1 + else + setTimeout(() => { + fadeIn(element, duration) + }, duration / 10) + } + + const loadScriptSettings = () => { + const result = getLocalStorageItem('gpe_anbtSettings', null) + if (!result) return + for (const i in result) window.options[i] = result[i] + } + + const updateScriptSettings = ({ currentTarget: theForm }) => { + const result = {} + theForm.querySelectorAll('input,textarea').forEach(fromField => { + if (fromField.type === 'checkbox') + result[fromField.name] = fromField.checked ? 1 : 0 + else if (fromField.getAttribute('data-subtype') === 'number') + result[fromField.name] = parseFloat(fromField.value) || 0 + else result[fromField.name] = fromField.value + }) + localStorage.setItem('gpe_anbtSettings', JSON.stringify(result)) + loadScriptSettings() + fadeIn($('#anbtSettingsOK'), 'slow') + setTimeout(() => { + fadeOut($('#anbtSettingsOK'), 'slow') + }, 800) + } + + const betterSettings = () => { + const theForm = $( + '<form class="regForm form-horizontal settingsForm" action="#"></form>' + ) + theForm.appendChild($('<legend>ANBT script settings</legend>')) + theForm.appendChild( + addGroup('Pen Tablet (unavailable for the moment...)', [ + [ + 'enableWacom', + 'boolean', + 'Enable Wacom plugin / pressure sensitivity support' + ], + [ + 'fixTabletPluginGoingAWOL', + 'boolean', + 'Try to prevent Wacom plugin from disappearing' + ] + ]) + ) + theForm.appendChild( + addGroup('Play (most settings are for the new canvas only)', [ + [ + 'newCanvas', + 'boolean', + 'New drawing canvas (also allows <a href="http://grompe.org.ru/replayable-drawception/">watching playback</a>)' + ], + [ + 'submitConfirm', + 'boolean', + 'Confirm submitting if more than a minute is left' + ], + ['smoothening', 'boolean', 'Smoothing of strokes'], + ['hideCross', 'boolean', 'Hide the cross when drawing'], + [ + 'enterToCaption', + 'boolean', + 'Submit captions (and start games) by pressing Enter' + ], + [ + 'backup', + 'boolean', + 'Save the drawing in case of error and restore it in sandbox' + ], + [ + 'timeoutSound', + 'boolean', + 'Warning sound when only a minute is left (normal games)' + ], + [ + 'timeoutSoundBlitz', + 'boolean', + 'Warning sound when only 5 seconds left (blitz)' + ], + ['timeoutSoundVolume', 'number', 'Volume of the warning sound, in %'], + [ + 'rememberPosition', + 'boolean', + 'Show your panel position and track changes in unfinished games list' + ], + ['colorNumberShortcuts', 'boolean', 'Use 0-9 keys to select the color'], + [ + 'colorUnderCursorHint', + 'boolean', + 'Show the color under the cursor in the palette' + ], + [ + 'colorDoublePress', + 'boolean', + 'Double press 0-9 keys to select color without pressing shift' + ], + [ + 'bookmarkOwnCaptions', + 'boolean', + 'Automatically bookmark your own captions in case of dustcatchers' + ] + ]) + ) + theForm.appendChild( + addGroup('Miscellaneous', [ + [ + 'localeTimestamp', + 'boolean', + `Format timestamps as your system locale (${new Date().toLocaleString()})` + ], + [ + 'proxyImgur', + 'boolean', + 'Replace imgur.com links to filmot.com to load, in case your ISP blocks them' + ], + ['ajaxRetry', 'boolean', 'Retry failed AJAX requests'], + [ + 'autoplay', + 'boolean', + 'Automatically start replay when watching playback' + ], + ['autoBypassNSFW', 'boolean', 'Automatically bypass NSFW game warning'], + ['markStalePosts', 'boolean', 'Mark stale forum posts'], + [ + 'maxCommentHeight', + 'number', + 'Maximum comments and posts height until directly linked (px, 0 = no limit)' + ], + [ + 'useOldFont', + 'boolean', + 'Use old Nunito font (which is usually bolder and less wiggly)' + ], + ['useOldFontSize', 'boolean', 'Use old, smaller font size'], + ['markdownTools', 'boolean', 'Markdown tools for messages'], + [ + 'anbtDarkMode', + 'boolean', + "Switch between ANBT's and Drawception's dark mode" + ] + ]) + ) + theForm.appendChild( + addGroup('Advanced', [ + [ + 'newCanvasCSS', + 'longstr', + 'Custom CSS for new canvas (experimental, <a href="https://github.com/EnderDragonneau/Drawception-ANBT/tree/master/newcanvas_styles">get styles here</a>)' + ], + [ + 'forumHiddenUsers', + 'longstr', + 'Comma-separated list of user IDs whose forum posts are hidden' + ] + ]) + ) + $( + '<br><div class="control-group"><div class="controls"><input name="submit" type="submit" class="btn btn-primary settingsFormSubmit" value="Apply"> <b id="anbtSettingsOK" class="label label-theme_holiday" style="display:none">Saved!</b></div></div>' + ).forEach(node => theForm.appendChild(node)) + $('#main').insertAdjacentHTML('afterbegin', theForm.outerHTML) + $('.settingsForm').addEventListener( + 'submit', + form => updateScriptSettings(form) && false + ) + if ($('input[name="location"]')) + $('input[name="location"]').setAttribute('maxlength', '65') + } + + const betterPages = { + betterCreate, + betterForums, + betterGame, + betterPanel, + betterPlayer, + betterSettings + } + + const bold = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = new RegExp(`\\*\\*(${selection.replace(/\*/g, '')})\\*\\*`) + if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1') + else if (selectionStart > 0 && selectionEnd < length) { + if ( + value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex) + ) { + selectionStart-- + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + value.substring(selectionStart - 2, selectionEnd + 2).match(selRegex) + ) { + selectionStart -= 2 + selectionEnd += 2 + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else + selection = selection.match(/\*\*.+\*\*/g) + ? selection.replace(/\*\*/g, '') + : `**${selection.replace(/\n/g, '**\n**')}**` + } else { + if ( + !selectionStart && + value.substring(selectionStart, selectionEnd + 1).match(selRegex) + ) { + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + selectionEnd === length && + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else + selection = selection.match(/\*\*.+\*\*/g) + ? selection.replace(/\*\*/g, '') + : `**${selection.replace(/\n/g, '**\n**')}**` + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + } + + const code = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = /^ {4}(.*)/gm + if (selection.match(selRegex)) selection = selection.replace(/^ {4}/gm, '') + else if ( + selectionStart === 0 || + value.substring(selectionStart - 1, selectionEnd).match(/\n.*/gm) + ) { + if (selection.match(/^ {4}/gm)) + selection = selection.replace(/^ {4}/gm, '') + else + selection = `${ + selectionStart === 0 + ? '' + : value.substring(selectionStart - 1, selectionEnd).match(/^\n/) + ? '\n' + : '\n\n' + } ${selection.replace(/\n/g, '\n ')}` + } else + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/^\n/) + ? '\n' + : '\n\n' + } ${selection.replace(/\n^(.*)/gm, '\n $1')}${ + value.substring(selectionEnd, selectionEnd + 1).match(/\n/) ? '' : '\n' + }` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + } + + const heading = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = /^#+ .*/gm + if (selection.match(selRegex)) { + selection = selection.replace(/^# /gm, '') + if (selection.match(/^#{2,} /gm)) selection.replace(/(^#*)# /gm, '$1 ') + } else if ( + !selectionStart || + value.substring(selectionStart - 1, selectionEnd).match(/\n.*/gm) + ) + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) || + !selectionStart + ? '' + : '\n' + }###### ${selection.replace(/\n/g, '\n###### ')}` + else if ( + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart -= 4 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/(^#*)# /gm, '$1 ') + } else if ( + value.substring(selectionStart - 2, selectionEnd).match(selRegex) + ) { + selectionStart -= 5 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/(^#*)# /gm, '$1 ') + } else selection = `\n###### ${selection.replace(/\n/g, '\n###### ')}` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + } + + const highlighter = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = new RegExp(`\`(${selection.replace(/`/g, '')})\``) + if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1') + else if (selectionStart > 0 && selectionEnd < length) { + if ( + value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex) + ) { + selectionStart-- + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(/`.+`/g) + ? selection.replace(/`/g, '') + : `\`${selection.replace(/\n/g, '`\n`')}\`` + } + } else { + if ( + !selectionStart && + value.substring(selectionStart, selectionEnd + 1).match(selRegex) + ) { + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + selectionEnd === length && + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(/`.+`/g) + ? selection.replace(/`/g, '') + : `\`${selection.replace(/\n/g, '`\n`')}\`` + } + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + } + + const image = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = /!\[(.*)\]\((\S*)( ".*")?\)/ + if (selection.match(selRegex)) + textarea.value = + value.substring(0, selectionStart) + + selection.replace(selRegex, '$1 $2') + + value.substring(selectionEnd, length) + else { + let link = '' + if (!selection.match(/\[(.*)\]\((\S*)( ".*")?\)/)) { + link = selection.match(/https?:\/\/\S*/) || '' + selection = selection + .replace(link[0], '') + .replace(/ +/g, ' ') + .trim() + } else selection = '' + const divModal = $( + `<div class="v--modal-overlay scrollable overlay-fade-enter-active" style="opacity: 0" id="markdown"><div class="v--modal-background-click"><div class="v--modal-top-right"></div><div class="v--modal-box v--modal" style="top: 89px; left: 240px; width: 800px; height: auto;"><div style="padding: 30px;"><button type="button" class="close">Ă—</button><h4 class="clear-top">Markdown informations box</h4><hr><div><h4 class="clear-top">Text:</h4><input id="markdown-text" type="text" placeholder="Insert text here" class="form-control input-lg input-prompt"><h4>Link:</h4><input id="markdown-link" type="text" placeholder="Insert link here" class="form-control input-lg input-prompt"><h4>Hover message:</h4><input id="markdown-hover" type="text" placeholder="Message when hover the link (optional)" class="form-control input-lg input-prompt"></div><hr><p class="text-center clear-bot"><button type="button" id="markdown-done" class="btn btn-default">Done</button></p></div></div></div></div>` + ) + $('.navbar-header>div:last-child').append(divModal) + setTimeout(() => { + document.body.classList.add('v--modal-block-scroll') + $('#markdown').style.opacity = 1 + }, 1) + $('#markdown-text').value = selection ? selection : '' + $('#markdown-link').value = link ? link[0] : '' + $('.close').addEventListener('click', () => { + document.body.classList.remove('v--modal-block-scroll') + $('#markdown').outerHTML = '' + }) + $('#markdown-done').addEventListener('click', () => { + const tag = `![${$('#markdown-text').value}](${ + $('#markdown-link').value + }${ + $('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : '' + })` + selection = value.substring(selectionStart, selectionEnd) + textarea.value = + value.substring(0, selectionStart) + + (selection.match(/\[(.*)\]\((\S*)( ".*")?\)/) + ? selection.replace(/\[.*\]/, `[${tag}]`) + : tag) + + value.substring(selectionEnd, length) + document.body.classList.remove('v--modal-block-scroll') + $('#markdown').outerHTML = '' + }) + } + } + + const italic = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = new RegExp( + `\\*(?=\\S*${selection.replace( + /(.*)\*(.*)/g, + '' + )})((?:\\*\\*|\\\\[\\s\\S]|\\s+(?:\\\\[\\s\\S]|[^\\s\\*\\\\]|\\*\\*)|[^\\s\\*\\\\])+?)\\*(?!\\*)` + ) + const italicRegex = /\*(?=\S)((?:\*\*|\\[\s\S]|\s+(?:\\[\s\S]|[^\s\*\\]|\*\*)|[^\s\*\\])+?)\*(?!\*)/g + if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1') + else if (selectionStart > 0 && selectionEnd < length) { + if ( + value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex) + ) { + selectionStart-- + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(italicRegex) + ? selection.replace(italicRegex, '$1') + : `*${selection.replace(/\n/g, '*\n*')}*` + } + } else { + if ( + !selectionStart && + value.substring(selectionStart, selectionEnd + 1).match(selRegex) + ) { + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + selectionEnd === length && + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else + selection = selection.match(italicRegex) + ? selection.replace(italicRegex, '$1') + : `*${selection.replace(/\n/g, '*\n*')}*` + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + } + + const link = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = /^(?!!)\[(.*)\]\((\S*)( ".*")?\)/ + if (selection.match(selRegex)) + textarea.value = + value.substring(0, selectionStart) + + selection.replace(selRegex, '$1 $2') + + value.substring(selectionEnd, length) + else { + let imageLink = '' + if (!selection.match(/!\[(.*)\]\((\S*)( ".*")?\)/)) { + imageLink = selection.match(/https?:\/\/\S*/) || '' + selection = selection + .replace(imageLink[0], '') + .replace(/ +/g, ' ') + .trim() + } + const divModal = $( + `<div class="v--modal-overlay scrollable overlay-fade-enter-active" style="opacity: 0" id="markdown"><div class="v--modal-background-click"><div class="v--modal-top-right"></div><div class="v--modal-box v--modal" style="top: 89px; left: 240px; width: 800px; height: auto;"><div style="padding: 30px;"><button type="button" class="close">Ă—</button><h4 class="clear-top">Markdown informations box</h4><hr><div><h4 class="clear-top">Text:</h4><input id="markdown-text" type="text" placeholder="Insert text here" class="form-control input-lg input-prompt"><h4>Link:</h4><input id="markdown-link" type="text" placeholder="Insert link here" class="form-control input-lg input-prompt"><h4>Hover message:</h4><input id="markdown-hover" type="text" placeholder="Message when hover the link (optional)" class="form-control input-lg input-prompt"></div><hr><p class="text-center clear-bot"><button type="button" id="markdown-done" class="btn btn-default">Done</button></p></div></div></div></div>` + ) + $('.navbar-header>div:last-child').append(divModal) + setTimeout(() => { + document.body.classList.add('v--modal-block-scroll') + $('#markdown').style.opacity = 1 + }, 1) + $('#markdown-text').value = selection ? selection : '' + $('#markdown-link').value = imageLink ? imageLink[0] : '' + $('.close').addEventListener('click', () => { + document.body.classList.remove('v--modal-block-scroll') + $('#markdown').outerHTML = '' + }) + $('#markdown-done').addEventListener('click', () => { + selection = `[${$('#markdown-text').value}](${ + $('#markdown-link').value + }${ + $('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : '' + })` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + document.body.classList.remove('v--modal-block-scroll') + $('#markdown').outerHTML = '' + }) + } + } + + const listOl = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = /^( {3})*\d+\. (.*)/gm + if (selection.match(selRegex)) { + selection = selection.match(/^ {3}/) + ? selection.replace(/^ {3}/gm, '') + : selection.replace(/^\d+\. /gm, '') + } else if ( + !selectionStart || + value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/) + ) { + let countOl = 0 + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) || + !selectionStart + ? '' + : '\n' + }0. ${selection.replace(/\n/g, () => { + countOl++ + return `\n${countOl}. ` + })}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + } else if ( + value + .substring(selectionStart - 4, selectionEnd) + .match(/( {3})*\d+\. (.*)/) + ) { + selectionStart -= 4 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/( {3})*(\d+\.) /g, ' $1$2 ') + } else if ( + value + .substring(selectionStart - 5, selectionEnd) + .match(/( {3})*\d+\. (.*)/) + ) { + selectionStart -= 5 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/( {3})*(\d+\.) /g, ' $1$2 ') + } else { + let countOl = 0 + selection = `\n0. ${selection.replace(/\n/g, () => { + countOl++ + return `\n${countOl}. ` + })}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) || + selectionEnd === length + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + } + + const listUl = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = /^( {3})*- (.*)/ + if (selection.match(selRegex)) + selection = selection.match(/^ {3}/) + ? selection.replace(/^ {3}/gm, '') + : selection.replace(/^- /gm, '') + else if ( + !selectionStart || + value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/) + ) + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) || + !selectionStart + ? '' + : '\n' + }- ${selection.replace(/\n/g, '\n- ')}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + else if ( + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(/( {3})*- /g, '$1 - ') + } else if ( + value.substring(selectionStart - 2, selectionEnd).match(selRegex) + ) { + selectionStart -= 2 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/( {3})*- /g, '$1 - ') + } else + selection = `\n- ${selection.replace(/\n/g, '\n- ')}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) || + selectionEnd === length + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + } + + const quoteRight = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = /^>+\s.*/gm + if (selection.match(selRegex)) + selection = selection.match(/^> /gm) + ? selection.replace(/^> /gm, '') + : selection.replace(/(^>*)> /gm, '$1 ') + else if ( + !selectionStart || + value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/) + ) + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) || + !selectionStart + ? '' + : '\n' + }> ${selection.replace(/\n/g, '\n> ')}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + else if ( + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(/(^>*)\s/gm, '$1> ') + } else if ( + value.substring(selectionStart - 2, selectionEnd).match(selRegex) + ) { + selectionStart -= 2 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/(^>*)\s/gm, '$1> ') + } else + selection = `\n> ${selection.replace(/\n/g, '\n> ')}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) || + selectionEnd === length + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + } + + const strikethrough = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) => { + const selRegex = /~~((.*\W?)*)~~/ + if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1') + else if (selectionStart > 0 && selectionEnd < length) { + if (selection.match(selRegex)) selection.replace(selRegex, '$1') + else if ( + value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex) + ) { + selectionStart-- + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + value.substring(selectionStart - 2, selectionEnd + 2).match(selRegex) + ) { + selectionStart -= 2 + selectionEnd += 2 + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(/~~.+~~/g) + ? selection.replace(/~~/g, '') + : `~~${selection}~~` + } + } else { + if ( + !selectionStart && + value.substring(selectionStart, selectionEnd + 1).match(selRegex) + ) { + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + selectionEnd === length && + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(/~~.+~~/g) + ? selection.replace(/~~/g, '') + : `~~${selection}~~` + } + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + } + + const markdown = { + bold: { + title: 'bold text', + replaceFunc: bold + }, + italic: { + title: 'italic text', + replaceFunc: italic + }, + heading: { + title: 'enlarges/reduces the text', + replaceFunc: heading + }, + strikethrough: { + title: 'strikethrough text', + replaceFunc: strikethrough + }, + highlighter: { + title: 'highlighted text', + replaceFunc: highlighter + }, + 'list-ul': { + title: 'unordered list', + replaceFunc: listUl + }, + 'list-ol': { + title: 'ordered list', + replaceFunc: listOl + }, + 'quote-right': { + title: 'quote', + replaceFunc: quoteRight + }, + code: { + title: 'block of code', + replaceFunc: code + }, + link: { + title: 'insert link', + replaceFunc: link + }, + image: { + title: 'insert image', + replaceFunc: image + } + } + + const getSelectedText = event => { + const textarea = $('#input-comment') + const { value, selectionStart, selectionEnd } = textarea + const { length } = value + const selection = value.substring(selectionStart, selectionEnd) + markdown[`${event.currentTarget.id}`].replaceFunc( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) + } + + const addMarkdownTools = () => { + const textarea = $('#input-comment') + if (!textarea) return + const markdownDiv = $('<div id="markdown-editor"></div>') + Object.keys(markdown).forEach(toolName => + markdownDiv.appendChild( + $( + `<div id="${toolName}" class="test-markdown fas fa-${toolName} btn btn-default" title="${markdown[toolName].title}"></div>` + ) + ) + ) + textarea.insertAdjacentHTML('beforebegin', markdownDiv.outerHTML) + ;[...$('#markdown-editor').children].forEach(children => + children.addEventListener('click', getSelectedText) + ) + } + + const getNotifications = () => { + if (!window.notificationsOpened) { + $('#user-notify-list').innerHTML = + '<img src="/img/loading.gif" alt="Loading...."/>' + const xhr = new XMLHttpRequest() + xhr.open('GET', '/notification/view/') + xhr.onload = () => { + if (xhr.status === 200) { + $('#user-notify-list').innerHTML = xhr.responseText + $('#user-notify-count').textContent = '0' + window.notificationsOpened = true + } else { + $('#user-notify-list').innerHTML = xhr.responseText + window.notificationsOpened = true + } + } + } + } + + const pageEnhancements = () => { + loadScriptSettings() + if (typeof DrawceptionPlay === 'undefined') return + if (document.getElementById('newcanvasyo')) return + try { + const tmpuserlink = $('.player-dropdown a[href^="/player/"]') + const username = tmpuserlink.querySelector('strong').textContent + const userid = tmpuserlink.href.match(/\/player\/(\d+)\//)[1] + localStorage.setItem('gpe_lastSeenName', username) + localStorage.setItem('gpe_lastSeenId', userid) + } catch (e) {} + const currentPage = location.href.match(/drawception\.com\/([^/]+)/) + if (currentPage) { + const page = currentPage[1] + const functionName = `better${page.replace( + page[0], + page[0].toUpperCase() + )}` + if (betterPages[functionName]) betterPages[functionName]() + } + addStyle( + '.panel-user {width: auto} .panel-details img.loading {display: none}' + + '.gpe-wide, .gpe-wide-block {display: none}' + + '.gpe-btn {padding: 5px 8px; height: 28px}' + + '.gpe-spacer {margin-right: 7px; float:left}' + + '@media (min-width:992px) {.navbar-toggle,.btn-menu-player {display: none} .gpe-wide {display: inline} .gpe-wide-block {display: block}}' + + '@media (min-width:1200px) {.gpe-btn {padding: 5px 16px;} .gpe-spacer {margin-right: 20px;} .panel-number {left: -30px}}' + + '#anbtver {font-size: 10px; position:absolute; opacity:0.3; right:10px; top: 0;}' + + '.anbt_paneldel {position:absolute; padding:1px 6px; color:#FFF; background:#d9534f; text-decoration: none !important; right: 18px; border-radius: 5px}' + + '.anbt_paneldel:hover {background:#d2322d}' + + '.anbt_favpanel {top: 20px; font-weight: normal; padding: 0 2px}' + + '.anbt_favpanel:hover {color: #d9534f; cursor:pointer}' + + '.anbt_favedpanel {color: #d9534f; border-color: #d9534f}' + + '.anbt_replaypanel {top: 55px; font-weight: normal; padding: 0 8px}' + + '.anbt_replaypanel:hover {color: #8af; text-decoration: none}' + + ".anbt_owncaption:before {content: ''; display: inline-block; background: #5C5; border: 1px solid #080; width: 10px; height: 10px; border-radius: 10px; margin-right: 10px;}" + + '.gamepanel, .thumbpanel, .comment-body {word-wrap: break-word}' + + '.comment-body img {max-width: 100%}' + + '.forum-thread.anbt_hidden {display: none}' + + '.anbt_showt .forum-thread.anbt_hidden {display: block; opacity: 0.6}' + + ".anbt_unhidet:after {content: ' threads hidden. Show'}" + + ".anbt_showt .anbt_unhidet:after {content: ' threads hidden. Hide'}" + + ".anbt_hft:after {content: '[hide]'}" + + '.anbt_hft, .anbt_unhidet {padding-left: 0.4em; cursor:pointer}' + + ".forum-thread.anbt_hidden .anbt_hft:after {content: '[show]'}" + + '.anbt_threadtitle {margin: 0 0 10px}' + + '.avatar {box-sizing: content-box}' + + '.pagination {margin: 0px}' + + '#nav-drag {position: fixed; width: 100%; z-index: 2000}' + + '#header-bar-container {position: relative; width: 100%; top: 6rem}' + + '.wrapper {position: relative; top: 6rem}' + + 'footer {position: relative; top: 6rem}' + + '.option span:first-child {display: flex; flex-direction: row; justify-content: space-between}' + + '.grid-settings div[class^="grid-"] label {display: inline-flex}' + + 'input[type="checkbox"], input[type="radio"] {margin:4px 4px 0 0}' + + '@-moz-document url-prefix() {input[type="checkbox"], input[type="radio"] {margin:0 4px 0 0}}' + + '.tooltip {z-index: 3000;}' + ) + if (options.maxCommentHeight) { + const maxHeight = options.maxCommentHeight + addStyle( + `.comment-holder[id]:not(:target) .comment-body {overflow-y: hidden; max-height: ${maxHeight}px; position:relative}.comment-holder[id]:not(:target) .comment-body:before{content: 'Click to read more'; position:absolute; width:100%; height:50px; left:0; top:${maxHeight - + 50}px;text-align: center; font-weight: bold; color: #fff; text-shadow: 0 0 2px #000; padding-top: 20px; background:linear-gradient(transparent, rgba(0,0,0,0.4))}` + ) + $('.comment-body', true).forEach(comment => + comment.addEventListener('click', () => { + if ( + comment.clientHeight > maxHeight - 50 && + location.hash.indexOf(comment) === -1 + ) + location.hash = `#${comment.parentNode.parentNode.id}` + }) + ) + } + if (options.useOldFontSize) document.body.style.fontSize = '15px' + if (options.useOldFont) { + const nunito = $("link[href*='Nunito']") + nunito.parentNode.removeChild(nunito) + addStyle( + "@import url('https://fonts.googleapis.com/css?family=Nunito&display=swap')" + ) + } + if (options.anbtDarkMode) { + if (document.body.classList.contains('theme-night')) { + document.body.classList.remove('theme-night') + setCookie('theme-night') + } + } + if (options.markdownTools) addMarkdownTools() + if (options.newCanvas) { + const inSandbox = location.href.match(/drawception\.com\/sandbox\/#?(.*)/) + const inPlay = location.href.match( + /drawception\.com\/(:?contests\/)?play\/(.*)/ + ) + const hasCanvas = document.getElementById('canvas-holder') + const hasCanvasOrGameForm = document.querySelector('.playtimer') + const captionContest = + location.href.match(/contests\/play\//) && !hasCanvas + if (!captionContest && (inSandbox || (inPlay && hasCanvasOrGameForm))) { + setTimeout(() => setupNewCanvas(inSandbox, location.href), 1) + return + } + $('a[href^="/sandbox/"]', true).forEach(sandboxButton => + sandboxButton.addEventListener('click', event => { + if (event.which === 2) return + event.preventDefault() + setupNewCanvas(true, event.currentTarget.href) + }) + ) + $('a[href="/play/"]', true).forEach(playButton => + playButton.addEventListener('click', event => { + if (event.which === 2) return + event.preventDefault() + setupNewCanvas(false, event.currentTarget.href) + }) + ) + } + if ($('.navbar-toggle')) { + const navbarToggle = $('.navbar-toggle').parentNode + const navbarButtonsList = [ + '<span class="gpe-wide gpe-spacer"></span>', + '<a href="/sandbox/" title="Sandbox" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item" style="background:#5A5"><span class="fas fa-edit" style="color:#BFB" /></a>', + '<a href="/browse/all-games/" title="Browse Games" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-folder-open" /></a>', + '<a href="/contests/" title="Contests" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-trophy" /></a>', + '<a href="#" title="Toggle light" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item toggleLight" style="background:#AA5"><span class="fas fa-eye" style="color:#FFB" /></a>', + '<a href="/leaderboard/" title="Leaderboards" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-fire" /></a>', + '<a href="/faq/" title="FAQ" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-question-circle " /></a>', + '<a href="/forums/" title="Forums" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item" style="background:#55A"><span class="fas fa-comments" style="color:#BBF" /></a>', + '<a href="/search/" title="Search" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-search" /></a>', + '<a href="/dashboard/" title="Dashboard" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-bell" /></a>', + '<a href="/settings/" id="menusettings" title="Settings" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-cog" /></a>', + '<a href="/logout" title="Log Out" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item" style="background:#A55"><span class="fas fa-sign-out-alt" style="color:#FBB" /></a>' + ] + navbarButtonsList.forEach(button => navbarToggle.appendChild($(button))) + $('#main-menu').insertAdjacentHTML( + 'afterbegin', + '<a href="#" class="list-group-item toggleLight"><span class="fas fa-eye"></span> Toggle light</a>' + ) + } + const menuPlayer = $('.btn-menu-player') + if (menuPlayer) { + const userlink = $('.player-dropdown a[href^="/player/"]').href + const useravatar = $('.btn-menu-player').innerHTML + const element = $( + `<a href="${userlink}" title="View Profile" class="gpe-wide-block navbar-btn navbar-user-item" style="margin-top:8px">${useravatar}</a>` + ) + menuPlayer.parentNode.appendChild(element) + } + const num = + $('#user-notify-count') && $('#user-notify-count').textContent.trim() + addStyle( + `#user-notify-list .list-group .list-group-item .fas {color: #888}#user-notify-list .list-group .list-group-item:nth-child(-n+${num}) .fas {color: #2F5}a.wrong-order {color: #F99} div.comment-holder:target {background-color: #DFD}.comment-new a.text-muted:last-child:after {content: 'New'; color: #2F5; font-weight: bold; background-color: #183; border-radius: 9px; display: inline-block; padding: 0px 6px; margin-left: 10px;}` + ) + window.getNotifications = getNotifications + let versionDisplay = `ANBT v${versions.scriptVersion}` + try { + const appver = $('script[src^="/build/app"]').src.match(/(\w+)\.js$/)[1] + const runtimever = $('script[src^="/build/runtime"]').src.match( + /(\w+)\.js$/ + )[1] + versionDisplay += ` | app ${appver}` + if (appver !== versions.siteVersion) versionDisplay += '*' + versionDisplay += ` | runtime ${runtimever}` + if (runtimever !== versions.runtimeVersion) versionDisplay += '*!!!' + } catch (e) {} + const wrapperSection = $('.wrapper') + if (wrapperSection) + wrapperSection.appendChild($(`<div id="anbtver">${versionDisplay}</div>`)) + const linkList = [ + '<li><a href="/forums/-/11830/-/">ANBT script</a></li>', + '<li><a href="http://drawception.wikia.com/">Wiki</a></li>', + '<li><a href="http://chat.grompe.org.ru/#drawception">Chat</a> (<a href="https://discord.gg/CNd5KTJ">Discord</a>)</li>' + ] + $('.footer-main .list-unstyled').forEach((list, index) => + list.appendChild($(linkList[index])) + ) + } + + const wrapper = () => { + window.options = options + const mark = document.createElement('b') + mark.id = '_anbt_' + mark.style.display = 'none' + document.body.appendChild(mark) + if (!window.DrawceptionPlay) { + const loader = setInterval(() => { + if (!window.DrawceptionPlay) return + pageEnhancements() + clearInterval(loader) + }, 100) + } else pageEnhancements() + } + + addDarkCSS() + setDarkMode() + if (document && document.body) { + if (!document.getElementById('_anbt_')) wrapper() + if (window.opera && !getLocalStorageItem('gpe_operaWarning', 0)) { + const anbtTitle = document.createElement('h2') + anbtTitle.innerHTML = + 'ANBT speaking:<br/>Rename your script file so it doesn\'t contain ".user." part for smoother loading!<br/>This warning is only shown once.' + const mainSection = document.getElementById('main') + mainSection.insertBefore(anbtTitle, mainSection.firstChild) + localStorage.setItem('gpe_operaWarning', 1) + } + } + document.addEventListener( + 'DOMContentLoaded', + () => { + if (!document.getElementById('_anbt_')) wrapper() + }, + false + ) +})() diff --git a/build/index.html b/build/index.html new file mode 100644 index 0000000..9fe71c5 --- /dev/null +++ b/build/index.html @@ -0,0 +1,174 @@ +<!doctype html> +<html lang="en"> + +<head> + <link rel="author" href="http://grompe.org.ru/"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <link href="https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz" rel="stylesheet" type="text/css"> + <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css"> + <title>New Canvas - Drawception + + + + +
+
+
+ + New Canvas v53 +
+ +
+
+
Fetching your Drawception game...
+
+
+
+
00:00 + +
+
+
Normal
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
L
+
R
+
+
+
+ + + + +
+ + + + +
+
+ +
Time's up!

Submit now or miss the game!
+
+
+ +
+ +
46
+
+
+
+
+
+
+
+ Sandbox + Public safe for work game +
+
+ + + + + + + + +
+ + +
Guidelines +
    +
  • Simple drawings are welcome! Just give it your best shot
  • +
  • If the phrase is too difficult, use the skip button
  • +
  • Don't draw text - always draw a picture
  • +
  • Explicit (NSFW) drawings will get you banned
  • +
+
+
Tips +
    +
  • No explicit language allowed
  • +
  • Keep descriptions short and simple!
  • +
  • Avoid unnecessary detail, just describe the main subject
  • +
  • No metacommentary (e.g., don't comment on drawing quality)
  • +
  • No idea? Use the skip button
  • +
+
+
Sandbox +
    +
  • Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
  • +
  • Saved drawings will contain your drawing process in PNG format.
  • +
  • After loading a drawing you can continue to draw or watch its playback.
  • +
  • Note that editing the PNG with playback with other programs will likely destroy the playback data.
  • +
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/build/newcanvas/index.html b/build/newcanvas/index.html new file mode 100644 index 0000000..3140894 --- /dev/null +++ b/build/newcanvas/index.html @@ -0,0 +1,170 @@ + + + + + + + + + + New Canvas - Drawception + + + + +
+
+
+ + New Canvas v53 +
+ +
+
+
Fetching your Drawception game...
+
+
+
+
00:00 + +
+
+
Normal
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
L
+
R
+
+
+
+ + + + +
+ + + + +
+
+ +
Time's up!

Submit now or miss the game!
+
+
+ +
+ +
46
+
+
+
+
+
+
+
+ Sandbox + Public safe for work game +
+
+ + + + + + + + +
+ + +
Guidelines +
    +
  • Simple drawings are welcome! Just give it your best shot
  • +
  • If the phrase is too difficult, use the skip button
  • +
  • Don't draw text - always draw a picture
  • +
  • Explicit (NSFW) drawings will get you banned
  • +
+
+
Tips +
    +
  • No explicit language allowed
  • +
  • Keep descriptions short and simple!
  • +
  • Avoid unnecessary detail, just describe the main subject
  • +
  • No metacommentary (e.g., don't comment on drawing quality)
  • +
  • No idea? Use the skip button
  • +
+
+
Sandbox +
    +
  • Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
  • +
  • Saved drawings will contain your drawing process in PNG format.
  • +
  • After loading a drawing you can continue to draw or watch its playback.
  • +
  • Note that editing the PNG with playback with other programs will likely destroy the playback data.
  • +
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/build/newcanvas/pako.js b/build/newcanvas/pako.js new file mode 100644 index 0000000..95f74ab --- /dev/null +++ b/build/newcanvas/pako.js @@ -0,0 +1 @@ +!function(){"use strict";var t,e=(function(t,e){var a="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;function i(t,e){return Object.prototype.hasOwnProperty.call(t,e)}e.assign=function(t){for(var e=Array.prototype.slice.call(arguments,1);e.length;){var a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(var n in a)i(a,n)&&(t[n]=a[n])}}return t},e.shrinkBuf=function(t,e){return t.length===e?t:t.subarray?t.subarray(0,e):(t.length=e,t)};var n={arraySet:function(t,e,a,i,n){if(e.subarray&&t.subarray)t.set(e.subarray(a,a+i),n);else for(var r=0;r=0;)t[e]=0}var o=0,h=1,l=2,d=29,_=256,f=_+1+d,u=30,c=19,w=2*f+1,g=15,b=16,m=7,p=256,v=16,k=17,y=18,x=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],z=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],B=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],S=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],E=new Array(2*(f+2));s(E);var A=new Array(2*u);s(A);var Z=new Array(512);s(Z);var R=new Array(256);s(R);var C=new Array(d);s(C);var N,I,O,D=new Array(u);function T(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}function U(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}function F(t){return t<256?Z[t]:Z[256+(t>>>7)]}function L(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function H(t,e,a){t.bi_valid>b-a?(t.bi_buf|=e<>b-t.bi_valid,t.bi_valid+=a-b):(t.bi_buf|=e<>>=1,a<<=1}while(--e>0);return a>>>1}function M(t,e,a){var i,n,r=new Array(g+1),s=0;for(i=1;i<=g;i++)r[i]=s=s+a[i-1]<<1;for(n=0;n<=e;n++){var o=t[2*n+1];0!==o&&(t[2*n]=K(r[o]++,o))}}function P(t){var e;for(e=0;e8?L(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0}function G(t,e,a,i){var n=2*e,r=2*a;return t[n]>1;a>=1;a--)X(t,r,a);n=h;do{a=t.heap[1],t.heap[1]=t.heap[t.heap_len--],X(t,r,1),i=t.heap[1],t.heap[--t.heap_max]=a,t.heap[--t.heap_max]=i,r[2*n]=r[2*a]+r[2*i],t.depth[n]=(t.depth[a]>=t.depth[i]?t.depth[a]:t.depth[i])+1,r[2*a+1]=r[2*i+1]=n,t.heap[1]=n++,X(t,r,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],function(t,e){var a,i,n,r,s,o,h=e.dyn_tree,l=e.max_code,d=e.stat_desc.static_tree,_=e.stat_desc.has_stree,f=e.stat_desc.extra_bits,u=e.stat_desc.extra_base,c=e.stat_desc.max_length,b=0;for(r=0;r<=g;r++)t.bl_count[r]=0;for(h[2*t.heap[t.heap_max]+1]=0,a=t.heap_max+1;ac&&(r=c,b++),h[2*i+1]=r,i>l||(t.bl_count[r]++,s=0,i>=u&&(s=f[i-u]),o=h[2*i],t.opt_len+=o*(r+s),_&&(t.static_len+=o*(d[2*i+1]+s)));if(0!==b){do{for(r=c-1;0===t.bl_count[r];)r--;t.bl_count[r]--,t.bl_count[r+1]+=2,t.bl_count[c]--,b-=2}while(b>0);for(r=c;0!==r;r--)for(i=t.bl_count[r];0!==i;)(n=t.heap[--a])>l||(h[2*n+1]!==r&&(t.opt_len+=(r-h[2*n+1])*h[2*n],h[2*n+1]=r),i--)}}(t,e),M(r,l,t.bl_count)}function J(t,e,a){var i,n,r=-1,s=e[1],o=0,h=7,l=4;for(0===s&&(h=138,l=3),e[2*(a+1)+1]=65535,i=0;i<=a;i++)n=s,s=e[2*(i+1)+1],++o>=7;i0?(t.strm.data_type===r&&(t.strm.data_type=function(t){var e,a=4093624447;for(e=0;e<=31;e++,a>>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return i;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return n;for(e=32;e<_;e++)if(0!==t.dyn_ltree[2*e])return n;return i}(t)),q(t,t.l_desc),q(t,t.d_desc),u=function(t){var e;for(J(t,t.dyn_ltree,t.l_desc.max_code),J(t,t.dyn_dtree,t.d_desc.max_code),q(t,t.bl_desc),e=c-1;e>=3&&0===t.bl_tree[2*S[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(t),d=t.opt_len+3+7>>>3,(f=t.static_len+3+7>>>3)<=d&&(d=f)):d=f=s+5,s+4<=d&&-1!==e?$(t,e,s,o):t.strategy===a||f===d?(H(t,(h<<1)+(o?1:0),3),W(t,E,A)):(H(t,(l<<1)+(o?1:0),3),function(t,e,a,i){var n;for(H(t,e-257,5),H(t,a-1,5),H(t,i-4,4),n=0;n>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&a,t.last_lit++,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(R[a]+_+1)]++,t.dyn_dtree[2*F(e)]++),t.last_lit===t.lit_bufsize-1},_tr_align:function(t){H(t,h<<1,3),j(t,p,E),function(t){16===t.bi_valid?(L(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}(t)}};var et=function(t,e,a,i){for(var n=65535&t|0,r=t>>>16&65535|0,s=0;0!==a;){a-=s=a>2e3?2e3:a;do{r=r+(n=n+e[i++]|0)|0}while(--s);n%=65521,r%=65521}return n|r<<16|0};var at=function(){for(var t,e=[],a=0;a<256;a++){t=a;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e}();var it,nt=function(t,e,a,i){var n=at,r=i+a;t^=-1;for(var s=i;s>>8^n[255&(t^e[s])];return-1^t},rt={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"},st=0,ot=1,ht=3,lt=4,dt=5,_t=0,ft=1,ut=-2,ct=-3,wt=-5,gt=-1,bt=1,mt=2,pt=3,vt=4,kt=0,yt=2,xt=8,zt=9,Bt=15,St=8,Et=286,At=30,Zt=19,Rt=2*Et+1,Ct=15,Nt=3,It=258,Ot=It+Nt+1,Dt=32,Tt=42,Ut=69,Ft=73,Lt=91,Ht=103,jt=113,Kt=666,Mt=1,Pt=2,Yt=3,Gt=4,Xt=3;function Wt(t,e){return t.msg=rt[e],e}function qt(t){return(t<<1)-(t>4?9:0)}function Jt(t){for(var e=t.length;--e>=0;)t[e]=0}function Qt(t){var a=t.state,i=a.pending;i>t.avail_out&&(i=t.avail_out),0!==i&&(e.arraySet(t.output,a.pending_buf,a.pending_out,i,t.next_out),t.next_out+=i,a.pending_out+=i,t.total_out+=i,t.avail_out-=i,a.pending-=i,0===a.pending&&(a.pending_out=0))}function Vt(t,e){tt._tr_flush_block(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,Qt(t.strm)}function $t(t,e){t.pending_buf[t.pending++]=e}function te(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function ee(t,e){var a,i,n=t.max_chain_length,r=t.strstart,s=t.prev_length,o=t.nice_match,h=t.strstart>t.w_size-Ot?t.strstart-(t.w_size-Ot):0,l=t.window,d=t.w_mask,_=t.prev,f=t.strstart+It,u=l[r+s-1],c=l[r+s];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do{if(l[(a=e)+s]===c&&l[a+s-1]===u&&l[a]===l[r]&&l[++a]===l[r+1]){r+=2,a++;do{}while(l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&rs){if(t.match_start=e,s=i,i>=o)break;u=l[r+s-1],c=l[r+s]}}}while((e=_[e&d])>h&&0!=--n);return s<=t.lookahead?s:t.lookahead}function ae(t){var a,i,n,r,s,o,h,l,d,_,f=t.w_size;do{if(r=t.window_size-t.lookahead-t.strstart,t.strstart>=f+(f-Ot)){e.arraySet(t.window,t.window,f,f,0),t.match_start-=f,t.strstart-=f,t.block_start-=f,a=i=t.hash_size;do{n=t.head[--a],t.head[a]=n>=f?n-f:0}while(--i);a=i=f;do{n=t.prev[--a],t.prev[a]=n>=f?n-f:0}while(--i);r+=f}if(0===t.strm.avail_in)break;if(o=t.strm,h=t.window,l=t.strstart+t.lookahead,d=r,_=void 0,(_=o.avail_in)>d&&(_=d),i=0===_?0:(o.avail_in-=_,e.arraySet(h,o.input,o.next_in,_,l),1===o.state.wrap?o.adler=et(o.adler,h,_,l):2===o.state.wrap&&(o.adler=nt(o.adler,h,_,l)),o.next_in+=_,o.total_in+=_,_),t.lookahead+=i,t.lookahead+t.insert>=Nt)for(s=t.strstart-t.insert,t.ins_h=t.window[s],t.ins_h=(t.ins_h<=Nt&&(t.ins_h=(t.ins_h<=Nt)if(i=tt._tr_tally(t,t.strstart-t.match_start,t.match_length-Nt),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=Nt){t.match_length--;do{t.strstart++,t.ins_h=(t.ins_h<=Nt&&(t.ins_h=(t.ins_h<4096)&&(t.match_length=Nt-1)),t.prev_length>=Nt&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-Nt,i=tt._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-Nt),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=n&&(t.ins_h=(t.ins_h<15&&(o=2,n-=16),r<1||r>zt||i!==xt||n<8||n>15||a<0||a>9||s<0||s>vt)return Wt(t,ut);8===n&&(n=9);var h=new se;return t.state=h,h.strm=t,h.wrap=o,h.gzhead=null,h.w_bits=n,h.w_size=1<t.pending_buf_size-5&&(a=t.pending_buf_size-5);;){if(t.lookahead<=1){if(ae(t),0===t.lookahead&&e===st)return Mt;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var i=t.block_start+a;if((0===t.strstart||t.strstart>=i)&&(t.lookahead=t.strstart-i,t.strstart=i,Vt(t,!1),0===t.strm.avail_out))return Mt;if(t.strstart-t.block_start>=t.w_size-Ot&&(Vt(t,!1),0===t.strm.avail_out))return Mt}return t.insert=0,e===lt?(Vt(t,!0),0===t.strm.avail_out?Yt:Gt):(t.strstart>t.block_start&&(Vt(t,!1),t.strm.avail_out),Mt)}),new re(4,4,8,4,ie),new re(4,5,16,8,ie),new re(4,6,32,32,ie),new re(4,4,16,16,ne),new re(8,16,32,32,ne),new re(8,16,128,128,ne),new re(8,32,128,256,ne),new re(32,128,258,1024,ne),new re(32,258,258,4096,ne)];var de={deflateInit:function(t,e){return le(t,e,xt,Bt,St,kt)},deflateInit2:le,deflateReset:he,deflateResetKeep:oe,deflateSetHeader:function(t,e){return t&&t.state?2!==t.state.wrap?ut:(t.state.gzhead=e,_t):ut},deflate:function(t,e){var a,i,n,r;if(!t||!t.state||e>dt||e<0)return t?Wt(t,ut):ut;if(i=t.state,!t.output||!t.input&&0!==t.avail_in||i.status===Kt&&e!==lt)return Wt(t,0===t.avail_out?wt:ut);if(i.strm=t,a=i.last_flush,i.last_flush=e,i.status===Tt)if(2===i.wrap)t.adler=0,$t(i,31),$t(i,139),$t(i,8),i.gzhead?($t(i,(i.gzhead.text?1:0)+(i.gzhead.hcrc?2:0)+(i.gzhead.extra?4:0)+(i.gzhead.name?8:0)+(i.gzhead.comment?16:0)),$t(i,255&i.gzhead.time),$t(i,i.gzhead.time>>8&255),$t(i,i.gzhead.time>>16&255),$t(i,i.gzhead.time>>24&255),$t(i,9===i.level?2:i.strategy>=mt||i.level<2?4:0),$t(i,255&i.gzhead.os),i.gzhead.extra&&i.gzhead.extra.length&&($t(i,255&i.gzhead.extra.length),$t(i,i.gzhead.extra.length>>8&255)),i.gzhead.hcrc&&(t.adler=nt(t.adler,i.pending_buf,i.pending,0)),i.gzindex=0,i.status=Ut):($t(i,0),$t(i,0),$t(i,0),$t(i,0),$t(i,0),$t(i,9===i.level?2:i.strategy>=mt||i.level<2?4:0),$t(i,Xt),i.status=jt);else{var s=xt+(i.w_bits-8<<4)<<8;s|=(i.strategy>=mt||i.level<2?0:i.level<6?1:6===i.level?2:3)<<6,0!==i.strstart&&(s|=Dt),s+=31-s%31,i.status=jt,te(i,s),0!==i.strstart&&(te(i,t.adler>>>16),te(i,65535&t.adler)),t.adler=1}if(i.status===Ut)if(i.gzhead.extra){for(n=i.pending;i.gzindex<(65535&i.gzhead.extra.length)&&(i.pending!==i.pending_buf_size||(i.gzhead.hcrc&&i.pending>n&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),Qt(t),n=i.pending,i.pending!==i.pending_buf_size));)$t(i,255&i.gzhead.extra[i.gzindex]),i.gzindex++;i.gzhead.hcrc&&i.pending>n&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),i.gzindex===i.gzhead.extra.length&&(i.gzindex=0,i.status=Ft)}else i.status=Ft;if(i.status===Ft)if(i.gzhead.name){n=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>n&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),Qt(t),n=i.pending,i.pending===i.pending_buf_size)){r=1;break}r=i.gzindexn&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),0===r&&(i.gzindex=0,i.status=Lt)}else i.status=Lt;if(i.status===Lt)if(i.gzhead.comment){n=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>n&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),Qt(t),n=i.pending,i.pending===i.pending_buf_size)){r=1;break}r=i.gzindexn&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),0===r&&(i.status=Ht)}else i.status=Ht;if(i.status===Ht&&(i.gzhead.hcrc?(i.pending+2>i.pending_buf_size&&Qt(t),i.pending+2<=i.pending_buf_size&&($t(i,255&t.adler),$t(i,t.adler>>8&255),t.adler=0,i.status=jt)):i.status=jt),0!==i.pending){if(Qt(t),0===t.avail_out)return i.last_flush=-1,_t}else if(0===t.avail_in&&qt(e)<=qt(a)&&e!==lt)return Wt(t,wt);if(i.status===Kt&&0!==t.avail_in)return Wt(t,wt);if(0!==t.avail_in||0!==i.lookahead||e!==st&&i.status!==Kt){var o=i.strategy===mt?function(t,e){for(var a;;){if(0===t.lookahead&&(ae(t),0===t.lookahead)){if(e===st)return Mt;break}if(t.match_length=0,a=tt._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(Vt(t,!1),0===t.strm.avail_out))return Mt}return t.insert=0,e===lt?(Vt(t,!0),0===t.strm.avail_out?Yt:Gt):t.last_lit&&(Vt(t,!1),0===t.strm.avail_out)?Mt:Pt}(i,e):i.strategy===pt?function(t,e){for(var a,i,n,r,s=t.window;;){if(t.lookahead<=It){if(ae(t),t.lookahead<=It&&e===st)return Mt;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=Nt&&t.strstart>0&&(i=s[n=t.strstart-1])===s[++n]&&i===s[++n]&&i===s[++n]){r=t.strstart+It;do{}while(i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&nt.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=Nt?(a=tt._tr_tally(t,1,t.match_length-Nt),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=tt._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(Vt(t,!1),0===t.strm.avail_out))return Mt}return t.insert=0,e===lt?(Vt(t,!0),0===t.strm.avail_out?Yt:Gt):t.last_lit&&(Vt(t,!1),0===t.strm.avail_out)?Mt:Pt}(i,e):it[i.level].func(i,e);if(o!==Yt&&o!==Gt||(i.status=Kt),o===Mt||o===Yt)return 0===t.avail_out&&(i.last_flush=-1),_t;if(o===Pt&&(e===ot?tt._tr_align(i):e!==dt&&(tt._tr_stored_block(i,0,0,!1),e===ht&&(Jt(i.head),0===i.lookahead&&(i.strstart=0,i.block_start=0,i.insert=0))),Qt(t),0===t.avail_out))return i.last_flush=-1,_t}return e!==lt?_t:i.wrap<=0?ft:(2===i.wrap?($t(i,255&t.adler),$t(i,t.adler>>8&255),$t(i,t.adler>>16&255),$t(i,t.adler>>24&255),$t(i,255&t.total_in),$t(i,t.total_in>>8&255),$t(i,t.total_in>>16&255),$t(i,t.total_in>>24&255)):(te(i,t.adler>>>16),te(i,65535&t.adler)),Qt(t),i.wrap>0&&(i.wrap=-i.wrap),0!==i.pending?_t:ft)},deflateEnd:function(t){var e;return t&&t.state?(e=t.state.status)!==Tt&&e!==Ut&&e!==Ft&&e!==Lt&&e!==Ht&&e!==jt&&e!==Kt?Wt(t,ut):(t.state=null,e===jt?Wt(t,ct):_t):ut},deflateSetDictionary:function(t,a){var i,n,r,s,o,h,l,d,_=a.length;if(!t||!t.state)return ut;if(2===(s=(i=t.state).wrap)||1===s&&i.status!==Tt||i.lookahead)return ut;for(1===s&&(t.adler=et(t.adler,a,_,0)),i.wrap=0,_>=i.w_size&&(0===s&&(Jt(i.head),i.strstart=0,i.block_start=0,i.insert=0),d=new e.Buf8(i.w_size),e.arraySet(d,a,_-i.w_size,i.w_size,0),a=d,_=i.w_size),o=t.avail_in,h=t.next_in,l=t.input,t.avail_in=_,t.next_in=0,t.input=a,ae(i);i.lookahead>=Nt;){n=i.strstart,r=i.lookahead-(Nt-1);do{i.ins_h=(i.ins_h<=252?6:ce>=248?5:ce>=240?4:ce>=224?3:ce>=192?2:1;ue[254]=ue[254]=1;function we(t,a){if(a<65534&&(t.subarray&&fe||!t.subarray&&_e))return String.fromCharCode.apply(null,e.shrinkBuf(t,a));for(var i="",n=0;n>>6,a[s++]=128|63&i):i<65536?(a[s++]=224|i>>>12,a[s++]=128|i>>>6&63,a[s++]=128|63&i):(a[s++]=240|i>>>18,a[s++]=128|i>>>12&63,a[s++]=128|i>>>6&63,a[s++]=128|63&i);return a},buf2binstring:function(t){return we(t,t.length)},binstring2buf:function(t){for(var a=new e.Buf8(t.length),i=0,n=a.length;i4)o[i++]=65533,a+=r-1;else{for(n&=2===r?31:3===r?15:7;r>1&&a1?o[i++]=65533:n<65536?o[i++]=n:(n-=65536,o[i++]=55296|n>>10&1023,o[i++]=56320|1023&n)}return we(o,i)},utf8border:function(t,e){var a;for((e=e||t.length)>t.length&&(e=t.length),a=e-1;a>=0&&128==(192&t[a]);)a--;return a<0?e:0===a?e:a+ue[t[a]]>e?a:e}};var be=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0},me=Object.prototype.toString,pe=0,ve=-1,ke=0,ye=8;function xe(t){if(!(this instanceof xe))return new xe(t);this.options=e.assign({level:ve,method:ye,chunkSize:16384,windowBits:15,memLevel:8,strategy:ke,to:""},t||{});var a=this.options;a.raw&&a.windowBits>0?a.windowBits=-a.windowBits:a.gzip&&a.windowBits>0&&a.windowBits<16&&(a.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new be,this.strm.avail_out=0;var i=de.deflateInit2(this.strm,a.level,a.method,a.windowBits,a.memLevel,a.strategy);if(i!==pe)throw new Error(rt[i]);if(a.header&&de.deflateSetHeader(this.strm,a.header),a.dictionary){var n;if(n="string"==typeof a.dictionary?ge.string2buf(a.dictionary):"[object ArrayBuffer]"===me.call(a.dictionary)?new Uint8Array(a.dictionary):a.dictionary,(i=de.deflateSetDictionary(this.strm,n))!==pe)throw new Error(rt[i]);this._dict_set=!0}}function ze(t,e){var a=new xe(e);if(a.push(t,!0),a.err)throw a.msg||rt[a.err];return a.result}xe.prototype.push=function(t,a){var i,n,r=this.strm,s=this.options.chunkSize;if(this.ended)return!1;n=a===~~a?a:!0===a?4:0,"string"==typeof t?r.input=ge.string2buf(t):"[object ArrayBuffer]"===me.call(t)?r.input=new Uint8Array(t):r.input=t,r.next_in=0,r.avail_in=r.input.length;do{if(0===r.avail_out&&(r.output=new e.Buf8(s),r.next_out=0,r.avail_out=s),1!==(i=de.deflate(r,n))&&i!==pe)return this.onEnd(i),this.ended=!0,!1;0!==r.avail_out&&(0!==r.avail_in||4!==n&&2!==n)||("string"===this.options.to?this.onData(ge.buf2binstring(e.shrinkBuf(r.output,r.next_out))):this.onData(e.shrinkBuf(r.output,r.next_out)))}while((r.avail_in>0||0===r.avail_out)&&1!==i);return 4===n?(i=de.deflateEnd(this.strm),this.onEnd(i),this.ended=!0,i===pe):2!==n||(this.onEnd(pe),r.avail_out=0,!0)},xe.prototype.onData=function(t){this.chunks.push(t)},xe.prototype.onEnd=function(t){t===pe&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=e.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Be={Deflate:xe,deflate:ze,deflateRaw:function(t,e){return(e=e||{}).raw=!0,ze(t,e)},gzip:function(t,e){return(e=e||{}).gzip=!0,ze(t,e)}},Se=function(t,e){var a,i,n,r,s,o,h,l,d,_,f,u,c,w,g,b,m,p,v,k,y,x,z,B,S;a=t.state,i=t.next_in,B=t.input,n=i+(t.avail_in-5),r=t.next_out,S=t.output,s=r-(e-t.avail_out),o=r+(t.avail_out-257),h=a.dmax,l=a.wsize,d=a.whave,_=a.wnext,f=a.window,u=a.hold,c=a.bits,w=a.lencode,g=a.distcode,b=(1<>>=v=p>>>24,c-=v,0===(v=p>>>16&255))S[r++]=65535&p;else{if(!(16&v)){if(0==(64&v)){p=w[(65535&p)+(u&(1<>>=v,c-=v),c<15&&(u+=B[i++]<>>=v=p>>>24,c-=v,!(16&(v=p>>>16&255))){if(0==(64&v)){p=g[(65535&p)+(u&(1<h){t.msg="invalid distance too far back",a.mode=30;break t}if(u>>>=v,c-=v,y>(v=r-s)){if((v=y-v)>d&&a.sane){t.msg="invalid distance too far back",a.mode=30;break t}if(x=0,z=f,0===_){if(x+=l-v,v2;)S[r++]=z[x++],S[r++]=z[x++],S[r++]=z[x++],k-=3;k&&(S[r++]=z[x++],k>1&&(S[r++]=z[x++]))}else{x=r-y;do{S[r++]=S[x++],S[r++]=S[x++],S[r++]=S[x++],k-=3}while(k>2);k&&(S[r++]=S[x++],k>1&&(S[r++]=S[x++]))}break}}break}}while(i>3,u&=(1<<(c-=k<<3))-1,t.next_in=i,t.next_out=r,t.avail_in=i=1&&0===C[y];y--);if(x>y&&(x=y),0===y)return r[s++]=20971520,r[s++]=20971520,h.bits=1,0;for(k=1;k0&&(0===t||1!==y))return-1;for(N[1]=0,p=1;p<15;p++)N[p+1]=N[p]+C[p];for(v=0;v852||2===t&&E>592)return 1;for(;;){w=p-B,o[v]c?(g=I[O+o[v]],b=Z[R+o[v]]):(g=96,b=0),l=1<>B)+(d-=l)]=w<<24|g<<16|b|0}while(0!==d);for(l=1<>=1;if(0!==l?(A&=l-1,A+=l):A=0,v++,0==--C[p]){if(p===y)break;p=a[i+o[v]]}if(p>x&&(A&f)!==_){for(0===B&&(B=x),u+=k,S=1<<(z=p-B);z+B852||2===t&&E>592)return 1;r[_=A&f]=x<<24|z<<16|u-s|0}}return 0!==A&&(r[u+A]=p-B<<24|64<<16|0),h.bits=x,0},Ne=0,Ie=1,Oe=2,De=4,Te=5,Ue=6,Fe=0,Le=1,He=2,je=-2,Ke=-3,Me=-4,Pe=-5,Ye=8,Ge=1,Xe=2,We=3,qe=4,Je=5,Qe=6,Ve=7,$e=8,ta=9,ea=10,aa=11,ia=12,na=13,ra=14,sa=15,oa=16,ha=17,la=18,da=19,_a=20,fa=21,ua=22,ca=23,wa=24,ga=25,ba=26,ma=27,pa=28,va=29,ka=30,ya=31,xa=32,za=852,Ba=592,Sa=15;function Ea(t){return(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function Aa(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new e.Buf16(320),this.work=new e.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function Za(t){var a;return t&&t.state?(a=t.state,t.total_in=t.total_out=a.total=0,t.msg="",a.wrap&&(t.adler=1&a.wrap),a.mode=Ge,a.last=0,a.havedict=0,a.dmax=32768,a.head=null,a.hold=0,a.bits=0,a.lencode=a.lendyn=new e.Buf32(za),a.distcode=a.distdyn=new e.Buf32(Ba),a.sane=1,a.back=-1,Fe):je}function Ra(t){var e;return t&&t.state?((e=t.state).wsize=0,e.whave=0,e.wnext=0,Za(t)):je}function Ca(t,e){var a,i;return t&&t.state?(i=t.state,e<0?(a=0,e=-e):(a=1+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?je:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,Ra(t))):je}function Na(t,e){var a,i;return t?(i=new Aa,t.state=i,i.window=null,(a=Ca(t,e))!==Fe&&(t.state=null),a):je}var Ia,Oa,Da=!0;function Ta(t){if(Da){var a;for(Ia=new e.Buf32(512),Oa=new e.Buf32(32),a=0;a<144;)t.lens[a++]=8;for(;a<256;)t.lens[a++]=9;for(;a<280;)t.lens[a++]=7;for(;a<288;)t.lens[a++]=8;for(Ce(Ie,t.lens,0,288,Ia,0,t.work,{bits:9}),a=0;a<32;)t.lens[a++]=5;Ce(Oe,t.lens,0,32,Oa,0,t.work,{bits:5}),Da=!1}t.lencode=Ia,t.lenbits=9,t.distcode=Oa,t.distbits=5}function Ua(t,a,i,n){var r,s=t.state;return null===s.window&&(s.wsize=1<=s.wsize?(e.arraySet(s.window,a,i-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):((r=s.wsize-s.wnext)>n&&(r=n),e.arraySet(s.window,a,i-n,r,s.wnext),(n-=r)?(e.arraySet(s.window,a,i-n,n,0),s.wnext=n,s.whave=s.wsize):(s.wnext+=r,s.wnext===s.wsize&&(s.wnext=0),s.whave>>8&255,i.check=nt(i.check,A,2,0),d=0,_=0,i.mode=Xe;break}if(i.flags=0,i.head&&(i.head.done=!1),!(1&i.wrap)||(((255&d)<<8)+(d>>8))%31){t.msg="incorrect header check",i.mode=ka;break}if((15&d)!==Ye){t.msg="unknown compression method",i.mode=ka;break}if(_-=4,x=8+(15&(d>>>=4)),0===i.wbits)i.wbits=x;else if(x>i.wbits){t.msg="invalid window size",i.mode=ka;break}i.dmax=1<>8&1),512&i.flags&&(A[0]=255&d,A[1]=d>>>8&255,i.check=nt(i.check,A,2,0)),d=0,_=0,i.mode=We;case We:for(;_<32;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}i.head&&(i.head.time=d),512&i.flags&&(A[0]=255&d,A[1]=d>>>8&255,A[2]=d>>>16&255,A[3]=d>>>24&255,i.check=nt(i.check,A,4,0)),d=0,_=0,i.mode=qe;case qe:for(;_<16;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}i.head&&(i.head.xflags=255&d,i.head.os=d>>8),512&i.flags&&(A[0]=255&d,A[1]=d>>>8&255,i.check=nt(i.check,A,2,0)),d=0,_=0,i.mode=Je;case Je:if(1024&i.flags){for(;_<16;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}i.length=d,i.head&&(i.head.extra_len=d),512&i.flags&&(A[0]=255&d,A[1]=d>>>8&255,i.check=nt(i.check,A,2,0)),d=0,_=0}else i.head&&(i.head.extra=null);i.mode=Qe;case Qe:if(1024&i.flags&&((c=i.length)>h&&(c=h),c&&(i.head&&(x=i.head.extra_len-i.length,i.head.extra||(i.head.extra=new Array(i.head.extra_len)),e.arraySet(i.head.extra,n,s,c,x)),512&i.flags&&(i.check=nt(i.check,n,c,s)),h-=c,s+=c,i.length-=c),i.length))break t;i.length=0,i.mode=Ve;case Ve:if(2048&i.flags){if(0===h)break t;c=0;do{x=n[s+c++],i.head&&x&&i.length<65536&&(i.head.name+=String.fromCharCode(x))}while(x&&c>9&1,i.head.done=!0),t.adler=i.check=0,i.mode=ia;break;case ea:for(;_<32;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}t.adler=i.check=Ea(d),d=0,_=0,i.mode=aa;case aa:if(0===i.havedict)return t.next_out=o,t.avail_out=l,t.next_in=s,t.avail_in=h,i.hold=d,i.bits=_,He;t.adler=i.check=1,i.mode=ia;case ia:if(a===Te||a===Ue)break t;case na:if(i.last){d>>>=7&_,_-=7&_,i.mode=ma;break}for(;_<3;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}switch(i.last=1&d,_-=1,3&(d>>>=1)){case 0:i.mode=ra;break;case 1:if(Ta(i),i.mode=_a,a===Ue){d>>>=2,_-=2;break t}break;case 2:i.mode=ha;break;case 3:t.msg="invalid block type",i.mode=ka}d>>>=2,_-=2;break;case ra:for(d>>>=7&_,_-=7&_;_<32;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if((65535&d)!=(d>>>16^65535)){t.msg="invalid stored block lengths",i.mode=ka;break}if(i.length=65535&d,d=0,_=0,i.mode=sa,a===Ue)break t;case sa:i.mode=oa;case oa:if(c=i.length){if(c>h&&(c=h),c>l&&(c=l),0===c)break t;e.arraySet(r,n,s,c,o),h-=c,s+=c,l-=c,o+=c,i.length-=c;break}i.mode=ia;break;case ha:for(;_<14;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(i.nlen=257+(31&d),d>>>=5,_-=5,i.ndist=1+(31&d),d>>>=5,_-=5,i.ncode=4+(15&d),d>>>=4,_-=4,i.nlen>286||i.ndist>30){t.msg="too many length or distance symbols",i.mode=ka;break}i.have=0,i.mode=la;case la:for(;i.have>>=3,_-=3}for(;i.have<19;)i.lens[Z[i.have++]]=0;if(i.lencode=i.lendyn,i.lenbits=7,B={bits:i.lenbits},z=Ce(Ne,i.lens,0,19,i.lencode,0,i.work,B),i.lenbits=B.bits,z){t.msg="invalid code lengths set",i.mode=ka;break}i.have=0,i.mode=da;case da:for(;i.have>>16&255,p=65535&E,!((b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(p<16)d>>>=b,_-=b,i.lens[i.have++]=p;else{if(16===p){for(S=b+2;_>>=b,_-=b,0===i.have){t.msg="invalid bit length repeat",i.mode=ka;break}x=i.lens[i.have-1],c=3+(3&d),d>>>=2,_-=2}else if(17===p){for(S=b+3;_>>=b)),d>>>=3,_-=3}else{for(S=b+7;_>>=b)),d>>>=7,_-=7}if(i.have+c>i.nlen+i.ndist){t.msg="invalid bit length repeat",i.mode=ka;break}for(;c--;)i.lens[i.have++]=x}}if(i.mode===ka)break;if(0===i.lens[256]){t.msg="invalid code -- missing end-of-block",i.mode=ka;break}if(i.lenbits=9,B={bits:i.lenbits},z=Ce(Ie,i.lens,0,i.nlen,i.lencode,0,i.work,B),i.lenbits=B.bits,z){t.msg="invalid literal/lengths set",i.mode=ka;break}if(i.distbits=6,i.distcode=i.distdyn,B={bits:i.distbits},z=Ce(Oe,i.lens,i.nlen,i.ndist,i.distcode,0,i.work,B),i.distbits=B.bits,z){t.msg="invalid distances set",i.mode=ka;break}if(i.mode=_a,a===Ue)break t;case _a:i.mode=fa;case fa:if(h>=6&&l>=258){t.next_out=o,t.avail_out=l,t.next_in=s,t.avail_in=h,i.hold=d,i.bits=_,Se(t,u),o=t.next_out,r=t.output,l=t.avail_out,s=t.next_in,n=t.input,h=t.avail_in,d=i.hold,_=i.bits,i.mode===ia&&(i.back=-1);break}for(i.back=0;m=(E=i.lencode[d&(1<>>16&255,p=65535&E,!((b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(m&&0==(240&m)){for(v=b,k=m,y=p;m=(E=i.lencode[y+((d&(1<>v)])>>>16&255,p=65535&E,!(v+(b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}d>>>=v,_-=v,i.back+=v}if(d>>>=b,_-=b,i.back+=b,i.length=p,0===m){i.mode=ba;break}if(32&m){i.back=-1,i.mode=ia;break}if(64&m){t.msg="invalid literal/length code",i.mode=ka;break}i.extra=15&m,i.mode=ua;case ua:if(i.extra){for(S=i.extra;_>>=i.extra,_-=i.extra,i.back+=i.extra}i.was=i.length,i.mode=ca;case ca:for(;m=(E=i.distcode[d&(1<>>16&255,p=65535&E,!((b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(0==(240&m)){for(v=b,k=m,y=p;m=(E=i.distcode[y+((d&(1<>v)])>>>16&255,p=65535&E,!(v+(b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}d>>>=v,_-=v,i.back+=v}if(d>>>=b,_-=b,i.back+=b,64&m){t.msg="invalid distance code",i.mode=ka;break}i.offset=p,i.extra=15&m,i.mode=wa;case wa:if(i.extra){for(S=i.extra;_>>=i.extra,_-=i.extra,i.back+=i.extra}if(i.offset>i.dmax){t.msg="invalid distance too far back",i.mode=ka;break}i.mode=ga;case ga:if(0===l)break t;if(c=u-l,i.offset>c){if((c=i.offset-c)>i.whave&&i.sane){t.msg="invalid distance too far back",i.mode=ka;break}c>i.wnext?(c-=i.wnext,w=i.wsize-c):w=i.wnext-c,c>i.length&&(c=i.length),g=i.window}else g=r,w=o-i.offset,c=i.length;c>l&&(c=l),l-=c,i.length-=c;do{r[o++]=g[w++]}while(--c);0===i.length&&(i.mode=fa);break;case ba:if(0===l)break t;r[o++]=i.length,l--,i.mode=fa;break;case ma:if(i.wrap){for(;_<32;){if(0===h)break t;h--,d|=n[s++]<<_,_+=8}if(u-=l,t.total_out+=u,i.total+=u,u&&(t.adler=i.check=i.flags?nt(i.check,r,u,o-u):et(i.check,r,u,o-u)),u=l,(i.flags?d:Ea(d))!==i.check){t.msg="incorrect data check",i.mode=ka;break}d=0,_=0}i.mode=pa;case pa:if(i.wrap&&i.flags){for(;_<32;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(d!==(4294967295&i.total)){t.msg="incorrect length check",i.mode=ka;break}d=0,_=0}i.mode=va;case va:z=Le;break t;case ka:z=Ke;break t;case ya:return Me;case xa:default:return je}return t.next_out=o,t.avail_out=l,t.next_in=s,t.avail_in=h,i.hold=d,i.bits=_,(i.wsize||u!==t.avail_out&&i.mode=0&&a.windowBits<16&&(a.windowBits=-a.windowBits,0===a.windowBits&&(a.windowBits=-15)),!(a.windowBits>=0&&a.windowBits<16)||t&&t.windowBits||(a.windowBits+=32),a.windowBits>15&&a.windowBits<48&&0==(15&a.windowBits)&&(a.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new be,this.strm.avail_out=0;var i=Fa.inflateInit2(this.strm,a.windowBits);if(i!==La.Z_OK)throw new Error(rt[i]);if(this.header=new Ha,Fa.inflateGetHeader(this.strm,this.header),a.dictionary&&("string"==typeof a.dictionary?a.dictionary=ge.string2buf(a.dictionary):"[object ArrayBuffer]"===ja.call(a.dictionary)&&(a.dictionary=new Uint8Array(a.dictionary)),a.raw&&(i=Fa.inflateSetDictionary(this.strm,a.dictionary))!==La.Z_OK))throw new Error(rt[i])}function Ma(t,e){var a=new Ka(e);if(a.push(t,!0),a.err)throw a.msg||rt[a.err];return a.result}Ka.prototype.push=function(t,a){var i,n,r,s,o,h=this.strm,l=this.options.chunkSize,d=this.options.dictionary,_=!1;if(this.ended)return!1;n=a===~~a?a:!0===a?La.Z_FINISH:La.Z_NO_FLUSH,"string"==typeof t?h.input=ge.binstring2buf(t):"[object ArrayBuffer]"===ja.call(t)?h.input=new Uint8Array(t):h.input=t,h.next_in=0,h.avail_in=h.input.length;do{if(0===h.avail_out&&(h.output=new e.Buf8(l),h.next_out=0,h.avail_out=l),(i=Fa.inflate(h,La.Z_NO_FLUSH))===La.Z_NEED_DICT&&d&&(i=Fa.inflateSetDictionary(this.strm,d)),i===La.Z_BUF_ERROR&&!0===_&&(i=La.Z_OK,_=!1),i!==La.Z_STREAM_END&&i!==La.Z_OK)return this.onEnd(i),this.ended=!0,!1;h.next_out&&(0!==h.avail_out&&i!==La.Z_STREAM_END&&(0!==h.avail_in||n!==La.Z_FINISH&&n!==La.Z_SYNC_FLUSH)||("string"===this.options.to?(r=ge.utf8border(h.output,h.next_out),s=h.next_out-r,o=ge.buf2string(h.output,r),h.next_out=s,h.avail_out=l-s,s&&e.arraySet(h.output,h.output,r,s,0),this.onData(o)):this.onData(e.shrinkBuf(h.output,h.next_out)))),0===h.avail_in&&0===h.avail_out&&(_=!0)}while((h.avail_in>0||0===h.avail_out)&&i!==La.Z_STREAM_END);return i===La.Z_STREAM_END&&(n=La.Z_FINISH),n===La.Z_FINISH?(i=Fa.inflateEnd(this.strm),this.onEnd(i),this.ended=!0,i===La.Z_OK):n!==La.Z_SYNC_FLUSH||(this.onEnd(La.Z_OK),h.avail_out=0,!0)},Ka.prototype.onData=function(t){this.chunks.push(t)},Ka.prototype.onEnd=function(t){t===La.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=e.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Pa={Inflate:Ka,inflate:Ma,inflateRaw:function(t,e){return(e=e||{}).raw=!0,Ma(t,e)},ungzip:Ma},Ya={};(0,e.assign)(Ya,Be,Pa,La);var Ga=Ya;window.pako=Ga}(); diff --git a/build/newcanvas/pathseg.js b/build/newcanvas/pathseg.js new file mode 100644 index 0000000..b369b23 --- /dev/null +++ b/build/newcanvas/pathseg.js @@ -0,0 +1 @@ +!function(){"use strict";"SVGPathSeg"in window||(window.SVGPathSeg=function(t,e,n){this.pathSegType=t,this.pathSegTypeAsLetter=e,this._owningPathSegList=n},window.SVGPathSeg.prototype.classname="SVGPathSeg",window.SVGPathSeg.PATHSEG_UNKNOWN=0,window.SVGPathSeg.PATHSEG_CLOSEPATH=1,window.SVGPathSeg.PATHSEG_MOVETO_ABS=2,window.SVGPathSeg.PATHSEG_MOVETO_REL=3,window.SVGPathSeg.PATHSEG_LINETO_ABS=4,window.SVGPathSeg.PATHSEG_LINETO_REL=5,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS=6,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL=7,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS=8,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL=9,window.SVGPathSeg.PATHSEG_ARC_ABS=10,window.SVGPathSeg.PATHSEG_ARC_REL=11,window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS=12,window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL=13,window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS=14,window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL=15,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS=16,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL=17,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS=18,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL=19,window.SVGPathSeg.prototype._segmentChanged=function(){this._owningPathSegList&&this._owningPathSegList.segmentChanged(this)},window.SVGPathSegClosePath=function(t){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CLOSEPATH,"z",t)},window.SVGPathSegClosePath.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegClosePath.prototype.toString=function(){return"[object SVGPathSegClosePath]"},window.SVGPathSegClosePath.prototype._asPathString=function(){return this.pathSegTypeAsLetter},window.SVGPathSegClosePath.prototype.clone=function(){return new window.SVGPathSegClosePath(void 0)},window.SVGPathSegMovetoAbs=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_MOVETO_ABS,"M",t),this._x=e,this._y=n},window.SVGPathSegMovetoAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegMovetoAbs.prototype.toString=function(){return"[object SVGPathSegMovetoAbs]"},window.SVGPathSegMovetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegMovetoAbs.prototype.clone=function(){return new window.SVGPathSegMovetoAbs(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegMovetoAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegMovetoAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegMovetoRel=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_MOVETO_REL,"m",t),this._x=e,this._y=n},window.SVGPathSegMovetoRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegMovetoRel.prototype.toString=function(){return"[object SVGPathSegMovetoRel]"},window.SVGPathSegMovetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegMovetoRel.prototype.clone=function(){return new window.SVGPathSegMovetoRel(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegMovetoRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegMovetoRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoAbs=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_ABS,"L",t),this._x=e,this._y=n},window.SVGPathSegLinetoAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoAbs.prototype.toString=function(){return"[object SVGPathSegLinetoAbs]"},window.SVGPathSegLinetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegLinetoAbs.prototype.clone=function(){return new window.SVGPathSegLinetoAbs(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegLinetoAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegLinetoAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoRel=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_REL,"l",t),this._x=e,this._y=n},window.SVGPathSegLinetoRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoRel.prototype.toString=function(){return"[object SVGPathSegLinetoRel]"},window.SVGPathSegLinetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegLinetoRel.prototype.clone=function(){return new window.SVGPathSegLinetoRel(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegLinetoRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegLinetoRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoCubicAbs=function(t,e,n,i,o,r,h){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS,"C",t),this._x=e,this._y=n,this._x1=i,this._y1=o,this._x2=r,this._y2=h},window.SVGPathSegCurvetoCubicAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoCubicAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicAbs]"},window.SVGPathSegCurvetoCubicAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},window.SVGPathSegCurvetoCubicAbs.prototype.clone=function(){return new window.SVGPathSegCurvetoCubicAbs(void 0,this._x,this._y,this._x1,this._y1,this._x2,this._y2)},Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"x1",{get:function(){return this._x1},set:function(t){this._x1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"y1",{get:function(){return this._y1},set:function(t){this._y1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"x2",{get:function(){return this._x2},set:function(t){this._x2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"y2",{get:function(){return this._y2},set:function(t){this._y2=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoCubicRel=function(t,e,n,i,o,r,h){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL,"c",t),this._x=e,this._y=n,this._x1=i,this._y1=o,this._x2=r,this._y2=h},window.SVGPathSegCurvetoCubicRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoCubicRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicRel]"},window.SVGPathSegCurvetoCubicRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},window.SVGPathSegCurvetoCubicRel.prototype.clone=function(){return new window.SVGPathSegCurvetoCubicRel(void 0,this._x,this._y,this._x1,this._y1,this._x2,this._y2)},Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"x1",{get:function(){return this._x1},set:function(t){this._x1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"y1",{get:function(){return this._y1},set:function(t){this._y1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"x2",{get:function(){return this._x2},set:function(t){this._x2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"y2",{get:function(){return this._y2},set:function(t){this._y2=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoQuadraticAbs=function(t,e,n,i,o){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS,"Q",t),this._x=e,this._y=n,this._x1=i,this._y1=o},window.SVGPathSegCurvetoQuadraticAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoQuadraticAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticAbs]"},window.SVGPathSegCurvetoQuadraticAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y},window.SVGPathSegCurvetoQuadraticAbs.prototype.clone=function(){return new window.SVGPathSegCurvetoQuadraticAbs(void 0,this._x,this._y,this._x1,this._y1)},Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype,"x1",{get:function(){return this._x1},set:function(t){this._x1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype,"y1",{get:function(){return this._y1},set:function(t){this._y1=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoQuadraticRel=function(t,e,n,i,o){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL,"q",t),this._x=e,this._y=n,this._x1=i,this._y1=o},window.SVGPathSegCurvetoQuadraticRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoQuadraticRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticRel]"},window.SVGPathSegCurvetoQuadraticRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y},window.SVGPathSegCurvetoQuadraticRel.prototype.clone=function(){return new window.SVGPathSegCurvetoQuadraticRel(void 0,this._x,this._y,this._x1,this._y1)},Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype,"x1",{get:function(){return this._x1},set:function(t){this._x1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype,"y1",{get:function(){return this._y1},set:function(t){this._y1=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegArcAbs=function(t,e,n,i,o,r,h,s){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_ARC_ABS,"A",t),this._x=e,this._y=n,this._r1=i,this._r2=o,this._angle=r,this._largeArcFlag=h,this._sweepFlag=s},window.SVGPathSegArcAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegArcAbs.prototype.toString=function(){return"[object SVGPathSegArcAbs]"},window.SVGPathSegArcAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y},window.SVGPathSegArcAbs.prototype.clone=function(){return new window.SVGPathSegArcAbs(void 0,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag)},Object.defineProperty(window.SVGPathSegArcAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"r1",{get:function(){return this._r1},set:function(t){this._r1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"r2",{get:function(){return this._r2},set:function(t){this._r2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"angle",{get:function(){return this._angle},set:function(t){this._angle=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag},set:function(t){this._largeArcFlag=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"sweepFlag",{get:function(){return this._sweepFlag},set:function(t){this._sweepFlag=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegArcRel=function(t,e,n,i,o,r,h,s){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_ARC_REL,"a",t),this._x=e,this._y=n,this._r1=i,this._r2=o,this._angle=r,this._largeArcFlag=h,this._sweepFlag=s},window.SVGPathSegArcRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegArcRel.prototype.toString=function(){return"[object SVGPathSegArcRel]"},window.SVGPathSegArcRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y},window.SVGPathSegArcRel.prototype.clone=function(){return new window.SVGPathSegArcRel(void 0,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag)},Object.defineProperty(window.SVGPathSegArcRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"r1",{get:function(){return this._r1},set:function(t){this._r1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"r2",{get:function(){return this._r2},set:function(t){this._r2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"angle",{get:function(){return this._angle},set:function(t){this._angle=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag},set:function(t){this._largeArcFlag=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"sweepFlag",{get:function(){return this._sweepFlag},set:function(t){this._sweepFlag=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoHorizontalAbs=function(t,e){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS,"H",t),this._x=e},window.SVGPathSegLinetoHorizontalAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoHorizontalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalAbs]"},window.SVGPathSegLinetoHorizontalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x},window.SVGPathSegLinetoHorizontalAbs.prototype.clone=function(){return new window.SVGPathSegLinetoHorizontalAbs(void 0,this._x)},Object.defineProperty(window.SVGPathSegLinetoHorizontalAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoHorizontalRel=function(t,e){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL,"h",t),this._x=e},window.SVGPathSegLinetoHorizontalRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoHorizontalRel.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalRel]"},window.SVGPathSegLinetoHorizontalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x},window.SVGPathSegLinetoHorizontalRel.prototype.clone=function(){return new window.SVGPathSegLinetoHorizontalRel(void 0,this._x)},Object.defineProperty(window.SVGPathSegLinetoHorizontalRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoVerticalAbs=function(t,e){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS,"V",t),this._y=e},window.SVGPathSegLinetoVerticalAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoVerticalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalAbs]"},window.SVGPathSegLinetoVerticalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y},window.SVGPathSegLinetoVerticalAbs.prototype.clone=function(){return new window.SVGPathSegLinetoVerticalAbs(void 0,this._y)},Object.defineProperty(window.SVGPathSegLinetoVerticalAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoVerticalRel=function(t,e){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL,"v",t),this._y=e},window.SVGPathSegLinetoVerticalRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoVerticalRel.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalRel]"},window.SVGPathSegLinetoVerticalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y},window.SVGPathSegLinetoVerticalRel.prototype.clone=function(){return new window.SVGPathSegLinetoVerticalRel(void 0,this._y)},Object.defineProperty(window.SVGPathSegLinetoVerticalRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoCubicSmoothAbs=function(t,e,n,i,o){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS,"S",t),this._x=e,this._y=n,this._x2=i,this._y2=o},window.SVGPathSegCurvetoCubicSmoothAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoCubicSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothAbs]"},window.SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},window.SVGPathSegCurvetoCubicSmoothAbs.prototype.clone=function(){return new window.SVGPathSegCurvetoCubicSmoothAbs(void 0,this._x,this._y,this._x2,this._y2)},Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype,"x2",{get:function(){return this._x2},set:function(t){this._x2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype,"y2",{get:function(){return this._y2},set:function(t){this._y2=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoCubicSmoothRel=function(t,e,n,i,o){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL,"s",t),this._x=e,this._y=n,this._x2=i,this._y2=o},window.SVGPathSegCurvetoCubicSmoothRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoCubicSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothRel]"},window.SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},window.SVGPathSegCurvetoCubicSmoothRel.prototype.clone=function(){return new window.SVGPathSegCurvetoCubicSmoothRel(void 0,this._x,this._y,this._x2,this._y2)},Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype,"x2",{get:function(){return this._x2},set:function(t){this._x2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype,"y2",{get:function(){return this._y2},set:function(t){this._y2=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoQuadraticSmoothAbs=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS,"T",t),this._x=e,this._y=n},window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothAbs]"},window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone=function(){return new window.SVGPathSegCurvetoQuadraticSmoothAbs(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoQuadraticSmoothRel=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,"t",t),this._x=e,this._y=n},window.SVGPathSegCurvetoQuadraticSmoothRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothRel]"},window.SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone=function(){return new window.SVGPathSegCurvetoQuadraticSmoothRel(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathElement.prototype.createSVGPathSegClosePath=function(){return new window.SVGPathSegClosePath(void 0)},window.SVGPathElement.prototype.createSVGPathSegMovetoAbs=function(t,e){return new window.SVGPathSegMovetoAbs(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegMovetoRel=function(t,e){return new window.SVGPathSegMovetoRel(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegLinetoAbs=function(t,e){return new window.SVGPathSegLinetoAbs(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegLinetoRel=function(t,e){return new window.SVGPathSegLinetoRel(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs=function(t,e,n,i,o,r){return new window.SVGPathSegCurvetoCubicAbs(void 0,t,e,n,i,o,r)},window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel=function(t,e,n,i,o,r){return new window.SVGPathSegCurvetoCubicRel(void 0,t,e,n,i,o,r)},window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs=function(t,e,n,i){return new window.SVGPathSegCurvetoQuadraticAbs(void 0,t,e,n,i)},window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel=function(t,e,n,i){return new window.SVGPathSegCurvetoQuadraticRel(void 0,t,e,n,i)},window.SVGPathElement.prototype.createSVGPathSegArcAbs=function(t,e,n,i,o,r,h){return new window.SVGPathSegArcAbs(void 0,t,e,n,i,o,r,h)},window.SVGPathElement.prototype.createSVGPathSegArcRel=function(t,e,n,i,o,r,h){return new window.SVGPathSegArcRel(void 0,t,e,n,i,o,r,h)},window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs=function(t){return new window.SVGPathSegLinetoHorizontalAbs(void 0,t)},window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel=function(t){return new window.SVGPathSegLinetoHorizontalRel(void 0,t)},window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs=function(t){return new window.SVGPathSegLinetoVerticalAbs(void 0,t)},window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel=function(t){return new window.SVGPathSegLinetoVerticalRel(void 0,t)},window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs=function(t,e,n,i){return new window.SVGPathSegCurvetoCubicSmoothAbs(void 0,t,e,n,i)},window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel=function(t,e,n,i){return new window.SVGPathSegCurvetoCubicSmoothRel(void 0,t,e,n,i)},window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs=function(t,e){return new window.SVGPathSegCurvetoQuadraticSmoothAbs(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel=function(t,e){return new window.SVGPathSegCurvetoQuadraticSmoothRel(void 0,t,e)},"getPathSegAtLength"in window.SVGPathElement.prototype||(window.SVGPathElement.prototype.getPathSegAtLength=function(t){if(void 0===t||!isFinite(t))throw"Invalid arguments.";var e=document.createElementNS("http://www.w3.org/2000/svg","path");e.setAttribute("d",this.getAttribute("d"));var n=e.pathSegList.numberOfItems-1;if(n<=0)return 0;do{if(e.pathSegList.removeItem(n),t>e.getTotalLength())break;n--}while(n>0);return n})),"SVGPathSegList"in window&&"appendItem"in window.SVGPathSegList.prototype||(window.SVGPathSegList=function(t){this._pathElement=t,this._list=this._parsePath(this._pathElement.getAttribute("d")),this._mutationObserverConfig={attributes:!0,attributeFilter:["d"]},this._pathElementMutationObserver=new MutationObserver(this._updateListFromPathMutations.bind(this)),this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig)},window.SVGPathSegList.prototype.classname="SVGPathSegList",Object.defineProperty(window.SVGPathSegList.prototype,"numberOfItems",{get:function(){return this._checkPathSynchronizedToList(),this._list.length},enumerable:!0}),Object.defineProperty(window.SVGPathElement.prototype,"pathSegList",{get:function(){return this._pathSegList||(this._pathSegList=new window.SVGPathSegList(this)),this._pathSegList},enumerable:!0}),Object.defineProperty(window.SVGPathElement.prototype,"normalizedPathSegList",{get:function(){return this.pathSegList},enumerable:!0}),Object.defineProperty(window.SVGPathElement.prototype,"animatedPathSegList",{get:function(){return this.pathSegList},enumerable:!0}),Object.defineProperty(window.SVGPathElement.prototype,"animatedNormalizedPathSegList",{get:function(){return this.pathSegList},enumerable:!0}),window.SVGPathSegList.prototype._checkPathSynchronizedToList=function(){this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords())},window.SVGPathSegList.prototype._updateListFromPathMutations=function(t){if(this._pathElement){var e=!1;t.forEach(function(t){"d"==t.attributeName&&(e=!0)}),e&&(this._list=this._parsePath(this._pathElement.getAttribute("d")))}},window.SVGPathSegList.prototype._writeListToPath=function(){this._pathElementMutationObserver.disconnect(),this._pathElement.setAttribute("d",window.SVGPathSegList._pathSegArrayAsString(this._list)),this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig)},window.SVGPathSegList.prototype.segmentChanged=function(t){this._writeListToPath()},window.SVGPathSegList.prototype.clear=function(){this._checkPathSynchronizedToList(),this._list.forEach(function(t){t._owningPathSegList=null}),this._list=[],this._writeListToPath()},window.SVGPathSegList.prototype.initialize=function(t){return this._checkPathSynchronizedToList(),this._list=[t],t._owningPathSegList=this,this._writeListToPath(),t},window.SVGPathSegList.prototype._checkValidIndex=function(t){if(isNaN(t)||t<0||t>=this.numberOfItems)throw"INDEX_SIZE_ERR"},window.SVGPathSegList.prototype.getItem=function(t){return this._checkPathSynchronizedToList(),this._checkValidIndex(t),this._list[t]},window.SVGPathSegList.prototype.insertItemBefore=function(t,e){return this._checkPathSynchronizedToList(),e>this.numberOfItems&&(e=this.numberOfItems),t._owningPathSegList&&(t=t.clone()),this._list.splice(e,0,t),t._owningPathSegList=this,this._writeListToPath(),t},window.SVGPathSegList.prototype.replaceItem=function(t,e){return this._checkPathSynchronizedToList(),t._owningPathSegList&&(t=t.clone()),this._checkValidIndex(e),this._list[e]=t,t._owningPathSegList=this,this._writeListToPath(),t},window.SVGPathSegList.prototype.removeItem=function(t){this._checkPathSynchronizedToList(),this._checkValidIndex(t);var e=this._list[t];return this._list.splice(t,1),this._writeListToPath(),e},window.SVGPathSegList.prototype.appendItem=function(t){return this._checkPathSynchronizedToList(),t._owningPathSegList&&(t=t.clone()),this._list.push(t),t._owningPathSegList=this,this._writeListToPath(),t},window.SVGPathSegList._pathSegArrayAsString=function(t){var e="",n=!0;return t.forEach(function(t){n?(n=!1,e+=t._asPathString()):e+=" "+t._asPathString()}),e},window.SVGPathSegList.prototype._parsePath=function(t){if(!t||0==t.length)return[];var e=this,n=function(){this.pathSegList=[]};n.prototype.appendSegment=function(t){this.pathSegList.push(t)};var i=function(t){this._string=t,this._currentIndex=0,this._endIndex=this._string.length,this._previousCommand=window.SVGPathSeg.PATHSEG_UNKNOWN,this._skipOptionalSpaces()};i.prototype._isCurrentSpace=function(){var t=this._string[this._currentIndex];return t<=" "&&(" "==t||"\n"==t||"\t"==t||"\r"==t||"\f"==t)},i.prototype._skipOptionalSpaces=function(){for(;this._currentIndex="0"&&t<="9")&&e!=window.SVGPathSeg.PATHSEG_CLOSEPATH?e==window.SVGPathSeg.PATHSEG_MOVETO_ABS?window.SVGPathSeg.PATHSEG_LINETO_ABS:e==window.SVGPathSeg.PATHSEG_MOVETO_REL?window.SVGPathSeg.PATHSEG_LINETO_REL:e:window.SVGPathSeg.PATHSEG_UNKNOWN},i.prototype.initialCommandIsMoveTo=function(){if(!this.hasMoreData())return!0;var t=this.peekSegmentType();return t==window.SVGPathSeg.PATHSEG_MOVETO_ABS||t==window.SVGPathSeg.PATHSEG_MOVETO_REL},i.prototype._parseNumber=function(){var t=0,e=0,n=1,i=0,o=1,r=1,h=this._currentIndex;if(this._skipOptionalSpaces(),this._currentIndex"9")&&"."!=this._string.charAt(this._currentIndex))){for(var s=this._currentIndex;this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9";)this._currentIndex++;if(this._currentIndex!=s)for(var a=this._currentIndex-1,S=1;a>=s;)e+=S*(this._string.charAt(a--)-"0"),S*=10;if(this._currentIndex=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9")return;for(;this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9";)n*=10,i+=(this._string.charAt(this._currentIndex)-"0")/n,this._currentIndex+=1}if(this._currentIndex!=h&&this._currentIndex+1=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9")return;for(;this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9";)t*=10,t+=this._string.charAt(this._currentIndex)-"0",this._currentIndex++}var u=e+i;if(u*=o,t&&(u*=Math.pow(10,r*t)),h!=this._currentIndex)return this._skipOptionalSpacesOrDelimiter(),u}},i.prototype._parseArcFlag=function(){if(!(this._currentIndex>=this._endIndex)){var t=!1,e=this._string.charAt(this._currentIndex++);if("0"==e)t=!1;else{if("1"!=e)return;t=!0}return this._skipOptionalSpacesOrDelimiter(),t}},i.prototype.parseSegment=function(){var t=this._string[this._currentIndex],n=this._pathSegTypeFromChar(t);if(n==window.SVGPathSeg.PATHSEG_UNKNOWN){if(this._previousCommand==window.SVGPathSeg.PATHSEG_UNKNOWN)return null;if((n=this._nextCommandHelper(t,this._previousCommand))==window.SVGPathSeg.PATHSEG_UNKNOWN)return null}else this._currentIndex++;switch(this._previousCommand=n,n){case window.SVGPathSeg.PATHSEG_MOVETO_REL:return new window.SVGPathSegMovetoRel(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_MOVETO_ABS:return new window.SVGPathSegMovetoAbs(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_REL:return new window.SVGPathSegLinetoRel(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_ABS:return new window.SVGPathSegLinetoAbs(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:return new window.SVGPathSegLinetoHorizontalRel(e,this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:return new window.SVGPathSegLinetoHorizontalAbs(e,this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:return new window.SVGPathSegLinetoVerticalRel(e,this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:return new window.SVGPathSegLinetoVerticalAbs(e,this._parseNumber());case window.SVGPathSeg.PATHSEG_CLOSEPATH:return this._skipOptionalSpaces(),new window.SVGPathSegClosePath(e);case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:var i={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new window.SVGPathSegCurvetoCubicRel(e,i.x,i.y,i.x1,i.y1,i.x2,i.y2);case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:return i={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoCubicAbs(e,i.x,i.y,i.x1,i.y1,i.x2,i.y2);case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:return i={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoCubicSmoothRel(e,i.x,i.y,i.x2,i.y2);case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:return i={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoCubicSmoothAbs(e,i.x,i.y,i.x2,i.y2);case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:return i={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoQuadraticRel(e,i.x,i.y,i.x1,i.y1);case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:return i={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoQuadraticAbs(e,i.x,i.y,i.x1,i.y1);case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:return new window.SVGPathSegCurvetoQuadraticSmoothRel(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:return new window.SVGPathSegCurvetoQuadraticSmoothAbs(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_ARC_REL:return i={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegArcRel(e,i.x,i.y,i.x1,i.y1,i.arcAngle,i.arcLarge,i.arcSweep);case window.SVGPathSeg.PATHSEG_ARC_ABS:return i={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegArcAbs(e,i.x,i.y,i.x1,i.y1,i.arcAngle,i.arcLarge,i.arcSweep);default:throw"Unknown path seg type."}};var o=new n,r=new i(t);if(!r.initialCommandIsMoveTo())return[];for(;r.hasMoreData();){var h=r.parseSegment();if(!h)return[];o.appendSegment(h)}return o.pathSegList})}(); diff --git a/build/newcanvas/script.js b/build/newcanvas/script.js new file mode 100644 index 0000000..46dfb89 --- /dev/null +++ b/build/newcanvas/script.js @@ -0,0 +1 @@ +!function(){"use strict";const e=(e,t)=>{if(t||(t=Z.ctx),t.globalCompositeOperation="eraser"===e.getAttribute("class")?"destination-out":"source-over","path"===e.nodeName){t.strokeStyle=e.getAttribute("stroke"),t.lineWidth=e.getAttribute("stroke-width"),t.beginPath();for(let o=0;o{Z.seekbarMove&&Z.seekbarMove(e)},o=o=>{if(Z.rewindCache.length>=Z.fastUndoLevels&&Z.rewindCache.pop(),Z.rewindCache.unshift(Z.ctx.getImageData(0,0,600,500)),e(o),Z.timeedit&&Z.position!==Z.svg.childNodes.length-1)Z.svg.insertBefore(o,Z.svg.childNodes[Z.position+1]);else{for(let e=Z.svg.childNodes.length-1;e>Z.position;e--)Z.svg.removeChild(Z.svg.childNodes[e]);Z.svg.appendChild(o),Z.position=Z.svg.childNodes.length-1,t(1)}},n=(e,t)=>{const o=document.createElementNS("http://www.w3.org/2000/svg",e);return t&&Object.keys(t).forEach(e=>{t[e]&&o.setAttribute(e,t[e])}),o},a=e=>{o(n("rect",{class:e,x:0,y:0,width:600,height:500,fill:Z.background})),Z.lastrect=Z.position},r=()=>{let e=!1;for(let t=Z.svg.childNodes.length-1;t>0;t--){const o=Z.svg.childNodes[t];e||t>Z.position?Z.svg.removeChild(o):"rect"===o.nodeName&&t<=Z.position&&(e=!0,"eraser"===o.getAttribute("class")&&Z.svg.removeChild(o))}},i=(e,t,o,n)=>{const{ctxDisp:a}=Z;a.strokeStyle=Z.lastcolor,a.lineWidth=Z.size,a.beginPath(),a.moveTo(e,t),a.lineTo(o,n),a.stroke()},s=e=>{e&&Z.svgDisp.insertBefore(Z.path,Z.svgDisp.firstChild)},d=e=>"#"===e[0]?4===e.length?[...e.substr(1,3)+"F"].map(e=>parseInt(e+e,16)):(e+"FF").substr(1,8).match(/.{2}/g).map(e=>parseInt(e,16)):"rgba"===e.substr(0,4)?e.match(/[\d\.]+/g).map((e,t)=>3===t?Math.floor(255*parseFloat(e)):parseInt(e,10)):"rgb"===e.substr(0,3)?(e+255).match(/[\d\.]+/g).map(e=>parseInt(e,10)):[0,0,0,255],l=e=>"#"+e.map((e,t)=>t<3?("0"+e.toString(16)).slice(-2):"").join(""),c=e=>l(d(e)),f=e=>{const[t,o,n]=e.map(e=>e>10?Math.pow((e/255+.055)/1.055,2.4):e/255/12.92),[a,r,i]=[(.4124*t+.3576*o+.1805*n)/.95047,.2126*t+.7152*o+.0722*n,(.0193*t+.1192*o+.9505*n)/1.08883].map(e=>e>.008856?Math.pow(e,1/3):7.787*e+16/116);return[116*r-16,500*(a-r),200*(r-i)]},u=e=>document.getElementById(e),p=(e,t)=>{if(u("newcanvasyo").classList.contains("sandbox")||window.gameInfo&&window.gameInfo.friend)return l([...e]);const o=t.slice(0).map(t=>((e,t)=>{const o=f(e),n=f(t),a=n[0]-o[0],r=n[1]-o[1],i=n[2]-o[2];return Math.sqrt(a**2*2+r**2+i**2)})([...e],d(t))),n=Math.min(...o),a=t[o.indexOf(n)];return c(a)},g=(e,t)=>{const o=Z.ctx.getImageData(e,t,1,1).data;return o[3]>0?p(o,Z.palette):Z.background},h=e=>{e||(e=Z.svg.childNodes.length-1);for(let t=e;t>0;t--){if("rect"===Z.svg.childNodes[t].nodeName)return t}return 0},m=e=>String.fromCharCode(e>>24&255,e>>16&255,e>>8&255,255&e),b=e=>{const t="eraser"===e;Z.transparent=t,Z.canvas.style.background=t?"none":e,e=t?"#ffffff":c(e),Z.background=e,Z.svg.querySelectorAll(".eraser").forEach(t=>t.setAttribute("path"===t.nodeName?"stroke":"fill",e))},w=(e,t)=>{const{length:o}=e;if(o<2)return;if(t.pathSegList.initialize(t.createSVGPathSegMovetoAbs(e[0].x,e[0].y)),!window.options.smoothening){for(let o=1;o1){let e=!1;if(Math.abs(p-l)>=Math.PI/4)t.pathSegList.appendItem(t.createSVGPathSegLinetoAbs(r.x,r.y));else if(e&&c/g>=.4&&c/g<=2.5){const e={x:o.x+Math.cos(n)*c*.4,y:o.y+Math.sin(n)*c*.4},a={x:r.x-Math.cos(h)*g*.4,y:r.y-Math.sin(h)*g*.4};t.pathSegList.appendItem(t.createSVGPathSegCurvetoCubicAbs(r.x,r.y,e.x,e.y,a.x,a.y))}else t.pathSegList.appendItem(t.createSVGPathSegLinetoAbs(r.x,r.y)),e=!0}n=h}const a=e[o-1];t.pathSegList.appendItem(t.createSVGPathSegLinetoAbs(a.x,a.y))},v=e=>new Uint8Array([...e].map(e=>e.charCodeAt(0))),y=(e,t)=>{const o=e<<8|t;return o>32767?o-65536:o},k=e=>{const{pako:t}=window,o=e[0];let a;if(4===o)e=t.inflate(e.subarray(1)),a=0;else if(3===o)e=v(t.inflate(e.subarray(1),{to:"string"})),a=0;else{if(2!==o)throw new Error(`Unsupported version: ${o}`);a=1}const r=n("svg",{xmlns:"http://www.w3.org/2000/svg",version:"1.1",width:600,height:500}),i={color:"#000000",size:14,x:0,y:0,pattern:0},s=[],d=`rgb(${e[a]}, ${e[a+1]}, ${e[a+2]})`;r.background=d,r.appendChild(n("rect",{class:"eraser",x:0,y:0,width:600,height:500,fill:d}));for(let t=a+4;t[...Z.svg.childNodes].splice(Z.lastrecte(t)),S=e=>{const o=new DataView(e),n=o.getUint32(0);if(2303741511!==n)throw new Error(`Invalid PNG format: ${m(n)}`);for(let n=8;n{Z.fileInput||(Z.fileInput=document.createElement("input"),Z.fileInput.style.position="absolute",Z.fileInput.style.top="-1000px",Z.fileInput.type="file",Z.fileInput.accept=".png",document.body.appendChild(Z.fileInput),Z.fileInput.addEventListener("change",e=>{const t=new FileReader;t.onload=()=>S(t.result),e.currentTarget.files[0]&&t.readAsArrayBuffer(e.currentTarget.files[0])},!1)),Z.fileInput.click()},A=e=>{const t=new XMLHttpRequest;if(t.open("GET",e,!0),!("responseType"in t))return alert("Your browser is too old for this");t.responseType="arraybuffer",t.onload=()=>S(t.response),t.send()},x=()=>Z.svg.childNodes.length-1,E=(e,t)=>{Z.locked||(Z.brushCursor||(Z.brushCursor=n("circle",{"stroke-width":"1",stroke:"#000",fill:"none"}),Z.svgDisp.appendChild(Z.brushCursor),Z.brushCursor2=n("circle",{"stroke-width":"1",stroke:"#fff",fill:"none"}),Z.svgDisp.appendChild(Z.brushCursor2),Z.eyedropperCursor=n("image",{width:16,height:16,visibility:"hidden"}),Z.eyedropperCursor.setAttributeNS("http://www.w3.org/1999/xlink","href","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARklEQVR4XoXRwQoAIAgEUf//pzeGDgq5G3PrCQqVbIAqsDz9WM2qhTX4GZgPV+JpSFxAC0PwbeVZZIpMgXvAMwoj4U9B3wGySxvzk6ZjvwAAAABJRU5ErkJggg=="),Z.svgDisp.appendChild(Z.eyedropperCursor)),void 0!==e&&(Z.brushCursor.setAttribute("cx",e),Z.brushCursor.setAttribute("cy",t),Z.brushCursor2.setAttribute("cx",e),Z.brushCursor2.setAttribute("cy",t),Z.eyedropperCursor.setAttribute("x",e-1),Z.eyedropperCursor.setAttribute("y",t-15)),Z.brushCursor.setAttribute("r",Z.size/2+.5),Z.brushCursor2.setAttribute("r",Z.size/2-.5))},D=(e,t,o)=>{let{x:n,y:a}=t,r=o.x-n,i=o.y-a;if(0!==r||0!==i){var s=((e.x-n)*r+(e.y-a)*i)/(r*r+i*i);s>1?(n=o.x,a=o.y):s>0&&(n+=r*s,a+=i*s)}return(r=e.x-n)*r+(i=e.y-a)*i},T=()=>{if(Z.locked)return;Z.unsaved=!0;const e=Z.points.length>2?(({points:e,smoothening:t})=>{const o=e.length,n=new("undefined"!=typeof Uint8Array?Uint8Array:Array)(o);let a=0,r=o-1;const i=[],s=[];for(n[a]=n[r]=1;r;){let o,s=0;for(let t=a+1;ts&&(o=t,s=n)}s>t&&(n[o]=1,i.push(a,o,o,r)),r=i.pop(),a=i.pop()}for(let t=0;t{Z.isStroking&&T(),Z.locked=!0,E(-100,-100)},N=(e,t)=>{const o=(()=>{const e=[];for(let t=0;t<256;t++){let o=t;for(let e=0;e<8;e++)o=1&o?3988292384^o>>>1:o>>>1;e.push(o)}return e})();let n=-1;for(let t=0;t>>8^o[255&(n^e.charCodeAt(t))];if(t)for(let e=0;e>>8^o[255&(n^t.charCodeAt(e))];return(-1^n)>>>0},P=e=>d(e).map(e=>String.fromCharCode(e)).join(""),M=e=>String.fromCharCode(e>>8&255,255&e),_=e=>{const{pako:t}=window,o=[P(Z.background)],n={color:P("#000000"),size:14,x:-1,y:-1,pattern:0};return e.childNodes.forEach(e=>{if("path"===e.nodeName){const t="eraser"===e.getAttribute("class")?"ÿÿÿ\0":P(e.getAttribute("stroke")),a=e.getAttribute("stroke-width"),r=e.pattern||0;t===n.color&&a===n.size||(o.push(M(-1)),o.push(M(100*a)),o.push(t),n.color=t,n.size=a),r!==n.pattern&&(o.push(M(-3)),o.push(M(r)),o.push("\0\0\0\0"),n.pattern=r),n.x=e.orig[0].x,n.y=e.orig[0].y,o.push(M(n.x)),o.push(M(n.y));for(let t=1;t[...e].map(e=>String.fromCharCode(e)).join(""))(t.deflate(v(o.join(""))))},B=(o,n,a)=>{r(),t(1);const i=document.createElement("canvas");i.width=o,i.height=n;const s=i.getContext("2d");if(Z.transparent||(s.fillStyle=Z.background,s.fillRect(0,0,o,n)),a)s.drawImage(Z.canvas,0,0,o,n);else{s.lineJoin=s.lineCap="round",s.scale(o/600,n/500);for(let t=0;t{Z.isPlaying&&(Z.isAnimating&&(Z.isAnimating=!1,Z.svgDisp.removeChild(Z.path),e(Z.animatePath),Z.position++,o||t(Z.position/(Z.svg.childNodes.length-1))),Z.isPlaying=!1)},j=()=>{if(!Z.isPlaying)return;const o=Z.svg.childNodes.length-1;let{delay:n}=Z,a=0;if(Z.position{if(!Z.locked){if(Z.rewindCache.length=0,Z.position===Z.svg.childNodes.length-1){if(0===Z.position)return t(1);Z.position=0,t(0),e(Z.svg.childNodes[0])}Z.isPlaying=!0,j()}},G=t=>{if(Z.locked)return;let o=-1;if($(!0),t!==Z.position){if(tZ.position&&(o=Z.position);if(-1!==o){if(t-o>=Z.fastUndoLevels)Z.rewindCache.length=0;else{const{length:e}=Z.rewindCache,n=Math.min(e,t-o+e-Z.fastUndoLevels);Z.rewindCache.splice(e-n,n)}for(let n=o+1;n<=t;n++)t-n{if(!Z.locked){var e=Z.svg.childNodes.length-1;Z.position{if(e||(e=Z.pngBase64,t=".png",Z.unsaved=!1),Z.saveLink||(Z.saveLink=document.createElement("a"),document.body.appendChild(Z.saveLink)),"download"in Z.saveLink){Z.saveLink.href=e;const o=new Date;Z.saveLink.download=["DrawingInTime_",o.getFullYear(),"_",(101+o.getMonth()+"").slice(-2),(100+o.getDate()+"").slice(-2),"_",(100+o.getHours()+"").slice(-2),(100+o.getMinutes()+"").slice(-2),(100+o.getSeconds()+"").slice(-2),t].join(""),Z.saveLink.click()}else window.open(e);return!0},H=(e,t)=>Z.colors[e]=t,V=e=>Z.seekbarMove=e,U=e=>{Z.size=e,E()},q=e=>{if(!Z.brushCursor)return;const t=e?"hidden":"visible",o=e?"visible":"hidden";Z.brushCursor.setAttribute("visibility",t),Z.brushCursor2.setAttribute("visibility",t),Z.eyedropperCursor.setAttribute("visibility",o)},K=(e,t)=>{if(Z.locked)return;if(!Z.isStroking)throw new Error("StrokeAdd without StrokeBegin!");const o=Z.points[Z.points.length-1];o.x===e&&o.y===t||(Z.blot&&(Z.path.pathSegList.removeItem(1),Z.blot=!1),Z.path.pathSegList.appendItem(Z.path.createSVGPathSegLinetoAbs(e,t)),navigator.userAgent.match(/\bPresto\b/)?s(!1):i(o.x,o.y,e,t),Z.points.push({x:e,y:t}))},Y=(e,t,o)=>{if(Z.locked)return;o?Z.lastleft=o:o=Z.lastleft;let a=o?Z.colors[0]:Z.colors[1];const r="eraser"===a?a:null;a="eraser"===a?Z.background:a,Z.path=n("path",{class:r,stroke:a,"stroke-width":Z.size,"stroke-linejoin":"round","stroke-linecap":"round",fill:"none"}),Z.lastcolor=a,Z.path.pattern=Z.pattern,Z.path.pathSegList.appendItem(Z.path.createSVGPathSegMovetoAbs(e,t)),Z.path.pathSegList.appendItem(Z.path.createSVGPathSegLinetoAbs(e,t+.001)),navigator.userAgent.match(/\bPresto\b/)?s(!0):i(e,t,e,t+.001),Z.points=[],Z.points.push({x:e,y:t}),Z.blot=!0,Z.isStroking=!0},X=()=>{Z.locked||Z.position>0&&(G(Z.position-1),t(Z.position/(Z.svg.childNodes.length-1)))},F=()=>Z.locked=!1,J=e=>v(atob(e)),W=e=>{const t=new XMLHttpRequest;t.open("POST","https://api.imgur.com/3/image"),t.onload=()=>{let o=t.responseText;try{o=JSON.parse(o)}catch(e){}if(o.success){const e=new XMLHttpRequest;e.open("POST","https://api.imgur.com/3/image/"+o.data.deletehash),e.setRequestHeader("Authorization","Client-ID 4809db83c8897af");const t=new FormData;t.append("description","Playback: http://grompe.org.ru/drawit/#"+o.data.id),e.send(t)}e(o)},t.onerror=t=>e(`error: ${t}`),t.setRequestHeader("Authorization","Client-ID 4809db83c8897af");const o=new FormData;o.append("image",new Blob([J(Z.pngBase64.substr(22)).buffer],{type:"image/png"})),o.append("type","file"),o.append("title","Made with Drawing in Time"),o.append("description","http://grompe.org.ru/drawit/"),t.send(o)},Q={Normal:["#000000","#444444","#999999","#ffffff","#603913","#c69c6d","#ffdab9","#ff0000","#ffd700","#ff6600","#16ff00","#0fad00","#00ffff","#0247fe","#ec008c","#8601af","#fffdc9"],Sepia:["#402305","#503315","#604325","#705335","#806345","#907355","#a08365","#b09375","#bfa284","#cfb294","#dfc2a4","#ffe2c4"],Grayscale:["#000000","#ffffff","#151515","#2a2a2a","#3f3f3f","#555555","#6a6a6a","#7f7f7f","#949494","#aaaaaa","#bfbfbf","#d4d4d4","#e9e9e9"],"Black and white":["#ffffff","#000000"],CGA:["#555555","#000000","#0000aa","#5555ff","#00aa00","#55ff55","#00aaaa","#55ffff","#aa0000","#ff5555","#aa00aa","#ff55ff","#aa5500","#ffff55","#aaaaaa","#ffffff"],Gameboy:["#8bac0f","#9bbc0f","#306230","#0f380f"],Neon:["#ffffff","#000000","#adfd09","#f3f315","#feac09","#fe0bab","#ad0bfb","#00abff"],Thanksgiving:["#673718","#3c2d27","#c23322","#850005","#c67200","#77785b","#5e6524","#cfb178","#f5e9ce"],Holiday_old:["#3d9949","#7bbd82","#7d1a0c","#bf2a23","#fdd017","#00b7f1","#bababa","#ffffff"],"Valentine's":["#2d1014","#ffffff","#600d17","#c2113a","#b71d1d","#e54d5a","#ff7d63","#fd8647","#fed067","#ffe4b7","#fdc0c6"],Halloween:["#444444","#000000","#999999","#ffffff","#603913","#c69c6d","#7a0e0e","#b40528","#fd2119","#fa5b11","#faa611","#ffd700","#602749","#724b97","#bef202","#519548","#b2bb1e"],"the blues":["#b6cbe4","#618abc","#d0d5ce","#82a2a1","#92b8c1","#607884","#c19292","#8c2c2c","#295c6f"],Spring:["#9ed396","#57b947","#4d7736","#365431","#231302","#3e2409","#a66621","#a67e21","#ebbb49","#ffc0cb","#ffffff"],Beach:["#1ca4d2","#65bbe2","#6ab7bf","#94cbda","#9cbf80","#d2e1ab","#b8a593","#d7cfb9","#dc863e","#f7dca2"],"Tide Pool":["#ffe8b9","#fad489","#ffb44c","#d6b1de","#b197a8","#e5f2ff","#a1ffb8","#53e6ef","#3ad3a8","#1ca4d2","#2271a2"],"Colors of 2016":["#91a7d0","#f6cac9","#eb9587","#776a5f","#d1c2ab","#a39d9d","#648589"],Bee:["#000000","#7a5c00","#b58800","#eab618","#f6de97","#ffffff"],"Colors of 2017":["#86af49","#44883d","#1f4478","#0062a3","#00939a","#59c9d5","#8a9a9a","#5f7278"],"Fire and Ice":["#520909","#b40528","#fd2119","#faa611","#ffe96a","#ffffff","#69ddff","#1c8ae5","#0a3fa9","#040526"],"Canyon Sunset":["#fce3ca","#feb789","#f27c8a","#af5081","#8e6dae","#5f4a8b","#2e1b50"],Juice:["#f3ab54","#ec5e66","#ab5871","#f2a19b","#f9f4d4","#fadfb7","#869e3c","#cbdd7e","#fced95"],Tropical:["#f68357","#fbc040","#fefa56","#fef0f5","#90fc51","#07f182","#1d6ab2","#12041b","#2f0946"],"Grimby Grays":["#000000","#ffffff","#2f3032","#252422","#545758","#4b4a46","#797d80","#71706c","#9ea1a4","#979692","#c4c8cb","#d7d6d2","#dee1e4","#f0efeb"],"DawnBringer 16":["#140c1c","#442434","#30346d","#4e4a4e","#854c30","#346524","#d04648","#757161","#597dce","#d27d2c","#8595a1","#6daa2c","#d2aa99","#6dc2ca","#dad45e","#deeed6"],"Fury Road":["#020c16","#023745","#08616d","#36d4b6","#0afef6","#fce173","#e29f30","#b56942","#ad3f16","#893f1d"],Candy:["#06063c","#4f95ff","#68f9ff","#fffef9","#ff96f8","#ff44d3","#793abd"],Holiday:["#e91434","#97200a","#c66a20","#fdbe30","#688625","#004f28","#112825","#1c69bf","#6096d3","#a5c4e6","#f7d9f0","#f6f6f6"],Blues:["#929aa8","#896868","#546c7d","#633d3d","#284660","#421f29","#232e3f","#0f1328"],"Sin City":["#ffffff","#ff0000","#000000"],"Lucky Clover":["#ffffff","#fcf4c4","#f7b307","#fc8404","#cd7a14","#9bf23e","#40d910","#34900b","#0c442c"],"D's Exclusive":["#000000","#717474","#ffffff","#f25b99","#e4965e","#ffc416","#ffe38f","#0074d9","#09a3ec","#12d1ff","#bcf5ff","#0ee446"],"Retina Burn":["#bc0bff","#ff0b11"],Easter:["#9678ba","#bc9ff0","#e4ccff","#ffa1f1","#fbd0ee","#e6f2ff","#aaedfb","#f4dc7b","#fdfabd","#a1ef85","#ddf7a8"],Neapolitan:["#3f3245","#ff5c98","#ecb2a4","#fff7e1"]},Z={container:null,svg:n("svg",{xmlns:"http://www.w3.org/2000/svg",version:"1.1",width:"600",height:"500"}),canvas:document.createElement("canvas"),canvasDisp:document.createElement("canvas"),svgDisp:n("svg",{version:"1.1",width:"600",height:"500","pointer-events":"none"}),svgHist:null,path:null,points:null,pngBase64:null,lastrect:0,position:0,isStroking:!1,isPlaying:!1,size:14,smoothening:1,palette:Q.Normal,patternCache:{},delay:100,unsaved:!1,background:"#fffdc9",transparent:!1,colors:["#000000","eraser"],fastUndoLevels:10,rewindCache:[],bindContainer:e=>{Z.container=e,Z.canvas.width=600,Z.canvas.height=500,Z.canvas.style.background=Z.background,Z.ctx=Z.canvas.getContext("2d"),Z.ctx.lineJoin=Z.ctx.lineCap="round",Z.container.appendChild(Z.canvas),navigator.userAgent.match(/\bPresto\b/)?Z.DrawDispLine=Z.DrawDispLinePresto:(Z.canvasDisp.width=600,Z.canvasDisp.height=500,Z.ctxDisp=Z.canvasDisp.getContext("2d"),Z.ctxDisp.lineJoin=Z.ctxDisp.lineCap="round",Z.container.appendChild(Z.canvasDisp)),Z.container.appendChild(Z.svgDisp);const t=n("rect",{class:"eraser",x:0,y:0,width:600,height:500,fill:Z.background});Z.svg.appendChild(t)},packPlayback:_,unpackPlayback:k,findLastRect:h,cutHistoryBeforeClearAndAfterPosition:r,makePng:B,fromPng:S,fromUrl:A,fromLocalFile:C,setBackground:b,setColor:H,setSize:U,drawSvgElement:e,updateView:L,drawDispLinePresto:s,drawDispLine:i,strokeBegin:Y,strokeEnd:T,strokeAdd:K,clearWithColor:a,addToSvg:o,undo:X,redo:z,moveSeekbar:t,setSeekbarMove:V,getSeekMax:x,seek:G,play:O,playTimer:j,pause:$,moveCursor:E,showEyedropperCursor:q,eyedropper:g,requestSave:R,uploadToImgur:W,lock:I,unlock:F},ee={rectangle:{},touchSingle:!1,lastTouch:{},lastSeenColorToHighlight:Z.background,brushSizes:[2,6,14,42],timerStart:0},te=e=>{e.preventDefault();const t=[...e.currentTarget.classList].filter(e=>e.startsWith("size-"))[0].match(/\d+/)[0];U(t);const o=u("tools").querySelector(".sel");if(o&&o.classList.remove("sel"),e.currentTarget.classList.add("sel"),!Z.isStroking)return;T();const n=Z.points[Z.points.length-1];Y(n.x,n.y)},oe=e=>{e.preventDefault(),u("play").classList.remove("pause"),z()},ne=e=>{ee.chooseBackground=e,e?(u("colors").classList.add("setbackground"),u("setbackground").classList.add("sel")):(u("colors").classList.remove("setbackground"),u("setbackground").classList.remove("sel"))},ae=e=>{e.preventDefault(),ne(!ee.chooseBackground)},re=e=>{e.preventDefault(),a("eraser"),u("newcanvasyo").classList.contains("sandbox")&&(ee.timerStart=Date.now())},ie=e=>{e.preventDefault(),u("play").classList.remove("pause"),X()},se=()=>u("wacom")&&u("wacom").penAPI&&u("wacom").penAPI.isWacom?u("wacom").penAPI.pointerType:0,de=()=>{const{colors:e}=Z;["primary","secondary"].forEach((t,o)=>{"eraser"===e[o]?(u(t).style.backgroundColor="pink",u(t).classList.add("eraser")):(u(t).style.backgroundColor=e[o],u(t).classList.remove("eraser"))})},le=e=>{if(e.touches||0===e.button||2===e.button){e.preventDefault();const t=e.currentTarget;let o=t.style.backgroundColor;ee.chooseBackground?("eraser"!==t.id&&b(o),ne(!1)):("eraser"===t.id&&(o="eraser"),2===e.button||3===se()?H(1,o):H(0,o),de())}},ce=e=>{e.stopPropagation(),e.preventDefault(),Z.isPlaying?(u("play").classList.remove("pause"),$()):(u("play").classList.add("pause"),O())},fe=e=>{e.altKey||(e.currentTarget.classList.remove("hidecursor"),q(!1),e.currentTarget.removeEventListener("mousemove",fe))},ue=e=>{const{options:t}=window;if(document.activeElement instanceof HTMLInputElement)return!0;if(18===e.keyCode)navigator.userAgent.match(/\bPresto\b/)||u("svgContainer").classList.add("hidecursor"),q(!0),u("svgContainer").addEventListener("mousemove",fe);else if(e.keyCode==="Q".charCodeAt(0))e.preventDefault(),t.colorDoublePress=!t.colorDoublePress;else if(e.keyCode==="Z".charCodeAt(0)||8===e.keyCode&&Z.unsaved)e.preventDefault(),u("play").classList.remove("pause"),X();else if(e.keyCode==="Y".charCodeAt(0))e.preventDefault(),u("play").classList.remove("pause"),z();else if(e.keyCode==="X".charCodeAt(0)){e.preventDefault();const[t,o]=Z.color;H(0,o),H(1,t),de()}else if(e.keyCode==="B".charCodeAt(0)){if(u("setbackground").hidden)return;e.preventDefault(),ne(!ee.chooseBackground)}else if(e.keyCode!=="E".charCodeAt(0)||e.ctrlKey||e.metaKey)if(e.keyCode>=48&&e.keyCode<=57&&!e.ctrlKey&&!e.metaKey&&t.colorNumberShortcuts){e.preventDefault();let o=48===e.keyCode?9:e.keyCode-49;(e.shiftKey||t.colorDoublePress&&Z.prevColorKey===o)&&(o+=8),Z.prevColorKey=o,t.colorDoublePress&&(Z.prevColorKeyTimer&&clearTimeout(Z.prevColorKeyTimer),Z.prevColorKeyTimer=setTimeout(()=>Z.prevColorKey=-1,500));const n=u("colors").querySelectorAll("b");if(o=49&&e.keyCode<=52&&(e.ctrlKey||e.metaKey)?(e.preventDefault(),u("brush"+(e.keyCode-49)).click()):32!==e.keyCode||!e.ctrlKey&&!e.metaKey||e.altKey||e.shiftKey||ce(e);else{e.preventDefault();for(let e=0;e{18===e.keyCode&&(u("svgContainer").classList.remove("hidecursor"),q(!1))},ge=()=>{if(Z.position{e.preventDefault(),ge()||(B(600,500,!0),R())},me=e=>{e.preventDefault(),u("svgContainer").classList.add("loading"),C(),u("svgContainer").classList.remove("loading")},be=e=>{e.preventDefault(),ge()||(u("imgur").childNodes[0].nodeValue="Uploading...",u("imgur").disabled=!0,B(600,500,!0),W(e=>{if(u("imgur").childNodes[0].nodeValue="Upload to imgur",u("popup").classList.add("show"),u("popuptitle").childNodes[0].nodeValue="Imgur upload result",e&&e.success)Z.unsaved=!1,u("imgururl").href=`http://imgur.com/${e.data.id}`,u("imgururl").childNodes[0].nodeValue="Uploaded image",u("imgurdelete").href=`http://imgur.com/delete/${e.data.deletehash}`,u("imgurerror").childNodes[0].nodeValue="",window.inforum&&(window.frameElement.ownerDocument.getElementById("input-comment").value+=`![](http://i.imgur.com/${e.data.id}.png)`);else{const t=e.data?`Imgur error: ${e.data.error}`:`Error: ${e}`;u("imgurerror").childNodes[0].nodeValue=t}u("imgur").disabled=!1}))},we=e=>{e.preventDefault();const t=x();let o=e.touches?e.touches[0].pageX-ee.rectangle.left-34:e.pageX-ee.rectangle.left-pageXOffset-34;o=Math.min(Math.max(-10,o),492);const n=Math.round((o+10)/502*t);o=n/t*502-10,u("knob").classList.add("smooth"),u("knob").style.marginLeft=o+"px",G(n),u("play").classList.remove("pause")},ve=e=>{e.button&&(e.touches||e.touches.length)||(e.preventDefault(),window.removeEventListener("mouseup",ve),window.removeEventListener("touchend",ve),window.removeEventListener("mousemove",we),window.removeEventListener("touchmove",we))},ye=e=>{(0===e.button||e.touches&&1===e.touches.length)&&(ee.rectangle=u("seekbar").getBoundingClientRect(),we(e),window.addEventListener("mouseup",ve),window.addEventListener("touchend",ve),window.addEventListener("mousemove",we),window.addEventListener("touchmove",we))},ke=e=>{const t=Math.floor(502*e-10);e>0?u("knob").classList.add("smooth"):u("knob").classList.remove("smooth"),u("knob").style.marginLeft=t+"px",e>=1&&u("play").classList.remove("pause")},Le=e=>e.preventDefault(),Se=e=>{u("palettename").childNodes[0].nodeValue=e;const t=Q[e];Z.palette=t;const o=u("palette"),n=o.querySelectorAll("b");n.forEach(e=>o.removeChild(e));const a=n[n.length-1];t.forEach(e=>{const t=document.createElement("b");t.style.backgroundColor=e,t.addEventListener("mousedown",le),t.addEventListener("touchend",le),t.addEventListener("contextmenu",Le),o.appendChild(t),o.appendChild(a)})},Ce=e=>{if(e.touches||0===e.button){e.preventDefault();const t=e.currentTarget.childNodes[0].nodeValue;Se(t)}},Ae=e=>{(e.touches||0===e.button)&&(u("palettechooser").classList.remove("open"),window.removeEventListener("mousedown",Ae),window.removeEventListener("touchend",Ae))},xe=e=>{if(e.touches||0===e.button){e.preventDefault();const t=u("palettechooser");t.classList.toggle("open"),t.classList.contains("open")&&setTimeout(()=>{window.addEventListener("mousedown",Ae),window.addEventListener("touchend",Ae)},1);const o=Object.keys(Q);if(t.childNodes.length{n.fillStyle=e,n.fillRect(8*t+1,1,8,8)});const r=document.createElement("div");r.appendChild(document.createTextNode(o[a])),r.style.backgroundImage=`url("${e.toDataURL()}")`,r.style.backgroundRepeat="no-repeat",r.style.backgroundPosition="center 35px",r.addEventListener("mousedown",Ce),r.addEventListener("touchend",Ce),t.appendChild(r)}}}},Ee=e=>{e.preventDefault(),u("popup").classList.remove("show")},De=e=>e.preventDefault(),Te=()=>!!Z.isPlaying&&(Z.Pause(),u("play").classList.remove("pause"),!0),Ie=e=>{if(e.preventDefault(),!Z.isStroking)return;const t=e.pageX-ee.rectangle.left-pageXOffset,o=e.pageY-ee.rectangle.top-pageYOffset;K(t,o)},Ne=e=>{const{options:t}=window;0!==e.button&&2!==e.button||(e.preventDefault(),Z.isStroking&&T(),t.hideCross&&u("svgContainer").classList.remove("hidecursor"),window.removeEventListener("mouseup",Ne),window.removeEventListener("mousemove",Ie))},Pe=e=>{const{options:t}=window;if(0===e.button||2===e.button){if(Z.isStroking)return Ne(e);if(Te())return;e.preventDefault(),ee.rectangle=e.currentTarget.getBoundingClientRect();const o=e.pageX-ee.rectangle.left-pageXOffset,n=e.pageY-ee.rectangle.top-pageYOffset;if(e.altKey)H(e.button?1:0,g(o,n)),de();else{const a=0===e.button&&3!==se();t.hideCross&&u("svgContainer").classList.add("hidecursor"),Y(o,n,a),window.addEventListener("mouseup",Ne),window.addEventListener("mousemove",Ie)}}},Me=()=>E(-100,-100),_e=e=>{const{options:t}=window;ee.rectangle=e.currentTarget.getBoundingClientRect();const o=e.pageX-ee.rectangle.left-pageXOffset,n=e.pageY-ee.rectangle.top-pageYOffset;if(E(o,n),t.colorUnderCursorHint&&!Z.isStroking){const e=g(o,n);if(ee.stSeenColorToHighlight!==e){const t=u("colors").querySelector("b.hint");t&&t.classList.remove("hint");const o=Z.palette.indexOf(e);if(o>=0){u("colors").querySelectorAll("b")[o].classList.add("hint")}}ee.lastSeenColorToHighlight=e}},Be=()=>{if(!ee.touchSingle)return;const e=ee.lastTouch.pageX-ee.rectangle.left,t=ee.lastTouch.pageY-ee.rectangle.top;Y(e,t,!0),ee.touchSingle=!1},$e=e=>{if(1!==e.touches.length)return;if(Be(),e.preventDefault(),!Z.isStroking)return;const t=e.touches[0].pageX-ee.rectangle.left,o=e.touches[0].pageY-ee.rectangle.top;Z.StrokeAdd(t,o)},je=e=>{0===e.touches.length&&(Be(),e.preventDefault(),window.removeEventListener("touchend",je),window.removeEventListener("touchmove",$e),T())},Oe=e=>{if(1===e.changedTouches.length&&1===e.touches.length){const{pageX:t,pageY:o}=e.changedTouches[0];Math.abs(t-ee.lastTouch.pageX)<10&&Math.abs(o-ee.lastTouch.pageY)<10&&(u("play").classList.remove("pause"),t{if(1===e.touches.length){if(Te())return;ee.rectangle=e.currentTarget.getBoundingClientRect(),ee.touchSingle=!0,ee.lastTouch=e.touches[0],window.addEventListener("touchend",je),window.addEventListener("touchmove",$e)}else ee.touchSingle&&3===e.touches.length&&(ee.lastTouch=e.touches[1],window.addEventListener("touchend",Oe)),ee.touchSingle=!1,window.removeEventListener("touchend",je),window.removeEventListener("touchmove",$e),Z.isStroking&&T()},ze=e=>{if(!Z.unsaved)return;const t="You haven't saved the drawing. Abandon?";return e.returnValue=t,t},Re=e=>{Z.isStroking&&e.preventDefault()},He=e=>alert(e),Ve=(e,t,o)=>{const{options:n}=window,a=new XMLHttpRequest;a.open(e,t),o.header&&a.setRequestHeader(o.header[0],o.header[1]),o.retry=5,a.timeout=15e3,a.ontimeout=()=>{if(o.retry>0){if(!n.retryEnabled)return;document.body.style.cursor="progress",o.retry--,Ve(e,t,o)}else document.body.style.cursor="",o.error()},a.onload=()=>{"/play/skip.json"!==t||"Sorry, but we couldn't find your current game."!==a.error?"/play/exit.json"!==t||"Sorry, but we couldn't find your current game."!==a.error?o.load(a.responseText):location.pathname="/":location.reload()},a.onerror=()=>{o.error?o.error(a):o.load(a)},o.obj?a.send(JSON.stringify(o.obj)):a.send(),document.body.style.cursor=""},Ue=e=>{e.preventDefault(),window.frameElement.ownerDocument.querySelector(".v--modal-overlay").outerHTML=""},qe=e=>{const t=document.createElement("textarea");return t.innerHTML=e,t.value},Ke=()=>{const{getLocalStorageItem:e}=window;u("bookmark").disabled=!0;const t=e("gpe_gameBookmarks",{}),o=window.gameInfo.caption;t[window.gameInfo.gameid]={time:Date.now(),caption:o?qe(o):""},localStorage.setItem("gpe_gameBookmarks",JSON.stringify(t))},Ye=e=>{13===e.keyCode&&(e.preventDefault(),u("submitcaption").click())},Xe=()=>{let e=(ee.timerStart-Date.now())/1e3;try{window.timerCallback&&window.timerCallback(e)}catch(e){}e=Math.abs(e);const t=`0${Math.floor(e/60)}`.slice(-2);e=`0${Math.floor(e%60)}`.slice(-2),u("timer").childNodes[0].nodeValue=`${t}:${e}`},Fe=()=>{const{incontest:e,gameInfo:t,drawing_aborted:o,vertitle:n}=window;e&&!o&&Ve("POST","/contests/exit.json",{load:()=>alert("You have missed your contest.")}),t.drawfirst&&!o&&Ve("POST","/play/abort-start.json",{obj:{game_token:t.gameid},load:()=>alert("You have missed your Draw First game.\nIt has been aborted."),error:()=>alert("You have missed your Draw First game.\nI tried aborting it, but an error occured. :(")}),ee.timerStart=Date.now(),u("newcanvasyo").className="sandbox",window.timerCallback=()=>{},Xe(),document.title="Sandbox - Drawception",u("gamemode").innerHTML="Sandbox",u("headerinfo").innerHTML=`Sandbox with ${n}`;try{history.replaceState({},null,"/sandbox/")}catch(e){}F()},Je=()=>{const{gameInfo:e,incontest:t}=window;if(t){if(!confirm("Quit the contest? Entry coins will be lost!"))return;return u("exit").disabled=!0,void Ve("POST","/contests/exit.json",{load:()=>{u("exit").disabled=!1,window.drawing_aborted=!0,Fe(),document.location.pathname="/contests/"},error:()=>{u("exit").disabled=!1,alert("Server error. :( Try again?")}})}if(e.drawfirst){if(!confirm("Abort creating a draw first game?"))return;return u("exit").disabled=!0,void Ve("POST","/play/abort-start.json",{obj:{game_token:e.gameid},load:()=>{u("exit").disabled=!1,window.drawing_aborted=!0,Fe(),document.location.pathname="/create/"},error:()=>{u("exit").disabled=!1,alert("Server error. :( Try again?")}})}confirm("Really exit?")&&(u("exit").disabled=!0,Ve("POST","/play/exit.json",{obj:{game_token:e.gameid},load:()=>{u("exit").disabled=!1,Fe()}}))},We=e=>{e.preventDefault(),window.top.location.href="https://drawception.com/"},Qe=e=>{const t=document.implementation.createHTMLDocument("");t.body.innerHTML=e;const o=t.querySelector("draw-app")||t.querySelector("describe")||{getAttribute:()=>!1},n=e=>t.querySelector(e);return{error:(e=>!!e&&e.src)(n(".error")),gameid:o.getAttribute("game_token"),blitz:"true"===o.getAttribute(":blitz_mode"),nsfw:"true"===o.getAttribute(":nsfw"),friend:"true"!==o.getAttribute(":game_public"),drawfirst:"true"===o.getAttribute(":draw_first"),timeleft:1*o.getAttribute(":seconds"),caption:o.getAttribute("phrase"),image:o.getAttribute("img_url"),palette:o.getAttribute("theme_id"),bgbutton:"true"===o.getAttribute(":bg_layer"),playerurl:"/profile/",avatar:null,coins:"-",pubgames:"-",friendgames:"-",notifications:"-",drawinglink:(e=>!!e&&e.src)(n(".gamepanel img")),drawingbylink:(e=>!!e&&[e.textContent.trim(),e.href])(n("#main p a")),drawncaption:(e=>!!e&&e.src)(n("h1.game-title")),notloggedin:null!==n("form.form-login"),limitreached:!1,html:e}},Ze={default:["Normal","#fffdc9"],theme_thanksgiving:["Thanksgiving","#f5e9ce"],halloween:["Halloween","#444444"],theme_cga:["CGA","#ffff55"],shades_of_grey:["Grayscale","#e9e9e9"],theme_bw:["Black and white","#ffffff"],theme_gameboy:["Gameboy","#9bbc0f"],theme_neon:["Neon","#00abff"],theme_sepia:["Sepia","#ffe2c4"],theme_valentines:["Valentine's","#ffccdf"],theme_blues:["the blues","#295c6f"],theme_spring:["Spring","#ffffff"],theme_beach:["Beach","#f7dca2"],theme_beach_2:["Tide Pool","#2271a2"],theme_coty_2016:["Colors of 2016","#648589"],theme_bee:["Bee","#ffffff"],theme_coty_2017:["Colors of 2017","#5f7278"],theme_fire_ice:["Fire and Ice","#040526"],theme_coty_2018:["Canyon Sunset","#2e1b50"],theme_juice:["Juice","#fced95"],theme_tropical:["Tropical","#2f0946"],theme_grimby_grays:["Grimby Grays","#f0efeb"],theme_fury_road:["Fury Road","#893f1d"],theme_candy:["Candy","#793abd"],theme_holiday_2:["Holiday","#f6f6f6"],theme_blues_2:["Blues","#0f1328"],theme_sin_city:["Sin City","#000000"],theme_lucky_clover:["Lucky Clover","#0c442c"],theme_drawception:["D's Exclusive","#0ee446"],theme_retina_burn:["Retina Burn","#ff0b11"],theme_easter:["Easter","#ddf7a8"],theme_neapolitan:["Neapolitan","#fff7e1"]},et=()=>{const{gameInfo:e,inforum:t}=window;if(e.notloggedin)return u("start").parentNode.innerHTML='Login Register';e.avatar&&(u("infoavatar").src=e.avatar),u("infoprofile").href=e.playerurl,u("infocoins").innerHTML=e.coins,u("infogames").innerHTML=e.pubgames,u("infofriendgames").innerHTML=e.friendgames||0,u("infonotifications").innerHTML=e.notifications,t&&(document.querySelector(".headerright").hidden=!0)},tt=e=>{const{gameInfo:t}=window;e<1?(document.title="[TIME'S UP!] Playing Drawception",t.image||window.timesup?window.submitting||(t.image?nt():Fe()):(u("newcanvasyo").classList.add("locked"),I(),ee.timerStart+=15e3,Xe(),window.timesup=!0)):document.title=`[${`0${Math.floor(e/60)}`.slice(-2)}:${`0${Math.floor(e%60)}`.slice(-2)}] Playing Drawception`,window.alarm&&!window.playedWarningSound&&e<=(t.blitz?5:61)&&e>0&&(window.alarm.play(),window.playedWarningSound=!0)},ot=()=>{const{options:e,gameInfo:o,incontest:n,vertitle:a}=window;if(u("skip").disabled=o.drawfirst||n,u("report").disabled=o.drawfirst||n,u("exit").disabled=!1,u("start").disabled=!1,u("bookmark").disabled=o.drawfirst||n,u("options").disabled=!0,u("timeplus").disabled=n,u("submit").disabled=!1,u("headerinfo").innerHTML=`Playing with ${a}`,u("drawthis").classList.add("onlyplay"),u("emptytitle").classList.remove("onlyplay"),window.submitting=!1,window.drawing_aborted=!1,o.error)return alert(`Play Error:\n${o.error}`),Fe();if(o.limitreached)return alert("Play limit reached!"),Fe();u("gamemode").innerHTML=n?"Contest":`${(o.friend?"Friend ":"Public ")+(o.nsfw?"Not Safe For Work (18+) ":"safe for work ")+(o.blitz?"BLITZ ":"")}Game`,u("drawthis").innerHTML=o.caption||o.drawfirst&&"(Start your game!)"||"",u("tocaption").src="";const r=u("newcanvasyo");r.className="play",o.friend&&r.classList.add("friend"),u("palettechooser").className=o.friend?"":"onlysandbox",o.nsfw&&r.classList.add("nsfw"),o.blitz&&r.classList.add("blitz"),r.classList.add(o.image?"captioning":"drawing"),Z.isStroking&&T(),F();for(let e=Z.svg.childNodes.length-1;e>0;e--)Z.svg.removeChild(Z.svg.childNodes[e]);G(0),t(1),Z.unsaved=!1;const{palette:i}=o;if(o.image)u("tocaption").src=o.image.length<=30?"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAD6AQMAAAAho+iwAAAABlBMVEWAQED///94jotxAAABiklEQVR4Xu3W0UrCUBjA8eOO5CLK7VxLzDWFrjK6Eaha8FHuppfwBRJvdjlMIK/K3qA3OZBBd/UIm9UL2O2inMJBptNuog/6/h4Q2Y8J387Y2KIoiqIoiqIoiuIxXnbI5cmXSiJjD3LmFyrGY46PqVAx/HPDv9/w3wsJTTgapuDkcEIQMFxzo937S8+F5OkWI2IKymQl3yiZ6j8zYsRY6vUYDcOfGkuMknE5/aQAMczX9O+iKIrKJWuSxliQqT61hOmMucsYK6uzLWfDenF34EXhOX+s377KLCZcs1bxhNXQqnAvrExWM8vvY3amORCNsplu2nZPWKdj1tecTHZZLA97ZnjBB/XrkWIZWT+bsmTowp+7FHSnyMi7CpuMrWcwNsMMxnJzrCUbwwq/2/MLJb8lP4L2zVHJ35Bp1rE8Uc2bALoNHQvcoNG3Yf5Pm6EnHG50Ye0YmiG4V08LmWD7wmF9gJwFgoHbnZzNSDE/Co3orSB2YGsbovAgaD9vlkB/WbkbdQVWMNxR1Ddnf4eSZpHZYAAAAABJRU5ErkJggg==":o.image,u("caption").value="",u("caption").focus(),u("caption").setAttribute("maxlength",45),u("usedchars").textContent="45";else{const e=(e=>{if("theme_roulette"===e){alert("Warning: Drawception roulette didn't give a theme. ANBT will choose a random palette."),delete Q.Roulette;const e=Object.keys(Ze),t=e[e.length*Math.random()<<0];return Q.Roulette=Q[Ze[t][0]],["Roulette",Ze[t][1]]}if(e)return Ze[e.toLowerCase()]})(i);e?(Se(e[0]),b(e[1]),Z.color=[Q[e[0]][0],"eraser"],de()):(i?alert(`Error, please report! Unknown palette: '${i}'.\nAre you using the latest ANBT version?`):alert("Error, please report! Failed to extract the palette.\nAre you using the latest ANBT version?"),u("submit").disabled=!0),u("setbackground").hidden=!o.bgbutton}(e.timeoutSound&&!o.blitz||e.timeoutSoundBlitz&&o.blitz)&&(window.playedWarningSound=!1,window.alarm=new Audio(window.alarmSoundOgg),window.alarm.volume=e.timeoutSoundVolume/100),ee.timerStart=Date.now()+1e3*o.timeleft,window.timerCallback=tt,et(),window.timesup=!1,Xe()},nt=()=>{const{incontest:e,friendgameid:t}=window,o=e?"/contests/play/":`/play/${t?`${t}/`:""}`;try{location.pathname!==o&&history.replaceState({},null,o)}catch(e){}Ve("GET",`${o}?${Date.now()}`,{load:e=>{window.gameInfo=e?Qe(e):{error:"Server returned a blank response :("},ot()},error:e=>{window.gameInfo={error:`Server error: ${e.statusText}`},ot()}})},at=()=>{confirm("Report this panel?")&&Ve("POST","/play/flag.json",{obj:{game_token:window.gameInfo.gameid},load:()=>{u("report").disabled=!1,nt()}})},rt=()=>Z.unsaved&&!confirm("You haven't saved the drawing. Abandon?"),it=()=>{rt()||(u("skip").disabled=!0,Ve("POST","/play/skip.json",{obj:{game_token:window.gameInfo.gameid},load:()=>nt(),error:()=>{u("skip").disabled=!1,nt()}}))},st=()=>{rt()||(u("start").disabled=!0,nt())},dt=()=>{const{incontest:e,gameInfo:t}=window,o=u("caption").value;if(!o)return u("caption").focus(),alert("You haven't entered a caption!");window.submitting=!0,u("submitcaption").disabled=!0,Ve("POST",e?"/contests/submit-caption.json":"/play/describe.json",{obj:{game_token:t.gameid,title:o},load:e=>{try{e=JSON.parse(e)}catch(t){e={error:e}}e.error?(u("submitcaption").disabled=!1,"object"==typeof e.error?alert(`Error! Please report this data:\ngame: ${t.gameid}\n\nresponse: \n${JSON.stringify(e.error)}`):alert(e.error)):e.message?(u("submitcaption").disabled=!1,alert(e.message)):e.url&&((e=>{const{options:t,gameInfo:o}=window;if(!t.bookmarkOwnCaptions)return;const n=window.getLocalStorageItem("gpe_gameBookmarks",{});n[o.gameid]={time:Date.now(),caption:`"${e}"`,own:!0},localStorage.setItem("gpe_gameBookmarks",JSON.stringify(n))})(o),location.replace(e.url))},error:()=>{u("submitcaption").disabled=!1,alert("Server error. :( Try again?")}})},lt=()=>{const{incontest:e,gameInfo:t,options:o}=window,n=ee.timerStart-Date.now()>6e4;o.submitConfirm&&n&&!confirm("Ready to submit this drawing?")||(u("submit").disabled=!0,B(300,250,!0),o.backup&&localStorage.setItem("anbt_drawingbackup_newcanvas",Z.pngBase64),window.submitting=!0,Ve("POST",e?"/contests/submit-drawing.json":"/play/draw.json",{obj:{game_token:t.gameid,panel:Z.pngBase64},load:e=>{try{e=JSON.parse(e)}catch(t){e={error:e}}e.error?(u("submit").disabled=!1,"object"==typeof e.error?alert(`Error! Please report this data:\ngame: ${t.gameid}\n\nresponse:\n${JSON.stringify(e.error)}`):alert(e.error)):e.message?(u("submit").disabled=!1,alert(e.message)):e.url&&(window.onbeforeunload=()=>{},location.replace(e.url))},error:()=>{u("submit").disabled=!1,alert("Server error. :( Try again?")}}))},ct=()=>{let{gameInfo:e}=window;e.friend&&(u("timeplus").disabled=!0,Ve("POST","/play/exit.json",{obj:{game_token:e.gameid},load:()=>{Ve("GET",`/play/${e.gameid}/?${Date.now()}`,{load:t=>{u("timeplus").disabled=!1,e=t?Qe(t):{error:"Server returned a blank response :("},ee.timerStart=Date.now()+1e3*e.timeleft},error:()=>{u("timeplus").disabled=!1,alert("Server error. :( Try again?")}})},error:()=>{u("timeplus").disabled=!1,alert("Server error. :( Try again?")}}))},ft=()=>{u("usedchars").textContent=45-u("caption").value.length},ut=()=>{const{gameInfo:e,vertitle:t,options:o}=window;if(e.drawingbylink){const[t,n]=e.drawingbylink,a=`Drawing`;u("headerinfo").innerHTML=`${a} by ${t}`,document.title=`${t}'s drawing - Drawception`,e.drawncaption&&(u("drawthis").innerHTML=`"${e.drawncaption}"`,u("drawthis").classList.remove("onlyplay"),u("emptytitle").classList.add("onlyplay")),o.autoplay&&O()}else u("headerinfo").innerHTML=`Sandbox with ${t}`,u("drawthis").classList.add("onlyplay");et()};window.needToGoDeeper=()=>{const{options:e,insandbox:t,panelid:o}=window;if(window.onerror=(e,t,o)=>{e.toString().includes("periodsToSeconds")||e.toString().match(/script error/i)||alert(o?`${e}\nline: ${o}`:e)},e.newCanvasCSS){const t=document.getElementsByTagName("head")[0]||document.documentElement,o=document.createElement("style");o.type="text/css";const n=document.createTextNode(e.newCanvasCSS);o.appendChild(n),t.appendChild(o)}if(e.enableWacom){const t=document.createElement("object"),o=u("wacomContainer");t.setAttribute("id","wacom"),t.setAttribute("type","application/x-wacomtabletplugin"),t.setAttribute("width","1"),t.setAttribute("height","1"),o.appendChild(t),e.fixTabletPluginGoingAWOL&&(()=>{const e=u("wacom"),t=u("wacomContainer");window.onblur=()=>{1===t.childNodes.length&&t.removeChild(e)},window.onfocus=()=>{0===t.childNodes.length&&t.appendChild(e)}})()}if((()=>{const{options:e,inforum:t}=window;if(t){u("quit").addEventListener("click",We);const e=document.createElement("button");e.href="/",e.setAttribute("class","submit exit"),e.title="Exit",e.textContent="Exit",e.addEventListener("click",Ue),u("submit").parentNode.insertBefore(e,u("submit").nextSibling)}u("exit").addEventListener("click",Je),u("skip").addEventListener("click",it),u("start").addEventListener("click",st),u("report").addEventListener("click",at),u("bookmark").addEventListener("click",Ke),u("submit").addEventListener("click",lt),u("submitcaption").addEventListener("click",dt),e.enterToCaption&&u("caption").addEventListener("keydown",Ye),u("caption").addEventListener("change",ft),u("caption").addEventListener("keydown",ft),u("caption").addEventListener("input",ft),u("timeplus").addEventListener("click",ct)})(),t){if(o)Ve("GET",`/panel/drawing/${o}/-/`,{load:e=>{window.gameInfo=Qe(e),A(`${window.gameInfo.drawinglink}?anbt`),ut()},error:()=>{alert("Error loading the panel page. Please try again.")}});else if(Ve("GET","/sandbox/",{load:e=>{window.gameInfo=Qe(e),ut()},error:()=>{}}),e.backup){const e=localStorage.getItem("anbt_drawingbackup_newcanvas");e&&(S(J(e.substr(22)).buffer),localStorage.removeItem("anbt_drawingbackup_newcanvas"))}}else u("newcanvasyo").className="play",nt();/iPad|iPhone/.test(navigator.userAgent)&&(Z.fastUndoLevels=3),window.$=()=>{throw alert("Some additional script conflicts with ANBT new canvas, please disable it."),window.$=null,new Error("Script conflict with ANBT new canvas")}},window.options||(window.options={}),Z.bindContainer(u("svgContainer")),u("svgContainer").addEventListener("mousedown",Pe),u("svgContainer").addEventListener("mousemove",_e),u("svgContainer").addEventListener("touchstart",Ge),u("svgContainer").addEventListener("mouseleave",Me),u("svgContainer").addEventListener("contextmenu",De),u("import").addEventListener("click",me),u("export").addEventListener("click",he),u("imgur").addEventListener("click",be),document.querySelectorAll(".brush").forEach((e,t)=>{e.classList.add(`size-${ee.brushSizes[t]}`),e.addEventListener("mousedown",te),e.addEventListener("click",te)}),u("colors").querySelectorAll("b").forEach(e=>{e.addEventListener("mousedown",le),e.addEventListener("touchend",le),e.addEventListener("contextmenu",Le)}),u("setbackground").addEventListener("click",ae),u("undo").addEventListener("click",ie),u("redo").addEventListener("click",oe),u("trash").addEventListener("click",re),V(ke),u("knob").addEventListener("mousedown",ye),u("knob").addEventListener("touchstart",ye),u("seekbar").addEventListener("mousedown",ye),u("seekbar").addEventListener("touchstart",ye),u("play").addEventListener("mousedown",ce),u("play").addEventListener("touchstart",ce),u("palettename").addEventListener("mousedown",xe),u("palettename").addEventListener("touchend",xe),u("popupclose").addEventListener("click",Ee),document.addEventListener("keyup",pe),document.addEventListener("keydown",ue),window.addEventListener("contextmenu",Re),window.addEventListener("error",He),window.addEventListener("beforeunload",ze),ee.timerStart=Date.now(),setInterval(Xe,500),window.anbtReady&&window.anbtReady()}(); diff --git a/build/newcanvas/style.css b/build/newcanvas/style.css new file mode 100644 index 0000000..0595e62 --- /dev/null +++ b/build/newcanvas/style.css @@ -0,0 +1 @@ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}body{margin:0;background:#ccc;font-family:"Nunito", sans-serif;background:#555}.noselect{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#newcanvasyo{background:#555}#newcanvasyo.play .onlysandbox,#newcanvasyo.play .onlyfriend,#newcanvasyo.friend .no-friend,#newcanvasyo.nsfw .no-nsfw,#newcanvasyo.sandbox .onlycaption,#newcanvasyo.drawing .onlycaption,#newcanvasyo.captioning .onlydraw,#newcanvasyo.locked .onlyunlocked,#newcanvasyo.sandbox .onlyplay{display:none}#myheader{font-size:15pt;line-height:49px;height:49px;border-bottom:1px solid black;background:#333;text-align:center;color:#999;overflow:hidden;display:block}#myheader img{vertical-align:middle}@media screen and (max-width: 999px){#myheader{font-size:11pt}}#headercont{max-width:1280px;margin:auto}.headerleft{float:left;margin-left:50px}.headerright{float:right;margin-right:50px}#infoavatar{border:1px solid #555}.headerbutton{font-size:10.5pt;font-weight:bold;padding:4px 10px;background:#2e2e2e;vertical-align:middle;text-decoration:none;cursor:pointer}.headerbutton.active{background:#248;color:#eee}.headerbutton.active:hover{background:#236}.headerbutton img{margin:0 3px}a{color:#999}a.prominent{color:#7af}a.prominent:hover{color:#9ff}#menu{display:none;position:absolute;background:#666;color:#ddd;z-index:2;border:2px solid #777;box-shadow:5px 5px 5px rgba(0,0,0,0.5);list-style-type:none;padding:0;margin:0;font-size:13pt;text-align:left}#menu.open{display:block}#menu li{line-height:49px;height:49px;padding:0 10px;cursor:pointer}#menu li:hover{background:#777}#menu hr{margin:5px;border:1px solid #444}hr{border:1px solid #888}#noscriptwarn{color:#800;padding:10px;font-size:15pt;background:#faa;text-align:center}#drawthis{padding:4px;color:#fff;font-size:26pt;font-family:"Yanone Kaffeesatz";text-align:center;vertical-align:middle;display:table-cell;width:1%}#drawthis::before{color:#aaa;font-size:14pt;font-family:"Nunito";content:"Draw this:";padding-right:1em;width:1%;text-align:right;vertical-align:middle;margin-left:-10%}.captioning #drawthis:before{content:"Describe this drawing:";padding-right:0;width:100%;text-align:center;margin-left:0}.captioning #drawingtocaption{display:block}#emptytitle{height:10px}#bl{width:600px;margin:auto;position:relative}#toolpane{text-align:center;padding:0 10px}@media screen and (min-width: 1000px){#toolpane{width:180px;height:500px;position:absolute;margin-left:-200px}}@media screen and (max-width: 999px){#toolpane{width:580px;margin:auto}}@media screen and (max-width: 999px){#toolpane:after{display:block;content:"";clear:both}}#infopanel{text-align:center;padding:0 10px}@media screen and (min-width: 1000px){#infopanel{width:180px;height:500px;position:absolute;margin-left:600px;top:0}}@media screen and (max-width: 999px){#infopanel{width:580px;margin:40px 0}}.panel{border:2px solid #888;border-radius:15px;border-top:none;background:#444;margin:3px 0}#timer{font-size:26pt;color:#aaa;width:176px;float:left}@media screen and (max-width: 999px){#timer{float:right;width:260px}}.blitz #timer{border-color:#dda;color:#dda;background:#982}.locked #timer{border-color:#faa;color:#faa;background:#744}.locked #lockwarn{display:block}#timeplus{background:url("/img/icon-time_plus_60.png") center no-repeat;width:16px;height:16px;vertical-align:middle;padding:10px;margin-left:3px;border-radius:3px;cursor:pointer}#timeplus:hover{background-color:#222}#palettechooser{display:none;position:absolute;width:450px;background:#666;color:#ddd;z-index:1;-webkit-columns:3;-moz-columns:3;columns:3;-webkit-column-gap:0;-moz-column-gap:0;column-gap:0;border:2px solid #777;box-shadow:5px 5px 5px rgba(0,0,0,0.5)}#palettechooser.open{display:block}#palettechooser div{padding:10px 0 20px;cursor:pointer;break-inside:avoid}#palettechooser div:hover{background-color:#777;color:#eee}#colors{width:100px;float:left}#colors b{float:left;display:block;width:50px;height:44px;line-height:42px;cursor:pointer;counter-increment:color}#colors b.hint::after{content:"";position:absolute;border-radius:50%;margin:32px 2px;width:5px;height:5px;background-color:white;border:1px solid #000}#colors b.hint::before{opacity:0.6}#colors b::before{font-size:10pt;opacity:0.3;color:#000;content:counter(color)}#colors b:hover::before{opacity:0.4;color:#fff}#colors b:nth-child(10){counter-reset:color 1}#colors b:nth-child(n+10){counter-increment:color}#colors b:nth-child(n+10)::before{content:"↑" counter(color)}#colors #eraser::before{content:"eraser"}#colors.setbackground b::before{content:"BG " counter(color)}#colors.setbackground b:nth-child(n+10)::before{content:"BG↑" counter(color)}#colors.setbackground #eraser::before{content:"cancel"}#colors div{clear:both}@media screen and (max-width: 999px){#colors{width:300px;margin-left:5px;clear:left;float:left}}#palettename{color:#aaa;font-size:10pt;padding:0 5px;cursor:pointer}#palettename:hover{color:#ccc}#tools{width:68px;float:right;padding:5px 0}#tools hr{width:93%;margin:8px auto}@media screen and (max-width: 999px){#tools button{margin:0}}@media screen and (max-width: 999px){#tools{width:260px}}#tools button,#openmenu{width:55px;height:44px;vertical-align:top;margin:3px 0;padding:0}#brush0,#brush1,#brush2,#brush3,#setbackground,#undo,#redo,#trash,#play,#knob,#eraser,.eraser,#openmenu{background-image:url("data:image/png;base64,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")}#brush0{background-position:-2px -8px}#brush0.sel{background-position:-2px -68px}#brush1{background-position:-62px -8px}#brush1.sel{background-position:-62px -68px}#brush2{background-position:-122px -8px}#brush2.sel{background-position:-122px -68px}#brush3{background-position:-182px -8px}#brush3.sel{background-position:-182px -68px}#setbackground{background-position:-242px -8px}#setbackground.sel{background-position:-242px -68px}#undo{background-position:-302px -8px}#redo{background-position:-362px -8px}#trash{background-position:-422px -8px}#eraser{background-position:-365px -68px;background-color:pink}.eraser{background-position:-425px -68px;background-color:pink}#openmenu{background-position:-425px -68px;background-color:#999}#openmenu::after{content:"\25BC";color:#fff;opacity:0.6}#openmenu:hover{background-color:#57a}#openmenu:active{background-color:#555}button,#play{border:none;border-radius:4px;background-color:#eee}button:hover,#play:hover{background-color:#acf}button:active,#play:active{background-color:#aaa}button:disabled{background-color:#aaa}#primary,#secondary{display:inline-block;width:50px;border-radius:0 0 0 13px;color:#fff}@media screen and (max-width: 999px){#primary,#secondary{width:150px}}#secondary{border-radius:0 0 13px 0}#indicator{display:flex}#gamemode{font-size:13pt;color:#aaa;width:176px;float:left}.nsfw #gamemode{border-color:#faa;color:#faa;background:#744}.nsfw #drawthis{background:#744}#gamebuttons{font-size:13pt;color:#aaa;width:176px;float:left;padding:5px 0}#gamebuttons button{padding:10px;margin:3px 0;font-size:10pt}@media screen and (max-width: 999px){#gamebuttons{width:390px;margin-left:5px}}.submit{font-size:16pt;width:180px;height:50px;background-color:#6d6}.submit:hover{background-color:#1b7}@media screen and (min-width: 1000px){.submit{position:absolute;bottom:10px;left:10px;width:180px;height:50px}}.guidelines{color:#fff;font-size:10pt}.guidelines ul{margin:0;padding:0 1em;text-align:left}.guidelines li{color:#bbb}#seekbar{width:530px;height:0px;margin:20px 50px 0;background:#333;border:10px solid #333;border-radius:10px;cursor:pointer}#knob{position:absolute;width:48px;height:38px;margin-top:-19px;margin-left:492px;border-radius:10px;background-color:#fbb;background-position:-305px -71px}#knob.smooth{transition:margin-left 0.1s ease-out}#knob:hover{background-color:#fdd}#play{background-position:-486px -11px;position:absolute;width:48px;height:38px;margin:-19px -59px;border-radius:4px}#play.pause{background-position:-546px -11px}#wacomContainer{position:absolute;top:-10px;left:-10px}@media screen and (max-width: 999px){.hideonsmall{display:none}}.loadingbg{background:#fffdc9 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAAAVCAQAAAAJQsNPAAAAkUlEQVR4Xu2WgQbAMBBD9/8/vQ14KpC4UneqYurZ2eNGnnfouWLdsjwce59ikFCdk75ricGIQSEmhekcvRfE8s8jMJ9CdI7camLGYoQ0XzNjJukgRsaJkWFiKqhiLf8x2DAx6EExTV0Mns/sIPZnnBinJJb1h4p21BXrzSOpTZVF3VGCN4lB07U53+6H5Yr1zAdssNOdvbG/lgAAAABJRU5ErkJggg==") center no-repeat}#svgContainer{width:600px;height:500px;margin:auto;cursor:crosshair;touch-action:pinch-zoom}#svgContainer.hidecursor{cursor:none}#svgContainer.loading *{display:none}#svgContainer canvas,#svgContainer svg{position:absolute}#drawingtocaption{display:none;width:550px;height:400px;padding:50px 25px;margin:auto;background:#484848;text-align:center}#tocaption{width:300px;height:250px}#caption{font-family:"Nunito", sans-serif;margin-top:100px;width:100%;font-size:20pt}#usedchars{text-align:right;opacity:0.4}#lockwarn{display:none;position:absolute;width:500px;height:400px;padding:50px;opacity:0.8;background:#aaa;color:#a00;text-align:center;z-index:1;font-size:35pt;text-shadow:1px 1px 1px #fff, -1px -1px 1px #fff, -1px 1px 1px #fff, 1px -1px 1px #fff}#popup{display:none;position:absolute;width:400px;height:300px;margin:50px;padding:50px;opacity:0.95;background:#444;color:#ddd;border-radius:15px;text-align:center;z-index:1;font-size:20pt}#popup.show{display:block}#popuptitle{color:#999;font-size:25pt;margin:10px}#popupclose{display:block;position:absolute;width:50px;height:50px;background:#c44;color:#ddd;right:0;top:0;text-align:center;border-radius:13px;cursor:pointer}#popupclose:hover{background:#e77;color:#eee}#popupclose::before{content:"\d7";font-size:30pt;line-height:40px} diff --git a/copypost.py b/copypost.py index 940d479..5d4d089 100644 --- a/copypost.py +++ b/copypost.py @@ -1,6 +1,6 @@ import re,subprocess -template = """#### [Script updated to %s](https://github.com/grompe/Drawception-ANBT#drawception-anbt-): +template = """#### [Script updated to %s](https://github.com/EnderDragonneau/Drawception-ANBT#drawception-anbt-): %s diff --git a/drawception-anbt.user.js b/drawception-anbt.user.js deleted file mode 100644 index aeb6393..0000000 --- a/drawception-anbt.user.js +++ /dev/null @@ -1,2847 +0,0 @@ -// ==UserScript== -// @name Drawception ANBT -// @author Grom PE -// @namespace http://grompe.org.ru/ -// @version 1.156.2019.06 -// @description Enhancement script for Drawception.com - Artists Need Better Tools -// @downloadURL https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/drawception-anbt.user.js -// @match http://drawception.com/* -// @match https://drawception.com/* -// @grant none -// @run-at document-start -// @license Public domain -// ==/UserScript== -const wrapped = () => { - const SCRIPT_VERSION = '1.156.2019.06' - const NEWCANVAS_VERSION = 53 // Increase to update the cached canvas - const SITE_VERSION = 'f6b35cce' // Last seen site version - - // == DEFAULT OPTIONS == - - const options = { - enableWacom: 0, // Whether to enable Wacom plugin and thus pressure sensitivity support - fixTabletPluginGoingAWOL: 1, // Fix pressure sensitivity disappearing in case of stupid/old Wacom plugin - hideCross: 0, // Whether to hide the cross when drawing - enterToCaption: 0, // Whether to submit caption by pressing Enter - pressureExponent: 0.5, // Smaller = softer tablet response, bigger = sharper - brushSizes: [2, 5, 12, 35], // Brush sizes for choosing via keyboard - chatAutoConnect: 0, // Whether to automatically connect to the chat - backup: 1, - timeoutSound: 0, - timeoutSoundBlitz: 0, - timeoutSoundVolume: 100, - newCanvas: 1, - proxyImgur: 0, - rememberPosition: 0, - ajaxRetry: 1, - localeTimestamp: 0, - autoplay: 1, // Whether to automatically start playback of a recorded drawing - submitConfirm: 1, - smoothening: 1, - autoBypassNSFW: 0, - colorNumberShortcuts: 1, - colorUnderCursorHint: 1, - bookmarkOwnCaptions: 0, - colorDoublePress: 0, - markStalePosts: 1, - newCanvasCSS: '', - forumHiddenUsers: '', - maxCommentHeight: 1000, - useOldFont: true, - useOldFontSize: true, - markdownTools: 1, - anbtDarkMode: 1 - } - - /* - == HOW TO USE == - - Chrome/Iron: (Recommended: all features, best performance) - - add the script in Tampermonkey addon - - or open URL: chrome://extensions then drag and drop this .user.js file on it - - Firefox: add the script in Greasemonkey addon - - Opera 12.x: add the script in "site properties" - - == FEATURES == - General - - Menu buttons in the header for easier access - - No temptation to judge - - An embedded chat - - Automatically retry failed requests to reduce annoying error messages - Canvas: - - Completely new drawing canvas with ability to record and display the drawing process - View game - - Add reverse panels button - - Add "like all" button - - Track new comments - - Show when the game was started - - Ability to favorite panels - Play - - Much faster skipping - - Play modes for those who only caption or only draw - - Enter pressed in caption mode submits the caption - - Ability to bookmark games without participating - - Show your panel position and track changes in unfinished games list - Forum - - Better-looking timestamps with correct timezone - - Clickable drawing panels - - Clickable links - - Show and highlight direct links to forum posts - */ - //let __DEBUG__ - //let prestoOpera - let username - let userid - //let playMode = localStorage.getItem('gpe_playMode') - let inDark = localStorage.getItem('gpe_inDark') - - //playMode = playMode === null ? 0 : parseInt(playMode, 10) - inDark = inDark === null ? 0 : parseInt(inDark, 10) - - //const MODE_ALL = 0 - //const MODE_CAPTION_ONLY = 1 - //const MODE_DRAW_ONLY = 2 - //const availablePlayModes = ['Mode: captions and drawings', 'Mode: only make captions', 'Mode: only draw'] - const alarmSoundOgg = 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAABnHAAAAAAAAHQUSFoBHgF2b3JiaXMAAAAAAUSsAAAAAAAAYG0AAAAAAADJAU9nZ1MAAAAAAAAAAAAAZxwAAAEAAABq35G0DxD/////////////////NQN2b3JiaXMAAAAAAAAAAAEFdm9yYmlzH0JDVgEAAAEAFGNWKWaZUpJbihlzmDFnGWPUWoolhBRCKKVzVlurKbWaWsq5xZxzzpViUilFmVJQW4oZY1IpBhlTEltpIYQUQgehcxJbaa2l2FpqObacc62VUk4ppBhTiEromFJMKaQYU4pK6Jxz0DnmnFOMSgg1lVpTyTGFlFtLKXROQgephM5SS7F0kEoHJXRQOms5lRJTKZ1jVkJquaUcU8qtpphzjIHQkFUAAAEAwEAQGrIKAFAAABCGoSiKAoSGrAIAMgAABOAojuIokiI5kmM5FhAasgoAAAIAEAAAwHAUSZEUy9EcTdIszdI8U5ZlWZZlWZZlWZZd13VdIDRkFQAAAQBAKAcZxRgQhJSyEggNWQUAIAAAAIIowxADQkNWAQAAAQAIUR4h5qGj3nvvEXIeIeYdg9577yG0XjnqoaTee++99x5777n33nvvkWFeIeehk9577xFiHBnFmXLee+8hpJwx6J2D3nvvvfeec+451957752j3kHpqdTee++Vk14x6Z2jXnvvJdUeQuqlpN5777333nvvvffee++9955777333nvvrefeau+9995777333nvvvffee++9995777333nvvgdCQVQAAEAAAYRg2iHHHpPfae2GYJ4Zp56T3nnvlqGcMegqx9557773X3nvvvffeeyA0ZBUAAAgAACGEEFJIIYUUUkghhhhiyCGHHIIIKqmkoooqqqiiiiqqLKOMMsook4wyyiyjjjrqqMPOQgoppNJKC620VFtvLdUehBBCCCGEEEIIIYQQvvceCA1ZBQCAAAAwxhhjjEEIIYQQQkgppZRiiimmmAJCQ1YBAIAAAAIAAAAsSZM0R3M8x3M8x1M8R3RER3RER5RESbRETfREUTRFVbRF3dRN3dRNXdVN27VVW7ZlXdddXddlXdZlXdd1Xdd1Xdd1Xdd1XbeB0JBVAAAIAABhkEEGGYQQQkghhZRSijHGGHPOOSA0ZBUAAAgAIAAAAEBxFEdxHMmRJMmyLM3yLM8SNVMzNVNzNVdzRVd1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' - - const GM_addStyle = css => { - const parent = document.getElementsByTagName('head')[0] || document.documentElement, - style = document.createElement('style'), - textNode = document.createTextNode(css) - style.type = 'text/css' - style.appendChild(textNode) - parent.appendChild(style) - } - /* - canvas integration todo: - - autoskipping captions/drawings - */ - - // Executed on completely empty page. - const setupNewCanvas = (insandbox, url, origpage) => { - const canvasHTML = localStorage.getItem('anbt_canvasHTML') - const canvasHTMLver = localStorage.getItem('anbt_canvasHTMLver') - if (!canvasHTML || canvasHTMLver < NEWCANVAS_VERSION || canvasHTML.length < 10000) { - const req = new XMLHttpRequest() - req.open('GET', 'https://api.github.com/repos/EnderDragonneau/Drawception-ANBT/contents/newcanvas_embedding.html') - req.setRequestHeader('Accept', 'application/vnd.github.3.raw') - req.onload = function() { - if (this.responseText.length < 10000) { - alert(`Error: instead of new canvas code, got this response from GitHub:\n${this.responseText}`) - location.pathname = '/' - } else { - localStorage.setItem('anbt_canvasHTML', this.responseText) - localStorage.setItem('anbt_canvasHTMLver', NEWCANVAS_VERSION) - setupNewCanvas(insandbox, url) - } - } - req.onerror = () => { - alert('Error loading the new canvas code. Please try again.') - location.pathname = '/' - } - req.send() - return - } - const inforum = url.match(/forums\//) - const friendgameid = url.match(/play\/(.+)\//) // Save friend game id if any - const panelid = url.match(/sandbox\/#?([^\/]+)/) - const incontest = url.match(/contests\/play\//) && document.getElementById('canvas-holder') // Handle drawing contests only - const vertitle = `ANBT v${SCRIPT_VERSION}` - - // Disable built-in safety warning - if (incontest) window.onbeforeunload = () => {} - - // Show normal address - const normalurl = insandbox && !inforum ? `/sandbox/${panelid ? `#${panelid[1]}` : ''}` : incontest ? '/contests/play/' : inforum ? url.match(/\/forums\/?.+/) : `/play/${friendgameid ? `${friendgameid[1]}/` : ''}` - try { - if (location.pathname + location.hash !== normalurl) history.pushState({}, document.title, normalurl) - } catch (e) {} - - if (inforum) { - if (document.querySelector('.v--modal-overlay')) document.querySelector('.v--modal-overlay').outerHTML = '' - const div = document.querySelector('.wrapper').children[1] - const iframe = document.createElement('iframe') - const modalOverlay = document.createElement('div') - modalOverlay.setAttribute('class', 'v--modal-overlay') - iframe.id = 'iframe' - iframe.setAttribute('class', 'v--modal-background-click') - modalOverlay.appendChild(iframe) - div.appendChild(modalOverlay) - const iframeContent = iframe.contentWindow - iframeContent.document.open() - iframeContent.anbtReady = () => { - iframeContent.inforum = inforum - iframeContent.insandbox = insandbox - iframeContent.incontest = incontest - iframeContent.options = options - iframeContent.alarmSoundOgg = alarmSoundOgg - iframeContent.vertitle = vertitle - } - iframeContent.document.write(canvasHTML) - iframeContent.document.close() - let iframeFinish = setInterval(() => { - if (typeof document.getElementById('iframe').contentWindow.ID === 'undefined') return - const script = document.createElement('script') - script.textContent = '(' + needToGoDeeper.toString() + ')()' - iframeContent.document.body.appendChild(script) - clearInterval(iframeFinish) - }, 100) - return - } - document.open() - window.anbtReady = () => { - if (friendgameid) window.friendgameid = friendgameid[1] - if (panelid) window.panelid = panelid[1] - window.inforum = inforum - window.insandbox = insandbox - window.incontest = incontest - window.options = options - window.alarmSoundOgg = alarmSoundOgg - window.vertitle = vertitle - // Vue.js makes current page too different to reuse - //if (origpage) window.origpage = origpage; - - const script = document.createElement('script') - script.textContent = `(${needToGoDeeper.toString()})()` - document.body.appendChild(script) - } - document.write(canvasHTML) - document.close() - } - - // To be inserted on new canvas page. No jQuery! - const needToGoDeeper = () => { - const ajax = (type, url, params) => { - const req = new XMLHttpRequest() - req.open(type, url) - if (params.header) req.setRequestHeader(params.header[0], params.header[1]) - params.retry = 5 - req.timeout = 15000 - req.ontimeout = () => { - if (params.retry > 0) { - if (!options.retryEnabled) return - document.body.style.cursor = 'progress' - params.retry-- - ajax(type, url, params) - } else { - document.body.style.cursor = '' - params.error() - } - } - req.onload = () => { - if (url === '/play/skip.json' && req.error === 'Sorry, but we couldn\u0027t find your current game.') { - location.reload() - return - } - if (url === '/play/exit.json' && req.error === 'Sorry, but we couldn\u0027t find your current game.') { - location.pathname = '/' - return - } - params.load(req.responseText) - } - req.onerror = () => { - if (params.error) { - params.error(req) - } else { - params.load(req) - } - } - if (params.obj) { - req.send(JSON.stringify(params.obj)) - } else { - req.send() - } - document.body.style.cursor = '' - return - } - const extractInfoFromHTML = html => { - const doc = document.implementation.createHTMLDocument('') - doc.body.innerHTML = html - let el - let drawapp = doc.querySelector('draw-app') || doc.querySelector('describe') - if (!drawapp) - drawapp = { - getAttribute() { - return false - } - } - - const getel = query => { - el = doc.querySelector(query) - return el - } - return { - error: getel('.error') ? el.textContent.trim() : false, - gameid: drawapp.getAttribute('game_token'), - blitz: drawapp.getAttribute(':blitz_mode') == 'true', - nsfw: drawapp.getAttribute(':nsfw') == 'true', - friend: drawapp.getAttribute(':game_public') != 'true', - drawfirst: drawapp.getAttribute(':draw_first') == 'true', - timeleft: drawapp.getAttribute(':seconds') * 1, - caption: drawapp.getAttribute('phrase'), - image: drawapp.getAttribute('img_url'), - palette: drawapp.getAttribute('theme_id'), - bgbutton: drawapp.getAttribute(':bg_layer') == 'true', - playerurl: '/profile/', - avatar: null, - coins: '-', - pubgames: '-', - friendgames: '-', - notifications: '-', - drawinglink: getel('.gamepanel img') ? el.src : false, - drawingbylink: getel('#main p a') ? [el.textContent.trim(), el.href] : false, - drawncaption: getel('h1.game-title') ? el.textContent.trim() : false, - notloggedin: getel('form.form-login') != null, - limitreached: false, // ??? appears to be redirecting to /play/limit/ which gives "game not found" error - html - } - } - - const getParametersFromPlay = () => { - let url = window.incontest ? '/contests/play/' : '/play/' - if (window.friendgameid) { - url += `${window.friendgameid}/` - window.friendgameid = false - } - try { - if (location.pathname != url) history.replaceState({}, null, url) - } catch (e) {} - if (window.origpage) { - window.gameinfo = extractInfoFromHTML(window.origpage) - handlePlayParameters() - window.origpage = null - return - } - // On Firefox, requesting "/play/" url would immediately return a cached error. - // Firefox, WTF? So we use cache-busting here. - ajax('GET', `${url}?${Date.now()}`, { - load: x => { - if (x === '') { - window.gameinfo = { - error: 'Server returned a blank response :(' - } - } else { - window.gameinfo = extractInfoFromHTML(x) - } - handlePlayParameters() - }, - error: x => { - window.gameinfo = { - error: `Server error: ${x.statusText}` - } - handlePlayParameters() - } - }) - } - - const exitToSandbox = () => { - if (window.incontext && !window.drawing_aborted) { - ajax('POST', '/contests/exit.json', { - load: () => alert('You have missed your contest.') - }) - } - if (window.gameinfo.drawfirst && !window.drawing_aborted) { - ajax('POST', '/play/abort-start.json', { - obj: { - game_token: window.gameinfo.gameid - }, - load: () => alert('You have missed your Draw First game.\nIt has been aborted.'), - error: () => alert('You have missed your Draw First game.\nI tried aborting it, but an error occured. :(') - }) - } - timerStart = Date.now() - ID('newcanvasyo').className = 'sandbox' - timerCallback = () => {} - updateTimer() - document.title = 'Sandbox - Drawception' - ID('gamemode').innerHTML = 'Sandbox' - ID('headerinfo').innerHTML = `Sandbox with ${vertitle}` - try { - history.replaceState({}, null, '/sandbox/') - } catch (e) {} - anbt.Unlock() - } - - const handleCommonParameters = () => { - if (gameinfo.notloggedin) { - ID('start').parentNode.innerHTML = 'Login' + ' Register' - return - } - if (gameinfo.avatar) { - ID('infoavatar').src = gameinfo.avatar - } - ID('infoprofile').href = gameinfo.playerurl - ID('infocoins').innerHTML = gameinfo.coins - ID('infogames').innerHTML = gameinfo.pubgames - ID('infofriendgames').innerHTML = gameinfo.friendgames || 0 - ID('infonotifications').innerHTML = gameinfo.notifications - if (inforum) { - document.querySelector('.headerright').hidden = true - } - } - - const handleSandboxParameters = () => { - if (gameinfo.drawingbylink) { - const playername = gameinfo.drawingbylink[0] - const playerlink = gameinfo.drawingbylink[1] - const replaylink = `Drawing` - ID('headerinfo').innerHTML = `${replaylink} by ${playername}` - document.title = `${playername}'s drawing - Drawception` - if (gameinfo.drawncaption) { - ID('drawthis').innerHTML = `"${gameinfo.drawncaption}"` - ID('drawthis').classList.remove('onlyplay') - ID('emptytitle').classList.add('onlyplay') - } - if (options.autoplay) anbt.Play() - } else { - ID('headerinfo').innerHTML = `Sandbox with ${vertitle}` - ID('drawthis').classList.add('onlyplay') - } - handleCommonParameters() - } - - const handlePlayParameters = () => { - const info = window.gameinfo - ID('skip').disabled = info.drawfirst || window.incontest - ID('report').disabled = info.drawfirst || window.incontest - ID('exit').disabled = false - ID('start').disabled = false - ID('bookmark').disabled = info.drawfirst || window.incontest - ID('options').disabled = true // Not implemented yet! - ID('timeplus').disabled = window.incontest - ID('submit').disabled = false - ID('headerinfo').innerHTML = `Playing with ${vertitle}` - ID('drawthis').classList.add('onlyplay') - ID('emptytitle').classList.remove('onlyplay') - window.submitting = false - window.drawing_aborted = false - if (info.error) { - alert(`Play Error:\n${info.error}`) - return exitToSandbox() - } - if (info.limitreached) { - alert('Play limit reached!') - return exitToSandbox() - } - if (window.incontest) { - ID('gamemode').innerHTML = 'Contest' - } else { - ID('gamemode').innerHTML = `${(info.friend ? 'Friend ' : 'Public ') + (info.nsfw ? 'Not Safe For Work (18+) ' : 'safe for work ') + (info.blitz ? 'BLITZ ' : '')}Game` - } - ID('drawthis').innerHTML = info.caption || (info.drawfirst && '(Start your game!)') || '' - ID('tocaption').src = '' - const newcanvas = ID('newcanvasyo') - newcanvas.className = 'play' - if (info.friend) newcanvas.classList.add('friend') - ID('palettechooser').className = info.friend ? '' : 'onlysandbox' - if (info.nsfw) newcanvas.classList.add('nsfw') - if (info.blitz) newcanvas.classList.add('blitz') - newcanvas.classList.add(info.image ? 'captioning' : 'drawing') - // Clear - if (anbt.isStroking) anbt.StrokeEnd() - anbt.Unlock() - for (let i = anbt.svg.childNodes.length - 1; i > 0; i--) { - const el = anbt.svg.childNodes[i] - anbt.svg.removeChild(el) - } - anbt.Seek(0) - anbt.MoveSeekbar(1) - anbt.unsaved = false - const palettemap = { - default: ['Normal', '#fffdc9'], - theme_thanksgiving: ['Thanksgiving', '#f5e9ce'], - halloween: ['Halloween', '#444444'], - theme_cga: ['CGA', '#ffff55'], - shades_of_grey: ['Grayscale', '#e9e9e9'], - theme_bw: ['Black and white', '#ffffff'], - theme_gameboy: ['Gameboy', '#9bbc0f'], - theme_neon: ['Neon', '#00abff'], - theme_sepia: ['Sepia', '#ffe2c4'], - theme_valentines: ["Valentine's", '#ffccdf'], - theme_blues: ['the blues', '#295c6f'], - theme_spring: ['Spring', '#ffffff'], - theme_beach: ['Beach', '#f7dca2'], - theme_beach_2: ['Tide Pool', '#2271a2'], - theme_coty_2016: ['Colors of 2016', '#648589'], - theme_bee: ['Bee', '#ffffff'], - theme_coty_2017: ['Colors of 2017', '#5f7278'], - theme_fire_ice: ['Fire and Ice', '#040526'], - theme_coty_2018: ['Canyon Sunset', '#2e1b50'], - theme_juice: ['Juice', '#fced95'], - theme_tropical: ['Tropical', '#2f0946'], - theme_grimby_grays: ['Grimby Grays', '#f0efeb'], - theme_fury_road: ['Fury Road', '#893f1d'], - theme_candy: ['Candy', '#793abd'], - theme_holiday_2: ['Holiday', '#f6f6f6'], - theme_blues_2: ['Blues', '#0f1328'], - theme_sin_city: ['Sin City', '#000000'], - theme_lucky_clover: ['Lucky Clover', '#0c442c'], - theme_drawception: ["D's Exclusive", '#0ee446'], - theme_retina_burn: ['Retina Burn', '#ff0b11'], - theme_easter: ['Easter', '#ddf7a8'], - theme_neapolitan: ['Neapolitan', '#fff7e1'] - } - const pal = info.palette - let paldata - if (!info.image) { - // Drawing - if (pal == 'theme_roulette') { - // Since site update, the game reports already chosen palette, - // but apparently this still happens sometimes. ??? - alert("Warning: Drawception roulette didn't give a theme. ANBT will choose a random palette.") - delete palettes.Roulette - const k = Object.keys(palettemap) - const n = k[(k.length * Math.random()) << 0] - palettes.Roulette = palettes[palettemap[n][0]] - paldata = ['Roulette', palettemap[n][1]] - } else { - if (pal) paldata = palettemap[pal.toLowerCase()] - } - if (!paldata) { - if (!pal) { - alert('Error, please report! Failed to extract the palette.\nAre you using the latest ANBT version?') - } else { - alert(`Error, please report! Unknown palette: '${pal}'.\nAre you using the latest ANBT version?`) - } - // Prevent from drawing with a wrong palette - anbt.Lock() - ID('submit').disabled = true - } else { - setPaletteByName(paldata[0]) - anbt.SetBackground(paldata[1]) - anbt.color = [palettes[paldata[0]][0], 'eraser'] - updateColorIndicators() - } - ID('setbackground').hidden = !info.bgbutton - } else { - // Caption - if (info.image.length <= 30) { - // Broken drawing =( - ID('tocaption').src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAD6AQMAAAAho+iwAAAABlBMVEWAQED///94jotxAAABiklEQVR4Xu3W0UrCUBjA8eOO5CLK7VxLzDWFrjK6Eaha8FHuppfwBRJvdjlMIK/K3qA3OZBBd/UIm9UL2O2inMJBptNuog/6/h4Q2Y8J387Y2KIoiqIoiqIoiuIxXnbI5cmXSiJjD3LmFyrGY46PqVAx/HPDv9/w3wsJTTgapuDkcEIQMFxzo937S8+F5OkWI2IKymQl3yiZ6j8zYsRY6vUYDcOfGkuMknE5/aQAMczX9O+iKIrKJWuSxliQqT61hOmMucsYK6uzLWfDenF34EXhOX+s377KLCZcs1bxhNXQqnAvrExWM8vvY3amORCNsplu2nZPWKdj1tecTHZZLA97ZnjBB/XrkWIZWT+bsmTowp+7FHSnyMi7CpuMrWcwNsMMxnJzrCUbwwq/2/MLJb8lP4L2zVHJ35Bp1rE8Uc2bALoNHQvcoNG3Yf5Pm6EnHG50Ye0YmiG4V08LmWD7wmF9gJwFgoHbnZzNSDE/Co3orSB2YGsbovAgaD9vlkB/WbkbdQVWMNxR1Ddnf4eSZpHZYAAAAABJRU5ErkJggg==' - } else { - ID('tocaption').src = info.image - } - ID('caption').value = '' - ID('caption').focus() - ID('caption').setAttribute('maxlength', 45) - ID('usedchars').textContent = '45' - } - if ((options.timeoutSound && !info.blitz) || (options.timeoutSoundBlitz && info.blitz)) { - window.playedWarningSound = false - const alarm = new Audio(window.alarmSoundOgg) - alarm.volume = options.timeoutSoundVolume / 100 - } - timerStart = Date.now() + 1000 * info.timeleft - timerCallback = s => { - if (s < 1) { - document.title = "[TIME'S UP!] Playing Drawception" - if (info.image || window.timesup) { - // If pressed submit before timer expired, let it process or retry in case of error - if (!window.submitting) { - if (info.image) { - getParametersFromPlay() - } else { - // Allow to save the drawing after time's up - exitToSandbox() - } - } - } else { - newcanvas.classList.add('locked') - anbt.Lock() - timerStart += 15000 // 15 seconds to submit - updateTimer() - window.timesup = true - } - } else { - let m1 = Math.floor(s / 60) - let s1 = Math.floor(s % 60) - m1 = `0${m1}`.slice(-2) - s1 = `0${s1}`.slice(-2) - document.title = `[${m1}:${s1}] Playing Drawception` - } - if (alarm && !window.playedWarningSound && s <= (info.blitz ? 5 : 61) && s > 0) { - alarm.play() - window.playedWarningSound = true - } - } - handleCommonParameters() - window.timesup = false - updateTimer() - } - - /*const include = (script, callback) => { - const tag = document.createElement('script') - tag.src = script - tag.onload = callback - document.body.appendChild(tag) - }*/ - - const decodeHTML = html => { - const txt = document.createElement('textarea') - txt.innerHTML = html - return txt.value - } - - const bindCanvasEvents = () => { - const unsavedStopAction = () => anbt.unsaved && !confirm("You haven't saved the drawing. Abandon?") - if (window.inforum) { - ID('quit').addEventListener('click', ({preventDefault}) => { - preventDefault - window.top.location.href = 'https://drawception.com/' - }) - const backForum = document.createElement('button') - backForum.href = '/' - backForum.setAttribute('class', 'submit exit') - backForum.title = 'Exit' - backForum.textContent = 'Exit' - backForum.addEventListener('click', ({preventDefault}) => { - preventDefault - window.frameElement.ownerDocument.querySelector('.v--modal-overlay').outerHTML = '' - }) - ID('submit').parentNode.insertBefore(backForum, ID('submit').nextSibling) - } - ID('exit').addEventListener('click', () => { - if (window.incontest) { - if (!confirm('Quit the contest? Entry coins will be lost!')) return - ID('exit').disabled = true - ajax('POST', '/contests/exit.json', { - load: () => { - ID('exit').disabled = false - window.drawing_aborted = true - exitToSandbox() - document.location.pathname = '/contests/' - }, - error: () => { - ID('exit').disabled = false - alert('Server error. :( Try again?') - } - }) - return - } - if (window.gameinfo.drawfirst) { - if (!confirm('Abort creating a draw first game?')) return - ID('exit').disabled = true - ajax('POST', '/play/abort-start.json', { - obj: { - game_token: window.gameinfo.gameid - }, - load: () => { - ID('exit').disabled = false - window.drawing_aborted = true - exitToSandbox() - document.location.pathname = '/create/' - }, - error: () => { - ID('exit').disabled = false - alert('Server error. :( Try again?') - } - }) - return - } - if (!confirm('Really exit?')) return - ID('exit').disabled = true - ajax('POST', '/play/exit.json', { - obj: { - game_token: window.gameinfo.gameid - }, - load: () => { - ID('exit').disabled = false - exitToSandbox() - } - }) - }) - ID('skip').addEventListener('click', () => { - if (unsavedStopAction()) return - ID('skip').disabled = true - ajax('POST', '/play/skip.json', { - obj: { - game_token: window.gameinfo.gameid - }, - load: () => getParametersFromPlay(), // Postpone enabling skip until we get game info - error: () => { - ID('skip').disabled = false - getParametersFromPlay() - } - }) - }) - ID('start').addEventListener('click', () => { - if (unsavedStopAction()) return - ID('start').disabled = true - getParametersFromPlay() - }) - ID('report').addEventListener('click', () => { - if (!confirm('Report this panel?')) return - ajax('POST', '/play/flag.json', { - obj: { - game_token: window.gameinfo.gameid - }, - load: () => { - ID('report').disabled = false - getParametersFromPlay() - } - }) - }) - ID('bookmark').addEventListener('click', () => { - ID('bookmark').disabled = true - let games = localStorage.getItem('gpe_gameBookmarks') - games = games ? JSON.parse(games) : {} - const caption = window.gameinfo.caption - games[window.gameinfo.gameid] = { - time: Date.now(), - caption: caption ? decodeHTML(caption) : '' - } - localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) - }) - ID('submit').addEventListener('click', () => { - const moreThanMinuteLeft = timerStart - Date.now() > 60000 - if (options.submitConfirm && moreThanMinuteLeft && !confirm('Ready to submit this drawing?')) return - ID('submit').disabled = true - anbt.MakePNG(300, 250, true) - if (options.backup) { - localStorage.setItem('anbt_drawingbackup_newcanvas', anbt.pngBase64) - } - window.submitting = true - let url - if (window.incontest) { - url = '/contests/submit-drawing.json' - } else { - url = '/play/draw.json' - } - ajax('POST', url, { - obj: { - game_token: window.gameinfo.gameid, - panel: anbt.pngBase64 - }, - load: x => { - let o - try { - o = JSON.parse(x) - } catch (e) { - o = { - error: x - } - } - if (o.error) { - ID('submit').disabled = false - if (typeof o.error === 'object') { - alert(`Error! Please report this data:\ngame: ${window.gameinfo.gameid}\n\nresponse:\n${JSON.stringify(o.error)}`) - } else { - alert(o.error) - } - } else if (o.message) { - ID('submit').disabled = false - alert(o.message) - } else if (o.url) { - window.onbeforeunload = () => {} - location.replace(o.url) - } - }, - error: () => { - ID('submit').disabled = false - alert('Server error. :( Try again?') - } - }) - }) - ID('submitcaption').addEventListener('click', () => { - const title = ID('caption').value - if (!title) { - ID('caption').focus() - return alert("You haven't entered a caption!") - } - const onCaptionSuccess = () => { - if (options.bookmarkOwnCaptions) { - let games = localStorage.getItem('gpe_gameBookmarks') - games = games ? JSON.parse(games) : {} - games[window.gameinfo.gameid] = { - time: Date.now(), - caption: `"${title}"`, - own: true - } - localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) - } - } - window.submitting = true - ID('submitcaption').disabled = true - let url - if (window.incontest) { - url = '/contests/submit-caption.json' - } else { - url = '/play/describe.json' - } - ajax('POST', url, { - obj: { - game_token: window.gameinfo.gameid, - title - }, - load: x => { - let o - try { - o = JSON.parse(x) - } catch (e) { - o = { - error: x - } - } - if (o.error) { - ID('submitcaption').disabled = false - if (typeof o.error == 'object') { - alert(`Error! Please report this data:\ngame: ${window.gameinfo.gameid}\n\nresponse: \n${JSON.stringify(o.error)}`) - } else { - alert(o.error) - } - } else if (o.message) { - ID('submitcaption').disabled = false - alert(o.message) - } else if (o.url) { - onCaptionSuccess() - location.replace(o.url) - } - }, - error: () => { - ID('submitcaption').disabled = false - alert('Server error. :( Try again?') - } - }) - }) - if (options.enterToCaption) { - ID('caption').addEventListener('keydown', e => { - if (e.keyCode == 13) { - e.preventDefault() - ID('submitcaption').click() - } - }) - } - const updateUsedChars = () => (ID('usedchars').textContent = 45 - ID('caption').value.length) - ID('caption').addEventListener('change', updateUsedChars) - ID('caption').addEventListener('keydown', updateUsedChars) - ID('caption').addEventListener('input', updateUsedChars) - ID('timeplus').addEventListener('click', () => { - if (window.gameinfo.friend) { - ID('timeplus').disabled = true - ajax('POST', '/play/exit.json', { - obj: { - game_token: window.gameinfo.gameid - }, - load: () => { - ajax('GET', `/play/${window.gameinfo.gameid}/?${Date.now()}`, { - load: x => { - ID('timeplus').disabled = false - if (x === '') { - window.gameinfo = { - error: 'Server returned a blank response :(' - } - } else { - window.gameinfo = extractInfoFromHTML(x) - } - timerStart = Date.now() + 1000 * window.gameinfo.timeleft - }, - error: () => { - ID('timeplus').disabled = false - alert('Server error. :( Try again?') - } - }) - }, - error: () => { - ID('timeplus').disabled = false - alert('Server error. :( Try again?') - } - }) - return - } - ID('timeplus').disabled = true - ajax('POST', '/play/add-time.json', { - obj: { - game_token: window.gameinfo.gameid - }, - load: x => { - const o = JSON.parse(x) - if (o.error) { - alert(o.error) - } else if (o.callJS == 'updatePlayTime') { - timerStart += o.data.seconds * 1000 - if (window.timesup) { - ID('newcanvasyo').classList.remove('locked') - anbt.Unlock() - timerStart -= 15000 // remove 15 seconds to submit - window.timesup = false - } - updateTimer() - ID('timeplus').classList.remove('show') - // Let play warning sound twice - window.playedWarningSound = false - } - ID('timeplus').disabled = false - }, - error: () => { - ID('timeplus').disabled = false - alert('Server error. :( Try again?') - } - }) - }) - const old_getClosestColor = window.getClosestColor - window.getClosestColor = (rgb, pal) => { - // Allow any color in friend games - if (window.gameinfo && window.gameinfo.friend) return rgb2hex(rgb[0], rgb[1], rgb[2]) - return old_getClosestColor(rgb, pal) - } - } - - const deeper_main = () => { - window.onerror = (e, file, line) => { - // Silence the bogus error message from the overwritten page's timer - if (e.toString().includes('periodsToSeconds')) return - // Silence the useless error message - if (e.toString().match(/script error/i)) return - if (line) { - alert(`${e}\nline: ${line}`) - } else { - alert(e) - } - } - if (options.newCanvasCSS) { - let parent = document.getElementsByTagName('head')[0] - if (!parent) parent = document.documentElement - const style = document.createElement('style') - style.type = 'text/css' - const textNode = document.createTextNode(options.newCanvasCSS) - style.appendChild(textNode) - parent.appendChild(style) - } - if (options.enableWacom) { - const stupidPlugin = document.createElement('object') - const container = ID('wacomContainer') - stupidPlugin.setAttribute('id', 'wacom') - stupidPlugin.setAttribute('type', 'application/x-wacomtabletplugin') - stupidPlugin.setAttribute('width', '1') - stupidPlugin.setAttribute('height', '1') - container.appendChild(stupidPlugin) - if (options.fixTabletPluginGoingAWOL) fixPluginGoingAWOL() - } - bindCanvasEvents() - if (window.insandbox) { - if (window.panelid) { - ajax('GET', `/panel/drawing/${window.panelid}/-/`, { - load: x => { - window.gameinfo = extractInfoFromHTML(x) - anbt.FromURL(`${gameinfo.drawinglink}?anbt`) // workaround for non-CORS cached image - handleSandboxParameters() - }, - error: () => { - alert('Error loading the panel page. Please try again.') - } - }) - } else { - if (window.origpage) { - window.gameinfo = extractInfoFromHTML(window.origpage) - handleSandboxParameters() - window.origpage = null - } else { - ajax('GET', '/sandbox/', { - load: x => { - window.gameinfo = extractInfoFromHTML(x) - handleSandboxParameters() - }, - error: () => {} - }) - } - if (options.backup) { - const pngdata = localStorage.getItem('anbt_drawingbackup_newcanvas') - if (pngdata) { - anbt.FromPNG(base642bytes(pngdata.substr(22)).buffer) - localStorage.removeItem('anbt_drawingbackup_newcanvas') - } - } - } - } else { - ID('newcanvasyo').className = 'play' - getParametersFromPlay() - } - if (!options.smoothening) { - buildSmoothPath = (points, path) => { - if (points.length < 2) return - path.pathSegList.initialize(path.createSVGPathSegMovetoAbs(points[0].x, points[0].y)) - for (let i = 1; i < points.length; i++) { - const c = points[i] - path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y)) - } - } - } - // Poor poor memory devices, let's save on memory to avoid them "crashing"... - if (/iPad|iPhone/.test(navigator.userAgent)) anbt.fastUndoLevels = 3 - window.$ = () => { - alert('Some additional script conflicts with ANBT new canvas, please disable it.') - window.$ = null - throw new Error('Script conflict with ANBT new canvas') - } - } - deeper_main() - } // needToGoDeeper end - - const $ = (selector, array = false) => { - if (selector[0] === '<') { - const elements = [...new DOMParser().parseFromString(selector, 'text/html').body.children] - return elements.length !== 1 ? elements : elements[0] - } - const elements = [...document.querySelectorAll(selector)] - return elements.length > 1 || array === true ? elements : elements[0] - } - - const setCookie = (name, value, expire) => { - if (expire) { - let time = new Date - time.setTime(time.getTime() + 24 * expire * 60 * 60 * 1e3) - expire = time.toUTCString() - } - document.cookie = `${name}=${value ? JSON.stringify(value) : ''}; expires=${expire ? expire : 'Thu, 01 Jan 1970 00:00:00 UTC'}; path=/` - } - - const getCookie = name => { - const ca = document.cookie.split(';') - for (let i = 0; i < ca.length; i++) { - let c = ca[i] - while (c.charAt(0) === ' ') c = c.substring(1, c.length) - if (c.indexOf(`${name}=`) === 0) return c.substring(`${name}=`.length, c.length) - } - return null - } - - //const isBlitzInPlay = () => ($('.label-game-mode') && $('.label-game-mode').textContent.match(/blitz/i) ? true : false) - - const linkifyNodeText = node => { - const t = node - if (!t.textContent.includes('://')) return - t.innerHTML = t.innerHTML.replace(/([^"]|^)(https?:\/\/(?:(?:(?:[^\s<()]*\([^\s<()]*\))+)|(?:[^\s<()]+)))/g, '$1$2') - } - - //const enhanceCanvas = insandbox => {} - - //const empowerPlay = () => {} - - // Event functions referred to in HTML must have unwrapped access - - const reversePanels = () => { - const e = $('.gamepanel-holder')[0].parentNode.parentNode - ;[...e.childNodes].reverse().forEach(x => e.appendChild(x)) - } - window.reversePanels = reversePanels - - const toggleLight = () => { - if (options.anbtDarkMode) { - let css = document.getElementById('darkgraycss') - if (!inDark) { - if (!css) { - css = document.createElement('style') - css.id = 'darkgraycss' - css.type = 'text/css' - css.appendChild(document.createTextNode(localStorage.getItem('gpe_darkCSS'))) - } - document.head.appendChild(css) - inDark = 1 - } else { - document.head.removeChild(css) - inDark = 0 - } - localStorage.setItem('gpe_inDark', inDark.toString()) - return - } else { - if (document.body.classList.contains('theme-night')) { - document.body.classList.remove('theme-night') - setCookie('theme-night') - } else { - document.body.classList.add('theme-night') - setCookie('theme-night', 1, 365) - } - } - } - window.toggleLight = toggleLight - - const panelUrlToDate = url => { - const m = url.match(/\/images\/panels\/(\d+)\/(\d+)-(\d+)\//) - if (!m) return - const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] - const day = (100 + parseInt(m[3], 10)).toString().slice(-2) - return `${monthNames[parseInt(m[2], 10) - 1]} ${day}, ${m[1]}` - } - - const fixLocationToCanonical = m => { - let ogurl = $('meta[property="og:url"]') ? $('meta[property="og:url"]').content : '' - if (ogurl && ogurl.match(m)) { - ogurl = ogurl.replace('https://drawception.com', '') - try { - if (location.pathname != ogurl) { - history.replaceState({}, null, ogurl + location.hash) - } - } catch (e) {} - } - } - - const betterCreateGame = () => { - if (!options.enterToCaption) { - if ($('#prompt')) { - $('#prompt').addEventListener('keydown', e => { - if (e.keyCode == 13) { - e.preventDefault() - } - }) - } - } - } - - const betterGame = () => { - if (document.title == 'Not Safe For Work (18+) Gate') { - if (options.autoBypassNSFW) { - DrawceptionPlay.bypassNsfwGate() - } - return - } - - fixLocationToCanonical('/game/') - - const drawings = $('img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]') - - // Show each drawing make date - if (drawings) drawings.forEach(x => { - const d = panelUrlToDate(x.src) - if (d) x.title = `Made on ${d}` - }) - - // Fix misaligned panels - /*const tryNextPanel = x => { - if (!x.naturalWidth && !x.triedFixing) { - let pos = x.src.match(/-(\d+)\.png$/)[1] + 1 - x.src = x.src.replace(/-(\d+)\.png$/, `-${pos}.png`) - x.triedFixing = true - } - } - // TODO: also fix if script is executed after page load - drawings.forEach(x => x.addEventListener('error', tryNextPanel(x)))*/ - - // Reverse panels button and like all button - if ($('#btn-copy-url')) $('#btn-copy-url').insertAdjacentHTML('afterend', ' Reverse') - - // Panel favorite buttons - const favButton = $('') - $('.panel-number', true).forEach(x => x.insertAdjacentHTML('afterend', favButton.outerHTML)) - $('.gamepanel', true).forEach(x => { - const t = x.parentNode - if (t.querySelector('.gamepanel-tools>a:last-child') === null) return - let panels = localStorage.getItem('gpe_panelFavorites') - panels = panels ? JSON.parse(panels) : {} - const id = t.querySelector('.gamepanel-tools>a:last-child').href.match(/\/panel\/[^\/]+\/([^\/]+)\/[^\/]+\//)[1] - if (panels[id]) { - t.querySelector('.anbt_favpanel').classList.add('anbt_favedpanel') - } else { - t.querySelector('.anbt_favpanel').classList.remove('anbt_favedpanel') - } - }) - $('.anbt_favpanel', true).forEach(x => { - x.addEventListener('click', e => { - if (x.classList.contains('anbt_favedpanel')) return - const tp = x.parentNode - const id = tp.querySelector('.gamepanel-tools>a:last-child').href.match(/\/panel\/[^\/]+\/([^\/]+)\/[^\/]+\//)[1] - - let panels = localStorage.getItem('gpe_panelFavorites') - panels = panels ? JSON.parse(panels) : {} - - const panel = { - time: Date.now(), - by: tp.querySelector('.panel-user a').textContent - } - panel.userLink = tp.querySelector('.panel-user a').href.match(/\/player\/[^\/]+\/[^\/]+\//)[0] - const img = tp.querySelector('.gamepanel img') - if (img) { - // Drawing panel - panel.image = img.src - panel.caption = img.alt - } else { - // Caption panel - panel.caption = tp.querySelector('.gamepanel').textContent.trim() - } - panels[id] = panel - localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) - x.classList.add('anbt_favedpanel') - }) - }) - - // Panel replay button - if (options.newCanvas) { - const addReplayButton = x => { - if (x.replayAdded) return - x.replayAdded = true - const panel = x.parentNode - const src = x.src - checkForRecording(src, () => { - const newid = $(`img[src='${src}']`).parentNode.querySelector('a[href^="/panel/"]').href.match(/\/panel\/[^\/]+\/([^\/]+)/)[1] - const id = newid.length >= 8 ? newid : scrambleID(panel.id.slice(6)) - const replayButton = $(``) - replayButton.addEventListener('click', e => { - if (e.which === 2) return - e.preventDefault() - setupNewCanvas(true, `/sandbox/#${id}`) - }) - panel.insertAdjacentHTML('beforebegin', replayButton.outerHTML) - }) - } - if (drawings) drawings.forEach(x => x.addEventListener('load', addReplayButton(x))) - } - - let comments - // Comments appear dynamically after the page is loaded now - const betterComments = () => { - // Linkify the links and add ability to address comment holders again - comments = [...$('#comments').nextElementSibling.children].slice(1) - comments.forEach(x => { - x.parentNode.parentNode.classList.add('comment-holder') - }) - // Interlink game panels and comments - const gamePlayers = [] - const playerdata = {} - $('.gamepanel-holder', true).forEach((x, i) => { - const det = x.querySelector('.panel-details') - const gamepanel = x.querySelector('.gamepanel') - const a = det.querySelector('.panel-user a') - if (!a) return - const id = a.href.match(/\/player\/(\d+)\//)[1] - playerdata[id] = { - panel_number: i + 1, - player_anchor: a, - panel_id: gamepanel.id, - drew: gamepanel.querySelector('img') !== null, - comments: 0 - } - gamePlayers.push(id) - }) - // Highlight new comments and remember seen comments - let seenComments = localStorage.getItem('gpe_seenComments') - seenComments = seenComments === null ? {} : JSON.parse(seenComments) - const gameid = document.location.href.match(/game\/([^\/]+)\//)[1] - const holders = comments - if (holders) { - // Clear old tracked comments - const hour = Math.floor(Date.now() / (1000 * 60 * 60)) // timestamp with 1 hour precision - for (let tempgame in seenComments) { - // Store game entry for up to a week after last tracked comment - if (seenComments[tempgame].h + 24 * 7 < hour) { - delete seenComments[tempgame] - } - } - let maxseenid = 0 - holders.forEach(x => { - const dateel = x.querySelector('a.text-muted') - const vue = x.__vue__ - if (vue) { - const text = dateel.textContent.trim() - dateel.textContent = `${text}, ${formatTimestamp(vue.comment_date * 1000)}` - if (vue.edit_date > 0) { - const el = dateel.parentNode.querySelector('span[rel="tooltip"]') - let text2 = el.title - text2 += `, ${formatTimestamp(vue.edit_date * 1000).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart - el.setAttribute('title', text2) - } - } - const ago = dateel.textContent - const anchorid = x.id - const commentid = parseInt(anchorid.slice(1), 10) - // Also allow linking to specific comment - dateel.setAttribute('title', 'Link to comment') - dateel.textContent = `${dateel.textContent.trim()} #${commentid}` - // Track comments from up to week ago - if (ago.match(/just now|min|hour|a day| [1-7] day/)) { - if (!(seenComments[gameid] && seenComments[gameid].id >= commentid)) { - x.classList.add('comment-new') - if (maxseenid < commentid) maxseenid = commentid - } - } - // Add game perticipation info - const m = x.querySelector('.text-bold a') ? x.querySelector('.text-bold a').href.match(/\/player\/(\d+)\//) : '' - if (m) { - const id = m[1] - if (gamePlayers.includes(id)) { - const drew = playerdata[id].drew ? 'drew' : 'wrote' - dateel.insertAdjacentHTML('beforebegin', `(${drew} #${playerdata[id].panel_number}) `) - playerdata[id].comments += 1 - } - } - }) - if (maxseenid) - seenComments[gameid] = { - h: hour, - id: maxseenid - } - localStorage.setItem('gpe_seenComments', JSON.stringify(seenComments)) - } - for (let i = 0; i < gamePlayers.length; i++) { - const data = playerdata[gamePlayers[i]] - if (data.comments != 0) { - const cmt = data.comments == 1 ? ' comment' : ' comments' - const cmt2 = `Player left ${data.comments}${cmt}` - data.player_anchor.title = cmt2 - data.player_anchor.insertAdjacentHTML('afterend', `${data.comments}`) - } - } - if (options.maxCommentHeight) { - const h = options.maxCommentHeight - comments.forEach(x => - x.addEventListener('click', x => { - if (x.clientHeight > h - 50 && !$(location.hash).has(x).length) location.hash = `#${x.parentNode.parentNode.id}` - }) - ) - } - } - const waitForComments = () => { - const comments = $('#comments') ? [...$('#comments').nextElementSibling.children].slice(1) : '' - if (comments.length && !comments[0].classList.contains('spinner')) { - betterComments() - } else { - if (comments.length === 0) return - setTimeout(waitForComments, 1000) - } - } - setTimeout(waitForComments, 200) - } - const checkForRecording = (url, yesfunc, retrying) => { - const xhr = new XMLHttpRequest() - xhr.open('GET', `${url}?anbt`, true) - xhr.responseType = 'arraybuffer' - xhr.onload = () => { - const buffer = xhr.response - const dv = new DataView(buffer) - const magic = dv.getUint32(0) - if (magic != 0x89504e47) return xhr.onerror() // Drawception started hijacking XHR errors and putting HTML in there - for (let i = 8; i < buffer.byteLength; i += 4 /* Skip CRC */) { - const chunklen = dv.getUint32(i) - i += 4 - const chunkname = dv.getUint32(i) - i += 4 - if (chunkname === 0x73764762) { - return yesfunc() - } else { - if (chunkname === 0x49454e44) break - i += chunklen - } - } - } - xhr.onerror = e => { - console.log('checkForRecording fail (likely due to cache without CORS), retrying') - if (!retrying) checkForRecording(`${url}?anbt`, yesfunc, true) - } - xhr.send() - } - - const betterPanel = () => { - // Just for quickly opening a panel by its numerical ID - if ($('.gamepanel') && !$('.gamepanel').length && location.hash) { - const id = location.hash.match(/\d+/) - location.pathname = `/panel/-/${scrambleID(id)}/-/` - } - - fixLocationToCanonical('/panel/') - - const favButton = $('') - if ($('.panel-caption-display>.flex,.gamepanel-holder>.gamepanel')) $('.panel-caption-display>.flex,.gamepanel-holder>.gamepanel').insertAdjacentHTML('afterend', favButton.outerHTML) - const favBtn = $('.btn.btn-info') - if (favBtn) { - favBtn.addEventListener('click', e => { - e.preventDefault() - let panels = localStorage.getItem('gpe_panelFavorites') - panels = panels ? JSON.parse(panels) : {} - const panel = { - time: Date.now(), - by: $('.lead a', true)[0].textContent - } - const id = document.location.href.match(/\/panel\/[^\/]+\/([^\/]+)\//)[1] - panel.userLink = $('.lead a', true)[0].href.match(/\/player\/[^\/]+\/[^\/]+\//)[0] - const img = $('.gamepanel img') - if (img) { - // Drawing panel - panel.image = img.src - panel.caption = img.alt - } else { - // Caption panel - panel.caption = $('.gamepanel').textContent.trim() - } - panels[id] = panel - localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) - favBtn.setAttribute('disabled', 'disabled') - favBtn.querySelector('b').textContent = 'Favorited!' - }) - } - let panels = localStorage.getItem('gpe_panelFavorites') - panels = panels ? JSON.parse(panels) : {} - if (document.location.href.match(/\/panel\/[^\/]+\/([^\/]+)\//) && panels[document.location.href.match(/\/panel\/[^\/]+\/([^\/]+)\//)[1]]) { - favBtn.setAttribute('disabled', 'disabled') - favBtn.querySelector('b').textContent = 'Favorited!' - } - const panelId = getPanelId(location.pathname) - - // Only panels after 14924553 might have a recording - if (options.newCanvas && panelId && unscrambleID(panelId) >= 14924553) { - const img = $('.gamepanel img') - if (img) { - checkForRecording(img.src, () => { - const replayLink = $(` Replay `) - replayLink.addEventListener('click', e => { - if (e.which === 2) return - e.preventDefault() - setupNewCanvas(true, `/sandbox/#${panelId}`) - }) - $('.gamepanel').insertAdjacentHTML('afterend', replayLink.outerHTML) - }) - } - } - if ($('.btn-primary').length > 1 && $('.btn-primary')[1].textContent === 'Play again') { - // Allow adding to cover creator - const ccButton = $('') - ccButton.addEventListener('click', e => { - e.preventDefault() - const id = unscrambleID(panelId) - const cookie = getCookie('covercreatorids') - const ids = cookie ? JSON.parse(cookie) : [] - if (!ids.includes(id)) { - if (ids.length > 98) { - apprise('Max cover creator drawings selected. Please remove some before adding more.') - return - } else { - ids.push(id.toString()) - } - } else { - ccButton.setAttribute('disabled', 'disabled').querySelector('b').textContent = 'Already added!' - return - } - setCookie('covercreatorids', JSON.stringify(ids)) - ccButton.setAttribute('disabled', 'disabled').querySelector('b').textContent = 'Added!' - }) - $('.gamepanel').insertAdjacentHTML('afterend', ccButton.outerHTML) - } - - /*if (options.rememberPosition && $('.regForm > .lead').textContent.match(/public game/)) { - // your own panel - panelPositions.load() - if (!panelPositions.player[panelId]) { - const xhr = new XMLHttpReuqest() - xhr.open('GET', `/player/${userid}/-/`) - xhr.onreadystatechange = html => { - if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { - html = html.replace(/]*>/gi, '') // prevent image preload - const profilePage = new DOMParser().parseFromString(html, 'text/html') - const panelProgressText = $(profilePage) - .querySelector(`a[href='${location.pathname}']`) - .nextElementSibling.querySelector('.progress-bar-text').textContent - const panelPosition = parseInt(panelProgressText.match(/\d+/)[0]) - panelPositions.player[panelId] = panelPosition - panelPositions.clear(profilePage) - panelPositions.save() - - $('.regForm > .lead') - .appendChild('
') - .appendChild(($('').textContent = panelProgressText)) - } - xhr.send() - } - } - }*/ - } - const panelPositions = { - player: null, - last: null, - load() { - const loadObj = key => { - const val = localStorage.getItem(key) - return (val && JSON.parse(val)) || {} - } - - panelPositions.player = loadObj('gpe_panelPositions') - panelPositions.last = loadObj('gpe_lastGamePositions') - }, - save() { - localStorage.setItem('gpe_panelPositions', JSON.stringify(panelPositions.player)) - localStorage.setItem('gpe_lastGamePositions', JSON.stringify(panelPositions.last)) - }, - clear(page) { - const clearKeys = (obj, keys) => { - for (let k in obj) { - if (!keys.includes(k)) delete obj[k] - } - } - - const existingIds = $(page) - .querySelector('.progress-striped') - .map(x => getPanelId(x.previousElementSibling.href)) - clearKeys(panelPositions.player, existingIds) - clearKeys(panelPositions.last, existingIds) - } - } - - const getPanelId = url => { - const match = url.match(/\/panel\/[^\/]+\/(\w+)\//) - return match && match[1] - } - - const simpleHash = s => - s - .toString() - .split('') - .reduce((a, b) => { - a = (a << 5) - a + b.charCodeAt(0) - return a & a - }, 0) - - const rot13 = s => - s - .toString() - .split('') - .map(c => { - c = c.charCodeAt(0) - if (c >= 97 && c <= 122) c = ((c - 97 + 13) % 26) + 97 - if (c >= 65 && c <= 90) c = ((c - 65 + 13) % 26) + 65 - return String.fromCharCode(c) - }) - .join('') - - const fadeIn = (element, duration = 400) => { - element.style.display = 'inline' - duration = duration === 'slow' ? 600 : duration - element.style.opacity = element.style.opacity ? parseFloat(element.style.opacity) + 0.1 : 0.2 - if (parseFloat(element.style.opacity) > 1) { - element.style.opacity = 1 - } else { - setTimeout(() => { - fadeIn(element, duration) - }, duration / 10) - } - } - - const fadeOut = (element, duration = 400) => { - duration = duration === 'slow' ? 600 : duration - element.style.opacity = element.style.opacity ? parseFloat(element.style.opacity) - 0.1 : 1 - if (parseFloat(element.style.opacity) < 0) { - element.style.opacity = 0 - element.style.display = 'none' - } else { - setTimeout(() => { - fadeOut(element, duration) - }, duration / 10) - } - } - - const randomGreeting = () => { - // Spoilers! - const g = ['Oruvaq lbh!', "Ubcr vg'f abg envavat gbqnl.", 'Jurer vf lbhe tbq abj?', 'Lbh fubhyq srry 5% zber cbjreshy abj.', 'Fhqqrayl, abguvat unccrarq!', '^_^', 'Guvf gnxrf fb ybat gb svavfu...', "Jungrire lbh qb, qba'g ernq guvf grkg.", 'Pyvpx urer sbe 1 serr KC', 'Or cngvrag.', "Whfg qba'g fgneg nal qenzn nobhg vg.", '47726S6Q2069732074686520677265617465737421', 'Cynl fzneg.', 'Cynl avpr.', 'Fzvyr!', "Qba'g sbetrg gb rng.", "V xabj jung lbh'ir qbar.", 'Fpvrapr!', 'Gbqnl vf n tbbq qnl.'] - const change_every_half_day = Math.floor(Date.now() / (1000 * 60 * 60 * 12)) - const rnddata = simpleHash(change_every_half_day + parseInt(userid, 10) + 178889) - return rot13(g[rnddata % g.length]) - } - - const formatTimestamp = d => { - if (typeof d == 'number') d = new Date(d) - if (options.localeTimestamp) return d.toLocaleString() - const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - const s = [`${100 + d.getDate()}`.slice(-2), ' ', months[d.getMonth()], ' ', d.getFullYear(), ' ', `${100 + d.getHours()}`.slice(-2), ':', `${100 + d.getMinutes()}`.slice(-2)].join('') - return s - } - - const viewMyPanelFavorites = () => { - let panels = localStorage.getItem('gpe_panelFavorites') - panels = panels ? JSON.parse(panels) : {} - let result = '' - let needsupdate = false - for (let id in panels) { - if (panels[id].image && panels[id].image.match(/^\/pub\/panels\//)) { - needsupdate = true - panels[id].image = panels[id].image.replace('/pub/panels/', 'https://cdn.drawception.com/images/panels/') - } - result += `` - } - if (needsupdate) localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) - result = result ? `${result}
` : "You don't have any favorited panels." - $('#anbt_userpage').innerHTML = result - $('#anbt_userpage .anbt_paneldel', true).forEach(x => - x.addEventListener('click', e => { - e.preventDefault() - const id = x.parentNode.parentNode.id - fadeOut($(`#${CSS.escape(id)}`)) - delete panels[id] - localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) - }) - ) - } - window.viewMyPanelFavorites = viewMyPanelFavorites - - const viewMyGameBookmarks = () => { - const removeButtonHTML = '' - let games = localStorage.getItem('gpe_gameBookmarks') - games = games ? JSON.parse(games) : {} - let result = '' - for (let id in games) { - let extraClass = '' - if (games[id].own) extraClass = 'anbt_owncaption' - if (id.length > 10) { - // token, seen lengths: 43, 32; just in case assuming everything > 10 is a token - result += `

${id}${removeButtonHTML}

` - const xhr = new XMLHttpRequest() - xhr.open('GET', `/play/${id}`) - xhr.onload = () => { - if (xhr.status === 200) { - let m = xhr.responseText.match(/Game is not private/) || (xhr.responseText.match(/Problem loading game/) && 'del') - if (m) { - let gamename = '' - if (games[id].caption) gamename += ` ${games[id].caption}` - if (games[id].own) gamename = ` with your caption${gamename}` - if (games[id].time) gamename += ` bookmarked on ${formatTimestamp(games[id].time)}` - if (!gamename) gamename = id - const status = m === 'del' ? 'Deleted' : 'Unfinished public' - $(`#${id}`).querySelector('span').textContent = `${status} game${gamename}` - return - } - const title = xhr.responseText.match(/(.+)<\/title>/)[1] - m = xhr.responseText.match(/\/game\/([^\/]+)\/[^\/]+\//) - const url = m[0] - const gameid = m[1] - delete games[id] - games[gameid] = { - title, - url - } - $(`#${id}`).id = gameid - const spanId = $(`#${gameid}`).querySelector('span') - spanId.parentNode.replaceChild($(`<a href="${url}">${title}</a>`), spanId) - localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) - } else { - $(`#${id}`).querySelector('span').textContent = `Error while retrieving game: ${xhr.responseText}` - } - } - xhr.send() - } else if (id.length == 10) result += `<p class="well${extraClass}" id="${id}"><a href="${games[id].url}">${games[id].title}</a>${removeButtonHTML}</p>` // game ID - } - if (!result) result = "You don't have any bookmarked games." - $('#anbt_userpage').innerHTML = result - $('#anbt_userpage .anbt_gamedel', true).forEach(x => - x.addEventListener('click', e => { - e.preventDefault() - const id = x.parentNode.id - fadeOut($(`#${id}`)) - delete games[id] - localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) - }) - ) - } - window.viewMyGameBookmarks = viewMyGameBookmarks - - // Convert times - // Forum time is Florida, GMT-6, to be +1 DST since 08 Mar 2015, 2:00 - // starts on the second Sunday in March and ends on the first Sunday in November - const isFloridaDST = () => { - d = new Date(Date.now() - 6 * 60 * 60 * 1000) - const month = d.getUTCMonth() - const day = d.getUTCDate() - const hours = d.getUTCHours() - const dayofweek = d.getUTCDay() - - if (month < 2 || month > 10) return false - if (month > 2 && month < 10) return true - if (month == 2) { - if (day < 8) return false - if (day > 14) return true - if (dayofweek == 7) return hours > 1 - return day > dayofweek + 7 - } - if (month == 10) { - if (day > 7) return false - if (dayofweek == 7) return hours < 1 - return day <= dayofweek - } - } - - const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - const convertForumTime = (year, month, day, hours, minutes) => { - const d = new Date(year, month, day, hours, minutes) - const tzo = d.getTimezoneOffset() * 60 * 1000 - const dst = isFloridaDST() - return formatTimestamp(d.getTime() - tzo + (6 - dst) * 60 * 60 * 1000) - } - - const betterPlayer = () => { - // Linkify the links in location - const pubinfo = $('.profile-header-info .text-muted > span:last-child') - if (pubinfo) linkifyNodeText(pubinfo.parentNode) - - const loc = document.location.href - // If it's user's homepage, add new buttons in there - if (loc.match(new RegExp(`/player/${userid}/[^/]+/(?:$|#)`))) { - const a = $('<h2>ANBT stuff: </h2>') - a.appendChild($('<a class="btn btn-primary" href="#anbt_panelfavorites" onclick="viewMyPanelFavorites()">Panel Favorites</a>')) - a.appendChild($('<a class="btn btn-primary" href="#anbt_gamebookmarks" onclick="viewMyGameBookmarks()">Game Bookmarks</a>')) - const profilemain = $('.profile-layout-content').firstChild - profilemain.insertAdjacentHTML('afterbegin', `<h5 id="anbt_userpage">${randomGreeting()}</h5>`) - profilemain.insertAdjacentHTML('afterbegin', a.outerHTML) - - if (document.location.hash.includes('#anbt_panelfavorites')) viewMyPanelFavorites() - if (document.location.hash.includes('#anbt_gamebookmarks')) viewMyGameBookmarks() - - /*if (options.rememberPosition) { - panelPositions.load(); - panelPositions.clear(document); - - $(".progress-striped").forEach(function () { - const panelId = getPanelId(this.previousElementSibling.href); - const playerPanelPosition = panelPositions.player[panelId]; - const lastSeenPanelPosition = panelPositions.last[panelId]; - const panelProgress = this.querySelector(".progress-bar-text"); - const panelProgressText = panelProgress.textContent; - const panelPosition = parseInt(panelProgressText.match(/\d+/)[0]); - const totalPanelCount = parseInt(panelProgressText.match(/\d+/g)[1]); - - panelProgress.style.pointerEvents = "none"; // to make tooltips work under label - if ((playerPanelPosition || lastSeenPanelPosition || panelPosition) < panelPosition) { - this.querySelector(".progress-bar") - .width(`${(playerPanelPosition || lastSeenPanelPosition) / totalPanelCount * 100}%`); - } - if (playerPanelPosition && panelPosition > playerPanelPosition && playerPanelPosition < lastSeenPanelPosition) { - $('<div class="progress-bar progress-bar-info" title="Panels added after yours">') - .width(`${(Math.min(lastSeenPanelPosition || panelPosition, panelPosition) - playerPanelPosition) / totalPanelCount * 100}%`) - .insertAdjacentHTML('beforebegin', panelProgress.outerHTML) - .tooltip(); - } - if (lastSeenPanelPosition && panelPosition > lastSeenPanelPosition) { - $('<div class="progress-bar progress-bar-success" title="Panels added recently">') - .width(`${(panelPosition - lastSeenPanelPosition) / totalPanelCount * 100}%`) - .insertAdjacentHTML('beforebegin', panelProgress.outerHTML) - .tooltip(); - } - if (lastSeenPanelPosition && panelPosition < lastSeenPanelPosition) { - $('<div class="progress-bar progress-bar-danger" title="Panel was removed recently">') - .width(`${1 / totalPanelCount * 100}%`) - .insertAdjacentHTML('beforebegin', panelProgress.outerHTML) - .tooltip(); - } - if (playerPanelPosition) { - $('<span title="Your panel position">') - .text(`#${playerPanelPosition}`) - .insertAdjacentHTML('beforebegin', this.outerHTML) - .tooltip(); - } - - panelPositions.last[panelId] = panelPosition; - }); - - panelPositions.save(); - }*/ - - // Show your exact registration date - if (window.date) { - const pubinfo = $('.profile-user-header>div.row>div>h1+p') - if (pubinfo) { - const newregdate = formatTimestamp(date) - ;[...pubinfo.childNodes][4].nodeValue = ` ${newregdate} \xa0` - } - } - } else { - // Not the current user's profile or not profile homepage - let drawings = $('img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]', true) - // Show replayable panels; links are not straightforward to make since there's no panel ID - if (options.newCanvas) { - const addReplaySign = x => { - if (x.replayAdded) return - x.replayAdded = true - const panel = x.parentNode.parentNode - const src = x.src - checkForRecording(src, () => { - const newid = src.match(/(\w+).png$/)[1] - const replaySign = newid.length >= 8 ? $(`<a href="/sandbox/#${newid}" class="pull-right fas fa-redo-alt" style="color:#8af;margin-right:4px" title="Replay!"></a>`) : $('<span class="pull-right fas fa-redo-alt" style="color:#8af;margin-right:4px" title="Replayable!"></span>') - panel.appendChild(replaySign) - //replaySign.tooltip(); - }) - } - drawings.forEach(x => x.addEventListener('load', addReplaySign(x))) - } - - // Detect Draw Firsts - drawings.forEach(({src, parentNode}) => { - if (src.match(/-1\.png$/)) { - const drawFirstSign = $('<span class="pull-right" title="Draw First game"><img src="/img/icon-coins.png"></span>') - parentNode.parentNode.appendChild(drawFirstSign) - //drawFirstSign.tooltip(); - } - }) - } - - // Convert timestamps in user profile's forum posts and game comments - if (loc.match(/player\/\d+\/[^/]+\/(posts)|(comments)\//)) { - // Show topic title at the top of the posts instead and display subforum - // Show game title at the top of the posts - $('.forum-thread-starter', true).forEach(x => { - const vue = x.childNodes[0].__vue__ - if (vue) { - const ts = x.querySelector('a.text-muted').firstChild - const text = ts.textContent.trim() - ts.textContent = `${text}, ${formatTimestamp(vue.comment_date * 1000)}` - if (vue.edit_date > 0) { - const el = ts.parentNode.parentNode.querySelector('span[rel="tooltip"]') - let text2 = el.title - text2 += `, ${formatTimestamp(vue.edit_date * 1000).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart - el.setAttribute('title', text2) - } - } - const postlink = x.querySelector('.add-margin-top small.text-muted') - const created = postlink.textContent.match(/^\s*Created/) - const commented = postlink.textContent.match(/^\s*Commented/) - const prefix = commented ? 'Comment in the game' : created ? 'New thread' : 'Reply in' - const n = $(`<h4 class="anbt_threadtitle">${prefix}: </h4>`) - const thread = postlink.querySelector('a') - n.appendChild(thread) - x.insertAdjacentHTML('afterbegin', n.outerHTML) - postlink.parentNode.parentNode.removeChild(postlink.parentNode) - }) - } - } - - const betterForum = () => { - const ncPosts = [] - $('span.muted, span.text-muted, small.text-muted', true).forEach((x, index) => { - const tx = x.textContent - // Don't touch relative times - if (tx.includes('ago')) return - if ((m = tx.match(/^\s*\(last post (...) (\d+).. (\d+):(\d+)([ap]m)\)\s*$/))) { - const d = new Date() - const month = months.indexOf(m[1]) - const day = parseInt(m[2], 10) - let hours = parseInt(m[3], 10) % 12 - const minutes = parseInt(m[4], 10) - let year = d.getFullYear() - if (d.getMonth() < 6 && month >= 6) year-- - hours += m[5] === 'pm' ? 12 : 0 - const time = convertForumTime(year, month, day, hours, minutes) - x.textContent = `(last post ${time})` - // Track new posts at subforum list - if (location.href.match(/forums\/$/)) { - if (time != localStorage.getItem(`anbt_subforum${index}`)) { - x.parentNode.insertAdjacentHTML('afterbegin', '<span class="label label-sm label-warning">NEW</span> ') - localStorage.setItem(`anbt_subforum${index}`, time) - } - } - } - }) - - if (options.markStalePosts) { - const markStalePost = ({classList}, age) => { - if (age < 30) return - let r = 0 - if (age > 60) r = 1 - if (age > 120) r = 2 - if (age > 365) r = 3 - classList.add(`anbt_necropost anbt_necropost${r}`) - } - // Skip the first post - for (let i = 1; i < ncPosts.length; i++) { - const el = ncPosts[i][0] - const time = ncPosts[i][1] - const lastpage = !$('.pagination').length || $('.pagination .active:last-child').length - const nexttime = ncPosts[i + 1] ? ncPosts[i + 1][1] : lastpage ? Date.now() / 86400000 : 0 - const age = nexttime - time - if (age > 30) markStalePost($(el).parentNode.parentNode.parentNode.parentNode, age) - } - - GM_addStyle(".anbt_necropost:after {display: block; height: 14px; border-bottom: 2px solid black; content: ' '; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAOCAMAAADOvxanAAAAElBMVEUAAAD///8wLiSYlYNnZFXm5Nfd4sMOAAAAAnRSTlMAAHaTzTgAAABBSURBVHheXchBEoBADAJBMsD/v2yZdS8Oly40k9NIk54ySm/8DYb6NWuuex1ak7VdbAcpNrCU8HmP4/hzd9oAXj6sBgHBLAHrRAAAAABJRU5ErkJggg==)}" + '.anbt_necropost0:after {background: none; border-bottom: 1px solid black}' + '.anbt_necropost1:after {background: none}' + '.anbt_necropost2:after {background-repeat: no-repeat; background-position: center}' + '.anbt_necropost3:after {}' + ".anbt_necropost span.muted:after {content: ' (old post)'}" + 'iframe {border: none}' + '.v--modal-overlay .v--modal-background-click {padding-bottom: 0}') - } - // Linkify the links - $('.comment-body *', true).forEach(x => linkifyNodeText(x)) - - // Linkify drawing panels - $('img[src*="/images/panels/"], img[src*="/pub/panels/"]', true).forEach(x => { - if (x.parentNode.nodeName !== 'A') x.outerHTML = `<a href="/game/${x.src.match(/\/([^-]+)-\d+.png/)[1]}/-/">${x.outerHTML}</a>` - }) - $('img[src*="/drawings/"]', true).forEach(x => { - if (x.parentNode.nodeName !== 'A') x.outerHTML = `<a href="/panel/drawing/${x.src.match(/(\w+).png$/)[1]}/-/">${x.outerHTML}</a>` - }) - $('img[src*="/panel/"]', true).forEach(x => { - if (x.parentNode.nodeName !== 'A') x.outerHTML = `<a href="${x.src}-/">${x.outerHTML}</a>` - }) - // Linkify full game image - $('img[src*="/images/games/"], img[src*="/pub/games/"]', true).forEach(x => { - if (x.parentNode.nodeName !== 'A') x.outerHTML = `<a href="/game/${x.src.match(/\/([^\/]+)\.png/)[1]}/-/">${x.outerHTML}</a>` - }) - // Fix the dead link - $('img[src*="/display-panel.php?"]', true).forEach(x => { - if (x.parentNode.nodeName !== 'A') { - const newsrc = `/panel/drawing/${scrambleID(x.src.match(/x=(\d+)/)[1])}/` - x.setAttribute('src', newsrc) - x.outerHTML = `<a href="${newsrc}-/">${x.outerHTML}</a>` - } - }) - - // Show posts IDs and link - if (document.location.pathname.match(/\/forums\/(\w+|-)\/.+/)) { - const hideuserids = options.forumHiddenUsers ? options.forumHiddenUsers.split(',') : '' - if (hideuserids !== '') { - GM_addStyle('.anbt_hideUserPost:not(:target) {opacity: 0.4; margin-bottom: 10px}' + '.anbt_hideUserPost:not(:target) .comment-body, .anbt_hideUserPost:not(:target) .avatar {display: none}' + '') - } - let lastid = 0 - $('.comment-avatar', true).forEach(({parentNode}) => { - const t = parentNode.parentNode.parentNode - let anch - let id - t.classList.add('comment-holder') // No identification for these anymore, this is unhelpful! - try { - anch = t.id - } catch (e) {} - const ts = t.querySelector('a.text-muted') - const vue = t.childNodes[0].__vue__ - if (vue) { - const text = ts.textContent.trim() - ts.textContent = `${text}, ${formatTimestamp(vue.comment_date * 1000)}` - if (vue.edit_date > 0) { - const el = ts.parentNode.querySelector('span[rel="tooltip"]') - let text2 = el.title - text2 += `, ${formatTimestamp(vue.edit_date * 1000).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart - el.setAttribute('title', text2) - } - } - if (anch) { - id = parseInt(anch.substring(1), 10) - const text = ts.textContent.trim() - ts.textContent = `${text} #${id}` - ts.setAttribute('title', 'Link to post') - if (id < lastid) { - ts.classList.add('wrong-order') - } - try { - const h = t.querySelector('a[href^="/player/"]').href - if (h) { - const userid = h.match(/\d+/)[0] - if (hideuserids.includes(userid)) t.classList.add('anbt_hideUserPost') - } - } catch (e) {} - lastid = id - } - }) - - // Warn about posting to another page - if ($('.comment-holder') && $('.comment-holder').length === 20 && $('#comment-form .btn-primary')) $('#comment-form .btn-primary').insertAdjacentHTML('afterend', '<div>Note: posting to another page</div>') - } - - if (options.proxyImgur) $('img[src*="imgur.com/"]', true).forEach(x => x.setAttribute('src', x.src.replace('imgur.com', 'filmot.com').replace('https', 'http'))) - - const pagination = $('.pagination', true) - if (pagination.length) $('.breadcrumb').insertAdjacentHTML('afterend', `<div class="text-center">${pagination[0].outerHTML}</div>`) - - // For the topic list pages only - if (document.location.pathname.match(/\/forums\/(\w+)\/$/)) { - let hidden_topics = localStorage.getItem('gpe_forumHiddenTopics') - hidden_topics = hidden_topics ? JSON.parse(hidden_topics) : [] - let hidden = 0 - - const tempUnhideLink = $('<a class="text-muted anbt_unhidet">') - - $('.forum-thread', true).forEach(x => { - const m = x.querySelector('a:first-child').href.match(/\/forums\/\w+\/(\d+)\//) - // Don't let them hide the ANBT topic ;) - if (!m || !m[1] || m[1] == 11830) return - - const id = m[1] - if (hidden_topics.includes(id)) { - x.classList.add('anbt_hidden') - hidden++ - } - const hideLink = $('<a class="text-muted anbt_hft">') - hideLink.addEventListener('click', () => { - let ht = localStorage.getItem('gpe_forumHiddenTopics') - ht = ht ? JSON.parse(ht) : [] - if (hidden_topics.includes(id)) { - if (ht.includes(id)) ht = ht.filter(y => y !== id) - hidden_topics = hidden_topics.filter(y => y !== id) - x.classList.remove('anbt_hidden') - hidden-- - } else { - if (!ht.includes(id)) ht.push(id) - hidden_topics.push(id) - x.classList.add('anbt_hidden') - hidden++ - tempUnhideLink.style.display = '' - } - tempUnhideLink.textContent = hidden - localStorage.setItem('gpe_forumHiddenTopics', JSON.stringify(ht)) - }) - x.querySelector('p:nth-child(2)').appendChild(hideLink) - }) - tempUnhideLink.textContent = hidden - tempUnhideLink.addEventListener('click', () => { - $('#main').classList.toggle('anbt_showt') - }) - if (!hidden) tempUnhideLink.style.display = 'none' - if ($('#js-btn-toggle-thread')) $('#js-btn-toggle-thread').parentNode.appendChild(tempUnhideLink) - } - $('.btn.btn-default', true).forEach(x => - x.addEventListener('click', e => { - if (x.textContent === 'Draw') { - if (!x.classList.contains('click')) { - let delModal = setInterval(() => { - if (!$('.v--modal-overlay')) return - $('.v--modal-overlay').outerHTML = '' - clearInterval(delModal) - }, 100) - x.classList.add('click') - } - setupNewCanvas(true, document.location.href) - } - }) - ) - } - - const loadScriptSettings = () => { - let result = localStorage.getItem('gpe_anbtSettings') - if (!result) return - result = JSON.parse(result) - for (let i in result) options[i] = result[i] - } - - const updateScriptSettings = theForm => { - const result = {} - theForm.querySelectorAll('input,textarea').forEach(x => { - if (x.type === 'checkbox') { - result[x.name] = x.checked ? 1 : 0 - } else if (x.getAttribute('data-subtype') === 'number') { - result[x.name] = parseFloat(x.value) || 0 - } else { - result[x.name] = x.value - } - }) - localStorage.setItem('gpe_anbtSettings', JSON.stringify(result)) - loadScriptSettings() - fadeIn($('#anbtSettingsOK'), 'slow') - setTimeout(() => { - fadeOut($('#anbtSettingsOK'), 'slow') - }, 800) - } - window.updateScriptSettings = updateScriptSettings - - const escapeHTML = t => - t - .toString() - .replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') - - const addScriptSettings = () => { - const theForm = $('<form class="regForm form-horizontal" action="#" onsubmit="return updateScriptSettings(this)"></form>') - theForm.appendChild($('<legend>ANBT script settings</legend>')) - - const addGroup = (name, settings) => { - const div = $('<div class="control-group"></div>') - div.appendChild($(`<label class="control-label">${name}</label>`)) - settings.forEach(id => { - let v = options[id[0]] - const name = id[0] - const t = id[1] - const desc = id[2] - const c = $('<div class="controls"></div>') - if (t == 'boolean') { - c.appendChild($(`<label><input type="checkbox" id="anbt_${name}" name="${name}" value="1" ${v ? 'checked="checked"' : ''}"> ${desc}</label>`)) - } else if (t == 'number') { - if (!v) v = 0 - $(`<b>${desc}:</b><input class="form-control" type="text" data-subtype="number" name="${name}" value="${escapeHTML(v)}">`).forEach(x => c.appendChild(x)) - } else if (t == 'longstr') { - $(`<b>${desc}:</b><textarea class="form-control" name="${name}">${escapeHTML(v)}</textarea>`).forEach(x => c.appendChild(x)) - } else { - $(`<b>${desc}:</b><input class="form-control" type="text" name="${name}" value="${escapeHTML(v)}">`).forEach(x => c.appendChild(x)) - } - div.appendChild(c) - }) - theForm.appendChild(div) - } - addGroup('Pen Tablet (requires plugin: <a href="http://www.wacomeng.com/web/fbWTPInstall.zip">Windows</a> | <a href="http://www.wacomeng.com/web/Wacom%20Mac%20Plug-in%20Installer.zip">Mac OS</a> | <a href="https://github.com/ZaneA/WacomWebPlugin">Linux</a>)', [ - ['enableWacom', 'boolean', 'Enable Wacom plugin / pressure sensitivity support'], - ['fixTabletPluginGoingAWOL', 'boolean', 'Try to prevent Wacom plugin from disappearing'] - //["pressureExponent", "number", "Pressure exponent (smaller = softer tablet response, bigger = sharper)"], - ]) - addGroup('Play (most settings are for the new canvas only)', [['newCanvas', 'boolean', 'New drawing canvas (also allows <a href="http://grompe.org.ru/replayable-drawception/">watching playback</a>)'], ['submitConfirm', 'boolean', 'Confirm submitting if more than a minute is left'], ['smoothening', 'boolean', 'Smoothing of strokes'], ['hideCross', 'boolean', 'Hide the cross when drawing'], ['enterToCaption', 'boolean', 'Submit captions (and start games) by pressing Enter'], ['backup', 'boolean', 'Save the drawing in case of error and restore it in sandbox'], ['timeoutSound', 'boolean', 'Warning sound when only a minute is left (normal games)'], ['timeoutSoundBlitz', 'boolean', 'Warning sound when only 5 seconds left (blitz)'], ['timeoutSoundVolume', 'number', 'Volume of the warning sound, in %'], ['rememberPosition', 'boolean', 'Show your panel position and track changes in unfinished games list'], ['colorNumberShortcuts', 'boolean', 'Use 0-9 keys to select the color'], ['colorUnderCursorHint', 'boolean', 'Show the color under the cursor in the palette'], ['colorDoublePress', 'boolean', 'Double press 0-9 keys to select color without pressing shift'], ['bookmarkOwnCaptions', 'boolean', 'Automatically bookmark your own captions in case of dustcatchers']]) - addGroup('Miscellaneous', [['localeTimestamp', 'boolean', `Format timestamps as your system locale (${new Date().toLocaleString()})`], ['proxyImgur', 'boolean', 'Replace imgur.com links to filmot.com to load, in case your ISP blocks them'], ['ajaxRetry', 'boolean', 'Retry failed AJAX requests'], ['autoplay', 'boolean', 'Automatically start replay when watching playback'], ['autoBypassNSFW', 'boolean', 'Automatically bypass NSFW game warning'], ['markStalePosts', 'boolean', 'Mark stale forum posts'], ['maxCommentHeight', 'number', 'Maximum comments and posts height until directly linked (px, 0 = no limit)'], ['useOldFont', 'boolean', 'Use old Nunito font (which is usually bolder and less wiggly)'], ['useOldFontSize', 'boolean', 'Use old, smaller font size'], ['markdownTools', 'boolean', 'Markdown tools for messages'], ['anbtDarkMode', 'boolean', 'Switch between ANBT\'s and Drawception\'s dark mode']]) - addGroup('Advanced', [['newCanvasCSS', 'longstr', 'Custom CSS for new canvas (experimental, <a href="https://github.com/grompe/Drawception-ANBT/tree/master/newcanvas_styles">get styles here</a>)'], ['forumHiddenUsers', 'longstr', 'Comma-separated list of user IDs whose forum posts are hidden']]) - $('<br><div class="control-group"><div class="controls"><input name="submit" type="submit" class="btn btn-primary" value="Apply"> <b id="anbtSettingsOK" class="label label-theme_holiday" style="display:none">Saved!</b></div></div>').forEach(x => theForm.appendChild(x)) - $('#main').insertAdjacentHTML('afterbegin', theForm.outerHTML) - - // Extend "location" input to max server-accepted 65 characters - if ($('input[name="location"]')) $('input[name="location"]').setAttribute('maxlength', '65') - } - - /*const autoSkip = (reason) => { - const autoSkipInfo = $(`<div id="autoSkipInfo" class="text-warning" style="cursor: pointer">(CLICK TO CANCEL)<br>Auto-skipping in <span id="autoSkipCounter">3</span>...<br>Reason: ${reason}</div>`) - $('.play-instruction').appendChild(autoSkipInfo) - autoSkipInfo.addEventListener('click', (e) => { - e.preventDefault() - $('#autoSkipCounter').countdown('pause') - autoSkipInfo.style.display = 'none' - }) - $('#autoSkipCounter').countdown({ - until: 3, - compact: 1, - format: 'S', - onExpiry: timesUp, - }) - }*/ - - const theAlphabet = '36QtfkmuFds0UjlvCGIXZ125bEMhz48JSYgipwKn7OVHRBPoy9DLWaceqxANTr' - // Game IDs will never contain these symbols: u 0U lv I J i V o - // So they are base 52 for some reason... - - const decTo62 = n => { - const b = theAlphabet - let result = '' - const bLen = b.length - while (n != 0) { - const q = n % bLen - result = b[q] + result - n = (n - q) / bLen - } - return result - } - - const _62ToDec = n => { - n = n.toString() - const b = theAlphabet - const cache_pos = {} - const bLen = b.length - let result = 0 - let pow = 1 - for (let i = n.length - 1; i >= 0; i--) { - const c = n[i] - if (typeof cache_pos[c] === 'undefined') { - cache_pos[c] = b.indexOf(c) - } - result += pow * cache_pos[c] - pow *= bLen - } - return result - } - - const scrambleID = num => { - if (isNaN(num)) throw new Error('Invalid panel ID') - return decTo62(parseInt(num, 10) + 3521614606208) - .split('') - .reverse() - .join('') - } - window.scrambleID = scrambleID - - const unscrambleID = str => - _62ToDec( - str - .split('') - .reverse() - .join('') - ) - 3521614606208 - window.unscrambleID = unscrambleID - - const stalkNextPanel = forward => { - if (!forward) forward = 1 - const sid = location.href.match(/\/panel\/[^\/]+\/(\w+)\/[^\/]+\//)[1] - const sid2 = scrambleID(unscrambleID(sid) + 1 * forward) - location.href = location.href.replace(sid, sid2) - } - window.stalkNextPanel = stalkNextPanel - - /*const valueToHex = val => (Math.floor(val / 16) % 16).toString(16) + (Math.floor(val) % 16).toString(16) - - const eyedropper = (x, y) => { - const p = drawApp.context.getImageData(x, y, 1, 1).data - return p[3] > 0 ? `#${valueToHex(p[0])}${valueToHex(p[1])}${valueToHex(p[2])}` : null - } - - const invertColor = c => { - // Support only hex color - if (c.charAt(0) != '#') return c - c = c.substring(1) - // Ensure it's in long form - if (c.length == 3) c = c.charAt(0) + c.charAt(0) + c.charAt(1) + c.charAt(1) + c.charAt(2) + c.charAt(2) - return `#${`000000${(parseInt(c, 16) ^ 0xffffff).toString(16)}`.slice(-6)}` - }*/ - - const pagodaBoxError = () => { - if (document.title === 'Pagoda Box' && (document.body.innerHTML.match('All Routes' + ' Down.') || document.body.innerHTML.match('There appears to be an error' + ' with this site.'))) { - GM_addStyle('body {background: #755 !important}' + '') - div = document.createElement('div') - div.innerHTML = '<h1>ANBT speaking:</h1>' + 'Meanwhile, you can visit the chat: ' + '<a href="http://chat.grompe.org.ru/#drawception">http://chat.grompe.org.ru/#drawception</a><br>' + 'Or use the new sandbox: <a href="http://grompe.org.ru/drawit/">http://grompe.org.ru/drawit/</a>' - document.body.appendChild(div) - } - } - - const pageEnhancements = () => { - const loc = document.location.href - loadScriptSettings() - - if (typeof DrawceptionPlay === 'undefined') return // Firefox Greasemonkey seems to call pageEnhancements() after document.write... - if (document.getElementById('newcanvasyo')) return // Chrome, I'm looking at you too... - - //__DEBUG__ = document.getElementById('_debug_') - //prestoOpera = navigator.userAgent.match(/\bPresto\b/) - - // Stop tracking me! Best to block - // api.mixpanel.com and cdn.mxpnl.com - if (typeof mixpanel !== 'undefined') - mixpanel = { - track: () => {}, - identify: () => {} - } - - try { - const tmpuserlink = $('.player-dropdown a[href^="/player/"]') - username = tmpuserlink.querySelector('strong').textContent - userid = tmpuserlink.href.match(/\/player\/(\d+)\//)[1] - localStorage.setItem('gpe_lastSeenName', username) - localStorage.setItem('gpe_lastSeenId', userid) - } catch (e) {} - - const insandbox = loc.match(/drawception\.com\/sandbox\/#?(.*)/) - const inplay = loc.match(/drawception\.com\/(:?contests\/)?play\/(.*)/) - if (options.newCanvas) { - const hasCanvas = document.getElementById('canvas-holder') - // If created a friend game, the link won't present playable canvas - const hasCanvasOrGameForm = $('.playtimer') - const captioncontest = loc.match(/contests\/play\//) && !hasCanvas - if (!captioncontest && (insandbox || (inplay && hasCanvasOrGameForm)) /* || __DEBUG__*/) { - setTimeout(() => { - setupNewCanvas(insandbox, loc, document.body.innerHTML) - }, 1) - return - } - } - /*else { - if (insandbox || inplay || __DEBUG__) { - enhanceCanvas(insandbox) - } - if (inplay || __DEBUG__) { - empowerPlay() - } - }*/ - if (loc.match(/drawception\.com\/game\//)) { - betterGame() - } - if (loc.match(/drawception\.com\/panel\//)) { - betterPanel() - } - if (loc.match(/drawception\.com\/player\//)) { - betterPlayer() - } - if (loc.match(/drawception\.com\/forums\//)) { - betterForum() - } - if (loc.match(/drawception\.com\/settings\//)) { - addScriptSettings() - } - if (loc.match(/drawception\.com\/create/)) { - betterCreateGame() - } - GM_addStyle('.panel-user {width: auto} .panel-details img.loading {display: none}' + '.gpe-wide, .gpe-wide-block {display: none}' + '.gpe-btn {padding: 5px 8px; height: 28px}' + '.gpe-spacer {margin-right: 7px; float:left}' + '@media (min-width:992px) {.navbar-toggle,.btn-menu-player {display: none} .gpe-wide {display: inline} .gpe-wide-block {display: block}}' + '@media (min-width:1200px) {.gpe-btn {padding: 5px 16px;} .gpe-spacer {margin-right: 20px;} .panel-number {left: -30px}}' + '#anbtver {font-size: 10px; position:absolute; opacity:0.3; right:10px; top: 0;}' + '.anbt_paneldel {position:absolute; padding:1px 6px; color:#FFF; background:#d9534f; text-decoration: none !important; right: 18px; border-radius: 5px}' + '.anbt_paneldel:hover {background:#d2322d}' + '.anbt_favpanel {top: 20px; font-weight: normal; padding: 0 2px}' + '.anbt_favpanel:hover {color: #d9534f; cursor:pointer}' + '.anbt_favedpanel {color: #d9534f; border-color: #d9534f}' + '.anbt_replaypanel {top: 55px; font-weight: normal; padding: 0 8px}' + '.anbt_replaypanel:hover {color: #8af; text-decoration: none}' + ".anbt_owncaption:before {content: ''; display: inline-block; background: #5C5; border: 1px solid #080; width: 10px; height: 10px; border-radius: 10px; margin-right: 10px;}" + '.gamepanel, .thumbpanel, .comment-body {word-wrap: break-word}' + '.comment-body img {max-width: 100%}' + '.forum-thread.anbt_hidden {display: none}' + '.anbt_showt .forum-thread.anbt_hidden {display: block; opacity: 0.6}' + ".anbt_unhidet:after {content: ' threads hidden. Show'}" + ".anbt_showt .anbt_unhidet:after {content: ' threads hidden. Hide'}" + ".anbt_hft:after {content: '[hide]'}" + '.anbt_hft, .anbt_unhidet {padding-left: 0.4em; cursor:pointer}' + ".forum-thread.anbt_hidden .anbt_hft:after {content: '[show]'}" + '.anbt_threadtitle {margin: 0 0 10px}' + '.avatar {box-sizing: content-box}' + '.pagination {margin: 0px}' + '#nav-drag {position: fixed; width: 100%; z-index: 2000}' + '#header-bar-container {position: relative; width: 100%; top: 6rem}' + '.wrapper {position: relative; top: 6rem}' + 'footer {position: relative; top: 6rem}' + '.option span:first-child {display: flex; flex-direction: row; justify-content: space-between}' + '.grid-settings div[class^="grid-"] label {display: inline-flex}' + 'input[type="checkbox"], input[type="radio"] {margin:4px 4px 0 0}' + '@-moz-document url-prefix() {input[type="checkbox"], input[type="radio"] {margin:0 4px 0 0}}' + '.tooltip {z-index: 3000;}') - if (options.maxCommentHeight) { - const h = options.maxCommentHeight - GM_addStyle(`.comment-holder[id]:not(:target) .comment-body {overflow-y: hidden; max-height: ${h}px; position:relative}.comment-holder[id]:not(:target) .comment-body:before{content: 'Click to read more'; position:absolute; width:100%; height:50px; left:0; top:${h - 50}px;text-align: center; font-weight: bold; color: #fff; text-shadow: 0 0 2px #000; padding-top: 20px; background:linear-gradient(transparent, rgba(0,0,0,0.4))}`) - $('.comment-body', true).forEach(x => - x.addEventListener('click', () => { - if (x.clientHeight > h - 50 && location.hash.indexOf(x) === -1) location.hash = `#${x.parentNode.parentNode.id}` - }) - ) - } - if (options.useOldFontSize) { - document.body.style.fontSize = '15px' - } - if (options.useOldFont) { - const nunito = $("link[href*='Nunito']") - nunito.parentNode.removeChild(nunito) - GM_addStyle("@font-face { font-family: 'Nunito'; font-style: normal; font-weight: 400; src: local('Nunito Regular'), local('Nunito-Regular'), " + 'url(data:application/font-woff;base64,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) format('woff');}") - } - if (options.anbtDarkMode) { - if (document.body.classList.contains('theme-night')) { - document.body.classList.remove('theme-night') - setCookie('theme-night') - } - } - if (options.markdownTools) { - const markdown = { - bold: { - title: 'bold text', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = new RegExp(`\\*\\*(${sel.replace(/\*/g, '')})\\*\\*`) - if (sel.match(selRegex)) { - sel = sel.replace(selRegex, '$1') - } else if (start > 0 && end < len) { - if (val.substring(start - 1, end + 1).match(selRegex)) { - start -= 1 - end += 1 - sel = val.substring(start, end).replace(selRegex, '$1') - } else if (val.substring(start - 2, end + 2).match(selRegex)) { - start -= 2 - end += 2 - sel = val.substring(start, end).replace(selRegex, '$1') - } else { - if (sel.match(/\*\*.+\*\*/g)) { - sel = sel.replace(/\*\*/g, '') - } else { - sel = `**${sel.replace(/\n/g, '**\n**')}**` - } - } - } else { - if (!start && val.substring(start, end + 1).match(selRegex)) { - end++ - sel = val.substring(start, end).replace(selRegex, '$1') - } else if (end === len && val.substring(start - 1, end).match(selRegex)) { - start-- - sel = val.substring(start, end).replace(selRegex, '$1') - } else { - if (sel.match(/\*\*.+\*\*/g)) { - sel = sel.replace(/\*\*/g, '') - } else { - sel = `**${sel.replace(/\n/g, '**\n**')}**` - } - } - } - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - } - }, - italic: { - title: 'italic text', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = new RegExp(`\\*(?=\\S*${sel.replace(/(.*)\*(.*)/g, '')})((?:\\*\\*|\\\\[\\s\\S]|\\s+(?:\\\\[\\s\\S]|[^\\s\\*\\\\]|\\*\\*)|[^\\s\\*\\\\])+?)\\*(?!\\*)`) - const italicRegex = /\*(?=\S)((?:\*\*|\\[\s\S]|\s+(?:\\[\s\S]|[^\s\*\\]|\*\*)|[^\s\*\\])+?)\*(?!\*)/g - if (sel.match(selRegex)) { - sel = sel.replace(selRegex, '$1') - } else if (start > 0 && end < len) { - if (val.substring(start - 1, end + 1).match(selRegex)) { - start -= 1 - end += 1 - sel = val.substring(start, end).replace(selRegex, '$1') - } else { - if (sel.match(italicRegex)) { - sel = sel.replace(italicRegex, '$1') - } else { - sel = `*${sel.replace(/\n/g, '*\n*')}*` - } - } - } else { - if (!start && val.substring(start, end + 1).match(selRegex)) { - end++ - sel = val.substring(start, end).replace(selRegex, '$1') - } else if (end === len && val.substring(start - 1, end).match(selRegex)) { - start-- - sel = val.substring(start, end).replace(selRegex, '$1') - } else { - if (sel.match(italicRegex)) { - sel = sel.replace(italicRegex, '$1') - } else { - sel = `*${sel.replace(/\n/g, '*\n*')}*` - } - } - } - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - } - }, - heading: { - title: 'enlarges/reduces the text', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = /^#+ .*/gm - if (sel.match(selRegex)) { - if (sel.match(/^#{2,} /gm)) { - sel = sel.replace(/^# /gm, '') - sel = sel.replace(/(^#*)# /gm, '$1 ') - } else { - sel = sel.replace(/^# /gm, '') - } - } else if (!start || val.substring(start - 1, end).match(/\n.*/gm)) { - sel = `${val.substring(start - 1, end).match(/\n^.*/gm) || !start ? '' : '\n'}###### ${sel.replace(/\n/g, '\n###### ')}` - } else if (val.substring(start - 1, end).match(selRegex)) { - start -= 4 - sel = val.substring(start, end).replace(/(^#*)# /gm, '$1 ') - } else if (val.substring(start - 2, end).match(selRegex)) { - start -= 5 - sel = val.substring(start, end).replace(/(^#*)# /gm, '$1 ') - } else { - sel = `\n###### ${sel.replace(/\n/g, '\n###### ')}` - } - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - } - }, - strikethrough: { - title: 'strikethrough text', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = /~~((.*\W?)*)~~/ - if (sel.match(selRegex)) { - sel = sel.replace(selRegex, '$1') - } else if (start > 0 && end < len) { - if (sel.match(selRegex)) { - sel.replace(selRegex, '$1') - } else if (val.substring(start - 1, end + 1).match(selRegex)) { - start -= 1 - end += 1 - sel = val.substring(start, end).replace(selRegex, '$1') - } else if (val.substring(start - 2, end + 2).match(selRegex)) { - start -= 2 - end += 2 - sel = val.substring(start, end).replace(selRegex, '$1') - } else { - if (sel.match(/~~.+~~/g)) { - sel = sel.replace(/~~/g, '') - } else { - sel = `~~${sel}~~` - } - } - } else { - if (!start && val.substring(start, end + 1).match(selRegex)) { - end++ - sel = val.substring(start, end).replace(selRegex, '$1') - } else if (end === len && val.substring(start - 1, end).match(selRegex)) { - start-- - sel = val.substring(start, end).replace(selRegex, '$1') - } else { - if (sel.match(/~~.+~~/g)) { - sel = sel.replace(/~~/g, '') - } else { - sel = `~~${sel}~~` - } - } - } - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - } - }, - highlighter: { - title: 'highlighted text', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = new RegExp(`\`(${sel.replace(/`/g, '')})\``) - if (sel.match(selRegex)) { - sel = sel.replace(selRegex, '$1') - } else if (start > 0 && end < len) { - if (val.substring(start - 1, end + 1).match(selRegex)) { - start -= 1 - end += 1 - sel = val.substring(start, end).replace(selRegex, '$1') - } else { - if (sel.match(/`.+`/g)) { - sel = sel.replace(/`/g, '') - } else { - sel = `\`${sel.replace(/\n/g, '`\n`')}\`` - } - } - } else { - if (!start && val.substring(start, end + 1).match(selRegex)) { - end++ - sel = val.substring(start, end).replace(selRegex, '$1') - } else if (end === len && val.substring(start - 1, end).match(selRegex)) { - start-- - sel = val.substring(start, end).replace(selRegex, '$1') - } else { - if (sel.match(/`.+`/g)) { - sel = sel.replace(/`/g, '') - } else { - sel = `\`${sel.replace(/\n/g, '`\n`')}\`` - } - } - } - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - } - }, - 'list-ul': { - title: 'unordered list', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = /^( {3})*- (.*)/ - if (sel.match(selRegex)) { - if (sel.match(/^ {3}/)) { - sel = sel.replace(/^ {3}/gm, '') - } else { - sel = sel.replace(/^- /gm, '') - } - } else if (!start || val.substring(start - 1, end).match(/^\n.*/)) { - sel = `${val.substring(start - 1, end).match(/\n^.*/gm) || !start ? '' : '\n'}- ${sel.replace(/\n/g, '\n- ')}${val.substring(end, end + 2).match(/\n\n/) ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}` - } else if (val.substring(start - 1, end).match(selRegex)) { - start-- - sel = val.substring(start, end).replace(/( {3})*- /g, '$1 - ') - } else if (val.substring(start - 2, end).match(selRegex)) { - start -= 2 - sel = val.substring(start, end).replace(/( {3})*- /g, '$1 - ') - } else { - sel = `\n- ${sel.replace(/\n/g, '\n- ')}${val.substring(end, end + 2).match(/\n\n/) || end === len ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}` - } - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - } - }, - 'list-ol': { - title: 'ordered list', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = /^( {3})*\d+\. (.*)/gm - if (sel.match(selRegex)) { - if (sel.match(/^ {3}/)) { - sel = sel.replace(/^ {3}/gm, '') - } else { - sel = sel.replace(/^\d+\. /gm, '') - } - } else if (!start || val.substring(start - 1, end).match(/^\n.*/)) { - let countOl = 0 - sel = `${val.substring(start - 1, end).match(/\n^.*/gm) || !start ? '' : '\n'}0. ${sel.replace(/\n/g, () => { - countOl++ - return `\n${countOl}. ` - })}${val.substring(end, end + 2).match(/\n\n/) ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}` - } else if (val.substring(start - 4, end).match(/( {3})*\d+\. (.*)/)) { - start -= 4 - sel = val.substring(start, end).replace(/( {3})*(\d+\.) /g, ' $1$2 ') - } else if (val.substring(start - 5, end).match(/( {3})*\d+\. (.*)/)) { - start -= 5 - sel = val.substring(start, end).replace(/( {3})*(\d+\.) /g, ' $1$2 ') - } else { - let countOl = 0 - sel = `\n0. ${sel.replace(/\n/g, () => { - countOl++ - return `\n${countOl}. ` - })}${val.substring(end, end + 2).match(/\n\n/) || end === len ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}` - } - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - } - }, - 'quote-right': { - title: 'quote', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = /^>+\s.*/gm - if (sel.match(selRegex)) { - if (sel.match(/^> /gm)) { - sel = sel.replace(/^> /gm, '') - } else { - sel = sel.replace(/(^>*)> /gm, '$1 ') - } - } else if (!start || val.substring(start - 1, end).match(/^\n.*/)) { - sel = `${val.substring(start - 1, end).match(/\n^.*/gm) || !start ? '' : '\n'}> ${sel.replace(/\n/g, '\n> ')}${val.substring(end, end + 2).match(/\n\n/) ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}` - } else if (val.substring(start - 1, end).match(selRegex)) { - start-- - sel = val.substring(start, end).replace(/(^>*)\s/gm, '$1> ') - } else if (val.substring(start - 2, end).match(selRegex)) { - start -= 2 - sel = val.substring(start, end).replace(/(^>*)\s/gm, '$1> ') - } else { - sel = `\n> ${sel.replace(/\n/g, '\n> ')}${val.substring(end, end + 2).match(/\n\n/) || end === len ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}` - } - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - } - }, - code: { - title: 'block of code', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = new RegExp(`^ {4}(.*)`, 'gm') - if (sel.match(selRegex)) { - sel = sel.replace(/^ {4}/gm, '') - } else if (start === 0 || val.substring(start - 1, end).match(/\n.*/gm)) { - if (sel.match(/^ {4}/gm)) { - sel = sel.replace(/^ {4}/gm, '') - } else { - sel = `${start === 0 ? '' : val.substring(start - 1, end).match(/^\n/) ? '\n' : '\n\n'} ${sel.replace(/\n/g, '\n ')}` - } - } else { - sel = `${val.substring(start - 1, end).match(/^\n/) ? '\n' : '\n\n'} ${sel.replace(/\n^(.*)/gm, '\n $1')}${val.substring(end, end + 1).match(/\n/) ? '' : '\n'}` - } - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - } - }, - link: { - title: 'insert link', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = /^(?!!)\[(.*)\]\((\S*)( ".*")?\)/ - if (sel.match(selRegex)) { - textarea.value = val.substring(0, start) + sel.replace(selRegex, '$1 $2') + val.substring(end, len) - } else { - let link = '' - if (!sel.match(/!\[(.*)\]\((\S*)( ".*")?\)/)) { - link = sel.match(/https?:\/\/\S*/) || '' - sel = sel - .replace(link[0], '') - .replace(/ +/g, ' ') - .trim() - } - const divModal = $(`<div class="v--modal-overlay scrollable overlay-fade-enter-active" style="opacity: 0" id="markdown"><div class="v--modal-background-click"><div class="v--modal-top-right"></div><div class="v--modal-box v--modal" style="top: 89px; left: 240px; width: 800px; height: auto;"><div style="padding: 30px;"><button type="button" class="close">Ă—</button><h4 class="clear-top">Markdown informations box</h4><hr><div><h4 class="clear-top">Text:</h4><input id="markdown-text" type="text" placeholder="Insert text here" class="form-control input-lg input-prompt"><h4>Link:</h4><input id="markdown-link" type="text" placeholder="Insert link here" class="form-control input-lg input-prompt"><h4>Hover message:</h4><input id="markdown-hover" type="text" placeholder="Message when hover the link (optional)" class="form-control input-lg input-prompt"></div><hr><p class="text-center clear-bot"><button type="button" id="markdown-done" class="btn btn-default">Done</button></p></div></div></div></div>`) - $('.navbar-header>div:last-child').append(divModal) - setTimeout(() => { - document.body.classList.add('v--modal-block-scroll') - $('#markdown').style.opacity = 1 - }, 1) - $('#markdown-text').value = sel ? sel : '' - $('#markdown-link').value = link ? link[0] : '' - $('.close').addEventListener('click', () => { - document.body.classList.remove('v--modal-block-scroll') - $('#markdown').outerHTML = '' - }) - $('#markdown-done').addEventListener('click', () => { - sel = `[${$('#markdown-text').value}](${$('#markdown-link').value}${$('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : ''})` - textarea.value = val.substring(0, start) + sel + val.substring(end, len) - document.body.classList.remove('v--modal-block-scroll') - $('#markdown').outerHTML = '' - }) - } - } - }, - image: { - title: 'insert image', - replaceFunc: (val, len, start, end, sel) => { - const selRegex = /!\[(.*)\]\((\S*)( ".*")?\)/ - if (sel.match(selRegex)) { - textarea.value = val.substring(0, start) + sel.replace(selRegex, '$1 $2') + val.substring(end, len) - } else { - let link = '' - if (!sel.match(/\[(.*)\]\((\S*)( ".*")?\)/)) { - link = sel.match(/https?:\/\/\S*/) || '' - sel = sel - .replace(link[0], '') - .replace(/ +/g, ' ') - .trim() - } else { - sel = '' - } - const divModal = $(`<div class="v--modal-overlay scrollable overlay-fade-enter-active" style="opacity: 0" id="markdown"><div class="v--modal-background-click"><div class="v--modal-top-right"></div><div class="v--modal-box v--modal" style="top: 89px; left: 240px; width: 800px; height: auto;"><div style="padding: 30px;"><button type="button" class="close">Ă—</button><h4 class="clear-top">Markdown informations box</h4><hr><div><h4 class="clear-top">Text:</h4><input id="markdown-text" type="text" placeholder="Insert text here" class="form-control input-lg input-prompt"><h4>Link:</h4><input id="markdown-link" type="text" placeholder="Insert link here" class="form-control input-lg input-prompt"><h4>Hover message:</h4><input id="markdown-hover" type="text" placeholder="Message when hover the link (optional)" class="form-control input-lg input-prompt"></div><hr><p class="text-center clear-bot"><button type="button" id="markdown-done" class="btn btn-default">Done</button></p></div></div></div></div>`) - $('.navbar-header>div:last-child').append(divModal) - setTimeout(() => { - document.body.classList.add('v--modal-block-scroll') - $('#markdown').style.opacity = 1 - }, 1) - $('#markdown-text').value = sel ? sel : '' - $('#markdown-link').value = link ? link[0] : '' - $('.close').addEventListener('click', () => { - document.body.classList.remove('v--modal-block-scroll') - $('#markdown').outerHTML = '' - }) - $('#markdown-done').addEventListener('click', () => { - const tag = `![${$('#markdown-text').value}](${$('#markdown-link').value}${$('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : ''})` - sel = val.substring(start, end) - if (!sel.match(/\[(.*)\]\((\S*)( ".*")?\)/)) { - textarea.value = val.substring(0, start) + tag + val.substring(end, len) - } else { - textarea.value = val.substring(0, start) + sel.replace(/\[.*\]/, `[${tag}]`) + val.substring(end, len) - } - document.body.classList.remove('v--modal-block-scroll') - $('#markdown').outerHTML = '' - }) - } - } - } - } - const textarea = $('#input-comment') - if (textarea) { - const getSelectedText = e => { - const val = textarea.value - const len = val.length - const start = textarea.selectionStart - const end = textarea.selectionEnd - const sel = val.substring(start, end) - markdown[`${e.target.id}`].replaceFunc(val, len, start, end, sel) - } - const markdownDiv = $('<div id="markdown-editor"></div>') - Object.keys(markdown).forEach(x => markdownDiv.appendChild($(`<div id="${x}" class="test-markdown fas fa-${x} btn btn-default" title="${markdown[x].title}"></div>`))) - textarea.insertAdjacentHTML('beforebegin', markdownDiv.outerHTML) - ;[...$('#markdown-editor').children].forEach(x => x.addEventListener('click', getSelectedText)) - } - } - // Enhance menu for higher resolutions - let p = $('.navbar-toggle').parentNode - - p.appendChild($('<span class="gpe-wide gpe-spacer"></span>')) - p.appendChild($('<a href="/sandbox/" title="Sandbox" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item" style="background:#5A5"><span class="fas fa-edit" style="color:#BFB" /></a>')) - p.appendChild($('<a href="/browse/all-games/" title="Browse Games" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-folder-open" /></a>')) - p.appendChild($('<a href="/contests/" title="Contests" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-trophy" /></a>')) - p.appendChild($('<a href="javascript:toggleLight()" title="Toggle light" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item" style="background:#AA5"><span class="fas fa-eye" style="color:#FFB" /></a>')) - p.appendChild($('<a href="/leaderboard/" title="Leaderboards" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-fire" /></a>')) - p.appendChild($('<a href="/faq/" title="FAQ" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-question-circle " /></a>')) - p.appendChild($('<a href="/forums/" title="Forums" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item" style="background:#55A"><span class="fas fa-comments" style="color:#BBF" /></a>')) - p.appendChild($('<a href="/search/" title="Search" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-search" /></a>')) - p.appendChild($('<a href="/dashboard/" title="Dashboard" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-bell" /></a>')) - p.appendChild($('<a id="menusettings" href="/settings/" title="Settings" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item"><span class="fas fa-cog" /></a>')) - p.appendChild($('<a href="/logout" title="Log Out" class="gpe-wide gpe-btn btn btn-menu navbar-btn navbar-user-item" style="background:#A55"><span class="fas fa-sign-out-alt" style="color:#FBB" /></a>')) - // Let users with screens narrow enough so top bar isn't visible still use toggle light function - $('#main-menu').insertAdjacentHTML('afterbegin', '<a href="javascript:toggleLight()" class="list-group-item"><span class="fas fa-eye"></span> Toggle light</a>') - - p = $('.btn-menu-player') - if (p) { - const userlink = $('.player-dropdown a[href^="/player/"]').href - const useravatar = $('.btn-menu-player').innerHTML - const element = $(`<a href="${userlink}" title="View Profile" class="gpe-wide-block navbar-btn navbar-user-item" style="margin-top:8px">${useravatar}</a>`) - p.parentNode.appendChild(element) - } - - // Tell to look at settings if freshly installed - /*const newSettingsSeen = localStorage.getItem('anbt_newSettingsSeen') - if (!newSettingsSeen && window.innerWidth > 974) { - const freshSettingsHint = 'Thanks for choosing ANBT! Script settings are on the settings page, click to remove this hint.' - if (!options.newCanvas) { - freshSettingsHint += " Don't forget to try the new canvas!" - } - $('#menusettings').setAttribute('title', '') - $("#menusettings").tooltip({ - container: "body", - placement: "bottom", - title: freshSettingsHint - }); - //$("#menusettings").tooltip("show"); - const freshHintRemove = () => { - localStorage.setItem('anbt_newSettingsSeen', 1) - } - $('#menusettings').addEventListener('click', freshHintRemove) - $('#menusettings').addEventListener('mousedown', freshHintRemove) - $('#menusettings').addEventListener('touchstart', freshHintRemove) - }*/ - // Make new notifications actually discernable from the old ones - let num = $('#user-notify-count') - if (num) num = num.textContent.trim() - GM_addStyle(`#user-notify-list .list-group .list-group-item .fas {color: #888}#user-notify-list .list-group .list-group-item:nth-child(-n+${num}) .fas {color: #2F5}a.wrong-order {color: #F99} div.comment-holder:target {background-color: #DFD}.comment-new a.text-muted:last-child:after {content: 'New'; color: #2F5; font-weight: bold; background-color: #183; border-radius: 9px; display: inline-block; padding: 0px 6px; margin-left: 10px;}`) - - // Show an error if it occurs instead of "loading forever" - window.getNotifications = () => { - if (!notificationsOpened) { - $('#user-notify-list').innerHTML = '<img src="/img/loading.gif" alt="Loading...."/>' - const xhr = new XMLHttpRequest() - xhr.open('GET', '/notification/view/') - xhr.onload = () => { - if (xhr.status === 200) { - $('#user-notify-list').innerHTML = xhr.responseText - $('#user-notify-count').textContent = '0' - notificationsOpened = true - } else { - '#user-notify-list'.innerHTML = xhr.responseText - notificationsOpened = true - } - } - } - } - - let versionDisplay - try { - const appver = $('script[src^="/build/app"]').src.match(/(\w+)\.js$/)[1] - const runtimever = $('script[src^="/build/runtime"]').src.match(/(\w+)\.js$/)[1] - versionDisplay = `ANBT v${SCRIPT_VERSION} | app ${appver}` - if (appver !== SITE_VERSION) versionDisplay += '*' - versionDisplay += ` | runtime ${runtimever}` - if (runtimever !== '1ba6bf05') versionDisplay += '*!!!' // didn't break with one update, hurray - } catch (e) { - versionDisplay = `ANBT v${SCRIPT_VERSION}` - } - const h = $('.wrapper') - if (h) h.appendChild($(`<div id="anbtver">${versionDisplay}</div>`)) - - $('.footer-main .list-unstyled')[0].appendChild($('<li><a href="/forums/-/11830/-/">ANBT script</a></li>')) - $('.footer-main .list-unstyled')[1].appendChild($('<li><a href="http://drawception.wikia.com/">Wiki</a></li>')) - $('.footer-main .list-unstyled')[2].appendChild($('<li><a href="http://chat.grompe.org.ru/#drawception">Chat</a> (<a href="https://discord.gg/CNd5KTJ">Discord</a>)</li>')) - - if (options.newCanvas) { - const directToNewSandbox = function(e) { - if (e.which === 2) return - e.preventDefault() - setupNewCanvas(true, this.href) - } - const directToNewPlay = function(e) { - if (e.which === 2) return - e.preventDefault() - setupNewCanvas(false, this.href) - } - $('a[href^="/sandbox/"]', true).forEach(x => x.addEventListener('click', directToNewSandbox)) - $('a[href="/play/"]', true).forEach(x => x.addEventListener('click', directToNewPlay)) - } - } - - const mark = document.createElement('b') - mark.id = '_anbt_' - mark.style.display = 'none' - document.body.appendChild(mark) - if (pagodaBoxError()) return - - if (typeof DrawceptionPlay === 'undefined') { - // Fix for Chrome new loading algorithm, apparently - let loader = setInterval(() => { - if (typeof DrawceptionPlay === 'undefined') return - pageEnhancements() - clearInterval(loader) - }, 100) - } else { - pageEnhancements() - } -} // wrapped - -// From http://userstyles.org/styles/93911/dark-gray-style-for-drawception-com -localStorage.setItem( - 'gpe_darkCSS', - ( - 'a{color:#77c0ff$}.wrapper{~#444$}#nav-drag{~#353535$}.btn-default{~#7f7f7f$;border-bottom-color:#666$;border-left-color:#666$;border-right-color:#666$;border-top-color:#666$;color:#CCC$}' + - '.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{~#757575$;border-bottom-color:#565656$;border-left-color:#565656$;border-right-color:#565656$;border-top-color:#565656$;color:#DDD$}' + - '.btn-success{~#2e2e2e$;border-bottom-color:#262626$;border-left-color:#262626$;border-right-color:#262626$;border-top-color:#262626$;color:#CCC$}' + - '.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{~#232323$;border-bottom-color:#1c1c1c$;border-left-color:#1c1c1c$;border-right-color:#1c1c1c$;border-top-color:#1c1c1c$;color:#DDD$}' + - '.btn-primary{~#213184$;border-bottom-color:#1a1a68$;border-left-color:#1a1a68$;border-right-color:#1a1a68$;border-top-color:#1a1a68$;color:#CCC$}' + - '.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{~#191964$;border-bottom-color:#141450$;border-left-color:#141450$;border-right-color:#141450$;border-top-color:#141450$;color:#DDD$}' + - '.btn-info{~#2d7787$;border-bottom-color:#236969$;border-left-color:#236969$;border-right-color:#236969$;border-top-color:#236969$;color:#CCC$}' + - '.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{~#1c5454$;border-bottom-color:#133939$;border-left-color:#133939$;border-right-color:#133939$;border-top-color:#133939$;color:#DDD$}' + - '.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{~#3b3b3b$}.navbar-toggle{~#393939$}.navbar{border-bottom:1px solid #000$}.forum-thread-starter,.breadcrumb,.regForm{~#555$}' + - '.form-control{~#666$;border:1px solid #333$;color:#EEE$}code,pre{~#656$;color:#FCC$}body{color:#EEE$}footer{~#333$;border-top:1px solid #000$}' + - '.pagination>li:not(.disabled):not(.active),.pagination>li:not(.disabled):not(.active)>a:hover,.pagination>li:not(.disabled):not(.active)>span:hover,.pagination>li:not(.disabled):not(.active)>a:focus,.pagination>li:not(.disabled):not(.active)>span:focus{~#444$}.pagination>li>a,.pagination>li>span{~#555$;border:1px solid #000$}' + - '.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{~#666$;border-top:1px solid #444$;border-bottom:1px solid #444$}' + - '.drawingForm{~#555$}.well{~#666$;border:1px solid #333$}#timeleft{color:#AAA$}legend{border-bottom:1px solid #000$}.thumbpanel{color:#EEE;~#555$}.thumbpanel img{~#fffdc9$}.panel-number,.modal-content,.profile-user-header{~#555$}' + - '#commentForm{~#555$;border:1px solid #000$}.modal-header,.nav-tabs{border-bottom:1px solid #000$}hr,.modal-footer{border-top:1px solid #000$}' + - '.store-item{background:#666$;~-moz-linear-gradient(top,#666 0,#333 100%)$;~-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(100%,#333))$;~-webkit-linear-gradient(top,#666 0,#333 100%)$;~-o-linear-gradient(top,#666 0,#333 100%)$;~-ms-linear-gradient(top,#666 0,#333 100%)$;~linear-gradient(to bottom,#666 0,#333 100%)$;border:1px solid #222$}' + - '.store-item:hover{border:1px solid #000$}.store-item-title{~#222$;color:#DDD$}.store-title-link{color:#DDD$}.profile-award{~#222$}.profile-award-unlocked{~#888$}.progress-bar{color:#CCC$;~#214565$}.progress{~#333$}' + - '.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(0,0,0,0.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(0,0,0,0.15)),color-stop(.75,rgba(0,0,0,0.15)),color-stop(.75,transparent),to(transparent))$;background-image:-webkit-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:-moz-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$}' + - '.progress-bar-success{~#363$}.progress-bar-info{~#367$}.progress-bar-warning{~#863$}.progress-bar-danger{~#733$}' + - '.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#DDD$;~#555$;border:1px solid #222$}.nav>li>a:hover,.nav>li>a:focus{~#333$;border-bottom-color:#222$;border-left-color:#111$;border-right-color:#111$;border-top-color:#111$}' + - '.nav>li.disabled>a,.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#555$}.table-striped>tbody>tr:nth-child(2n+1)>td,.table-striped>tbody>tr:nth-child(2n+1)>th{~#333$}' + - '.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{~#555$}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{border-top:1px solid #333$}.news-alert{~#555$;border:2px solid #444$}' + - '.btn-menu{~#2e2e2e$}.btn-menu:hover{~#232323$}.btn-yellow{~#8a874e$}.btn-yellow:hover{~#747034$}' + - 'a.label{color:#fff$}.text-muted,a.text-muted{color:#999$}a.wrong-order{color:#F99$}div.comment-holder:target{~#454$}' + - '.popover{~#777$}.popover-title{~#666$;border-bottom:1px solid #444$}.popover.top .arrow:after{border-top-color:#777$}.popover.right .arrow:after{border-right-color:#777$}.popover.bottom .arrow:after{border-bottom-color:#777$}.popover.left .arrow:after{border-left-color:#777$}' + - '.label-fancy{~#444$;border-color:#333$;color:#FFF$}' + - '.avatar,.profile-avatar{~#444$;border:1px solid #777$;}' + - '.bg-lifesupport{~#444$}body{~#555$}.snap-content{~#333$}' + - 'select,textarea{color:#000$}.help-block{color:#ddd$}.jumbotron{~#444$}' + - '.navbar-dropdown{~#444$}a.list-group-item{~#444$;color:#fff$;border:1px solid #222$}a.list-group-item:hover,a.list-group-item:focus{~#222$}' + - '.likebutton.btn-success{color:#050$;~#5A5$}.likebutton.btn-success:hover{~#494$}' + - ".thumbnail[style*='background-color: rgb(255, 255, 255)']{~#555$}" + - '.popup,.v--modal{~#666$;border:1px solid #222$}.btn-reaction{~#666$;border:none$;color:#AAA$}@media(min-width:625px){.create-game-wrapper{~#555$}}' + - '.profile-header{~#555$}.profile-nav > li > a{~#333$}.profile-nav>li.active>a,.profile-nav>li>a:hover{~#555$}' + - '.gsc-control-cse{~#444$;border-color:#333$}.gsc-above-wrapper-area,.gsc-result{border:none$}.gs-snippet{color:#AAA$}.gs-visibleUrl{color:#8A8$}a.gs-title b,.gs-visibleUrl b{color:#EEE$}.gsc-adBlock{display:none$}.gsc-input{~#444$;border-color:#333$;color:#EEE$}' + - '.comment-highlight{border:none$;background:#454$}#header-emotes{~#555$}#header-bar-container{border:none$}.paypal-button-tag-content{color:#EEE$}.numlikes{color:#EEE$}.gsc-input-box{~#444$;border-color:#333$}.gsc-completion-container{~#333$;border-color:#000$}.gsc-completion-selected{~#222$}.gsc-completion-container b{color:#AAA$}.alert-nice{~#4a4a4a$}.store-buy-coins{~#777$}.store-buy-coins:hover{~#666$}.store-buy-coins>h2,.store-buy-coins>h2>small{color:#EEE$}.store-package-selector{~#888$}.store-package-selector>label{color:#EEE$}.label-stat{~#444$;color:#EEE$;border:1px solid #555$}.label-stat.disabled{~#333$}.option{padding:4px 8px$;~#666$;color:#EEE$;border-color:#333$}.option.selected{border-color:#EEE$}.sleek-select{~#666$;border-color:#333$}select{color:#EEE$}.modal-note{color:#EEE$}.vue-dialog-button{~#555$;border:none$}.vue-dialog-button:hover{~#5a5a5a$}.vue-dialog-buttons{border-top:1px solid #222$}.dashboard-item{~#333$}legend{color:#EEE$}.list-group-item{~#444$;color:#EEE$;border:1px solid #222$}.alert-warning{color:#EEE$;~#555$;border-color:#555$}.btn-reaction.active{border:1px solid #EEE$}.bg-shadow-box{~#333$}.btn-gray{~#222$;border:none$}.btn-gray:hover{color:#EEE$;~#1a1a1a$}.btn-bright{~#333$;color:#EEE$}' + - '.player-name-new{color:#33b73f$}.gsc-tabsArea>div{overflow:hidden$}.gs-image-popup-box{~#333$;border-color:#222$}.gs-size{color:#6f6f6f$}.gsc-result-info{color:#EEE$}.gsc-refinementsArea{border:none$}.gsc-tabsArea{border-bottom-color:#333$}.gsc-cursor-page{color:#EEE$}.gsc-cursor-current-page{color:#AAA$}.profile-nav>.disabled>a{color:#555$;~#3a3a3a$}.profile-nav>.disabled>a:hover{~#3a3a3a$}.sleek-select:hover{border-color:#EEE$}' + - '.btn-menu{border-color:#1e1e1e$;text-shadow:0px 0px 3px #777$}.btn-menu:hover{border-color:#1e1e1e$}.pagination>.active>span{color:#EEE$}#btn-notifications{color:#EEE$}.btn-warning{color:#EEE$}.alert-nice{color:#EEE$}.emotes-popup{~#2e2e2e$}.navbar-toggle{~#2e2e2e$;border-color:#1e1e1e$}.navbar-toggle .icon-bar{~#EEE$}' + - '.gamepanel-highlight{box-shadow:0 0 20px #111$;~#222$}' + - // We have entered specificity hell... - 'a.anbt_replaypanel:hover{color:#8af$}' + - '.anbt_favedpanel{color:#d9534f$}' + - // Some lamey compression method! - '' - ) - .replace(/~/g, 'background-color:') - .replace(/\$/g, ' !important') -) - -let settings = localStorage.getItem('gpe_anbtSettings') -settings = settings ? JSON.parse(settings) : {} - -if (settings.anbtDarkMode || typeof settings.anbtDarkMode === 'undefined') { - if (parseInt(localStorage.getItem('gpe_inDark'), 10) == 1) { - const css = document.createElement('style') - css.id = 'darkgraycss' - css.type = 'text/css' - css.appendChild(document.createTextNode(localStorage.getItem('gpe_darkCSS'))) - if (document.head) { - document.head.appendChild(css) - } else { - let darkLoad = setInterval(() => { - if (!document.head) return - document.head.appendChild(css) - clearInterval(darkLoad) - }, 100) - } - } -} - -const anbtLoad = () => { - if (document.getElementById('_anbt_')) return - const script = document.createElement('script') - script.textContent = `(${wrapped.toString()})()` - document.body.appendChild(script) -} - -if (document && document.body) { - anbtLoad() - if (window.opera && parseInt(localStorage.getItem('gpe_operaWarning'), 10) !== 1) { - const w = document.createElement('h2') - w.innerHTML = 'ANBT speaking:<br/>Rename your script file so it doesn\'t contain ".user." part for smoother loading!<br/>This warning is only shown once.' - const m = document.getElementById('main') - m.insertBefore(w, m.firstChild) - localStorage.setItem('gpe_operaWarning', 1) - } -} -document.addEventListener('DOMContentLoaded', anbtLoad, false) diff --git a/newcanvas/drawit.css b/newcanvas/drawit.css deleted file mode 100644 index d4cec62..0000000 --- a/newcanvas/drawit.css +++ /dev/null @@ -1,526 +0,0 @@ -@font-face { - font-family: 'Yanone Kaffeesatz'; - font-style: normal; - font-weight: 400; - src: local('Yanone Kaffeesatz Regular'), local('YanoneKaffeesatz-Regular'), url('yanone.woff') format('woff'); -} -@font-face { - font-family: 'Nunito'; - font-style: normal; - font-weight: 400; - src: local('Nunito-Regular'), url('nunito.woff') format('woff'); -} -body { - margin: 0; - font-family: 'Nunito', sans-serif; - background: #555; -} -.noselect { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -#newcanvasyo { - background: #555; -} -#newcanvasyo.play .onlysandbox, -#newcanvasyo.sandbox .onlyplay { - display: none; -} -#myheader { - font-size: 15pt; - line-height: 49px; - height: 49px; - border-bottom: 1px solid black; - background: #333; - text-align: center; - vertical-align: middle; - color: #999; - overflow: hidden; - display: block; -} -a { - color: #999; -} -a.prominent { - color: #7af; -} -a.prominent:hover { - color: #9ff; -} -#menu { - display: none; - position: absolute; - background: #666; - color: #ddd; - z-index: 2; - border: 2px solid #777; - box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); - list-style-type: none; - padding: 0; - margin: 0; - font-size: 13pt; - text-align: left; -} -#menu.open { - display: block; -} -#menu li { - line-height: 49px; - height: 49px; - padding: 0 10px; - cursor: pointer; -} -#menu li:hover { - background: #777; -} -#menu hr { - margin: 5px; - border: 1px solid #444; -} -#noscriptwarn { - color: #800; - padding: 10px; - font-size: 15pt; - background: #faa; - text-align: center; -} -#drawthis { - padding: 4px; - color: #fff; - font-size: 26pt; - font-family: 'Yanone Kaffeesatz'; - text-align: center; - vertical-align: middle; - display: table-cell; - width: 1%; -} -#drawthis:before { - color: #aaa; - font-size: 14pt; - font-family: 'Nunito'; - content: 'Draw this:'; - padding-right: 1em; - width: 1%; - text-align: right; - vertical-align: middle; - margin-left: -10%; -} -#emptytitle { - height: 10px; -} -#bl { - width: 600px; - margin: auto; - position: relative; -} -#toolpane { - text-align: center; - padding: 0 10px; -} -#infopane { - text-align: center; - padding: 0 10px; -} -.panel { - border: 2px solid #888; - border-radius: 15px; - border-top: none; - background: #444; - margin: 3px 0; -} -#timer { - font-size: 26pt; - color: #aaa; - width: 176px; - float: left; -} -#palettechooser { - display: none; - position: absolute; - width: 300px; - background: #666; - color: #ddd; - z-index: 1; - -webkit-columns: 2; - -moz-columns: 2; - columns: 2; - -webkit-column-gap: 0; - -moz-column-gap: 0; - column-gap: 0; - border: 2px solid #777; - box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); -} -#palettechooser.open { - display: block; -} -#palettechooser div { - padding: 10px 0 20px; - cursor: pointer; - break-inside: avoid; -} -#palettechooser div:hover { - background-color: #777; - color: #eee; -} -#colors { - width: 100px; - float: left; -} -#colors b { - float: left; - display: block; - width: 50px; - height: 44px; - line-height: 42px; - cursor: pointer; -} -#colors b.hint:after { - content: ''; - position: absolute; - border-radius: 50%; - margin: 32px 2px; - width: 5px; - height: 5px; - background-color: white; - border: 1px solid #000; -} -#colors b.hint:before { - opacity: 0.6; -} -#colors b:before { - font-size: 10pt; - opacity: 0.3; - color: #000; -} -#colors b:hover:before { - opacity: 0.4; - color: #fff; -} -#palettename { - color: #aaa; - font-size: 10pt; - padding: 0 5px; - cursor: pointer; -} -#palettename:hover { - color: #ccc; -} -.sandbox #palettename:after { - content: ' \25BC'; -} -#colors #eraser:before { - content: 'eraser'; -} -#colors b { - counter-increment: color; -} -#colors b:before { - content: counter(color); -} -#colors b:nth-child(10) { - counter-reset: color 1; -} -#colors b:nth-child(n+10) { - counter-increment: color; -} -#colors b:nth-child(n+10):before { - content: '\2191' counter(color); -} -#colors.setbackground b:before { - content: 'BG ' counter(color); -} -#colors.setbackground b:nth-child(n+10):before { - content: 'BG\2191' counter(color); -} -#colors.setbackground #eraser:before { - content: 'cancel'; -} -#colors div { - clear: both; -} -#tools { - width: 68px; - float: right; - padding: 5px 0; -} -#tools button, #openmenu { - width: 55px; - height: 44px; - vertical-align: top; - margin: 3px 0; - padding: 0; -} -#brush0, #brush1, #brush2, #brush3, -#setbackground, #undo, #redo, #trash, -#play, #knob, #eraser, .eraser, #openmenu -{ - background-image: url("newcanvasicons.png"); -} -#brush0 { background-position: -2px -8px; } -#brush1 { background-position: -62px -8px; } -#brush2 { background-position: -122px -8px; } -#brush3 { background-position: -182px -8px; } -#brush0.sel { background-position: -2px -68px; } -#brush1.sel { background-position: -62px -68px; } -#brush2.sel { background-position: -122px -68px; } -#brush3.sel { background-position: -182px -68px; } -#setbackground { background-position: -242px -8px; } -#setbackground.sel { background-position: -242px -68px; } -#undo { background-position: -302px -8px; } -#redo { background-position: -362px -8px; } -#trash { background-position: -422px -8px; } -#eraser { background-position: -365px -68px; background-color: pink; } -.eraser { background-position: -425px -68px; background-color: pink; } -#openmenu { background-position: -425px -68px; } -button, #play { - border: none; - border-radius: 4px; - background-color: #eee; -} -button:hover, #play:hover { - background-color: #acf; -} -button:active, #play:active { - background-color: #aaa; -} -button:disabled { - background-color: #aaa; -} -#openmenu:after { - content: "\2630"; - color: #fff; - font-size: 16pt; - opacity: 0.6; -} -#openmenu { - background-color: #999; -} -#openmenu:hover { - background-color: #57a; -} -#openmenu:active { - background-color: #555; -} -#tools hr { - border: 1px solid #888; - width: 93%; - margin: 8px auto; -} -#primary, #secondary { - display:inline-block; - width: 50px; - border-radius: 0 0 0 13px; - color: #fff; -} -#secondary { - border-radius: 0 0 13px 0; -} -#gamemode { - font-size: 13pt; - color: #aaa; - width: 176px; - float: left; -} -#gamebuttons { - font-size: 13pt; - color: #aaa; - width: 176px; - float: left; - padding: 5px 0; -} -#gamebuttons button { - padding: 10px; - margin: 3px 0; - font-size: 10pt; -} -#submit { - font-size: 16pt; - width: 180px; - height: 50px; - background-color: #6d6; -} -#submit:hover { - background-color: #1b7; -} -.guidelines { - color: #fff; - font-size: 10pt; -} -.guidelines ul { - margin: 0; - padding: 0 1em; - text-align: left; -} -.guidelines li { - color: #bbb; -} -#seekbar { - width: 530px; - height: 0px; - margin: 20px 50px 0; - background: #333; - border: 10px solid #333; - border-radius: 10px; - cursor: pointer; -} -#knob { - position: absolute; - width: 48px; - height: 38px; - margin-top: -19px; - margin-left: 492px; /* -10px min */ - border-radius: 10px; - background-color: #fbb; - background-position: -305px -71px; -} -#knob.smooth { - transition: margin-left 0.1s ease-out; -} -#knob:hover { - background-color: #fdd; -} -#play { - background-position: -486px -11px; - position: absolute; - width: 48px; - height: 38px; - margin: -19px -59px; - border-radius: 4px; -} -#play.pause { - background-position: -546px -11px; -} -#wacomContainer { - position: absolute; - top: -10px; - left: -10px; -} -@media screen and (min-width: 1000px) { - #toolpane { - width: 180px; - height: 500px; - position: absolute; - margin-left: -200px; - } - #infopane { - width: 180px; - height: 500px; - position: absolute; - margin-left: 600px; - top: 0; - } - #submit { - position: absolute; - bottom: 10px; - left: 10px; - width: 180px; - height: 50px; - } -} -@media screen and (max-width: 999px) { - #myheader { - font-size: 11pt; - } - #toolpane { - width: 580px; - margin: auto; - } - #toolpane:after { - display: block; - content: ''; - clear: both; - } - #infopane { - width: 580px; - margin: 40px 0; - } - #timer { - float: right; - width: 260px; - } - #colors { - width: 300px; - margin-left: 5px; - clear: left; - float: left; - } - #primary, #secondary { - width: 150px; - } - #tools { - width: 260px; - } - #tools button { - margin: 0; - } - #gamebuttons { - width: 390px; - margin-left: 5px; - } -} -#svgContainer { - width: 600px; - height: 500px; - margin: auto; - background: #fffdc9 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAAAVCAQAAAAJQsNPAAAAkUlEQVR4Xu2WgQbAMBBD9/8/vQ14KpC4UneqYurZ2eNGnnfouWLdsjwce59ikFCdk75ricGIQSEmhekcvRfE8s8jMJ9CdI7camLGYoQ0XzNjJukgRsaJkWFiKqhiLf8x2DAx6EExTV0Mns/sIPZnnBinJJb1h4p21BXrzSOpTZVF3VGCN4lB07U53+6H5Yr1zAdssNOdvbG/lgAAAABJRU5ErkJggg==') center no-repeat; - cursor: crosshair; -} -#svgContainer.hidecursor { - cursor: none; -} -#svgContainer.loading * { - display: none; -} -#svgContainer canvas, #svgContainer svg { - position: absolute; -} -#popup { - display: none; - position: absolute; - width: 400px; - height: 300px; - margin: 50px; - padding: 50px; - opacity: 0.95; - background: #444; - color: #ddd; - border-radius: 15px; - text-align: center; - z-index: 1; - font-size: 20pt; -} -#popup.show { - display: block; -} -#popup hr { - border: 1px solid #888; -} -#popuptitle { - color: #999; - font-size: 25pt; - margin: 10px; -} -#popupclose { - display: block; - position: absolute; - width: 50px; - height: 50px; - background: #c44; - color: #ddd; - right: 0; - top: 0; - text-align: center; - border-radius: 13px; - cursor: pointer; -} -#popupclose:hover { - background: #e77; - color: #eee; -} -#popupclose:before { - content: "\d7"; - font-size: 30pt; - line-height: 40px; -} diff --git a/newcanvas/drawit.js b/newcanvas/drawit.js deleted file mode 100644 index e1d03ba..0000000 --- a/newcanvas/drawit.js +++ /dev/null @@ -1,2761 +0,0 @@ -// Drawing in Time by Grom PE. Public domain. -// Utilities -function ID(id) {return document.getElementById(id);} -function svgElement(name, attrs) -{ - var el = document.createElementNS("http://www.w3.org/2000/svg", name); - if (attrs) - { - var keys = Object.keys(attrs); - for (var i = 0; i < keys.length; i++) - { - if (attrs[keys[i]] !== null) el.setAttribute(keys[i], attrs[keys[i]]); - } - } - return el; -} -function require(script, callback) -{ - var tag = document.querySelector('script[src="' + script + '"]'); - if (tag) return callback(); - tag = document.createElement("script"); - tag.src = script; - tag.onload = callback; - document.body.appendChild(tag); -} -function bytes2string(bytes) -{ - var len = bytes.length; - var arr = []; - for (var i = 0; i < len; i++) - { - arr.push(String.fromCharCode(bytes[i])); - } - return arr.join(""); -} -function string2bytes(binary_string) -{ - var len = binary_string.length; - var bytes = new Uint8Array(len); - for (var i = 0; i < len; i++) - { - var ascii = binary_string.charCodeAt(i); - bytes[i] = ascii; - } - return bytes; -} -function base642bytes(base64) -{ - return string2bytes(atob(base64)); -} -function node2string(node) -{ - if ('outerHTML' in node) - { - return node.outerHTML; - } else { - var div = document.createElement("div"); - div.appendChild(node.cloneNode(true)); - return div.innerHTML; - } -} -function unpack_uint16be(s) -{ - return s.charCodeAt(0) << 8 | s.charCodeAt(1); -} -function unpack_int16be(s) -{ - var v = unpack_uint16be(s); - return v > 32767 ? v - 65536 : v; -} -function int16be(b1, b2) -{ - var v = b1 << 8 | b2; - return v > 32767 ? v - 65536 : v; -} -function unpack_uint32be(s) -{ - return s.charCodeAt(0) << 24 | s.charCodeAt(1) << 16 | s.charCodeAt(2) << 8 | s.charCodeAt(3); -} -function pack_uint16be(n) -{ - return String.fromCharCode(n >> 8 & 0xff, n & 0xff); -} -function pack_uint32be(n) -{ - return String.fromCharCode(n >> 24 & 0xff, n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff); -} -function color2rgba(color) -{ - var r = 0, g = 0, b = 0, a = 255; - if (color.substr(0, 1) == "#") - { - if (color.length == 4) - { - r = color.substr(1, 1); - g = color.substr(2, 1); - b = color.substr(3, 1); - r = parseInt(r + r, 16); - g = parseInt(g + g, 16); - b = parseInt(b + b, 16); - } else { - r = parseInt(color.substr(1, 2), 16); - g = parseInt(color.substr(3, 2), 16); - b = parseInt(color.substr(5, 2), 16); - } - } else if (color.substr(0, 4) == "rgba") - { - var tmp = color.split(/([\d\.]+)/); - r = parseInt(tmp[1], 10); - g = parseInt(tmp[3], 10); - b = parseInt(tmp[5], 10); - a = Math.floor(parseFloat(tmp[7]) * 255); - } - else if (color.substr(0, 3) == "rgb") - { - var tmp = color.split(/([\d\.]+)/); - r = parseInt(tmp[1], 10); - g = parseInt(tmp[3], 10); - b = parseInt(tmp[5], 10); - } else { - // ?! - } - return [r, g, b, a]; -} -function color2dword(color) -{ - var c = color2rgba(color); - return String.fromCharCode(c[0], c[1], c[2], c[3]); -} -function value2hex(val) -{ - return (Math.floor(val/16)%16).toString(16)+(Math.floor(val)%16).toString(16); -} -function rgb2hex(r, g, b) -{ - return "#" + value2hex(r) + value2hex(g) + value2hex(b); -} -function color2hex(color) -{ - var c = color2rgba(color); - return rgb2hex(c[0], c[1], c[2]); -} -function randomItem(l) -{ - return l[Math.floor(Math.random() * l.length)]; -} -function makeCRCTable() -{ - var c; - var crcTable = []; - for(var n = 0; n < 256; n++) - { - c = n; - for(var k = 0; k < 8; k++) - { - c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); - } - crcTable[n] = c; - } - return crcTable; -} -function crc32(str, str2) -{ - var crcTable = window.crcTable || (window.crcTable = makeCRCTable()); - var crc = 0 ^ (-1); - for (var i = 0; i < str.length; i++) - { - crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF]; - } - if (str2) - { - for (var i = 0; i < str2.length; i++) - { - crc = (crc >>> 8) ^ crcTable[(crc ^ str2.charCodeAt(i)) & 0xFF]; - } - } - return (crc ^ (-1)) >>> 0; -} -/* - (c) 2013, Vladimir Agafonkin - Simplify.js, a high-performance JS polyline simplification library - mourner.github.io/simplify-js -*/ -// square distance between 2 points -function getSqDist(p1, p2) -{ - var dx = p1.x - p2.x, - dy = p1.y - p2.y; - return dx * dx + dy * dy; -} -// square distance from a point to a segment -function getSqSegDist(p, p1, p2) -{ - var x = p1.x, - y = p1.y, - dx = p2.x - x, - dy = p2.y - y; - if (dx !== 0 || dy !== 0) - { - var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); - if (t > 1) - { - x = p2.x; - y = p2.y; - } else if (t > 0) { - x += dx * t; - y += dy * t; - } - } - dx = p.x - x; - dy = p.y - y; - return dx * dx + dy * dy; -} -// simplification using optimized Douglas-Peucker algorithm with recursion elimination -function simplifyDouglasPeucker(points, sqTolerance) -{ - var len = points.length, - MarkerArray = typeof Uint8Array !== 'undefined' ? Uint8Array : Array, - markers = new MarkerArray(len), - first = 0, - last = len - 1, - stack = [], - newPoints = [], - i, maxSqDist, sqDist, index; - markers[first] = markers[last] = 1; - while (last) - { - maxSqDist = 0; - for (i = first + 1; i < last; i++) - { - sqDist = getSqSegDist(points[i], points[first], points[last]); - if (sqDist > maxSqDist) - { - index = i; - maxSqDist = sqDist; - } - } - if (maxSqDist > sqTolerance) - { - markers[index] = 1; - stack.push(first, index, index, last); - } - last = stack.pop(); - first = stack.pop(); - } - for (i = 0; i < len; i++) - { - if (markers[i]) newPoints.push(points[i]); - } - return newPoints; -} -function buildSmoothPath(points, path) -{ - var dist1, dist2, angle1, angle2, prevtangent, tangent, t1, t2, x, y, p, c, n, l = points.length; - var good; - if (l < 2) return; - path.pathSegList.initialize(path.createSVGPathSegMovetoAbs(points[0].x, points[0].y)); - path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(points[1].x, points[1].y)); - if (l < 3) return; - for (var i = 1; i < l - 1; i++) - { - p = points[i - 1]; - c = points[i]; - n = points[i + 1]; - x = c.x - p.x; - y = c.y - p.y; - angle1 = Math.atan2(y, x); - dist1 = Math.sqrt(x * x + y * y); - x = n.x - c.x; - y = n.y - c.y; - angle2 = Math.atan2(y, x); - dist2 = Math.sqrt(x * x + y * y); - tangent = (angle1 + angle2) / 2; - if (i > 1) - { - if (Math.abs(angle2 - angle1) >= Math.PI / 4) - { - path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y)); - good = false; - } else { - if (good && dist1 / dist2 >= 0.4 && dist1 / dist2 <= 2.5) - { - t1 = {x: p.x + Math.cos(prevtangent) * dist1 * 0.4, y: p.y + Math.sin(prevtangent) * dist1 * 0.4}; - t2 = {x: c.x - Math.cos(tangent) * dist2 * 0.4, y: c.y - Math.sin(tangent) * dist2 * 0.4}; - path.pathSegList.appendItem(path.createSVGPathSegCurvetoCubicAbs(c.x, c.y, t1.x, t1.y, t2.x, t2.y)); - } else { - path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y)); - good = true; - } - } - } - prevtangent = tangent; - } - c = points[l - 1]; - path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y)); -} - -var random_things = ['hobo', 'shoe', 'log', 'bun', 'sandwich', 'bull', 'beer', 'hair', - 'hill', 'beans', 'man', 'sofa', 'dinosaur', 'road', 'plank', 'hole', 'food', - 'hedgehog', 'pine', 'toad', 'tooth', 'candy', 'rock', 'drop', 'book', 'button', 'carpet', - 'wheel', 'computer', 'box', 'cat', 'rat', 'hook', 'chunk', 'boat', 'spade', 'sack', - 'hammer', 'face', 'soap', 'nose', 'finger', 'steam', 'spring', 'hand', 'fish', - 'elephant', 'dog', 'chair', 'bag', 'phone', 'robot', 'axe', 'grass', 'crack', 'teacher', - 'breadcrumb', 'fridge', 'worm', 'nut', 'cloth', 'apple', 'tongue', 'jar']; -var random_acts = ['crazy from', 'thanks', 'hits', 'lies around on', 'sees', 'grows in', - 'attaches to', 'flies from', 'crawls from', 'chews', 'walks on', 'squishes', 'pecks', - 'wobbles in', 'smokes from', 'smokes', 'rides', 'eats', 'squeals from under', - 'is lost in', 'spins in', 'stuck in', 'hooks', 'angry at', 'bends', 'drips on', - 'rolls on', 'digs', 'crawls in', 'flies at', 'massages', 'dreams of', 'kills', 'pulls', - "doesn't want", 'licks', 'shoots', 'falls off', 'falls in', 'crawls on', 'turns into', - 'stuck to', 'jumps on', 'hides', 'hides in', 'disassembles', 'rips', 'dissolves', - 'stretches', 'crushes', 'pushes', 'drowns in', 'pokes', 'runs away from', 'wants', - 'scratches', 'throws', 'and', 'confused by', 'unimpressed by']; -var random_descs = ['white', 'concrete', 'shiny', 'ill', 'big', 'ex', 'fast', 'happy', - 'inside-out', 'hot', 'burning', 'thick', 'wooden', 'long', 'good', 'tattered', 'iron', - 'liquid', 'frozen', 'green', 'evil', 'bent', 'rough', 'pretty', 'red', 'round', - 'shaggy', 'bald', 'slow', 'wet', 'wrinkly', 'meaty', 'impudent', 'real', 'distraught', - 'sharp', 'plastic', 'gift', 'squished', 'chubby', 'crumbling', 'horned', 'angry', - 'sitting', 'stranded', 'dry', 'hard', 'thin', 'killer', 'walking', 'cold', 'wheezing', - 'grunting', 'chirping', 'wide', 'electric', 'nuclear', 'confused', 'unimpressed']; -function randomPhrase() -{ - function randomBase() - { - switch (Math.floor(Math.random() * 3)) - { - case 0: return [ - randomItem(random_descs), - randomItem(random_things), - ].join(' '); - case 1: return [ - randomItem(random_descs), - randomItem(random_things), - randomItem(random_acts), - randomItem(random_things), - ].join(' '); - case 2: return [ - randomItem(random_descs), - randomItem(random_things), - randomItem(random_acts), - randomItem(random_descs), - randomItem(random_things), - ].join(' '); - } - return "Error!"; - } - var s = randomBase(); - return s.charAt(0).toUpperCase() + s.substr(1); -} -function rgb2lab(rgb) -{ - var r = rgb[0] / 255, - g = rgb[1] / 255, - b = rgb[2] / 255, - x, y, z, l, a, b; - - r = rgb[0] > 10 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); - g = rgb[1] > 10 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); - b = rgb[2] > 10 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); - - x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); - y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); - z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); - - x /= 0.95047; - z /= 1.08883; - - x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); - y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); - z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); - - l = (116 * y) - 16; - a = 500 * (x - y); - b = 200 * (y - z); - - return [l, a, b]; -} -function getColorDistance(rgb1, rgb2) -{ - var lab1 = rgb2lab(rgb1); - var lab2 = rgb2lab(rgb2); - var l = lab2[0] - lab1[0]; - var a = lab2[1] - lab1[1]; - var b = lab2[2] - lab1[2]; - return Math.sqrt(l * l * 2 + a * a + b * b); -} -function getClosestColor(rgb, pal) -{ - // Allow any color in sandbox - if (ID("newcanvasyo").classList.contains("sandbox")) return rgb2hex(rgb[0], rgb[1], rgb[2]); - var c, d, idx = 0, min = 999; - for (var i = 0; i < pal.length; i++) - { - d = getColorDistance(rgb, color2rgba(pal[i])); - if (d < min) - { - min = d; - idx = i; - } - } - c = color2rgba(pal[idx]); - return rgb2hex(c[0], c[1], c[2]); -} -function getColorDistanceLab(lab1, lab2) -{ - var l = lab2[0] - lab1[0]; - var a = lab2[1] - lab1[1]; - var b = lab2[2] - lab1[2]; - return Math.sqrt(l * l * 2 + a * a + b * b); -} -function getClosestColorLab(lab, pal) -{ - var c, d, idx = 0, min = 999; - for (var i = 0; i < pal.length; i++) - { - d = getColorDistanceLab(lab, rgb2lab(color2rgba(pal[i]))); - if (d < min) - { - min = d; - idx = i; - } - } - c = color2rgba(pal[idx]); - return rgb2hex(c[0], c[1], c[2]); -} -function getColorAverage(c1, c2, bias) -{ - // Bias: - // 0 = c1 - // 0.5 = average - // 1 = c2 - return [ - Math.round(c1[0] * bias + c2[0] * (1 - bias)), - Math.round(c1[1] * bias + c2[1] * (1 - bias)), - Math.round(c1[2] * bias + c2[2] * (1 - bias)), - ]; -} - -var palettes = { - "Normal": ['#000000', '#444444', '#999999', '#ffffff', '#603913', '#c69c6d', - '#ffdab9', '#ff0000', '#ffd700', '#ff6600', '#16ff00', '#0fad00', - '#00ffff', '#0247fe', '#ec008c', '#8601af', '#fffdc9'], - "Sepia": ['#402305', '#503315', '#604325', '#705335', '#806345', '#907355', - '#a08365', '#b09375', '#bfa284', '#cfb294', '#dfc2a4', '#ffe2c4'], - "Grayscale": ['#000000', '#111111', '#222222', '#333333', '#444444', '#555555', '#666666', - '#777777', '#888888', '#999999', '#c0c0c0', '#ffffff', '#eeeeee' ], - "Black and white": ['#ffffff', '#000000'], - "CGA": ['#555555', '#000000', '#0000aa', '#5555ff', '#00aa00', '#55ff55', '#00aaaa', '#55ffff', - '#aa0000', '#ff5555', '#aa00aa', '#ff55ff', '#aa5500', '#ffff55', '#aaaaaa', '#ffffff'], - "Gameboy": ['#8bac0f', '#9bbc0f', '#306230', '#0f380f'], - "Neon": ['#ffffff', '#000000', '#adfd09', '#feac09', '#fe0bab', '#ad0bfb', '#00abff'], - "Thanksgiving": ['#673718', '#3c2d27', '#c23322', '#850005', '#c67200', '#77785b', - '#5e6524', '#cfb178', '#f5e9ce'], - "Holiday": ['#3d9949', '#7bbd82', '#7d1a0c', '#bf2a23', - '#fdd017', '#00b7f1', '#bababa', '#ffffff'], - "Valentine's": ['#8b2158', '#a81f61', '#bb1364', '#ce0e62', '#e40f64', '#ff0000', - '#f5afc8', '#ffccdf', '#e7e7e7', '#ffffff'], - "Halloween": ['#444444', '#000000', '#999999', '#ffffff', '#603913', '#c69c6d', - '#7a0e0e', '#b40528', '#fd2119', '#fa5b11', '#faa611', '#ffd700', - '#602749', '#724b97', '#bef202', '#519548', '#b2bb1e'], - "the blues": ['#b6cbe4', '#618abc', '#d0d5ce', '#82a2a1', '#92b8c1', '#607884', - '#c19292', '#8c2c2c', '#295c6f'], - "Spring": ['#9ed396', '#57b947', '#4d7736', '#365431', '#231302', - '#3e2409', '#a66621', '#a67e21', '#ebbb49', '#ffc0cb', '#ffffff'], - "Beach": ['#1ca4d2', '#65bbe2', '#6ab7bf', '#94cbda', '#9cbf80', '#d2e1ab', - '#b8a593', '#d7cfb9', '#dc863e', '#f7dca2'], - "DawnBringer 16": ['#140c1c', '#442434', '#30346d', '#4e4a4e', '#854c30', '#346524', - '#d04648', '#757161', '#597dce', '#d27d2c', '#8595a1', '#6daa2c', - '#d2aa99', '#6dc2ca', '#dad45e', '#deeed6'], - "Freedom": ['#000000', '#2c3539', '#2b3856', '#002a6c', '#800080', '#a52a2a', - '#c2113a', '#ff0000', '#ffd700', '#ffff00', '#ffffff'], - -}; - -var anbt = -{ - container: null, - svg: svgElement("svg", - { - // Even though Opera complains to have failed to set xmlns attribute: - // > Failed attribute on svg element: xmlns="http://www.w3.org/2000/svg". - // this is necessary for loading a saved SVG which otherwise wouldn't - // bind correct prototypes for functions such as path.pathSegList - xmlns: "http://www.w3.org/2000/svg", - version: "1.1", - width: "600", height: "500", - }), - canvas: document.createElement("canvas"), - canvasDisp: document.createElement("canvas"), - svgDisp: svgElement("svg", - { - //xmlns: "http://www.w3.org/2000/svg", - version: "1.1", - width: "600", height: "500", - "pointer-events": "none", - }), - svgHist: null, - path: null, - points: null, - pngBase64: null, - lastrect: 0, - position: 0, - isStroking: false, - isPlaying: false, - size: 14.4, - smoothening: 1, - palette: palettes.Normal, - patternCache: {}, - delay: 100, - unsaved: false, - background: '#fffdc9', - transparent: false, - color: ['#000000', "eraser"], - fastUndoLevels: 10, - rewindCache: [], - snap: false, - fillNext: false, - BindContainer: function(el) - { - this.container = el; - this.canvas.width = 600; - this.canvas.height = 500; - this.canvas.style.background = this.background; - this.ctx = this.canvas.getContext("2d"); - this.ctx.lineJoin = "round"; - this.ctx.lineCap = "round"; - this.container.appendChild(this.canvas); - if (!navigator.userAgent.match(/\bPresto\b/)) - { - this.canvasDisp.width = 600; - this.canvasDisp.height = 500; - this.ctxDisp = this.canvasDisp.getContext("2d"); - this.ctxDisp.lineJoin = "round"; - this.ctxDisp.lineCap = "round"; - this.container.appendChild(this.canvasDisp); - } else { - // Opera Presto is faster with SVG redrawing - this.DrawDispLine = this.DrawDispLinePresto; - } - this.container.appendChild(this.svgDisp); - var rect = svgElement("rect", - { - "class": "eraser", - x: 0, - y: 0, - width: 600, - height: 500, - fill: this.background, - } - ); - this.svg.appendChild(rect); - }, - FromOldSVG: function(buf) // TODO: This function is messy - { - var arr = []; - for (var i = 0; i < buf.length; i++) - { - arr.push(String.fromCharCode(buf[i])); - } - var svgdata = arr.join(""); - - var parser = new DOMParser(); - var svg = parser.parseFromString(svgdata, 'text/xml').documentElement; - for (var i = 0; i < svg.childNodes.length; i++) - { - var el = svg.childNodes[i]; - if (el.nodeName == "path") - { - el.setAttribute("stroke-linejoin", "round"); - el.setAttribute("stroke-linecap", "round"); - el.setAttribute("fill", "none"); - points = []; - var x, y; - for (var j = 0; j < el.pathSegList.numberOfItems; j++) - { - var seg = el.pathSegList.getItem(j); - if (seg.pathSegTypeAsLetter != "l") - { - x = seg.x; - y = seg.y; - points.push({x: x, y: y}); - } else { - el.pathSegList.replaceItem(el.createSVGPathSegLinetoAbs(x, y + 0.001), j); - } - } - if (points.length > 2) - { - points = simplifyDouglasPeucker(points, this.smoothening); - buildSmoothPath(points, el); - } - el.orig = points; - } - } - this.svg = svg; - this.lastrect = 0; - this.rewindCache.length = 0; - this.position = this.svg.childNodes.length - 1; - this.UpdateView(); - this.MoveSeekbar(1); - // Here we assume first element of svg is background rect - this.SetBackground(this.svg.childNodes[0].getAttribute("fill")); - }, - PackPlayback: function(svg) - { - var arr = [color2dword(this.background)]; - var lastcolor = color2dword("#000000"); - var lastsize = 14.4; - var lastx = -1, lasty = -1; - var lastpattern = 0; - - for (var i = 1; i < svg.childNodes.length; i++) - { - var el = svg.childNodes[i]; - if (el.nodeName == "path") - { - var color = color2dword(el.getAttribute("stroke")); - if (el.getAttribute("class") == "eraser") color = "\xFF\xFF\xFF\x00"; - var size = el.getAttribute("stroke-width"); - var fill = el.getAttribute("fill"); - var pattern = el.pattern || 0; - if (color != lastcolor || size != lastsize) - { - arr.push(pack_uint16be(-1)); - arr.push(pack_uint16be(size * 100)); - arr.push(color); - lastcolor = color; - lastsize = size; - } - if (fill != "none") - { - arr.push(pack_uint16be(-4)); - arr.push(pack_uint16be(0)); - arr.push(color2dword(fill)); - } - if (pattern != lastpattern) - { - arr.push(pack_uint16be(-3)); - arr.push(pack_uint16be(pattern)); - arr.push("\x00\x00\x00\x00"); // reserved for the future - lastpattern = pattern; - } - lastx = el.orig[0].x; - lasty = el.orig[0].y; - arr.push(pack_uint16be(lastx)); - arr.push(pack_uint16be(lasty)); - for (var j = 1; j < el.orig.length; j++) - { - var dx = Math.round(el.orig[j].x - lastx); - var dy = Math.round(el.orig[j].y - lasty); - // Ignore repeating points - if (dx === 0 && dy === 0) continue; - arr.push(pack_uint16be(dx)); - arr.push(pack_uint16be(dy)); - lastx = el.orig[j].x; - lasty = el.orig[j].y; - } - arr.push("\x00\x00\x00\x00"); - } else if (el.nodeName == "rect") - { - var color = color2dword(el.getAttribute("fill")); - arr.push(pack_uint16be(-2)); - arr.push(pack_uint16be(0)); // reserved for the future - arr.push(color); - } else { - throw new Error("Unknown node name: " + el.nodeName); - } - } - var result = "\x05" + bytes2string(pako.deflate(string2bytes(arr.join("")))); - return result; - }, - UnpackPlayback: function(bytes) - { - var version = bytes[0]; - var start; - if (version == 4 || version == 5) - { - bytes = pako.inflate(bytes.subarray(1)); - start = 0; - } else if (version == 3) - { - bytes = string2bytes(pako.inflate(bytes.subarray(1), {to: "string"})); - start = 0; - } else if (version == 2) - { - start = 1; - } else { - throw new Error("Unsupported version: " + version); - } - var svg = svgElement("svg", - { - xmlns: "http://www.w3.org/2000/svg", - version: "1.1", - width: "600", height: "500", - }); - var color = "#000000"; - var fill; - var size = 14.4; - var lastx, lasty, x, y; - var pattern = 0; - var points = []; - // Ignore background alpha - var background = [ - "rgb(", - bytes[start], - ',', - bytes[start + 1], - ',', - bytes[start + 2], - ')' - ].join(""); - svg.background = background; - - svg.appendChild(svgElement("rect", - { - "class": "eraser", - x: 0, - y: 0, - width: 600, - height: 500, - fill: background, - } - )); - - for (var i = start + 4; i < bytes.length;) - { - x = int16be(bytes[i], bytes[i + 1]); - i += 2; - y = int16be(bytes[i], bytes[i + 1]); - i += 2; - if (points.length) - { - if (x === 0 && y === 0) - { - var path = svgElement("path", - { - "class": color == "eraser" ? color : null, - stroke: color == "eraser" ? background : color, - "stroke-width": size, - "stroke-linejoin": "round", - "stroke-linecap": "round", - fill: fill ? fill : "none", - } - ); - // Restore blots - if (points.length === 1) - { - path.pathSegList.appendItem(path.createSVGPathSegMovetoAbs(lastx, lasty)); - path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(lastx, lasty + 0.001)); - } else { - buildSmoothPath(points, path); - } - path.orig = points; - path.pattern = pattern; - svg.appendChild(path); - points = []; - fill = null; - } else { - x = x + lastx; - y = y + lasty; - lastx = x; - lasty = y; - points.push({x: x, y: y}); - } - } else { - if (x < 0) - { - if (x === -1 || x === -2) - { - color = [ - "rgba(", - bytes[i], - ',', - bytes[i + 1], - ',', - bytes[i + 2], - ',', - bytes[i + 3] / 255, - ')' - ].join(""); - // TODO: fix ugly code - if (color == "rgba(255,255,255,0)") color = "eraser"; - i += 4; - if (x === -1) - { - size = y / 100; - } else { - svg.appendChild(svgElement("rect", - { - "class": color == "eraser" ? color : null, - x: 0, - y: 0, - width: 600, - height: 500, - fill: color == "eraser" ? background : color, - } - )); - } - } else if (x === -3) { - pattern = y; - i += 4; - } else if (x === -4) { - fill = [ - "rgba(", - bytes[i], - ',', - bytes[i + 1], - ',', - bytes[i + 2], - ',', - bytes[i + 3] / 255, - ')' - ].join(""); - i += 4; - } - } else { - points.push({x: x, y: y}); - lastx = x; - lasty = y; - } - } - } - return svg; - }, - FindLastRect: function(endpos) - { - if (!endpos) endpos = this.svg.childNodes.length - 1; - for (var i = endpos; i > 0; i--) - { - var el = this.svg.childNodes[i]; - if (el.nodeName == "rect") return i; - } - return 0; - }, - CutHistoryBeforePosition: function() - { - for (var i = this.position - 1; i > 0; i--) - { - var el = this.svg.childNodes[i]; - this.svg.removeChild(el); - } - }, - CutHistoryBeforeClearAndAfterPosition: function() - { - var removing = false; - for (var i = this.svg.childNodes.length - 1; i > 0; i--) - { - var el = this.svg.childNodes[i]; - if (removing || i > this.position) - { - this.svg.removeChild(el); - } - else if (el.nodeName == "rect" && i <= this.position) - { - removing = true; - // Optimize out two eraser rectangles next to each other - if (el.getAttribute("class") == "eraser") - { - this.svg.removeChild(el); - } - } - } - }, - MakePNG: function(width, height, fromBuffer) - { - // Cut all needless SVG data that comes before clearing whole canvas - this.CutHistoryBeforeClearAndAfterPosition(); - this.MoveSeekbar(1); - - var canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - var context = canvas.getContext("2d"); - if (!this.transparent) - { - context.fillStyle = this.background; - context.fillRect(0, 0, width, height); - } - if (fromBuffer) - { - context.drawImage(this.canvas, 0, 0, width, height); - } else { - context.lineJoin = "round"; - context.lineCap = "round"; - context.save(); - context.scale(width / 600, height / 500); - // Skip background rect - for (var i = 1; i < this.svg.childNodes.length; i++) - { - this.DrawSVGElement(this.svg.childNodes[i], context); - } - context.restore(); - context.globalCompositeOperation = "destination-over"; - context.fillStyle = this.background; - context.fillRect(0, 0, width, height); - } - this.pngBase64 = canvas.toDataURL("image/png"); - - var version = "svGb"; - var svgstr = this.PackPlayback(this.svg); - var padding = this.pngBase64.substr(-2); - var prepend, custom, iend; - // To append the custom chunk, we need to decode the end of the base64-encoded PNG - // and then reattach as btoa((prepend) + (custom data) + (iend)). - // As base64 encoding chunks are 3 bytes, iend chunk can start in the middle of those, - // so (prepend) contains the data before iend chunk that we had to cut. - if (padding == "==") - { - // Two padding characters - cut = 1; - } else if (padding[1] == "=") - { - // One padding character - cut = 2; - } else { - // No padding - cut = 3; - } - iend = atob(this.pngBase64.substr(-20)).substr(cut); - prepend = atob(this.pngBase64.substr(-20)).substr(0, cut); - custom = [ - prepend, - pack_uint32be(svgstr.length), - version, - svgstr, - pack_uint32be(crc32(version, svgstr)), - iend - ].join(""); - this.pngBase64 = this.pngBase64.substr(0, this.pngBase64.length - 20) + btoa(custom); - }, - FromPNG: function(buffer) - { - var dv = new DataView(buffer); - var magic = dv.getUint32(0); - if (magic != 0x89504e47) throw new Error("Invalid PNG format: " + pack_uint32be(magic)); - for (var i = 8; i < buffer.byteLength; i += 4 /* Skip CRC */) - { - var chunklen = dv.getUint32(i); - i += 4; - var chunkname = pack_uint32be(dv.getUint32(i)); - i += 4; - if (chunkname == "svGb") - { - var newsvg = this.UnpackPlayback(new Uint8Array(buffer, i, chunklen)); - this.svg = newsvg; - // Assume saved data is always optimized and is cut before last rect - //this.lastrect = this.FindLastRect(); - this.lastrect = 0; - this.rewindCache.length = 0; - this.position = this.svg.childNodes.length - 1; - this.UpdateView(); - this.MoveSeekbar(1); - // Here we assume first element of svg is background rect - this.SetBackground(this.svg.background); - return; - } else { - if (chunkname == "IEND") break; - i += chunklen; - } - } - throw new Error("No vector data found!"); - }, - FromURL: function(url) - { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - if ('responseType' in xhr) - { - xhr.responseType = 'arraybuffer'; - } else { - alert("Your browser is too old for this"); - return; - } - var _anbt = this; - xhr.onload = function() - { - _anbt.FromPNG(this.response); - }; - xhr.onerror = function() - { - alert("Error loading an image. Wrong URL?"); - }; - xhr.send(); - }, - FromLocalFile: function(forceAltMethod) - { - if (!this.fileInput) - { - this.fileInput = document.createElement("input"); - this.fileInput.style.position = "absolute"; - this.fileInput.style.top = "-1000px"; - this.fileInput.type = "file"; - this.fileInput.accept = ".png"; - document.body.appendChild(this.fileInput); - var _anbt = this; - this.fileInput.addEventListener("change", function(e) - { - var reader = new FileReader(); - reader.onload = function() - { - _anbt.FromPNG(this.result); - }; - if (e.target.files[0]) - { - reader.readAsArrayBuffer(e.target.files[0]); - } - }, - false - ); - } - if (!navigator.userAgent.match(/\bPresto\b/) && !forceAltMethod) - { - var clickEvent = document.createEvent("MouseEvent"); - clickEvent.initMouseEvent("click", true, true, window, 1, - 0, 0, 0, 0, false, false, false, false, 0, null); - this.fileInput.dispatchEvent(clickEvent); - } else { - setTimeout(this.fileInput.click.bind(this.fileInput), 1); - } - }, - SetBackground: function(color) - { - var transparent = color == "eraser"; - this.transparent = transparent; - this.canvas.style.background = transparent ? "none" : color; - // Normalize the color representation - color = transparent ? "#ffffff" : color2hex(color); - this.background = color; - var erased = this.svg.querySelectorAll(".eraser"); - for (var i = 0; i < erased.length; i++) - { - if (erased[i].nodeName == "path") - { - erased[i].setAttribute("stroke", color); - } else { - erased[i].setAttribute("fill", color); - } - } - }, - SetColor: function(num, color) - { - this.color[num] = color; - }, - SetSize: function(size) - { - this.size = size; - this.MoveCursor(); - }, - DrawSVGElement: function(el, ctx) - { - if (!ctx) ctx = this.ctx; - if (el.getAttribute("class") == "eraser") - { - ctx.globalCompositeOperation = "destination-out"; - } else { - ctx.globalCompositeOperation = "source-over"; - } - if (el.nodeName == "path") - { - var c = el.getAttribute("stroke"); - ctx.strokeStyle = el.pattern ? this.MakePattern(c, el.pattern) : c; - ctx.lineWidth = el.getAttribute("stroke-width"); - ctx.beginPath(); - for (var i = 0; i < el.pathSegList.numberOfItems; i++) - { - var seg = el.pathSegList.getItem(i); - if (seg.pathSegTypeAsLetter == "M") - { - ctx.moveTo(seg.x, seg.y); - } else if (seg.pathSegTypeAsLetter == "L") - { - ctx.lineTo(seg.x, seg.y); - } else if (seg.pathSegTypeAsLetter == "Q") - { - ctx.quadraticCurveTo(seg.x1, seg.y1, seg.x, seg.y); - } else if (seg.pathSegTypeAsLetter == "C") - { - ctx.bezierCurveTo(seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y); - } - } - var fill = el.getAttribute("fill"); - if (fill && fill != "none") - { - ctx.closePath(); - ctx.fillStyle = el.pattern ? this.MakePattern(fill, el.pattern) : fill; - ctx.fill(); - } - ctx.stroke(); - } - else if (el.nodeName == "rect") - { - ctx.fillStyle = el.getAttribute("fill"); - var x = el.getAttribute("x"); - var y = el.getAttribute("y"); - var w = el.getAttribute("width"); - var h = el.getAttribute("height"); - ctx.fillRect(x, y, w, h); - } - }, - UpdateView: function() - { - var start = this.lastrect < this.position ? this.lastrect : 0; - for (var i = start; i <= this.position; i++) - { - this.DrawSVGElement(this.svg.childNodes[i]); - } - }, - DrawDispLinePresto: function(x1, y1, x2, y2, first) - { - if (first) this.svgDisp.insertBefore(this.path, this.svgDisp.firstChild); - }, - DrawDispLine: function(x1, y1, x2, y2, first) - { - var ctx = this.ctxDisp; - var c = this.lastcolor; - ctx.strokeStyle = this.pattern ? this.MakePattern(c, this.pattern) : c; - ctx.lineWidth = this.size; - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); - }, - StrokeBegin: function(x, y, left) - { - if (left === undefined) - { - left = this.lastleft; - } else { - this.lastleft = left; - } - if (this.snap) - { - x = Math.round(x / this.snap) * this.snap; - y = Math.round(y / this.snap) * this.snap; - } - var cls = null; - var color = left ? this.color[0] : this.color[1]; - if (color == "eraser") - { - color = this.background; - cls = "eraser"; - } - this.path = svgElement("path", - { - "class": cls, - stroke: color, - "stroke-width": this.size, - "stroke-linejoin": "round", - "stroke-linecap": "round", - fill: this.fillNext ? color : "none", - } - ); - this.fillNext = false; - - this.lastcolor = color; - this.path.pattern = this.pattern; - //this.svgDisp.insertBefore(this.path, this.svgDisp.firstChild); - this.path.pathSegList.appendItem(this.path.createSVGPathSegMovetoAbs(x, y)); - this.path.pathSegList.appendItem(this.path.createSVGPathSegLinetoAbs(x, y + 0.001)); - this.DrawDispLine(x, y, x, y + 0.001, true); - this.points = []; - this.points.push({x: x, y: y}); - this.blot = true; - this.isStroking = true; - }, - StrokeEnd: function() - { - this.unsaved = true; - var p = this.points; - if (p.length > 2) - { - p = simplifyDouglasPeucker(p, this.smoothening); - buildSmoothPath(p, this.path); - } - this.path.orig = p; - this.Add(this.path); - this.ctxDisp && this.ctxDisp.clearRect(0, 0, 600, 500); - this.isStroking = false; - }, - StrokeAdd: function(x, y) - { - if (!this.isStroking) throw new Error("StrokeAdd without StrokeBegin!"); - if (this.snap) - { - x = Math.round(x / this.snap) * this.snap; - y = Math.round(y / this.snap) * this.snap; - } - var p = this.points[this.points.length - 1]; - if (p.x == x && p.y == y) return; - if (this.blot) - { - this.path.pathSegList.removeItem(1); - this.blot = false; - } - this.path.pathSegList.appendItem(this.path.createSVGPathSegLinetoAbs(x, y)); - this.DrawDispLine(p.x, p.y, x, y); - this.points.push({x: x, y: y}); - // Todo: realtime smoothening - /* - p = this.points; - if (p.length > 2) - { - p = simplifyDouglasPeucker(p, this.smoothening); - buildSmoothPath(p, this.path); - } - */ - }, - // Experimental, for making polylines like in Photoshop - // Caveat: undo will erase whole polyline - StrokeBeginModifyLast: function(x, y, left) - { - if (this.position == 0 || !this.points) return anbt.StrokeBegin(x, y, left); - if (this.snap) - { - x = Math.round(x / this.snap) * this.snap; - y = Math.round(y / this.snap) * this.snap; - } - this.path = this.svg.childNodes[this.position]; - this.points = this.path.orig; - this.Seek(this.position - 1); - this.svgDisp.insertBefore(this.path, this.svgDisp.firstChild); - this.path.pathSegList.appendItem(this.path.createSVGPathSegLinetoAbs(x, y)); - this.points.push({x: x, y: y}); - this.isStroking = true; - }, - ClearWithColor: function(color) - { - this.Add(svgElement("rect", - { - "class": color == "eraser" ? color : null, - x: 0, - y: 0, - width: 600, - height: 500, - fill: color == "eraser" ? this.background : color, - } - )); - this.lastrect = this.position; - }, - Add: function(el) - { - if (this.rewindCache.length >= this.fastUndoLevels) - { - this.rewindCache.pop(); - } - this.rewindCache.unshift(this.ctx.getImageData(0, 0, 600, 500)); - - this.DrawSVGElement(el); - if (!this.timeedit || this.position == this.svg.childNodes.length - 1) - { - // Remove everything past current position - for (var i = this.svg.childNodes.length - 1; i > this.position; i--) - { - this.svg.removeChild(this.svg.childNodes[i]); - } - this.svg.appendChild(el); - this.position = this.svg.childNodes.length - 1; - this.MoveSeekbar(1); - } else { - this.svg.insertBefore(el, this.svg.childNodes[this.position + 1]); - } - }, - Undo: function() - { - // Prevent "undoing" the background rectangle - if (this.position > 0) - { - this.Seek(this.position - 1); - this.MoveSeekbar(this.position / (this.svg.childNodes.length - 1)); - } - }, - Redo: function() - { - var posmax = this.svg.childNodes.length - 1; - if (this.position < posmax) - { - this.Seek(this.position + 1); - this.MoveSeekbar(this.position / posmax); - } - }, - MoveSeekbar: function(pos) - { - if (this.seekbarMove) - { - this.seekbarMove(pos); - } - }, - SetSeekbarMove: function(func) - { - this.seekbarMove = func; - }, - GetSeekMax: function() - { - return this.svg.childNodes.length - 1; - }, - Seek: function(newpos) - { - var start = -1; - this.Pause(true); - if (newpos == this.position) return; - if (newpos < this.position) - { - var rewindSteps = this.position - newpos; - if (rewindSteps <= this.rewindCache.length) - { - // Draw from cached - this.ctx.putImageData(this.rewindCache[rewindSteps - 1], 0, 0); - this.rewindCache.splice(0, rewindSteps); - } else { - // Not cached; rebuild cache - start = 0; - if (this.lastrect <= newpos) - { - start = this.lastrect; - } else { - start = this.FindLastRect(newpos); - } - this.DrawSVGElement(this.svg.childNodes[start]); - } - } else if (newpos > this.position) { - start = this.position; - } - if (start != -1) - { - var forwardSteps = newpos - start; - if (forwardSteps >= this.fastUndoLevels) - { - this.rewindCache.length = 0; - } else { - // Ex: 3 cached, 10 max, 8 steps to play => delete 1 from the end - var len = this.rewindCache.length; - var numRemove = Math.min(len, newpos - start + len - this.fastUndoLevels); - this.rewindCache.splice(len - numRemove, numRemove); - } - for (var i = start + 1; i <= newpos; i++) - { - if (newpos - i < this.fastUndoLevels) - { - this.rewindCache.unshift(this.ctx.getImageData(0, 0, 600, 500)); - } - this.DrawSVGElement(this.svg.childNodes[i]); - } - } - this.position = newpos; - }, - Play: function() - { - this.rewindCache.length = 0; // TODO: make rewind data remember its position - if (this.position == this.svg.childNodes.length - 1) - { - if (this.position === 0) - { - // To make button revert to play - this.MoveSeekbar(1); - return; - } - this.position = 0; - this.MoveSeekbar(0); - // Assume first svg child is background rect - this.DrawSVGElement(this.svg.childNodes[0]); - } - this.isPlaying = true; - this.playTimer = this.PlayTimer.bind(this); - this.playTimer(); - }, - PlayTimer: function() - { - if (!this.isPlaying) return; - var posmax = this.svg.childNodes.length - 1; - var delay = this.delay; - var maxidx = 0; - if (this.position < posmax || this.isAnimating) - { - if (this.isAnimating) - { - maxidx = this.animatePath.pathSegList.numberOfItems - 1; - if (this.animateIndex < maxidx) - { - // There doesn't seem to be a simplier way to copy the pathSeg - var seg = this.animatePath.pathSegList.getItem(this.animateIndex); - var newseg; - if (seg.pathSegTypeAsLetter == "L") - { - newseg = this.path.createSVGPathSegLinetoAbs(seg.x, seg.y); - } else if (seg.pathSegTypeAsLetter == "Q") - { - newseg = this.path.createSVGPathSegCurvetoQuadraticAbs(seg.x, seg.y, seg.x1, seg.y1); - } else if (seg.pathSegTypeAsLetter == "C") - { - newseg = this.path.createSVGPathSegCurvetoCubicAbs(seg.x, seg.y, seg.x1, seg.y1, seg.x2, seg.y2); - } - this.path.pathSegList.appendItem(newseg); - this.animateIndex++; - } else { - this.isAnimating = false; - this.svgDisp.removeChild(this.path); - this.DrawSVGElement(this.animatePath); - this.position++; - this.animateIndex = 0; - } - delay = this.delay / 6; - } else { - var el = this.svg.childNodes[this.position + 1]; - if (el.nodeName == "path") - { - this.isAnimating = true; - this.animatePath = el; - this.animateIndex = 1; - this.path = el.cloneNode(true); - var seg = el.pathSegList.getItem(0); - this.path.pathSegList.initialize(this.path.createSVGPathSegMovetoAbs(seg.x, seg.y)); - this.svgDisp.insertBefore(this.path, this.svgDisp.firstChild); - } else { - this.DrawSVGElement(el); - this.position++; - } - } - } - this.MoveSeekbar((this.position + (maxidx ? this.animateIndex / maxidx : 0)) / posmax); - if (this.position < posmax) - { - setTimeout(this.playTimer, delay); - } else { - this.Pause(); - } - }, - Pause: function(noSeekbar) - { - if (this.isPlaying) - { - if (this.isAnimating) - { - this.isAnimating = false; - this.svgDisp.removeChild(this.path); - this.DrawSVGElement(this.animatePath); - this.position++; - if (!noSeekbar) - { - this.MoveSeekbar(this.position / (this.svg.childNodes.length - 1)); - } - } - this.isPlaying = false; - } - }, - MoveCursor: function(x, y) - { - if (!this.brushCursor) - { - this.brushCursor = svgElement("circle", - { - "stroke-width": "0.5", - stroke: "#000", - fill: "none", - } - ); - this.svgDisp.appendChild(this.brushCursor); - this.brushCursor2 = svgElement("circle", - { - "stroke-width": "0.5", - stroke: "#fff", - fill: "none", - } - ); - this.svgDisp.appendChild(this.brushCursor2); - this.eyedropperCursor = svgElement("image", - { - width: 16, - height: 16, - visibility: "hidden", - } - ); - this.eyedropperCursor.setAttributeNS("http://www.w3.org/1999/xlink", "href", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARklEQVR4XoXRwQoAIAgEUf//pzeGDgq5G3PrCQqVbIAqsDz9WM2qhTX4GZgPV+JpSFxAC0PwbeVZZIpMgXvAMwoj4U9B3wGySxvzk6ZjvwAAAABJRU5ErkJggg=="); - this.svgDisp.appendChild(this.eyedropperCursor); - } - // Assume just size change if called with no parameters - if (typeof x != "undefined") - { - if (this.snap) - { - x = Math.round(x / this.snap) * this.snap; - y = Math.round(y / this.snap) * this.snap; - } - this.brushCursor.setAttribute("cx", x); - this.brushCursor.setAttribute("cy", y); - this.brushCursor2.setAttribute("cx", x); - this.brushCursor2.setAttribute("cy", y); - this.eyedropperCursor.setAttribute("x", x - 1); - this.eyedropperCursor.setAttribute("y", y - 15); - } - this.brushCursor.setAttribute("r", this.size / 2 + 0.5); - this.brushCursor2.setAttribute("r", this.size / 2 - 0.5); - }, - ShowEyedropperCursor: function(isEyedropper) - { - if (!this.brushCursor) return; - var vis = isEyedropper ? "hidden" : "visible"; - var vis2 = isEyedropper ? "visible" : "hidden"; - this.brushCursor.setAttribute("visibility", vis); - this.brushCursor2.setAttribute("visibility", vis); - this.eyedropperCursor.setAttribute("visibility", vis2); - }, - Eyedropper: function(x, y) - { - var p = this.ctx.getImageData(x, y, 1, 1).data; - if (p[3] > 0) - { - return getClosestColor(p, this.palette); - } else { - return this.background; - } - }, - RequestSave: function(dataurl, extension) - { - if (!dataurl) - { - dataurl = this.pngBase64; - extension = ".png"; - this.unsaved = false; - } - if (!this.saveLink) - { - this.saveLink = document.createElement("a"); - document.body.appendChild(this.saveLink); - } - if ("download" in this.saveLink) - { - this.saveLink.href = dataurl; - var d = new Date(); - this.saveLink.download = - [ - "DrawingInTime_", - d.getFullYear(), - "_", - (101 + d.getMonth() + "").slice(-2), - (100 + d.getDate() + "").slice(-2), - "_", - (100 + d.getHours() + "").slice(-2), - (100 + d.getMinutes() + "").slice(-2), - (100 + d.getSeconds() + "").slice(-2), - extension - ].join(""); - this.saveLink.click(); - } else { - window.open(dataurl); - } - return true; - }, - UploadToImgur: function(callback) - { - var xhr = new XMLHttpRequest(); - xhr.open("POST", "https://api.imgur.com/3/image"); - xhr.onload = function() - { - var res = xhr.responseText; - try - { - res = JSON.parse(res); - } - catch(e) {} - if (res.success) - { - // To set description - var xhr2 = new XMLHttpRequest(); - xhr2.open("POST", "https://api.imgur.com/3/image/" + res.data.deletehash); - xhr2.setRequestHeader('Authorization', 'Client-ID 4809db83c8897af'); - var fd = new FormData(); - fd.append("description", "Playback: http://grompe.org.ru/drawit/#" + res.data.id); - xhr2.send(fd); - } - callback(res); - }; - xhr.onerror = function(e) - { - callback("error: " + e); - }; - xhr.setRequestHeader('Authorization', 'Client-ID 4809db83c8897af'); - var fd = new FormData(); - fd.append("image", new Blob([base642bytes(this.pngBase64.substr(22)).buffer], {type: "image/png"})); - fd.append("type", "file"); - fd.append("title", "Made with Drawing in Time"); - fd.append("description", "http://grompe.org.ru/drawit/"); - xhr.send(fd); - }, - FromImgur: function(id) - { - // https link to prevent recompression by various optimizing proxies - this.FromURL("https://i.imgur.com/" + id + ".png"); - }, - MakePattern: function(color, patid) - { - if (this.patternCache[color] && this.patternCache[color][patid]) - { - return this.patternCache[color][patid]; - } else { - if (!this.patternCache[color]) - { - this.patternCache[color] = []; - } - if (!this.patternCanvas) - { - this.patternCanvas = document.createElement("canvas"); - this.patternCanvas.width = 16; - this.patternCanvas.height = 16; - } - var ctx = this.patternCanvas.getContext("2d"); - ctx.fillStyle = color; - ctx.clearRect(0, 0, 16, 16); - if (patid == 1) - { - ctx.beginPath(); - ctx.arc(2, 2, 2, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.beginPath(); - ctx.arc(6, 6, 2, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.drawImage(this.patternCanvas, 8, 0); - ctx.drawImage(this.patternCanvas, 0, 8); - } else if (patid == 2) { - ctx.beginPath(); - ctx.arc(2, 2, 1, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.beginPath(); - ctx.arc(6, 6, 1, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.drawImage(this.patternCanvas, 8, 0); - ctx.drawImage(this.patternCanvas, 0, 8); - } else if (patid == 3) { - ctx.beginPath(); - ctx.arc(4, 4, 2, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.beginPath(); - ctx.arc(4, 12, 2, 0, 2 * Math.PI, false); - ctx.fill(); - ctx.drawImage(this.patternCanvas, 8, -4); - ctx.drawImage(this.patternCanvas, 8, 12); - } else if (patid == 4) { - ctx.fillRect(0, 0, 2, 2); - ctx.fillRect(4, 0, 2, 2); - ctx.fillRect(0, 4, 2, 2); - ctx.fillRect(4, 4, 2, 2); - ctx.drawImage(this.patternCanvas, 8, 0); - ctx.drawImage(this.patternCanvas, 0, 8); - } else if (patid == 5) { - ctx.fillRect(0, 0, 1, 1); - ctx.fillRect(4, 0, 1, 1); - ctx.fillRect(0, 4, 1, 1); - ctx.fillRect(4, 4, 1, 1); - ctx.drawImage(this.patternCanvas, 8, 0); - ctx.drawImage(this.patternCanvas, 0, 8); - } - var pat = this.ctx.createPattern(this.patternCanvas, 'repeat'); - return (this.patternCache[color][patid] = pat); - } - }, - SetPattern: function(patid) - { - this.pattern = patid; - }, - ExportWebM: function() - { - var anbt = this; - require("whammy.min.js", function() - { - var canvas = document.createElement("canvas"); - canvas.width = 600; - canvas.height = 500; - var context = canvas.getContext("2d"); - context.fillStyle = anbt.background; - var encoder = new Whammy.Video(15); - var maxpos = anbt.svg.childNodes.length - 1; - var i = 0; - var nextFrame = function() - { - anbt.Seek(i); - anbt.MoveSeekbar(anbt.position / maxpos); - context.fillRect(0, 0, 600, 500); - context.drawImage(anbt.canvas, 0, 0, 600, 500); - encoder.add(canvas); - i++; - if (i <= maxpos) - { - setTimeout(nextFrame, 1); - } else { - var output = encoder.compile(); - var url = (window.webkitURL || window.URL).createObjectURL(output); - anbt.RequestSave(url, ".webm"); - } - } - nextFrame(); - }); - }, -}; - -var timerStart, timerCallback; - -function bindEvents() -{ - var wacom = ID("wacom"); - var getPointerType = function() - { - return wacom && wacom.penAPI && wacom.penAPI.isWacom ? wacom.penAPI.pointerType : 0; - }; - - var checkPlayingAndStop = function() - { - if (anbt.isPlaying) - { - anbt.Pause(); - ID("play").classList.remove("pause"); - return true; - } - return false; - }; - var rect; - var mouseMove = function(e) - { - e.preventDefault(); - var x = e.pageX - rect.left - pageXOffset; - var y = e.pageY - rect.top - pageYOffset; - anbt.StrokeAdd(x, y); - }; - var mouseUp = function(e) - { - if (e.button === 0 || e.button === 2) - { - e.preventDefault(); - if (anbt.isStroking) anbt.StrokeEnd(); - if (options.hideCross) ID("svgContainer").classList.remove("hidecursor"); - window.removeEventListener('mouseup', mouseUp); - window.removeEventListener('mousemove', mouseMove); - } - }; - ID("svgContainer").addEventListener('mousedown', function(e) - { - if (e.button === 0 || e.button === 2) - { - if (anbt.isStroking) return mouseUp(e); - if (checkPlayingAndStop()) return; - e.preventDefault(); - rect = this.getBoundingClientRect(); - var x = e.pageX - rect.left - pageXOffset; - var y = e.pageY - rect.top - pageYOffset; - - if (e.altKey) - { - var whichcolor = e.button === 0 ? 0 : 1; - if (e.shiftKey && (anbt.color[whichcolor] != "eraser")) - { - var alpha = Math.round(color2rgba(anbt.color[whichcolor])[3] / 2.55) / 100; - var c = color2rgba(anbt.Eyedropper(x, y)); - anbt.SetColor(whichcolor, "rgba(" + c[0] + "," + c[1] + "," + c[2] + "," + alpha + ")"); - } else { - anbt.SetColor(whichcolor, anbt.Eyedropper(x, y)); - } - updateColorIndicators(); - } else { - // PointerType == 3 is pen tablet eraser - var left = e.button === 0 && getPointerType() !== 3; - if (options.hideCross) ID("svgContainer").classList.add("hidecursor"); - if (e.shiftKey) - { - anbt.StrokeBeginModifyLast(x, y, left); - } else { - anbt.StrokeBegin(x, y, left); - } - window.addEventListener('mouseup', mouseUp); - window.addEventListener('mousemove', mouseMove); - } - } - }); - var lastSeenColorToHighlight = anbt.background; - ID("svgContainer").addEventListener('mousemove', function(e) - { - rect = this.getBoundingClientRect(); - var x = e.pageX - rect.left - pageXOffset; - var y = e.pageY - rect.top - pageYOffset; - anbt.MoveCursor(x, y); - // Highlight color we're pointing at - if (options.colorUnderCursorHint && !anbt.isStroking) - { - var color = anbt.Eyedropper(x, y); - if (lastSeenColorToHighlight != color) - { - var el = ID("colors").querySelector("b.hint"); - if (el) el.classList.remove("hint"); - var coloridx = anbt.palette.indexOf(color); - if (coloridx >= 0) - { - var els = ID("colors").querySelectorAll("b"); - els[coloridx].classList.add("hint"); - } - } - lastSeenColorToHighlight = color; - } - }); - window.addEventListener('contextmenu', function(e) - { - if (anbt.isStroking) e.preventDefault(); - }); - - var touchSingle = false; - var lastTouch; - var simulateSingleTouchStart = function() - { - if (touchSingle) - { - var x = lastTouch.pageX - rect.left - pageXOffset; - var y = lastTouch.pageY - rect.top - pageYOffset; - anbt.StrokeBegin(x, y, true); - touchSingle = false; - } - }; - var touchMove = function(e) - { - if (e.touches.length === 1) - { - simulateSingleTouchStart(); - e.preventDefault(); - if (anbt.isStroking) - { - var x = e.touches[0].pageX - rect.left - pageXOffset; - var y = e.touches[0].pageY - rect.top - pageYOffset; - anbt.StrokeAdd(x, y); - } - } - }; - var touchEnd = function(e) - { - if (e.touches.length === 0) - { - simulateSingleTouchStart(); - e.preventDefault(); - anbt.StrokeEnd(); - window.removeEventListener('touchend', touchEnd); - window.removeEventListener('touchmove', touchMove); - } - }; - var touchUndoRedo = function(e) - { - if (e.changedTouches.length === 1 && e.touches.length === 1) - { - var ch = e.changedTouches[0]; - if (Math.abs(ch.pageX - lastTouch.pageX) < 10 && - Math.abs(ch.pageY - lastTouch.pageY) < 10) - { - ID("play").classList.remove("pause"); - if (ch.pageX < e.touches[0].pageX) - { - anbt.Undo(); - } else { - anbt.Redo(); - } - } - } - window.removeEventListener('touchend', touchUndoRedo); - }; - ID("svgContainer").addEventListener('touchstart', function(e) - { - if (e.touches.length === 1) - { - if (checkPlayingAndStop()) return; - // Let two-finger scrolling, pinching, etc. work. - // This requires moving dot-drawing to simulateSingleTouchStart() - rect = this.getBoundingClientRect(); - touchSingle = true; - lastTouch = e.touches[0]; - window.addEventListener('touchend', touchEnd); - window.addEventListener('touchmove', touchMove); - } else { - // Enable two-finger undo and redo: - // 1 o o - // 2 o o o o - // 3 , o o . - // Undo Redo - if (touchSingle && e.touches.length === 3) - { - lastTouch = e.touches[1]; - window.addEventListener('touchend', touchUndoRedo); - } - if (anbt.isStroking) - { - anbt.StrokeEnd(); - } - touchSingle = false; - window.removeEventListener('touchend', touchEnd); - window.removeEventListener('touchmove', touchMove); - } - }); - - ID("svgContainer").addEventListener('mouseleave', function(e) - { - // Hide brush cursor - anbt.MoveCursor(-100, -100); - }); - ID("svgContainer").addEventListener('contextmenu', function(e) - { - e.preventDefault(); - }); - - ID("import").addEventListener('click', function(e) - { - e.preventDefault(); - ID("svgContainer").classList.add("loading"); - anbt.FromLocalFile(e.shiftKey || e.ctrlKey); - ID("svgContainer").classList.remove("loading"); - }); - var warnStrokesAfterPos = function() - { - return (anbt.position < anbt.GetSeekMax() && !confirm("Strokes after current position will be discarded. Continue?")); - }; - var doExport = function(e) - { - e.preventDefault(); - if (warnStrokesAfterPos()) return; - anbt.MakePNG(600, 500, true); - anbt.RequestSave(); - }; - ID("export").addEventListener('click', doExport); - ID("imgur").addEventListener('click', function(e) - { - e.preventDefault(); - if (warnStrokesAfterPos()) return; - ID("imgur").childNodes[0].nodeValue = "Uploading..."; - ID("imgur").disabled = true; - anbt.MakePNG(600, 500, true); - anbt.UploadToImgur(function(r) - { - ID("imgur").childNodes[0].nodeValue = "Upload to imgur"; - ID("popup").classList.add("show"); - ID("popuptitle").childNodes[0].nodeValue = "Imgur upload result"; - if (r && r.success) - { - anbt.unsaved = false; - history.replaceState(null, null, "#" + r.data.id); - ID("imgururl").href = "http://imgur.com/" + r.data.id; - ID("imgururl").childNodes[0].nodeValue = "Uploaded image"; - ID("imgurdelete").href = "http://imgur.com/delete/" + r.data.deletehash; - ID("imgurerror").childNodes[0].nodeValue = ""; - } else { - var err = r.data ? ("Imgur error: " + r.data.error) : ("Error: " + r); - ID("imgurerror").childNodes[0].nodeValue = err; - } - ID("imgur").disabled = false; - }); - }); - window.addEventListener('keydown', function(e) - { - }); - function makeBrushFunc(size) - { - var s = size; - return function(e) - { - e.preventDefault(); - anbt.SetSize(s); - var el = ID("tools").querySelector(".sel"); - if (el) el.classList.remove("sel"); - this.classList.add("sel"); - if (anbt.isStroking) - { - anbt.StrokeEnd(); - var p = anbt.points[anbt.points.length - 1]; - anbt.StrokeBegin(p.x, p.y); - } - }; - } - var brushSizes = [2.4, 6, 14.4, 42]; - - for (var i = 0; i < brushSizes.length; i++) - { - ID("brush" + i).addEventListener('click', makeBrushFunc(brushSizes[i]), false); - } - var updateColorIndicators = function() - { - var c0 = anbt.color[0]; - var c1 = anbt.color[1]; - if (c0 == 'eraser') - { - ID("primary").style.backgroundColor = 'pink'; - ID("primary").classList.add('eraser'); - } else { - ID("primary").style.backgroundColor = c0; - ID("primary").classList.remove('eraser'); - } - if (c1 == 'eraser') - { - ID("secondary").style.backgroundColor = 'pink'; - ID("secondary").classList.add('eraser'); - } else { - ID("secondary").style.backgroundColor = c1; - ID("secondary").classList.remove('eraser'); - } - }; - var chooseBackground = false; - var updateChooseBackground = function(b) - { - chooseBackground = b; - if (b) - { - ID("colors").classList.add("setbackground"); - ID("setbackground").classList.add("sel"); - } else { - ID("colors").classList.remove("setbackground"); - ID("setbackground").classList.remove("sel"); - } - }; - var colorClick = function(e) - { - if (e.touches || e.button === 0 || e.button === 2) - { - e.preventDefault(); - var color = this.style.backgroundColor; - if (chooseBackground) - { - if (this.id != "eraser") - { - anbt.SetBackground(color); - } - updateChooseBackground(false); - } else { - var showcolor = color; - if (this.id == "eraser") - { - color = "eraser"; - showcolor = "pink"; - } - // PointerType == 3 is pen tablet eraser - if (e.button === 2 || getPointerType() === 3) - { - anbt.SetColor(1, color); - } else { - anbt.SetColor(0, color); - } - updateColorIndicators(); - } - } - }; - var noDefault = function(e) - { - e.preventDefault(); - }; - var els = ID("colors").querySelectorAll("b"); - for (var i = 0; i < els.length; i++) - { - els[i].addEventListener('mousedown', colorClick); - els[i].addEventListener('touchend', colorClick); - els[i].addEventListener('contextmenu', noDefault); - } - ID("setbackground").addEventListener('click', function(e) - { - e.preventDefault(); - updateChooseBackground(!chooseBackground); - }); - ID("undo").addEventListener('click', function(e) - { - e.preventDefault(); - ID("play").classList.remove("pause"); - anbt.Undo(); - }); - ID("redo").addEventListener('click', function(e) - { - e.preventDefault(); - ID("play").classList.remove("pause"); - anbt.Redo(); - }); - ID("trash").addEventListener('click', function(e) - { - e.preventDefault(); - anbt.ClearWithColor("eraser"); - if (ID("newcanvasyo").classList.contains("sandbox")) - { - timerStart = Date.now(); - } - }); - - var knobMove = function(fraction) - { - var x = Math.floor(fraction * 502 - 10); - if (fraction > 0) - { - ID("knob").classList.add("smooth"); - } else { - ID("knob").classList.remove("smooth"); - } - ID("knob").style.marginLeft = x + 'px'; - if (fraction >= 1) - { - ID("play").classList.remove("pause"); - } - }; - anbt.SetSeekbarMove(knobMove); - var knobCommonMove = function(e) - { - e.preventDefault(); - var len = anbt.GetSeekMax(); - var x = (e.touches ? e.touches[0].pageX : e.pageX) - rect.left - pageXOffset - 34; - x = Math.min(Math.max(-10, x), 492); - var pos = Math.round((x + 10) / 502 * len); - x = pos / len * 502 - 10; - ID("knob").classList.add("smooth"); - ID("knob").style.marginLeft = x + 'px'; - anbt.Seek(pos); - ID("play").classList.remove("pause"); - }; - var knobCommonUp = function(e) - { - if (e.button === 0 || e.touches && e.touches.length === 0) - { - e.preventDefault(); - window.removeEventListener('mouseup', knobCommonUp); - window.removeEventListener('touchend', knobCommonUp); - window.removeEventListener('mousemove', knobCommonMove); - window.removeEventListener('touchmove', knobCommonMove); - } - }; - var knobCommonDown = function(e) - { - if (e.button === 0 || e.touches && e.touches.length === 1) - { - rect = ID("seekbar").getBoundingClientRect(); - knobCommonMove(e); - window.addEventListener('mouseup', knobCommonUp); - window.addEventListener('touchend', knobCommonUp); - window.addEventListener('mousemove', knobCommonMove); - window.addEventListener('touchmove', knobCommonMove); - } - }; - ID("knob").addEventListener('mousedown', knobCommonDown); - ID("knob").addEventListener('touchstart', knobCommonDown); - ID("seekbar").addEventListener('mousedown', knobCommonDown); - ID("seekbar").addEventListener('touchstart', knobCommonDown); - - var playCommonDown = function(e) - { - e.stopPropagation(); - e.preventDefault(); - if (anbt.isPlaying) - { - ID("play").classList.remove("pause"); - anbt.Pause(); - } else { - ID("play").classList.add("pause"); - anbt.Play(); - } - }; - ID("play").addEventListener('mousedown', playCommonDown); - ID("play").addEventListener('touchstart', playCommonDown); - - var choosePalette = function(e) - { - if (e.touches || e.button === 0) - { - e.preventDefault(); - var name = this.childNodes[0].nodeValue; - ID("palettename").childNodes[0].nodeValue = name; - var colors = palettes[name]; - anbt.palette = colors; - var pal = ID("palette"); - var els = pal.querySelectorAll("b"); - // Remove all current colors except for the eraser - for (var i = 0; i < els.length - 1; i++) - { - pal.removeChild(els[i]); - } - var eraser = els[els.length - 1]; - for (var i = 0; i < colors.length; i++) - { - var b = document.createElement("b"); - b.style.backgroundColor = colors[i]; - b.addEventListener('mousedown', colorClick); - b.addEventListener('touchend', colorClick); - b.addEventListener('contextmenu', noDefault); - pal.appendChild(b); - // Eraser got on the front, put it on the back - pal.appendChild(eraser); - } - } - }; - var closePaletteList = function(e) - { - if (e.touches || e.button === 0) - { - ID("palettechooser").classList.remove("open"); - window.removeEventListener('mousedown', closePaletteList); - window.removeEventListener('touchend', closePaletteList); - } - }; - var openPaletteList = function(e) - { - if (e.touches || e.button === 0) - { - e.preventDefault(); - var chooser = ID("palettechooser"); - chooser.classList.toggle("open"); - if (chooser.classList.contains("open")) - { - setTimeout(function() - { - window.addEventListener('mousedown', closePaletteList); - window.addEventListener('touchend', closePaletteList); - }, 1); - } - var keys = Object.keys(palettes); - if (chooser.childNodes.length < keys.length) - { - var canvas = document.createElement("canvas"); - canvas.height = 10; - var ctx = canvas.getContext("2d"); - for (var i = chooser.childNodes.length; i < keys.length; i++) - { - canvas.width = 8 * palettes[keys[i]].length + 2; - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.globalAlpha = 0.5; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.globalAlpha = 1; - for (var j = 0; j < palettes[keys[i]].length; j++) - { - ctx.fillStyle = palettes[keys[i]][j]; - ctx.fillRect(j * 8 + 1, 1, 8, 8); - } - var div = document.createElement("div"); - div.appendChild(document.createTextNode(keys[i])); - div.style.backgroundImage = 'url("' + canvas.toDataURL() + '")'; - div.style.backgroundRepeat = 'no-repeat'; - div.style.backgroundPosition = 'center 35px'; - div.addEventListener('mousedown', choosePalette); - div.addEventListener('touchend', choosePalette); - chooser.appendChild(div); - } - } - } - }; - ID("palettename").addEventListener('mousedown', openPaletteList); - ID("palettename").addEventListener('touchend', openPaletteList); - - // TODO: refactor: generalize with palette chooser menu - var closeMenu = function(e) - { - if (e.touches || e.button === 0) - { - ID("menu").classList.remove("open"); - window.removeEventListener('mousedown', closeMenu); - window.removeEventListener('touchend', closeMenu); - } - }; - var openMenu = function(e) - { - if (e.touches || e.button === 0) - { - e.preventDefault(); - var menu = ID("menu"); - menu.classList.toggle("open"); - if (menu.classList.contains("open")) - { - setTimeout(function() - { - window.addEventListener('mousedown', closeMenu); - window.addEventListener('touchend', closeMenu); - }, 1); - } - - } - }; - ID("openmenu").addEventListener('mousedown', openMenu); - ID("openmenu").addEventListener('touchend', openMenu); - - var exitPlay = function(e) - { - e.preventDefault(); - ID("newcanvasyo").classList.add("sandbox"); - ID("newcanvasyo").classList.remove("play"); - timerStart -= 10*60*1000; - timerCallback = null; - }; - ID("exit").addEventListener('click', exitPlay); - ID("submit").addEventListener('click', exitPlay); - - // Menu items - var simulatePlay = function(e) - { - e.preventDefault(); - ID("newcanvasyo").classList.remove("sandbox"); - ID("newcanvasyo").classList.add("play"); - timerStart = Date.now() + 10*60*1000; - timerCallback = function(s) - { - if (s < 0) exitPlay(e); - }; - ID("drawthis").childNodes[0].nodeValue = randomPhrase(); - }; - ID("simplay").addEventListener('mousedown', simulatePlay); - ID("simplay").addEventListener('touchend', simulatePlay); - ID("skip").addEventListener('click', simulatePlay); - - var exportCustomPNG = function(e) - { - e.preventDefault(); - if (warnStrokesAfterPos()) return; - var m = prompt("PNG size to export? Enter only width to maintain aspect ratio.", "600x500"); - if (!m) return; - m = m.match(/(\d+)/g); - if (!m) - { - alert("Invalid size."); - return; - } - var width = m[0]; - var height = m[1] || Math.round(width / 1.2); - anbt.MakePNG(width, height); - anbt.RequestSave(); - }; - ID("custompng").addEventListener('mousedown', exportCustomPNG); - ID("custompng").addEventListener('touchend', exportCustomPNG); - - var setTransparentBackground = function(e) - { - e.preventDefault(); - anbt.SetBackground("eraser"); - }; - ID("transparentbg").addEventListener('mousedown', setTransparentBackground); - ID("transparentbg").addEventListener('touchend', setTransparentBackground); - - var enablePatterns = function(e) - { - e.preventDefault(); - ID("enablepatterns").childNodes[0].nodeValue = "(patterns are enabled)" - ID("enablepatterns").removeEventListener('mousedown', enablePatterns); - ID("enablepatterns").removeEventListener('touchend', enablePatterns); - var div = document.createElement('div'); - div.className = "panel"; - div.appendChild(document.createTextNode("pattern:")); - - var canvas = document.createElement("canvas"); - canvas.width = 48; - canvas.height = 48; - var ctx = canvas.getContext("2d"); - var makePatternPreview = function(pattern) - { - ctx.clearRect(0, 0, 48, 48); - ctx.fillStyle = anbt.MakePattern("#000", pattern); - ctx.fillRect(0, 0, 48, 48); - return canvas.toDataURL(); - }; - var makePatternClick = function(pattern) - { - return function() { anbt.SetPattern(pattern); } - }; - for (var i = 0; i < 6; i++) - { - var button = document.createElement('button'); - button.style.width = "55px"; - button.style.height = "44px"; - button.style.margin = "0 2px"; - button.style.verticalAlign = "top"; - if (i === 0) button.appendChild(document.createTextNode("(none)")); - if (i > 0) button.style.backgroundImage = 'url("' + makePatternPreview(i) + '")'; - button.addEventListener('click', makePatternClick(i)); - div.appendChild(button); - } - ID("newcanvasyo").appendChild(div); - }; - ID("enablepatterns").addEventListener('mousedown', enablePatterns); - ID("enablepatterns").addEventListener('touchend', enablePatterns); - - var exportWebM = function(e) - { - e.preventDefault(); - anbt.ExportWebM(); - } - ID("exportwebm").addEventListener('mousedown', exportWebM); - ID("exportwebm").addEventListener('touchend', exportWebM); - - var usageTips = function(e) - { - e.preventDefault(); - alert("Read tooltips on the buttons!\n\n" + - "Press Alt to pick colors (with Shift to preserve opacity)\n" + - "Press T to halve primary color opacity\n" + - "Press X to swap colors\n\n" + - "On touchscreen:\n" + - "put one finger, tap with second finger on the left to undo,\non the right to redo"); - } - ID("usagetips").addEventListener('mousedown', usageTips); - ID("usagetips").addEventListener('touchend', usageTips); - - var drawTransitions = function(e) - { - if (anbt.unsaved && !confirm("You haven't saved the drawing. Continue?")) return; - e.preventDefault(); - anbt.SetBackground(anbt.palette[0]); - anbt.ctx.clearRect(0, 0, 600, 500); - var numcolors = anbt.palette.length; - var w = 36; - var h = 30; - var offsetx = 300 - numcolors * 18 + 30; - var offsety = 250 - numcolors * 15 - 24; - var color1, color2, c; - for (var y = 1; y <= numcolors; y++) - { - for (var x = -1; x < numcolors - 1; x++) - { - if (x == -1 || x == numcolors) - { - if (y == -1 || y == numcolors) continue; - if (y == 0) - { - anbt.ctx.fillStyle = anbt.palette[numcolors - 1] - anbt.ctx.fillRect(x * w + offsetx - 1, y * h + offsety - 1, w + 1, h + 1); - } - anbt.ctx.fillStyle = anbt.palette[y]; - anbt.ctx.fillRect(x * w + offsetx, y * h + offsety, w - 1, h - 1); - } else if (y == -1 || y == numcolors) - { - if (x == 0) - { - anbt.ctx.fillStyle = anbt.palette[numcolors - 1] - anbt.ctx.fillRect(x * w + offsetx - 1, y * h + offsety - 1, w + 1, h + 1); - } - anbt.ctx.fillStyle = anbt.palette[x]; - anbt.ctx.fillRect(x * w + offsetx, y * h + offsety, w - 1, h - 1); - } else { - if (x == y) - { - anbt.ctx.fillStyle = anbt.palette[x]; - anbt.ctx.fillRect(x * w + offsetx, y * h + offsety, w - 1, h - 1); - } - if (x >= y) continue; - color1 = rgb2lab(color2rgba(anbt.palette[x])); - color2 = rgb2lab(color2rgba(anbt.palette[y])); - for (var xx = 34; xx >= 0; xx--) - { - c = getColorAverage(color1, color2, xx / 34); - c = getClosestColorLab(c, anbt.palette); - anbt.ctx.fillStyle = c; - anbt.ctx.fillRect(x * w + offsetx + xx, y * h + offsety, 1, h - 1); - //anbt.ctx.beginPath(); - //anbt.ctx.arc(x * w + w/4 + xx/4 + offsetx, y * h + h/4 + xx/4 + offsety, xx / 2, 0, Math.PI * 2, true); - //anbt.ctx.fill(); - } - } - } - } - } - ID("drawtransitions").addEventListener('mousedown', drawTransitions); - ID("drawtransitions").addEventListener('touchend', drawTransitions); - - var setBackgroundImage = function(e) - { - if (anbt.unsaved && !confirm("You haven't saved the drawing. Continue?")) return; - e.preventDefault(); - anbt.SetBackground("eraser"); - ID("svgContainer").style.backgroundImage = 'url(' + prompt('Enter image URL to use as a background:') + ')'; - ID("svgContainer").style.backgroundSize = '100%'; - }; - ID("setbackgroundimage").addEventListener('mousedown', setBackgroundImage); - ID("setbackgroundimage").addEventListener('touchend', setBackgroundImage); - - var toggleSmooth = function(e) - { - if (window.toggleSmooth) - { - buildSmoothPath = old_buildSmoothPath; - } else { - window.old_buildSmoothPath = buildSmoothPath; - buildSmoothPath = function(points, path) - { - if (points.length < 2) return; - path.pathSegList.initialize(path.createSVGPathSegMovetoAbs(points[0].x, points[0].y)); - for (var i = 1; i < points.length; i++) - { - var c = points[i]; - path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y)); - } - }; - } - window.toggleSmooth = !window.toggleSmooth; - ID("togglesmooth").childNodes[0].nodeValue = - (window.toggleSmooth ? "Enable" : "Disable") + " stroke smoothening"; - }; - ID("togglesmooth").addEventListener('mousedown', toggleSmooth); - ID("togglesmooth").addEventListener('touchend', toggleSmooth); - - var setSnap = function(e) - { - var m = prompt("Grid size in pixels (6 makes brush 2 and brush 4 exact; 0 to disable):", "0"); - if (!m) return; - m = m.match(/(\d+)/g); - if (!m) return; - var a = parseInt(m[0], 10); - anbt.snap = a ? a : false; - }; - ID("setsnap").addEventListener('mousedown', setSnap); - ID("setsnap").addEventListener('touchend', setSnap); - - // --- - - ID("popupclose").addEventListener('click', function(e) - { - e.preventDefault(); - ID("popup").classList.remove("show"); - }); - - - document.addEventListener('keyup', function(e) - { - if (e.keyCode == 18) // Alt - { - ID("svgContainer").classList.remove("hidecursor"); - anbt.ShowEyedropperCursor(false); - } - }); - document.addEventListener('keydown', function(e) - { - if (document.activeElement instanceof HTMLInputElement) return true; - if (e.keyCode == 18) // Alt - { - // Opera Presto refuses to hide cursor =( - if (!navigator.userAgent.match(/\bPresto\b/)) - { - ID("svgContainer").classList.add("hidecursor"); - } - anbt.ShowEyedropperCursor(true); - // The following is needed in case of Alt+Tab causing eyedropper to be stuck - var removeEyedropper = function(e) - { - if (!e.altKey) - { - this.classList.remove("hidecursor"); - anbt.ShowEyedropperCursor(false); - this.removeEventListener('mousemove', removeEyedropper); - } - }; - ID("svgContainer").addEventListener('mousemove', removeEyedropper); - } - else if (e.keyCode == "Q".charCodeAt(0)) - { - e.preventDefault(); - options.colorDoublePress = !options.colorDoublePress; - } - else if (e.keyCode == "C".charCodeAt(0) && !e.ctrlKey && !e.metaKey) - { - e.preventDefault(); - options.hideCross = !options.hideCross; - } - else if (e.keyCode == "Z".charCodeAt(0) || ((e.keyCode == 8) && anbt.unsaved)) - { - e.preventDefault(); - ID("play").classList.remove("pause"); - anbt.Undo(); - } - else if (e.keyCode == "Y".charCodeAt(0)) - { - e.preventDefault(); - ID("play").classList.remove("pause"); - anbt.Redo(); - } - else if (e.keyCode == "X".charCodeAt(0)) - { - e.preventDefault(); - var c0 = anbt.color[0]; - var c1 = anbt.color[1]; - anbt.SetColor(0, c1); - anbt.SetColor(1, c0); - updateColorIndicators(); - } - else if (e.keyCode == "B".charCodeAt(0)) - { - e.preventDefault(); - updateChooseBackground(!chooseBackground); - } - else if (e.keyCode == "E".charCodeAt(0) && !e.ctrlKey && !e.metaKey) - { - e.preventDefault(); - anbt.SetColor(0, "eraser"); - updateColorIndicators(); - } - else if (e.keyCode >= 48 && e.keyCode <= 57 && !e.ctrlKey && !e.metaKey && options.colorNumberShortcuts) - { - e.preventDefault(); - var i = (e.keyCode == 48) ? 9 : e.keyCode - 49; - if (e.shiftKey || (options.colorDoublePress && (anbt.prevColorKey == i))) i += 8; - anbt.prevColorKey = i; - if (options.colorDoublePress) - { - if (anbt.prevColorKeyTimer) clearTimeout(anbt.prevColorKeyTimer); - anbt.prevColorKeyTimer = setTimeout(function() {anbt.prevColorKey = -1}, 500); - } - var els = ID("colors").querySelectorAll("b"); - if (i < els.length) - { - var color = els[i].style.backgroundColor; - if (els[i].id == "eraser") color = "eraser"; - if (chooseBackground) - { - if (color != "eraser") - { - anbt.SetBackground(color); - } - updateChooseBackground(false); - } else { - anbt.SetColor(0, color); - updateColorIndicators(); - } - } - if (anbt.isStroking) - { - anbt.StrokeEnd(); - var p = anbt.points[anbt.points.length - 1]; - anbt.StrokeBegin(p.x, p.y); - } - } - else if (e.keyCode == "T".charCodeAt(0) && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) - { - e.preventDefault(); - - if (anbt.color[0] != "eraser") - { - var c = color2rgba(anbt.color[0]); - var alpha = Math.round(Math.max(c[3]/510, 0.06) * 100) / 100; - anbt.SetColor(0, "rgba(" + c[0] + "," + c[1] + "," + c[2] + "," + alpha + ")"); - } - updateColorIndicators(); - } - else if (e.keyCode == "F".charCodeAt(0) && !e.shiftKey) - { - e.preventDefault(); - anbt.fillNext = true; - } - else if (e.keyCode == "F".charCodeAt(0) && e.shiftKey) - { - e.preventDefault(); - anbt.ClearWithColor(anbt.color[0]); - } - else if ((e.keyCode == 189 || e.keyCode == 219 || e.keyCode == 188) && !e.ctrlKey && !e.metaKey) // - or [ or , - { - e.preventDefault(); - for (var i = 1; i < brushSizes.length; i++) - { - if (anbt.size - brushSizes[i] < 0.01) - { - ID("brush" + (i - 1)).click(); - break; - } - } - } - else if ((e.keyCode == 187 || e.keyCode == 221 || e.keyCode == 190) && !e.ctrlKey && !e.metaKey) // = or ] or . - { - e.preventDefault(); - for (var i = 0; i < brushSizes.length - 1; i++) - { - if (anbt.size - brushSizes[i] < 0.01) - { - ID("brush" + (i + 1)).click(); - break; - } - } - } - else if (e.keyCode >= 49 && e.keyCode <= 52 && (e.ctrlKey || e.metaKey)) // Ctrl+1,2,3,4 - { - e.preventDefault(); - ID("brush" + (e.keyCode - 49)).click(); - } - else if (e.keyCode == 32 && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) - { - playCommonDown(e); - } - }); - window.onerror = function(e) - { - alert(e); - }; - window.onbeforeunload = function(e) - { - if (anbt.unsaved) - { - var msg = "You haven't saved the drawing. Abandon?"; - e.returnValue = msg; - return msg; - } - }; -} - -function fixPluginGoingAWOL() -{ - var stupidPlugin = ID("wacom"); - var container = ID("wacomContainer"); - window.onblur = function(e) - { - if (container.childNodes.length === 1) container.removeChild(stupidPlugin); - }; - window.onfocus = function(e) - { - if (container.childNodes.length === 0) container.appendChild(stupidPlugin); - }; -} - -function runTimer() -{ - setInterval(function() - { - var s = (timerStart - Date.now()) / 1000; - if (timerCallback) timerCallback(s); - s = Math.abs(s); - var m = Math.floor(s / 60); - s = Math.floor(s % 60); - if (m < 10) m = '0' + m; - if (s < 10) s = '0' + s; - ID("timer").childNodes[0].nodeValue = m + ':' + s; - }, - 500); -} - -function main() -{ - if (!window.options) window.options = {}; - if (options.enableWacom == "auto") - { - options.enableWacom = 0; - for (var i = 0; i < navigator.plugins.length; i++) - { - if (navigator.plugins[i].name.match(/wacom/i)) - { - options.enableWacom = 1; - break; - } - } - } - if (options.enableWacom) - { - var stupidPlugin = document.createElement("object"); - var container = ID("wacomContainer"); - stupidPlugin.setAttribute("id", "wacom"); - stupidPlugin.setAttribute("type", "application/x-wacomtabletplugin"); - stupidPlugin.setAttribute("width", "1"); - stupidPlugin.setAttribute("height", "1"); - container.appendChild(stupidPlugin); - if (options.fixTabletPluginGoingAWOL) fixPluginGoingAWOL(); - } - anbt.BindContainer(ID("svgContainer")); - bindEvents(); - ID("svgContainer").style.background = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAHElEQVR4AWPYgAM8wAFoo2FUAy4JXAbRRMOoBgD42lgf5s146gAAAABJRU5ErkJggg==")'; - if (window.location.hash.length > 7) - { - var id = window.location.hash.substr(1); - var m = id.match(/drawception\/(\w{8})/); - if (m) - { - anbt.FromURL("drawception-get-panel.php?panelid=" + m[1]); - } else { - anbt.FromImgur(id); - } - } - timerStart = Date.now(); - runTimer(); -} - -if (!("SVGPathSeg" in window)) -{ - require("pathseg.min.js", main) -} else { - main(); -} diff --git a/newcanvas/favicon.png b/newcanvas/favicon.png deleted file mode 100644 index 804326f..0000000 Binary files a/newcanvas/favicon.png and /dev/null differ diff --git a/newcanvas/index.html b/newcanvas/index.html deleted file mode 100644 index 5a22254..0000000 --- a/newcanvas/index.html +++ /dev/null @@ -1,138 +0,0 @@ -<!doctype html> -<link rel="author" href="http://grompe.org.ru/"> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> -<meta name="viewport" content="width=600"> -<link rel="stylesheet" href="drawit.css?3"> -<link rel="shortcut icon" href="favicon.png"> -<title>Drawing in Time! - -
- - Drawing in Time! (last update: 21 Dec 2015) - made by Grom PE - (discuss) -
- - -
A cow jumping over the moon
-
-
-
-
00:00
-
-
Normal
-
-
- - - - - - - - - - - - - - - - - - -
-
-
L
R
-
-
-
- - - - -
- - - - -
-
- -
-
-
-
- Sandbox - Public safe for work game -
-
- - - - - - -
- -
Guidelines -
    -
  • Simple drawings are welcome! Just give it your best shot
  • -
  • If the phrase is too difficult, use the skip button
  • -
  • Don't draw text - always draw a picture
  • -
  • Explicit (NSFW) drawings will get you banned
  • -
-
-
Sandbox -
    -
  • Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
  • -
  • Saved drawings will contain your drawing process in PNG format.
  • -
  • After loading a drawing you can continue to draw or watch its playback.
  • -
  • Note that editing the PNG with playback with other programs will likely destroy the playback data.
  • -
-
-
-
-
- - - - \ No newline at end of file diff --git a/newcanvas/newcanvasicons.png b/newcanvas/newcanvasicons.png deleted file mode 100644 index ffc9fc8..0000000 Binary files a/newcanvas/newcanvasicons.png and /dev/null differ diff --git a/newcanvas/nunito.woff b/newcanvas/nunito.woff deleted file mode 100644 index 1e9545d..0000000 Binary files a/newcanvas/nunito.woff and /dev/null differ diff --git a/newcanvas/pako.min.js b/newcanvas/pako.min.js deleted file mode 100644 index 28c0a2a..0000000 --- a/newcanvas/pako.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/* pako 0.2.5 nodeca/pako */ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.pako=t()}}(function(){return function t(e,a,i){function n(s,o){if(!a[s]){if(!e[s]){var l="function"==typeof require&&require;if(!o&&l)return l(s,!0);if(r)return r(s,!0);throw new Error("Cannot find module '"+s+"'")}var h=a[s]={exports:{}};e[s][0].call(h.exports,function(t){var a=e[s][1][t];return n(a?a:t)},h,h.exports,t,e,a,i)}return a[s].exports}for(var r="function"==typeof require&&require,s=0;s0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new d,this.strm.avail_out=0;var a=s.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==u)throw new Error(h[a]);e.header&&s.deflateSetHeader(this.strm,e.header)};w.prototype.push=function(t,e){var a,i,n=this.strm,r=this.options.chunkSize;if(this.ended)return!1;i=e===~~e?e:e===!0?_:f,n.input="string"==typeof t?l.string2buf(t):t,n.next_in=0,n.avail_in=n.input.length;do{if(0===n.avail_out&&(n.output=new o.Buf8(r),n.next_out=0,n.avail_out=r),a=s.deflate(n,i),a!==c&&a!==u)return this.onEnd(a),this.ended=!0,!1;(0===n.avail_out||0===n.avail_in&&i===_)&&this.onData("string"===this.options.to?l.buf2binstring(o.shrinkBuf(n.output,n.next_out)):o.shrinkBuf(n.output,n.next_out))}while((n.avail_in>0||0===n.avail_out)&&a!==c);return i===_?(a=s.deflateEnd(this.strm),this.onEnd(a),this.ended=!0,a===u):!0},w.prototype.onData=function(t){this.chunks.push(t)},w.prototype.onEnd=function(t){t===u&&(this.result="string"===this.options.to?this.chunks.join(""):o.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},a.Deflate=w,a.deflate=i,a.deflateRaw=n,a.gzip=r},{"./utils/common":4,"./utils/strings":5,"./zlib/deflate.js":9,"./zlib/messages":14,"./zlib/zstream":16}],3:[function(t,e,a){"use strict";function i(t,e){var a=new _(e);if(a.push(t,!0),a.err)throw a.msg;return a.result}function n(t,e){return e=e||{},e.raw=!0,i(t,e)}var r=t("./zlib/inflate.js"),s=t("./utils/common"),o=t("./utils/strings"),l=t("./zlib/constants"),h=t("./zlib/messages"),d=t("./zlib/zstream"),f=t("./zlib/gzheader"),_=function(t){this.options=s.assign({chunkSize:16384,windowBits:0,to:""},t||{});var e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0===(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new d,this.strm.avail_out=0;var a=r.inflateInit2(this.strm,e.windowBits);if(a!==l.Z_OK)throw new Error(h[a]);this.header=new f,r.inflateGetHeader(this.strm,this.header)};_.prototype.push=function(t,e){var a,i,n,h,d,f=this.strm,_=this.options.chunkSize;if(this.ended)return!1;i=e===~~e?e:e===!0?l.Z_FINISH:l.Z_NO_FLUSH,f.input="string"==typeof t?o.binstring2buf(t):t,f.next_in=0,f.avail_in=f.input.length;do{if(0===f.avail_out&&(f.output=new s.Buf8(_),f.next_out=0,f.avail_out=_),a=r.inflate(f,l.Z_NO_FLUSH),a!==l.Z_STREAM_END&&a!==l.Z_OK)return this.onEnd(a),this.ended=!0,!1;f.next_out&&(0===f.avail_out||a===l.Z_STREAM_END||0===f.avail_in&&i===l.Z_FINISH)&&("string"===this.options.to?(n=o.utf8border(f.output,f.next_out),h=f.next_out-n,d=o.buf2string(f.output,n),f.next_out=h,f.avail_out=_-h,h&&s.arraySet(f.output,f.output,n,h,0),this.onData(d)):this.onData(s.shrinkBuf(f.output,f.next_out)))}while(f.avail_in>0&&a!==l.Z_STREAM_END);return a===l.Z_STREAM_END&&(i=l.Z_FINISH),i===l.Z_FINISH?(a=r.inflateEnd(this.strm),this.onEnd(a),this.ended=!0,a===l.Z_OK):!0},_.prototype.onData=function(t){this.chunks.push(t)},_.prototype.onEnd=function(t){t===l.Z_OK&&(this.result="string"===this.options.to?this.chunks.join(""):s.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},a.Inflate=_,a.inflate=i,a.inflateRaw=n,a.ungzip=i},{"./utils/common":4,"./utils/strings":5,"./zlib/constants":7,"./zlib/gzheader":10,"./zlib/inflate.js":12,"./zlib/messages":14,"./zlib/zstream":16}],4:[function(t,e,a){"use strict";var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;a.assign=function(t){for(var e=Array.prototype.slice.call(arguments,1);e.length;){var a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(var i in a)a.hasOwnProperty(i)&&(t[i]=a[i])}}return t},a.shrinkBuf=function(t,e){return t.length===e?t:t.subarray?t.subarray(0,e):(t.length=e,t)};var n={arraySet:function(t,e,a,i,n){if(e.subarray&&t.subarray)return void t.set(e.subarray(a,a+i),n);for(var r=0;i>r;r++)t[n+r]=e[a+r]},flattenChunks:function(t){var e,a,i,n,r,s;for(i=0,e=0,a=t.length;a>e;e++)i+=t[e].length;for(s=new Uint8Array(i),n=0,e=0,a=t.length;a>e;e++)r=t[e],s.set(r,n),n+=r.length;return s}},r={arraySet:function(t,e,a,i,n){for(var r=0;i>r;r++)t[n+r]=e[a+r]},flattenChunks:function(t){return[].concat.apply([],t)}};a.setTyped=function(t){t?(a.Buf8=Uint8Array,a.Buf16=Uint16Array,a.Buf32=Int32Array,a.assign(a,n)):(a.Buf8=Array,a.Buf16=Array,a.Buf32=Array,a.assign(a,r))},a.setTyped(i)},{}],5:[function(t,e,a){"use strict";function i(t,e){if(65537>e&&(t.subarray&&s||!t.subarray&&r))return String.fromCharCode.apply(null,n.shrinkBuf(t,e));for(var a="",i=0;e>i;i++)a+=String.fromCharCode(t[i]);return a}var n=t("./common"),r=!0,s=!0;try{String.fromCharCode.apply(null,[0])}catch(o){r=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(o){s=!1}for(var l=new n.Buf8(256),h=0;256>h;h++)l[h]=h>=252?6:h>=248?5:h>=240?4:h>=224?3:h>=192?2:1;l[254]=l[254]=1,a.string2buf=function(t){var e,a,i,r,s,o=t.length,l=0;for(r=0;o>r;r++)a=t.charCodeAt(r),55296===(64512&a)&&o>r+1&&(i=t.charCodeAt(r+1),56320===(64512&i)&&(a=65536+(a-55296<<10)+(i-56320),r++)),l+=128>a?1:2048>a?2:65536>a?3:4;for(e=new n.Buf8(l),s=0,r=0;l>s;r++)a=t.charCodeAt(r),55296===(64512&a)&&o>r+1&&(i=t.charCodeAt(r+1),56320===(64512&i)&&(a=65536+(a-55296<<10)+(i-56320),r++)),128>a?e[s++]=a:2048>a?(e[s++]=192|a>>>6,e[s++]=128|63&a):65536>a?(e[s++]=224|a>>>12,e[s++]=128|a>>>6&63,e[s++]=128|63&a):(e[s++]=240|a>>>18,e[s++]=128|a>>>12&63,e[s++]=128|a>>>6&63,e[s++]=128|63&a);return e},a.buf2binstring=function(t){return i(t,t.length)},a.binstring2buf=function(t){for(var e=new n.Buf8(t.length),a=0,i=e.length;i>a;a++)e[a]=t.charCodeAt(a);return e},a.buf2string=function(t,e){var a,n,r,s,o=e||t.length,h=new Array(2*o);for(n=0,a=0;o>a;)if(r=t[a++],128>r)h[n++]=r;else if(s=l[r],s>4)h[n++]=65533,a+=s-1;else{for(r&=2===s?31:3===s?15:7;s>1&&o>a;)r=r<<6|63&t[a++],s--;s>1?h[n++]=65533:65536>r?h[n++]=r:(r-=65536,h[n++]=55296|r>>10&1023,h[n++]=56320|1023&r)}return i(h,n)},a.utf8border=function(t,e){var a;for(e=e||t.length,e>t.length&&(e=t.length),a=e-1;a>=0&&128===(192&t[a]);)a--;return 0>a?e:0===a?e:a+l[t[a]]>e?a:e}},{"./common":4}],6:[function(t,e){"use strict";function a(t,e,a,i){for(var n=65535&t|0,r=t>>>16&65535|0,s=0;0!==a;){s=a>2e3?2e3:a,a-=s;do n=n+e[i++]|0,r=r+n|0;while(--s);n%=65521,r%=65521}return n|r<<16|0}e.exports=a},{}],7:[function(t,e){e.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],8:[function(t,e){"use strict";function a(){for(var t,e=[],a=0;256>a;a++){t=a;for(var i=0;8>i;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e}function i(t,e,a,i){var r=n,s=i+a;t=-1^t;for(var o=i;s>o;o++)t=t>>>8^r[255&(t^e[o])];return-1^t}var n=a();e.exports=i},{}],9:[function(t,e,a){"use strict";function i(t,e){return t.msg=I[e],e}function n(t){return(t<<1)-(t>4?9:0)}function r(t){for(var e=t.length;--e>=0;)t[e]=0}function s(t){var e=t.state,a=e.pending;a>t.avail_out&&(a=t.avail_out),0!==a&&(A.arraySet(t.output,e.pending_buf,e.pending_out,a,t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))}function o(t,e){Z._tr_flush_block(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,s(t.strm)}function l(t,e){t.pending_buf[t.pending++]=e}function h(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function d(t,e,a,i){var n=t.avail_in;return n>i&&(n=i),0===n?0:(t.avail_in-=n,A.arraySet(e,t.input,t.next_in,n,a),1===t.state.wrap?t.adler=R(t.adler,e,n,a):2===t.state.wrap&&(t.adler=C(t.adler,e,n,a)),t.next_in+=n,t.total_in+=n,n)}function f(t,e){var a,i,n=t.max_chain_length,r=t.strstart,s=t.prev_length,o=t.nice_match,l=t.strstart>t.w_size-he?t.strstart-(t.w_size-he):0,h=t.window,d=t.w_mask,f=t.prev,_=t.strstart+le,u=h[r+s-1],c=h[r+s];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do if(a=e,h[a+s]===c&&h[a+s-1]===u&&h[a]===h[r]&&h[++a]===h[r+1]){r+=2,a++;do;while(h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&_>r);if(i=le-(_-r),r=_-le,i>s){if(t.match_start=e,s=i,i>=o)break;u=h[r+s-1],c=h[r+s]}}while((e=f[e&d])>l&&0!==--n);return s<=t.lookahead?s:t.lookahead}function _(t){var e,a,i,n,r,s=t.w_size;do{if(n=t.window_size-t.lookahead-t.strstart,t.strstart>=s+(s-he)){A.arraySet(t.window,t.window,s,s,0),t.match_start-=s,t.strstart-=s,t.block_start-=s,a=t.hash_size,e=a;do i=t.head[--e],t.head[e]=i>=s?i-s:0;while(--a);a=s,e=a;do i=t.prev[--e],t.prev[e]=i>=s?i-s:0;while(--a);n+=s}if(0===t.strm.avail_in)break;if(a=d(t.strm,t.window,t.strstart+t.lookahead,n),t.lookahead+=a,t.lookahead+t.insert>=oe)for(r=t.strstart-t.insert,t.ins_h=t.window[r],t.ins_h=(t.ins_h<t.pending_buf_size-5&&(a=t.pending_buf_size-5);;){if(t.lookahead<=1){if(_(t),0===t.lookahead&&e===N)return we;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var i=t.block_start+a;if((0===t.strstart||t.strstart>=i)&&(t.lookahead=t.strstart-i,t.strstart=i,o(t,!1),0===t.strm.avail_out))return we;if(t.strstart-t.block_start>=t.w_size-he&&(o(t,!1),0===t.strm.avail_out))return we}return t.insert=0,e===D?(o(t,!0),0===t.strm.avail_out?ve:ke):t.strstart>t.block_start&&(o(t,!1),0===t.strm.avail_out)?we:we}function c(t,e){for(var a,i;;){if(t.lookahead=oe&&(t.ins_h=(t.ins_h<=oe)if(i=Z._tr_tally(t,t.strstart-t.match_start,t.match_length-oe),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=oe){t.match_length--;do t.strstart++,t.ins_h=(t.ins_h<=oe&&(t.ins_h=(t.ins_h<4096)&&(t.match_length=oe-1)),t.prev_length>=oe&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-oe,i=Z._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-oe),t.lookahead-=t.prev_length-1,t.prev_length-=2;do++t.strstart<=n&&(t.ins_h=(t.ins_h<=oe&&t.strstart>0&&(n=t.strstart-1,i=s[n],i===s[++n]&&i===s[++n]&&i===s[++n])){r=t.strstart+le;do;while(i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&r>n);t.match_length=le-(r-n),t.match_length>t.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=oe?(a=Z._tr_tally(t,1,t.match_length-oe),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=Z._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(o(t,!1),0===t.strm.avail_out))return we}return t.insert=0,e===D?(o(t,!0),0===t.strm.avail_out?ve:ke):t.last_lit&&(o(t,!1),0===t.strm.avail_out)?we:pe}function m(t,e){for(var a;;){if(0===t.lookahead&&(_(t),0===t.lookahead)){if(e===N)return we;break}if(t.match_length=0,a=Z._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(o(t,!1),0===t.strm.avail_out))return we}return t.insert=0,e===D?(o(t,!0),0===t.strm.avail_out?ve:ke):t.last_lit&&(o(t,!1),0===t.strm.avail_out)?we:pe}function w(t){t.window_size=2*t.w_size,r(t.head),t.max_lazy_match=E[t.level].max_lazy,t.good_match=E[t.level].good_length,t.nice_match=E[t.level].nice_length,t.max_chain_length=E[t.level].max_chain,t.strstart=0,t.block_start=0,t.lookahead=0,t.insert=0,t.match_length=t.prev_length=oe-1,t.match_available=0,t.ins_h=0}function p(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=J,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new A.Buf16(2*re),this.dyn_dtree=new A.Buf16(2*(2*ie+1)),this.bl_tree=new A.Buf16(2*(2*ne+1)),r(this.dyn_ltree),r(this.dyn_dtree),r(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new A.Buf16(se+1),this.heap=new A.Buf16(2*ae+1),r(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new A.Buf16(2*ae+1),r(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function v(t){var e;return t&&t.state?(t.total_in=t.total_out=0,t.data_type=W,e=t.state,e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=e.wrap?fe:ge,t.adler=2===e.wrap?0:1,e.last_flush=N,Z._tr_init(e),L):i(t,H)}function k(t){var e=v(t);return e===L&&w(t.state),e}function x(t,e){return t&&t.state?2!==t.state.wrap?H:(t.state.gzhead=e,L):H}function y(t,e,a,n,r,s){if(!t)return H;var o=1;if(e===K&&(e=6),0>n?(o=0,n=-n):n>15&&(o=2,n-=16),1>r||r>Q||a!==J||8>n||n>15||0>e||e>9||0>s||s>G)return i(t,H);8===n&&(n=9);var l=new p;return t.state=l,l.strm=t,l.wrap=o,l.gzhead=null,l.w_bits=n,l.w_size=1<>1,l.l_buf=3*l.lit_bufsize,l.level=e,l.strategy=s,l.method=a,k(t)}function z(t,e){return y(t,e,J,V,$,X)}function B(t,e){var a,o,d,f;if(!t||!t.state||e>F||0>e)return t?i(t,H):H;if(o=t.state,!t.output||!t.input&&0!==t.avail_in||o.status===me&&e!==D)return i(t,0===t.avail_out?M:H);if(o.strm=t,a=o.last_flush,o.last_flush=e,o.status===fe)if(2===o.wrap)t.adler=0,l(o,31),l(o,139),l(o,8),o.gzhead?(l(o,(o.gzhead.text?1:0)+(o.gzhead.hcrc?2:0)+(o.gzhead.extra?4:0)+(o.gzhead.name?8:0)+(o.gzhead.comment?16:0)),l(o,255&o.gzhead.time),l(o,o.gzhead.time>>8&255),l(o,o.gzhead.time>>16&255),l(o,o.gzhead.time>>24&255),l(o,9===o.level?2:o.strategy>=q||o.level<2?4:0),l(o,255&o.gzhead.os),o.gzhead.extra&&o.gzhead.extra.length&&(l(o,255&o.gzhead.extra.length),l(o,o.gzhead.extra.length>>8&255)),o.gzhead.hcrc&&(t.adler=C(t.adler,o.pending_buf,o.pending,0)),o.gzindex=0,o.status=_e):(l(o,0),l(o,0),l(o,0),l(o,0),l(o,0),l(o,9===o.level?2:o.strategy>=q||o.level<2?4:0),l(o,xe),o.status=ge);else{var _=J+(o.w_bits-8<<4)<<8,u=-1;u=o.strategy>=q||o.level<2?0:o.level<6?1:6===o.level?2:3,_|=u<<6,0!==o.strstart&&(_|=de),_+=31-_%31,o.status=ge,h(o,_),0!==o.strstart&&(h(o,t.adler>>>16),h(o,65535&t.adler)),t.adler=1}if(o.status===_e)if(o.gzhead.extra){for(d=o.pending;o.gzindex<(65535&o.gzhead.extra.length)&&(o.pending!==o.pending_buf_size||(o.gzhead.hcrc&&o.pending>d&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),s(t),d=o.pending,o.pending!==o.pending_buf_size));)l(o,255&o.gzhead.extra[o.gzindex]),o.gzindex++;o.gzhead.hcrc&&o.pending>d&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),o.gzindex===o.gzhead.extra.length&&(o.gzindex=0,o.status=ue)}else o.status=ue;if(o.status===ue)if(o.gzhead.name){d=o.pending;do{if(o.pending===o.pending_buf_size&&(o.gzhead.hcrc&&o.pending>d&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),s(t),d=o.pending,o.pending===o.pending_buf_size)){f=1;break}f=o.gzindexd&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),0===f&&(o.gzindex=0,o.status=ce)}else o.status=ce;if(o.status===ce)if(o.gzhead.comment){d=o.pending;do{if(o.pending===o.pending_buf_size&&(o.gzhead.hcrc&&o.pending>d&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),s(t),d=o.pending,o.pending===o.pending_buf_size)){f=1;break}f=o.gzindexd&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),0===f&&(o.status=be)}else o.status=be;if(o.status===be&&(o.gzhead.hcrc?(o.pending+2>o.pending_buf_size&&s(t),o.pending+2<=o.pending_buf_size&&(l(o,255&t.adler),l(o,t.adler>>8&255),t.adler=0,o.status=ge)):o.status=ge),0!==o.pending){if(s(t),0===t.avail_out)return o.last_flush=-1,L}else if(0===t.avail_in&&n(e)<=n(a)&&e!==D)return i(t,M);if(o.status===me&&0!==t.avail_in)return i(t,M);if(0!==t.avail_in||0!==o.lookahead||e!==N&&o.status!==me){var c=o.strategy===q?m(o,e):o.strategy===Y?g(o,e):E[o.level].func(o,e);if((c===ve||c===ke)&&(o.status=me),c===we||c===ve)return 0===t.avail_out&&(o.last_flush=-1),L;if(c===pe&&(e===O?Z._tr_align(o):e!==F&&(Z._tr_stored_block(o,0,0,!1),e===T&&(r(o.head),0===o.lookahead&&(o.strstart=0,o.block_start=0,o.insert=0))),s(t),0===t.avail_out))return o.last_flush=-1,L}return e!==D?L:o.wrap<=0?U:(2===o.wrap?(l(o,255&t.adler),l(o,t.adler>>8&255),l(o,t.adler>>16&255),l(o,t.adler>>24&255),l(o,255&t.total_in),l(o,t.total_in>>8&255),l(o,t.total_in>>16&255),l(o,t.total_in>>24&255)):(h(o,t.adler>>>16),h(o,65535&t.adler)),s(t),o.wrap>0&&(o.wrap=-o.wrap),0!==o.pending?L:U)}function S(t){var e;return t&&t.state?(e=t.state.status,e!==fe&&e!==_e&&e!==ue&&e!==ce&&e!==be&&e!==ge&&e!==me?i(t,H):(t.state=null,e===ge?i(t,j):L)):H}var E,A=t("../utils/common"),Z=t("./trees"),R=t("./adler32"),C=t("./crc32"),I=t("./messages"),N=0,O=1,T=3,D=4,F=5,L=0,U=1,H=-2,j=-3,M=-5,K=-1,P=1,q=2,Y=3,G=4,X=0,W=2,J=8,Q=9,V=15,$=8,te=29,ee=256,ae=ee+1+te,ie=30,ne=19,re=2*ae+1,se=15,oe=3,le=258,he=le+oe+1,de=32,fe=42,_e=69,ue=73,ce=91,be=103,ge=113,me=666,we=1,pe=2,ve=3,ke=4,xe=3,ye=function(t,e,a,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=i,this.func=n};E=[new ye(0,0,0,0,u),new ye(4,4,8,4,c),new ye(4,5,16,8,c),new ye(4,6,32,32,c),new ye(4,4,16,16,b),new ye(8,16,32,32,b),new ye(8,16,128,128,b),new ye(8,32,128,256,b),new ye(32,128,258,1024,b),new ye(32,258,258,4096,b)],a.deflateInit=z,a.deflateInit2=y,a.deflateReset=k,a.deflateResetKeep=v,a.deflateSetHeader=x,a.deflate=B,a.deflateEnd=S,a.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":4,"./adler32":6,"./crc32":8,"./messages":14,"./trees":15}],10:[function(t,e){"use strict";function a(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}e.exports=a},{}],11:[function(t,e){"use strict";var a=30,i=12;e.exports=function(t,e){var n,r,s,o,l,h,d,f,_,u,c,b,g,m,w,p,v,k,x,y,z,B,S,E,A;n=t.state,r=t.next_in,E=t.input,s=r+(t.avail_in-5),o=t.next_out,A=t.output,l=o-(e-t.avail_out),h=o+(t.avail_out-257),d=n.dmax,f=n.wsize,_=n.whave,u=n.wnext,c=n.window,b=n.hold,g=n.bits,m=n.lencode,w=n.distcode,p=(1<g&&(b+=E[r++]<>>24,b>>>=x,g-=x,x=k>>>16&255,0===x)A[o++]=65535&k;else{if(!(16&x)){if(0===(64&x)){k=m[(65535&k)+(b&(1<g&&(b+=E[r++]<>>=x,g-=x),15>g&&(b+=E[r++]<>>24,b>>>=x,g-=x,x=k>>>16&255,!(16&x)){if(0===(64&x)){k=w[(65535&k)+(b&(1<g&&(b+=E[r++]<g&&(b+=E[r++]<d){t.msg="invalid distance too far back",n.mode=a;break t}if(b>>>=x,g-=x,x=o-l,z>x){if(x=z-x,x>_&&n.sane){t.msg="invalid distance too far back",n.mode=a;break t}if(B=0,S=c,0===u){if(B+=f-x,y>x){y-=x;do A[o++]=c[B++];while(--x);B=o-z,S=A}}else if(x>u){if(B+=f+u-x,x-=u,y>x){y-=x;do A[o++]=c[B++];while(--x);if(B=0,y>u){x=u,y-=x;do A[o++]=c[B++];while(--x);B=o-z,S=A}}}else if(B+=u-x,y>x){y-=x;do A[o++]=c[B++];while(--x);B=o-z,S=A}for(;y>2;)A[o++]=S[B++],A[o++]=S[B++],A[o++]=S[B++],y-=3;y&&(A[o++]=S[B++],y>1&&(A[o++]=S[B++]))}else{B=o-z;do A[o++]=A[B++],A[o++]=A[B++],A[o++]=A[B++],y-=3;while(y>2);y&&(A[o++]=A[B++],y>1&&(A[o++]=A[B++]))}break}}break}}while(s>r&&h>o);y=g>>3,r-=y,g-=y<<3,b&=(1<r?5+(s-r):5-(r-s),t.avail_out=h>o?257+(h-o):257-(o-h),n.hold=b,n.bits=g}},{}],12:[function(t,e,a){"use strict";function i(t){return(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function n(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new m.Buf16(320),this.work=new m.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function r(t){var e;return t&&t.state?(e=t.state,t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=D,e.last=0,e.havedict=0,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new m.Buf32(ce),e.distcode=e.distdyn=new m.Buf32(be),e.sane=1,e.back=-1,A):C}function s(t){var e;return t&&t.state?(e=t.state,e.wsize=0,e.whave=0,e.wnext=0,r(t)):C}function o(t,e){var a,i;return t&&t.state?(i=t.state,0>e?(a=0,e=-e):(a=(e>>4)+1,48>e&&(e&=15)),e&&(8>e||e>15)?C:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,s(t))):C}function l(t,e){var a,i;return t?(i=new n,t.state=i,i.window=null,a=o(t,e),a!==A&&(t.state=null),a):C}function h(t){return l(t,me)}function d(t){if(we){var e;for(b=new m.Buf32(512),g=new m.Buf32(32),e=0;144>e;)t.lens[e++]=8;for(;256>e;)t.lens[e++]=9;for(;280>e;)t.lens[e++]=7;for(;288>e;)t.lens[e++]=8;for(k(y,t.lens,0,288,b,0,t.work,{bits:9}),e=0;32>e;)t.lens[e++]=5;k(z,t.lens,0,32,g,0,t.work,{bits:5}),we=!1}t.lencode=b,t.lenbits=9,t.distcode=g,t.distbits=5}function f(t,e,a,i){var n,r=t.state;return null===r.window&&(r.wsize=1<=r.wsize?(m.arraySet(r.window,e,a-r.wsize,r.wsize,0),r.wnext=0,r.whave=r.wsize):(n=r.wsize-r.wnext,n>i&&(n=i),m.arraySet(r.window,e,a-i,n,r.wnext),i-=n,i?(m.arraySet(r.window,e,a-i,i,0),r.wnext=i,r.whave=r.wsize):(r.wnext+=n,r.wnext===r.wsize&&(r.wnext=0),r.whaveu;){if(0===l)break t;l--,_+=n[s++]<>>8&255,a.check=p(a.check,Ee,2,0),_=0,u=0,a.mode=F;break}if(a.flags=0,a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&_)<<8)+(_>>8))%31){t.msg="incorrect header check",a.mode=fe;break}if((15&_)!==T){t.msg="unknown compression method",a.mode=fe;break}if(_>>>=4,u-=4,xe=(15&_)+8,0===a.wbits)a.wbits=xe;else if(xe>a.wbits){t.msg="invalid window size",a.mode=fe;break}a.dmax=1<u;){if(0===l)break t;l--,_+=n[s++]<>8&1),512&a.flags&&(Ee[0]=255&_,Ee[1]=_>>>8&255,a.check=p(a.check,Ee,2,0)),_=0,u=0,a.mode=L;case L:for(;32>u;){if(0===l)break t;l--,_+=n[s++]<>>8&255,Ee[2]=_>>>16&255,Ee[3]=_>>>24&255,a.check=p(a.check,Ee,4,0)),_=0,u=0,a.mode=U;case U:for(;16>u;){if(0===l)break t;l--,_+=n[s++]<>8),512&a.flags&&(Ee[0]=255&_,Ee[1]=_>>>8&255,a.check=p(a.check,Ee,2,0)),_=0,u=0,a.mode=H;case H:if(1024&a.flags){for(;16>u;){if(0===l)break t;l--,_+=n[s++]<>>8&255,a.check=p(a.check,Ee,2,0)),_=0,u=0}else a.head&&(a.head.extra=null);a.mode=j;case j:if(1024&a.flags&&(g=a.length,g>l&&(g=l),g&&(a.head&&(xe=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Array(a.head.extra_len)),m.arraySet(a.head.extra,n,s,g,xe)),512&a.flags&&(a.check=p(a.check,n,g,s)),l-=g,s+=g,a.length-=g),a.length))break t;a.length=0,a.mode=M;case M:if(2048&a.flags){if(0===l)break t;g=0;do xe=n[s+g++],a.head&&xe&&a.length<65536&&(a.head.name+=String.fromCharCode(xe));while(xe&&l>g);if(512&a.flags&&(a.check=p(a.check,n,g,s)),l-=g,s+=g,xe)break t}else a.head&&(a.head.name=null);a.length=0,a.mode=K;case K:if(4096&a.flags){if(0===l)break t;g=0;do xe=n[s+g++],a.head&&xe&&a.length<65536&&(a.head.comment+=String.fromCharCode(xe));while(xe&&l>g);if(512&a.flags&&(a.check=p(a.check,n,g,s)),l-=g,s+=g,xe)break t}else a.head&&(a.head.comment=null);a.mode=P;case P:if(512&a.flags){for(;16>u;){if(0===l)break t;l--,_+=n[s++]<>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=G;break;case q:for(;32>u;){if(0===l)break t;l--,_+=n[s++]<>>=7&u,u-=7&u,a.mode=le;break}for(;3>u;){if(0===l)break t;l--,_+=n[s++]<>>=1,u-=1,3&_){case 0:a.mode=W;break;case 1:if(d(a),a.mode=ee,e===E){_>>>=2,u-=2;break t}break;case 2:a.mode=V;break;case 3:t.msg="invalid block type",a.mode=fe}_>>>=2,u-=2;break;case W:for(_>>>=7&u,u-=7&u;32>u;){if(0===l)break t;l--,_+=n[s++]<>>16^65535)){t.msg="invalid stored block lengths",a.mode=fe;break}if(a.length=65535&_,_=0,u=0,a.mode=J,e===E)break t;case J:a.mode=Q;case Q:if(g=a.length){if(g>l&&(g=l),g>h&&(g=h),0===g)break t;m.arraySet(r,n,s,g,o),l-=g,s+=g,h-=g,o+=g,a.length-=g;break}a.mode=G;break;case V:for(;14>u;){if(0===l)break t;l--,_+=n[s++]<>>=5,u-=5,a.ndist=(31&_)+1,_>>>=5,u-=5,a.ncode=(15&_)+4,_>>>=4,u-=4,a.nlen>286||a.ndist>30){t.msg="too many length or distance symbols",a.mode=fe;break}a.have=0,a.mode=$;case $:for(;a.haveu;){if(0===l)break t;l--,_+=n[s++]<>>=3,u-=3}for(;a.have<19;)a.lens[Ae[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,ze={bits:a.lenbits},ye=k(x,a.lens,0,19,a.lencode,0,a.work,ze),a.lenbits=ze.bits,ye){t.msg="invalid code lengths set",a.mode=fe;break}a.have=0,a.mode=te;case te:for(;a.have>>24,me=Se>>>16&255,we=65535&Se,!(u>=ge);){if(0===l)break t;l--,_+=n[s++]<we)_>>>=ge,u-=ge,a.lens[a.have++]=we;else{if(16===we){for(Be=ge+2;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=ge,u-=ge,0===a.have){t.msg="invalid bit length repeat",a.mode=fe;break}xe=a.lens[a.have-1],g=3+(3&_),_>>>=2,u-=2}else if(17===we){for(Be=ge+3;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=ge,u-=ge,xe=0,g=3+(7&_),_>>>=3,u-=3}else{for(Be=ge+7;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=ge,u-=ge,xe=0,g=11+(127&_),_>>>=7,u-=7}if(a.have+g>a.nlen+a.ndist){t.msg="invalid bit length repeat",a.mode=fe;break}for(;g--;)a.lens[a.have++]=xe}}if(a.mode===fe)break;if(0===a.lens[256]){t.msg="invalid code -- missing end-of-block",a.mode=fe;break}if(a.lenbits=9,ze={bits:a.lenbits},ye=k(y,a.lens,0,a.nlen,a.lencode,0,a.work,ze),a.lenbits=ze.bits,ye){t.msg="invalid literal/lengths set",a.mode=fe;break}if(a.distbits=6,a.distcode=a.distdyn,ze={bits:a.distbits},ye=k(z,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,ze),a.distbits=ze.bits,ye){t.msg="invalid distances set",a.mode=fe; -break}if(a.mode=ee,e===E)break t;case ee:a.mode=ae;case ae:if(l>=6&&h>=258){t.next_out=o,t.avail_out=h,t.next_in=s,t.avail_in=l,a.hold=_,a.bits=u,v(t,b),o=t.next_out,r=t.output,h=t.avail_out,s=t.next_in,n=t.input,l=t.avail_in,_=a.hold,u=a.bits,a.mode===G&&(a.back=-1);break}for(a.back=0;Se=a.lencode[_&(1<>>24,me=Se>>>16&255,we=65535&Se,!(u>=ge);){if(0===l)break t;l--,_+=n[s++]<>pe)],ge=Se>>>24,me=Se>>>16&255,we=65535&Se,!(u>=pe+ge);){if(0===l)break t;l--,_+=n[s++]<>>=pe,u-=pe,a.back+=pe}if(_>>>=ge,u-=ge,a.back+=ge,a.length=we,0===me){a.mode=oe;break}if(32&me){a.back=-1,a.mode=G;break}if(64&me){t.msg="invalid literal/length code",a.mode=fe;break}a.extra=15&me,a.mode=ie;case ie:if(a.extra){for(Be=a.extra;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=a.extra,u-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=ne;case ne:for(;Se=a.distcode[_&(1<>>24,me=Se>>>16&255,we=65535&Se,!(u>=ge);){if(0===l)break t;l--,_+=n[s++]<>pe)],ge=Se>>>24,me=Se>>>16&255,we=65535&Se,!(u>=pe+ge);){if(0===l)break t;l--,_+=n[s++]<>>=pe,u-=pe,a.back+=pe}if(_>>>=ge,u-=ge,a.back+=ge,64&me){t.msg="invalid distance code",a.mode=fe;break}a.offset=we,a.extra=15&me,a.mode=re;case re:if(a.extra){for(Be=a.extra;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=a.extra,u-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg="invalid distance too far back",a.mode=fe;break}a.mode=se;case se:if(0===h)break t;if(g=b-h,a.offset>g){if(g=a.offset-g,g>a.whave&&a.sane){t.msg="invalid distance too far back",a.mode=fe;break}g>a.wnext?(g-=a.wnext,ce=a.wsize-g):ce=a.wnext-g,g>a.length&&(g=a.length),be=a.window}else be=r,ce=o-a.offset,g=a.length;g>h&&(g=h),h-=g,a.length-=g;do r[o++]=be[ce++];while(--g);0===a.length&&(a.mode=ae);break;case oe:if(0===h)break t;r[o++]=a.length,h--,a.mode=ae;break;case le:if(a.wrap){for(;32>u;){if(0===l)break t;l--,_|=n[s++]<u;){if(0===l)break t;l--,_+=n[s++]<=Z;Z++)j[Z]=0;for(R=0;c>R;R++)j[e[u+R]]++;for(N=A,I=i;I>=1&&0===j[I];I--);if(N>I&&(N=I),0===I)return b[g++]=20971520,b[g++]=20971520,w.bits=1,0;for(C=1;I>C&&0===j[C];C++);for(C>N&&(N=C),D=1,Z=1;i>=Z;Z++)if(D<<=1,D-=j[Z],0>D)return-1;if(D>0&&(t===s||1!==I))return-1;for(M[1]=0,Z=1;i>Z;Z++)M[Z+1]=M[Z]+j[Z];for(R=0;c>R;R++)0!==e[u+R]&&(m[M[e[u+R]]++]=R);if(t===s?(U=K=m,z=19):t===o?(U=h,H-=257,K=d,P-=257,z=256):(U=f,K=_,z=-1),L=0,R=0,Z=C,y=g,O=N,T=0,k=-1,F=1<n||t===l&&F>r)return 1;for(var q=0;;){q++,B=Z-T,m[R]z?(S=K[P+m[R]],E=U[H+m[R]]):(S=96,E=0),p=1<>T)+v]=B<<24|S<<16|E|0;while(0!==v);for(p=1<>=1;if(0!==p?(L&=p-1,L+=p):L=0,R++,0===--j[Z]){if(Z===I)break;Z=e[u+m[R]]}if(Z>N&&(L&x)!==k){for(0===T&&(T=N),y+=C,O=Z-T,D=1<O+T&&(D-=j[O+T],!(0>=D));)O++,D<<=1;if(F+=1<n||t===l&&F>r)return 1;k=L&x,b[k]=N<<24|O<<16|y-g|0}}return 0!==L&&(b[y+L]=Z-T<<24|64<<16|0),w.bits=N,0}},{"../utils/common":4}],14:[function(t,e){"use strict";e.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],15:[function(t,e,a){"use strict";function i(t){for(var e=t.length;--e>=0;)t[e]=0}function n(t){return 256>t?se[t]:se[256+(t>>>7)]}function r(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function s(t,e,a){t.bi_valid>G-a?(t.bi_buf|=e<>G-t.bi_valid,t.bi_valid+=a-G):(t.bi_buf|=e<>>=1,a<<=1;while(--e>0);return a>>>1}function h(t){16===t.bi_valid?(r(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}function d(t,e){var a,i,n,r,s,o,l=e.dyn_tree,h=e.max_code,d=e.stat_desc.static_tree,f=e.stat_desc.has_stree,_=e.stat_desc.extra_bits,u=e.stat_desc.extra_base,c=e.stat_desc.max_length,b=0;for(r=0;Y>=r;r++)t.bl_count[r]=0;for(l[2*t.heap[t.heap_max]+1]=0,a=t.heap_max+1;q>a;a++)i=t.heap[a],r=l[2*l[2*i+1]+1]+1,r>c&&(r=c,b++),l[2*i+1]=r,i>h||(t.bl_count[r]++,s=0,i>=u&&(s=_[i-u]),o=l[2*i],t.opt_len+=o*(r+s),f&&(t.static_len+=o*(d[2*i+1]+s)));if(0!==b){do{for(r=c-1;0===t.bl_count[r];)r--;t.bl_count[r]--,t.bl_count[r+1]+=2,t.bl_count[c]--,b-=2}while(b>0);for(r=c;0!==r;r--)for(i=t.bl_count[r];0!==i;)n=t.heap[--a],n>h||(l[2*n+1]!==r&&(t.opt_len+=(r-l[2*n+1])*l[2*n],l[2*n+1]=r),i--)}}function f(t,e,a){var i,n,r=new Array(Y+1),s=0;for(i=1;Y>=i;i++)r[i]=s=s+a[i-1]<<1;for(n=0;e>=n;n++){var o=t[2*n+1];0!==o&&(t[2*n]=l(r[o]++,o))}}function _(){var t,e,a,i,n,r=new Array(Y+1);for(a=0,i=0;H-1>i;i++)for(le[i]=a,t=0;t<1<<$[i];t++)oe[a++]=i;for(oe[a-1]=i,n=0,i=0;16>i;i++)for(he[i]=n,t=0;t<1<>=7;K>i;i++)for(he[i]=n<<7,t=0;t<1<=e;e++)r[e]=0;for(t=0;143>=t;)ne[2*t+1]=8,t++,r[8]++;for(;255>=t;)ne[2*t+1]=9,t++,r[9]++;for(;279>=t;)ne[2*t+1]=7,t++,r[7]++;for(;287>=t;)ne[2*t+1]=8,t++,r[8]++;for(f(ne,M+1,r),t=0;K>t;t++)re[2*t+1]=5,re[2*t]=l(t,5);de=new ue(ne,$,j+1,M,Y),fe=new ue(re,te,0,K,Y),_e=new ue(new Array(0),ee,0,P,X)}function u(t){var e;for(e=0;M>e;e++)t.dyn_ltree[2*e]=0;for(e=0;K>e;e++)t.dyn_dtree[2*e]=0;for(e=0;P>e;e++)t.bl_tree[2*e]=0;t.dyn_ltree[2*W]=1,t.opt_len=t.static_len=0,t.last_lit=t.matches=0}function c(t){t.bi_valid>8?r(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0}function b(t,e,a,i){c(t),i&&(r(t,a),r(t,~a)),R.arraySet(t.pending_buf,t.window,e,a,t.pending),t.pending+=a}function g(t,e,a,i){var n=2*e,r=2*a;return t[n]a;a++)0!==r[2*a]?(t.heap[++t.heap_len]=h=a,t.depth[a]=0):r[2*a+1]=0;for(;t.heap_len<2;)n=t.heap[++t.heap_len]=2>h?++h:0,r[2*n]=1,t.depth[n]=0,t.opt_len--,o&&(t.static_len-=s[2*n+1]);for(e.max_code=h,a=t.heap_len>>1;a>=1;a--)m(t,r,a);n=l;do a=t.heap[1],t.heap[1]=t.heap[t.heap_len--],m(t,r,1),i=t.heap[1],t.heap[--t.heap_max]=a,t.heap[--t.heap_max]=i,r[2*n]=r[2*a]+r[2*i],t.depth[n]=(t.depth[a]>=t.depth[i]?t.depth[a]:t.depth[i])+1,r[2*a+1]=r[2*i+1]=n,t.heap[1]=n++,m(t,r,1);while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],d(t,e),f(r,h,t.bl_count)}function v(t,e,a){var i,n,r=-1,s=e[1],o=0,l=7,h=4;for(0===s&&(l=138,h=3),e[2*(a+1)+1]=65535,i=0;a>=i;i++)n=s,s=e[2*(i+1)+1],++oo?t.bl_tree[2*n]+=o:0!==n?(n!==r&&t.bl_tree[2*n]++,t.bl_tree[2*J]++):10>=o?t.bl_tree[2*Q]++:t.bl_tree[2*V]++,o=0,r=n,0===s?(l=138,h=3):n===s?(l=6,h=3):(l=7,h=4))}function k(t,e,a){var i,n,r=-1,l=e[1],h=0,d=7,f=4;for(0===l&&(d=138,f=3),i=0;a>=i;i++)if(n=l,l=e[2*(i+1)+1],!(++hh){do o(t,n,t.bl_tree);while(0!==--h)}else 0!==n?(n!==r&&(o(t,n,t.bl_tree),h--),o(t,J,t.bl_tree),s(t,h-3,2)):10>=h?(o(t,Q,t.bl_tree),s(t,h-3,3)):(o(t,V,t.bl_tree),s(t,h-11,7));h=0,r=n,0===l?(d=138,f=3):n===l?(d=6,f=3):(d=7,f=4)}}function x(t){var e;for(v(t,t.dyn_ltree,t.l_desc.max_code),v(t,t.dyn_dtree,t.d_desc.max_code),p(t,t.bl_desc),e=P-1;e>=3&&0===t.bl_tree[2*ae[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}function y(t,e,a,i){var n;for(s(t,e-257,5),s(t,a-1,5),s(t,i-4,4),n=0;i>n;n++)s(t,t.bl_tree[2*ae[n]+1],3);k(t,t.dyn_ltree,e-1),k(t,t.dyn_dtree,a-1)}function z(t){var e,a=4093624447;for(e=0;31>=e;e++,a>>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return I;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return N;for(e=32;j>e;e++)if(0!==t.dyn_ltree[2*e])return N;return I}function B(t){be||(_(),be=!0),t.l_desc=new ce(t.dyn_ltree,de),t.d_desc=new ce(t.dyn_dtree,fe),t.bl_desc=new ce(t.bl_tree,_e),t.bi_buf=0,t.bi_valid=0,u(t)}function S(t,e,a,i){s(t,(T<<1)+(i?1:0),3),b(t,e,a,!0)}function E(t){s(t,D<<1,3),o(t,W,ne),h(t)}function A(t,e,a,i){var n,r,o=0;t.level>0?(t.strm.data_type===O&&(t.strm.data_type=z(t)),p(t,t.l_desc),p(t,t.d_desc),o=x(t),n=t.opt_len+3+7>>>3,r=t.static_len+3+7>>>3,n>=r&&(n=r)):n=r=a+5,n>=a+4&&-1!==e?S(t,e,a,i):t.strategy===C||r===n?(s(t,(D<<1)+(i?1:0),3),w(t,ne,re)):(s(t,(F<<1)+(i?1:0),3),y(t,t.l_desc.max_code+1,t.d_desc.max_code+1,o+1),w(t,t.dyn_ltree,t.dyn_dtree)),u(t),i&&c(t)}function Z(t,e,a){return t.pending_buf[t.d_buf+2*t.last_lit]=e>>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&a,t.last_lit++,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(oe[a]+j+1)]++,t.dyn_dtree[2*n(e)]++),t.last_lit===t.lit_bufsize-1}var R=t("../utils/common"),C=4,I=0,N=1,O=2,T=0,D=1,F=2,L=3,U=258,H=29,j=256,M=j+1+H,K=30,P=19,q=2*M+1,Y=15,G=16,X=7,W=256,J=16,Q=17,V=18,$=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],te=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],ee=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],ae=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],ie=512,ne=new Array(2*(M+2));i(ne);var re=new Array(2*K);i(re);var se=new Array(ie);i(se);var oe=new Array(U-L+1);i(oe);var le=new Array(H);i(le);var he=new Array(K);i(he);var de,fe,_e,ue=function(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length},ce=function(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e},be=!1;a._tr_init=B,a._tr_stored_block=S,a._tr_flush_block=A,a._tr_tally=Z,a._tr_align=E},{"../utils/common":4}],16:[function(t,e){"use strict";function a(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}e.exports=a},{}]},{},[1])(1)}); \ No newline at end of file diff --git a/newcanvas/pathseg.min.js b/newcanvas/pathseg.min.js deleted file mode 100644 index ad59914..0000000 --- a/newcanvas/pathseg.min.js +++ /dev/null @@ -1,173 +0,0 @@ -// SVGPathSeg API polyfill -// https://github.com/progers/pathseg -// -// This is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from -// SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec -// changes which were implemented in Firefox 43 and Chrome 46. -(function(){"use strict";if(!("SVGPathSeg"in window)){window.SVGPathSeg=function(type,typeAsLetter,owningPathSegList){this.pathSegType=type;this.pathSegTypeAsLetter=typeAsLetter;this._owningPathSegList=owningPathSegList;} -SVGPathSeg.PATHSEG_UNKNOWN=0;SVGPathSeg.PATHSEG_CLOSEPATH=1;SVGPathSeg.PATHSEG_MOVETO_ABS=2;SVGPathSeg.PATHSEG_MOVETO_REL=3;SVGPathSeg.PATHSEG_LINETO_ABS=4;SVGPathSeg.PATHSEG_LINETO_REL=5;SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS=6;SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL=7;SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS=8;SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL=9;SVGPathSeg.PATHSEG_ARC_ABS=10;SVGPathSeg.PATHSEG_ARC_REL=11;SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS=12;SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL=13;SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS=14;SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL=15;SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS=16;SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL=17;SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS=18;SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL=19;SVGPathSeg.prototype._segmentChanged=function(){if(this._owningPathSegList) -this._owningPathSegList.segmentChanged(this);} -window.SVGPathSegClosePath=function(owningPathSegList){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CLOSEPATH,"z",owningPathSegList);} -SVGPathSegClosePath.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegClosePath.prototype.toString=function(){return"[object SVGPathSegClosePath]";} -SVGPathSegClosePath.prototype._asPathString=function(){return this.pathSegTypeAsLetter;} -SVGPathSegClosePath.prototype.clone=function(){return new SVGPathSegClosePath(undefined);} -window.SVGPathSegMovetoAbs=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_MOVETO_ABS,"M",owningPathSegList);this._x=x;this._y=y;} -SVGPathSegMovetoAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegMovetoAbs.prototype.toString=function(){return"[object SVGPathSegMovetoAbs]";} -SVGPathSegMovetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;} -SVGPathSegMovetoAbs.prototype.clone=function(){return new SVGPathSegMovetoAbs(undefined,this._x,this._y);} -Object.defineProperty(SVGPathSegMovetoAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegMovetoAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegMovetoRel=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_MOVETO_REL,"m",owningPathSegList);this._x=x;this._y=y;} -SVGPathSegMovetoRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegMovetoRel.prototype.toString=function(){return"[object SVGPathSegMovetoRel]";} -SVGPathSegMovetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;} -SVGPathSegMovetoRel.prototype.clone=function(){return new SVGPathSegMovetoRel(undefined,this._x,this._y);} -Object.defineProperty(SVGPathSegMovetoRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegMovetoRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoAbs=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_ABS,"L",owningPathSegList);this._x=x;this._y=y;} -SVGPathSegLinetoAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoAbs.prototype.toString=function(){return"[object SVGPathSegLinetoAbs]";} -SVGPathSegLinetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;} -SVGPathSegLinetoAbs.prototype.clone=function(){return new SVGPathSegLinetoAbs(undefined,this._x,this._y);} -Object.defineProperty(SVGPathSegLinetoAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegLinetoAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoRel=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_REL,"l",owningPathSegList);this._x=x;this._y=y;} -SVGPathSegLinetoRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoRel.prototype.toString=function(){return"[object SVGPathSegLinetoRel]";} -SVGPathSegLinetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;} -SVGPathSegLinetoRel.prototype.clone=function(){return new SVGPathSegLinetoRel(undefined,this._x,this._y);} -Object.defineProperty(SVGPathSegLinetoRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegLinetoRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoCubicAbs=function(owningPathSegList,x,y,x1,y1,x2,y2){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS,"C",owningPathSegList);this._x=x;this._y=y;this._x1=x1;this._y1=y1;this._x2=x2;this._y2=y2;} -SVGPathSegCurvetoCubicAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoCubicAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicAbs]";} -SVGPathSegCurvetoCubicAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y;} -SVGPathSegCurvetoCubicAbs.prototype.clone=function(){return new SVGPathSegCurvetoCubicAbs(undefined,this._x,this._y,this._x1,this._y1,this._x2,this._y2);} -Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"x1",{get:function(){return this._x1;},set:function(x1){this._x1=x1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"y1",{get:function(){return this._y1;},set:function(y1){this._y1=y1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"x2",{get:function(){return this._x2;},set:function(x2){this._x2=x2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"y2",{get:function(){return this._y2;},set:function(y2){this._y2=y2;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoCubicRel=function(owningPathSegList,x,y,x1,y1,x2,y2){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL,"c",owningPathSegList);this._x=x;this._y=y;this._x1=x1;this._y1=y1;this._x2=x2;this._y2=y2;} -SVGPathSegCurvetoCubicRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoCubicRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicRel]";} -SVGPathSegCurvetoCubicRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y;} -SVGPathSegCurvetoCubicRel.prototype.clone=function(){return new SVGPathSegCurvetoCubicRel(undefined,this._x,this._y,this._x1,this._y1,this._x2,this._y2);} -Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"x1",{get:function(){return this._x1;},set:function(x1){this._x1=x1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"y1",{get:function(){return this._y1;},set:function(y1){this._y1=y1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"x2",{get:function(){return this._x2;},set:function(x2){this._x2=x2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"y2",{get:function(){return this._y2;},set:function(y2){this._y2=y2;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoQuadraticAbs=function(owningPathSegList,x,y,x1,y1){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS,"Q",owningPathSegList);this._x=x;this._y=y;this._x1=x1;this._y1=y1;} -SVGPathSegCurvetoQuadraticAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoQuadraticAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticAbs]";} -SVGPathSegCurvetoQuadraticAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y;} -SVGPathSegCurvetoQuadraticAbs.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticAbs(undefined,this._x,this._y,this._x1,this._y1);} -Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"x1",{get:function(){return this._x1;},set:function(x1){this._x1=x1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"y1",{get:function(){return this._y1;},set:function(y1){this._y1=y1;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoQuadraticRel=function(owningPathSegList,x,y,x1,y1){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL,"q",owningPathSegList);this._x=x;this._y=y;this._x1=x1;this._y1=y1;} -SVGPathSegCurvetoQuadraticRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoQuadraticRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticRel]";} -SVGPathSegCurvetoQuadraticRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y;} -SVGPathSegCurvetoQuadraticRel.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticRel(undefined,this._x,this._y,this._x1,this._y1);} -Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"x1",{get:function(){return this._x1;},set:function(x1){this._x1=x1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"y1",{get:function(){return this._y1;},set:function(y1){this._y1=y1;this._segmentChanged();},enumerable:true});window.SVGPathSegArcAbs=function(owningPathSegList,x,y,r1,r2,angle,largeArcFlag,sweepFlag){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_ARC_ABS,"A",owningPathSegList);this._x=x;this._y=y;this._r1=r1;this._r2=r2;this._angle=angle;this._largeArcFlag=largeArcFlag;this._sweepFlag=sweepFlag;} -SVGPathSegArcAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegArcAbs.prototype.toString=function(){return"[object SVGPathSegArcAbs]";} -SVGPathSegArcAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y;} -SVGPathSegArcAbs.prototype.clone=function(){return new SVGPathSegArcAbs(undefined,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag);} -Object.defineProperty(SVGPathSegArcAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"r1",{get:function(){return this._r1;},set:function(r1){this._r1=r1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"r2",{get:function(){return this._r2;},set:function(r2){this._r2=r2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"angle",{get:function(){return this._angle;},set:function(angle){this._angle=angle;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag;},set:function(largeArcFlag){this._largeArcFlag=largeArcFlag;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"sweepFlag",{get:function(){return this._sweepFlag;},set:function(sweepFlag){this._sweepFlag=sweepFlag;this._segmentChanged();},enumerable:true});window.SVGPathSegArcRel=function(owningPathSegList,x,y,r1,r2,angle,largeArcFlag,sweepFlag){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_ARC_REL,"a",owningPathSegList);this._x=x;this._y=y;this._r1=r1;this._r2=r2;this._angle=angle;this._largeArcFlag=largeArcFlag;this._sweepFlag=sweepFlag;} -SVGPathSegArcRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegArcRel.prototype.toString=function(){return"[object SVGPathSegArcRel]";} -SVGPathSegArcRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y;} -SVGPathSegArcRel.prototype.clone=function(){return new SVGPathSegArcRel(undefined,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag);} -Object.defineProperty(SVGPathSegArcRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"r1",{get:function(){return this._r1;},set:function(r1){this._r1=r1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"r2",{get:function(){return this._r2;},set:function(r2){this._r2=r2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"angle",{get:function(){return this._angle;},set:function(angle){this._angle=angle;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag;},set:function(largeArcFlag){this._largeArcFlag=largeArcFlag;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"sweepFlag",{get:function(){return this._sweepFlag;},set:function(sweepFlag){this._sweepFlag=sweepFlag;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoHorizontalAbs=function(owningPathSegList,x){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS,"H",owningPathSegList);this._x=x;} -SVGPathSegLinetoHorizontalAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoHorizontalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalAbs]";} -SVGPathSegLinetoHorizontalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x;} -SVGPathSegLinetoHorizontalAbs.prototype.clone=function(){return new SVGPathSegLinetoHorizontalAbs(undefined,this._x);} -Object.defineProperty(SVGPathSegLinetoHorizontalAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoHorizontalRel=function(owningPathSegList,x){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL,"h",owningPathSegList);this._x=x;} -SVGPathSegLinetoHorizontalRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoHorizontalRel.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalRel]";} -SVGPathSegLinetoHorizontalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x;} -SVGPathSegLinetoHorizontalRel.prototype.clone=function(){return new SVGPathSegLinetoHorizontalRel(undefined,this._x);} -Object.defineProperty(SVGPathSegLinetoHorizontalRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoVerticalAbs=function(owningPathSegList,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS,"V",owningPathSegList);this._y=y;} -SVGPathSegLinetoVerticalAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoVerticalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalAbs]";} -SVGPathSegLinetoVerticalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y;} -SVGPathSegLinetoVerticalAbs.prototype.clone=function(){return new SVGPathSegLinetoVerticalAbs(undefined,this._y);} -Object.defineProperty(SVGPathSegLinetoVerticalAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoVerticalRel=function(owningPathSegList,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL,"v",owningPathSegList);this._y=y;} -SVGPathSegLinetoVerticalRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoVerticalRel.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalRel]";} -SVGPathSegLinetoVerticalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y;} -SVGPathSegLinetoVerticalRel.prototype.clone=function(){return new SVGPathSegLinetoVerticalRel(undefined,this._y);} -Object.defineProperty(SVGPathSegLinetoVerticalRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoCubicSmoothAbs=function(owningPathSegList,x,y,x2,y2){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS,"S",owningPathSegList);this._x=x;this._y=y;this._x2=x2;this._y2=y2;} -SVGPathSegCurvetoCubicSmoothAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoCubicSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothAbs]";} -SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y;} -SVGPathSegCurvetoCubicSmoothAbs.prototype.clone=function(){return new SVGPathSegCurvetoCubicSmoothAbs(undefined,this._x,this._y,this._x2,this._y2);} -Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"x2",{get:function(){return this._x2;},set:function(x2){this._x2=x2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"y2",{get:function(){return this._y2;},set:function(y2){this._y2=y2;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoCubicSmoothRel=function(owningPathSegList,x,y,x2,y2){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL,"s",owningPathSegList);this._x=x;this._y=y;this._x2=x2;this._y2=y2;} -SVGPathSegCurvetoCubicSmoothRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoCubicSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothRel]";} -SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y;} -SVGPathSegCurvetoCubicSmoothRel.prototype.clone=function(){return new SVGPathSegCurvetoCubicSmoothRel(undefined,this._x,this._y,this._x2,this._y2);} -Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"x2",{get:function(){return this._x2;},set:function(x2){this._x2=x2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"y2",{get:function(){return this._y2;},set:function(y2){this._y2=y2;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoQuadraticSmoothAbs=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS,"T",owningPathSegList);this._x=x;this._y=y;} -SVGPathSegCurvetoQuadraticSmoothAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothAbs]";} -SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;} -SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticSmoothAbs(undefined,this._x,this._y);} -Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoQuadraticSmoothRel=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,"t",owningPathSegList);this._x=x;this._y=y;} -SVGPathSegCurvetoQuadraticSmoothRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothRel]";} -SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;} -SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticSmoothRel(undefined,this._x,this._y);} -Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});SVGPathElement.prototype.createSVGPathSegClosePath=function(){return new SVGPathSegClosePath(undefined);} -SVGPathElement.prototype.createSVGPathSegMovetoAbs=function(x,y){return new SVGPathSegMovetoAbs(undefined,x,y);} -SVGPathElement.prototype.createSVGPathSegMovetoRel=function(x,y){return new SVGPathSegMovetoRel(undefined,x,y);} -SVGPathElement.prototype.createSVGPathSegLinetoAbs=function(x,y){return new SVGPathSegLinetoAbs(undefined,x,y);} -SVGPathElement.prototype.createSVGPathSegLinetoRel=function(x,y){return new SVGPathSegLinetoRel(undefined,x,y);} -SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs=function(x,y,x1,y1,x2,y2){return new SVGPathSegCurvetoCubicAbs(undefined,x,y,x1,y1,x2,y2);} -SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel=function(x,y,x1,y1,x2,y2){return new SVGPathSegCurvetoCubicRel(undefined,x,y,x1,y1,x2,y2);} -SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs=function(x,y,x1,y1){return new SVGPathSegCurvetoQuadraticAbs(undefined,x,y,x1,y1);} -SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel=function(x,y,x1,y1){return new SVGPathSegCurvetoQuadraticRel(undefined,x,y,x1,y1);} -SVGPathElement.prototype.createSVGPathSegArcAbs=function(x,y,r1,r2,angle,largeArcFlag,sweepFlag){return new SVGPathSegArcAbs(undefined,x,y,r1,r2,angle,largeArcFlag,sweepFlag);} -SVGPathElement.prototype.createSVGPathSegArcRel=function(x,y,r1,r2,angle,largeArcFlag,sweepFlag){return new SVGPathSegArcRel(undefined,x,y,r1,r2,angle,largeArcFlag,sweepFlag);} -SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs=function(x){return new SVGPathSegLinetoHorizontalAbs(undefined,x);} -SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel=function(x){return new SVGPathSegLinetoHorizontalRel(undefined,x);} -SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs=function(y){return new SVGPathSegLinetoVerticalAbs(undefined,y);} -SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel=function(y){return new SVGPathSegLinetoVerticalRel(undefined,y);} -SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs=function(x,y,x2,y2){return new SVGPathSegCurvetoCubicSmoothAbs(undefined,x,y,x2,y2);} -SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel=function(x,y,x2,y2){return new SVGPathSegCurvetoCubicSmoothRel(undefined,x,y,x2,y2);} -SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs=function(x,y){return new SVGPathSegCurvetoQuadraticSmoothAbs(undefined,x,y);} -SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel=function(x,y){return new SVGPathSegCurvetoQuadraticSmoothRel(undefined,x,y);}} -if(!("SVGPathSegList"in window)){window.SVGPathSegList=function(pathElement){this._pathElement=pathElement;this._list=this._parsePath(this._pathElement.getAttribute("d"));this._mutationObserverConfig={"attributes":true,"attributeFilter":["d"]};this._pathElementMutationObserver=new MutationObserver(this._updateListFromPathMutations.bind(this));this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig);} -Object.defineProperty(SVGPathSegList.prototype,"numberOfItems",{get:function(){this._checkPathSynchronizedToList();return this._list.length;},enumerable:true});Object.defineProperty(SVGPathElement.prototype,"pathSegList",{get:function(){if(!this._pathSegList) -this._pathSegList=new SVGPathSegList(this);return this._pathSegList;},enumerable:true});Object.defineProperty(SVGPathElement.prototype,"normalizedPathSegList",{get:function(){return this.pathSegList;},enumerable:true});Object.defineProperty(SVGPathElement.prototype,"animatedPathSegList",{get:function(){return this.pathSegList;},enumerable:true});Object.defineProperty(SVGPathElement.prototype,"animatedNormalizedPathSegList",{get:function(){return this.pathSegList;},enumerable:true});SVGPathSegList.prototype._checkPathSynchronizedToList=function(){this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords());} -SVGPathSegList.prototype._updateListFromPathMutations=function(mutationRecords){if(!this._pathElement) -return;var hasPathMutations=false;mutationRecords.forEach(function(record){if(record.attributeName=="d") -hasPathMutations=true;});if(hasPathMutations) -this._list=this._parsePath(this._pathElement.getAttribute("d"));} -SVGPathSegList.prototype._writeListToPath=function(){this._pathElementMutationObserver.disconnect();this._pathElement.setAttribute("d",SVGPathSegList._pathSegArrayAsString(this._list));this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig);} -SVGPathSegList.prototype.segmentChanged=function(pathSeg){this._writeListToPath();} -SVGPathSegList.prototype.clear=function(){this._checkPathSynchronizedToList();this._list.forEach(function(pathSeg){pathSeg._owningPathSegList=null;});this._list=[];this._writeListToPath();} -SVGPathSegList.prototype.initialize=function(newItem){this._checkPathSynchronizedToList();this._list=[newItem];newItem._owningPathSegList=this;this._writeListToPath();return newItem;} -SVGPathSegList.prototype._checkValidIndex=function(index){if(isNaN(index)||index<0||index>=this.numberOfItems) -throw"INDEX_SIZE_ERR";} -SVGPathSegList.prototype.getItem=function(index){this._checkPathSynchronizedToList();this._checkValidIndex(index);return this._list[index];} -SVGPathSegList.prototype.insertItemBefore=function(newItem,index){this._checkPathSynchronizedToList();if(index>this.numberOfItems) -index=this.numberOfItems;if(newItem._owningPathSegList){newItem=newItem.clone();} -this._list.splice(index,0,newItem);newItem._owningPathSegList=this;this._writeListToPath();return newItem;} -SVGPathSegList.prototype.replaceItem=function(newItem,index){this._checkPathSynchronizedToList();if(newItem._owningPathSegList){newItem=newItem.clone();} -this._checkValidIndex(index);this._list[index]=newItem;newItem._owningPathSegList=this;this._writeListToPath();return newItem;} -SVGPathSegList.prototype.removeItem=function(index){this._checkPathSynchronizedToList();this._checkValidIndex(index);var item=this._list[index];this._list.splice(index,1);this._writeListToPath();return item;} -SVGPathSegList.prototype.appendItem=function(newItem){this._checkPathSynchronizedToList();if(newItem._owningPathSegList){newItem=newItem.clone();} -this._list.push(newItem);newItem._owningPathSegList=this;this._writeListToPath();return newItem;} -SVGPathSegList._pathSegArrayAsString=function(pathSegArray){var string="";var first=true;pathSegArray.forEach(function(pathSeg){if(first){first=false;string+=pathSeg._asPathString();}else{string+=" "+pathSeg._asPathString();}});return string;} -SVGPathSegList.prototype._parsePath=function(string){if(!string||string.length==0) -return[];var owningPathSegList=this;var Builder=function(){this.pathSegList=[];} -Builder.prototype.appendSegment=function(pathSeg){this.pathSegList.push(pathSeg);} -var Source=function(string){this._string=string;this._currentIndex=0;this._endIndex=this._string.length;this._previousCommand=SVGPathSeg.PATHSEG_UNKNOWN;this._skipOptionalSpaces();} -Source.prototype._isCurrentSpace=function(){var character=this._string[this._currentIndex];return character<=" "&&(character==" "||character=="\n"||character=="\t"||character=="\r"||character=="\f");} -Source.prototype._skipOptionalSpaces=function(){while(this._currentIndex="0"&&lookahead<="9"))&&previousCommand!=SVGPathSeg.PATHSEG_CLOSEPATH){if(previousCommand==SVGPathSeg.PATHSEG_MOVETO_ABS) -return SVGPathSeg.PATHSEG_LINETO_ABS;if(previousCommand==SVGPathSeg.PATHSEG_MOVETO_REL) -return SVGPathSeg.PATHSEG_LINETO_REL;return previousCommand;} -return SVGPathSeg.PATHSEG_UNKNOWN;} -Source.prototype.initialCommandIsMoveTo=function(){if(!this.hasMoreData()) -return true;var command=this.peekSegmentType();return command==SVGPathSeg.PATHSEG_MOVETO_ABS||command==SVGPathSeg.PATHSEG_MOVETO_REL;} -Source.prototype._parseNumber=function(){var exponent=0;var integer=0;var frac=1;var decimal=0;var sign=1;var expsign=1;var startIndex=this._currentIndex;this._skipOptionalSpaces();if(this._currentIndex"9")&&this._string.charAt(this._currentIndex)!=".")) -return undefined;var startIntPartIndex=this._currentIndex;while(this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9") -this._currentIndex++;if(this._currentIndex!=startIntPartIndex){var scanIntPartIndex=this._currentIndex-1;var multiplier=1;while(scanIntPartIndex>=startIntPartIndex){integer+=multiplier*(this._string.charAt(scanIntPartIndex--)-"0");multiplier*=10;}} -if(this._currentIndex=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9") -return undefined;while(this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9") -decimal+=(this._string.charAt(this._currentIndex++)-"0")*(frac*=0.1);} -if(this._currentIndex!=startIndex&&this._currentIndex+1=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9") -return undefined;while(this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9"){exponent*=10;exponent+=(this._string.charAt(this._currentIndex)-"0");this._currentIndex++;}} -var number=integer+decimal;number*=sign;if(exponent) -number*=Math.pow(10,expsign*exponent);if(startIndex==this._currentIndex) -return undefined;this._skipOptionalSpacesOrDelimiter();return number;} -Source.prototype._parseArcFlag=function(){if(this._currentIndex>=this._endIndex) -return undefined;var flag=false;var flagChar=this._string.charAt(this._currentIndex++);if(flagChar=="0") -flag=false;else if(flagChar=="1") -flag=true;else -return undefined;this._skipOptionalSpacesOrDelimiter();return flag;} -Source.prototype.parseSegment=function(){var lookahead=this._string[this._currentIndex];var command=this._pathSegTypeFromChar(lookahead);if(command==SVGPathSeg.PATHSEG_UNKNOWN){if(this._previousCommand==SVGPathSeg.PATHSEG_UNKNOWN) -return null;command=this._nextCommandHelper(lookahead,this._previousCommand);if(command==SVGPathSeg.PATHSEG_UNKNOWN) -return null;}else{this._currentIndex++;} -this._previousCommand=command;switch(command){case SVGPathSeg.PATHSEG_MOVETO_REL:return new SVGPathSegMovetoRel(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_MOVETO_ABS:return new SVGPathSegMovetoAbs(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_REL:return new SVGPathSegLinetoRel(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_ABS:return new SVGPathSegLinetoAbs(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:return new SVGPathSegLinetoHorizontalRel(owningPathSegList,this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:return new SVGPathSegLinetoHorizontalAbs(owningPathSegList,this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:return new SVGPathSegLinetoVerticalRel(owningPathSegList,this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:return new SVGPathSegLinetoVerticalAbs(owningPathSegList,this._parseNumber());case SVGPathSeg.PATHSEG_CLOSEPATH:this._skipOptionalSpaces();return new SVGPathSegClosePath(owningPathSegList);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:var points={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicRel(owningPathSegList,points.x,points.y,points.x1,points.y1,points.x2,points.y2);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:var points={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicAbs(owningPathSegList,points.x,points.y,points.x1,points.y1,points.x2,points.y2);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:var points={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicSmoothRel(owningPathSegList,points.x,points.y,points.x2,points.y2);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:var points={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList,points.x,points.y,points.x2,points.y2);case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:var points={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoQuadraticRel(owningPathSegList,points.x,points.y,points.x1,points.y1);case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:var points={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoQuadraticAbs(owningPathSegList,points.x,points.y,points.x1,points.y1);case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:return new SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:return new SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_ARC_REL:var points={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegArcRel(owningPathSegList,points.x,points.y,points.x1,points.y1,points.arcAngle,points.arcLarge,points.arcSweep);case SVGPathSeg.PATHSEG_ARC_ABS:var points={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegArcAbs(owningPathSegList,points.x,points.y,points.x1,points.y1,points.arcAngle,points.arcLarge,points.arcSweep);default:throw"Unknown path seg type."}} -var builder=new Builder();var source=new Source(string);if(!source.initialCommandIsMoveTo()) -return[];while(source.hasMoreData()){var pathSeg=source.parseSegment();if(!pathSeg) -return[];builder.appendSegment(pathSeg);} -return builder.pathSegList;}}}()); \ No newline at end of file diff --git a/newcanvas/whammy.min.js b/newcanvas/whammy.min.js deleted file mode 100644 index ce72bff..0000000 --- a/newcanvas/whammy.min.js +++ /dev/null @@ -1,39 +0,0 @@ - -window.Whammy=(function(){function toWebM(frames,outputAsArray){var info=checkFrames(frames);var CLUSTER_MAX_DURATION=30000;var EBML=[{"id":0x1a45dfa3,"data":[{"data":1,"id":0x4286},{"data":1,"id":0x42f7},{"data":4,"id":0x42f2},{"data":8,"id":0x42f3},{"data":"webm","id":0x4282},{"data":2,"id":0x4287},{"data":2,"id":0x4285}]},{"id":0x18538067,"data":[{"id":0x1549a966,"data":[{"data":1e6,"id":0x2ad7b1},{"data":"whammy","id":0x4d80},{"data":"whammy","id":0x5741},{"data":doubleToString(info.duration),"id":0x4489}]},{"id":0x1654ae6b,"data":[{"id":0xae,"data":[{"data":1,"id":0xd7},{"data":1,"id":0x63c5},{"data":0,"id":0x9c},{"data":"und","id":0x22b59c},{"data":"V_VP8","id":0x86},{"data":"VP8","id":0x258688},{"data":1,"id":0x83},{"id":0xe0,"data":[{"data":info.width,"id":0xb0},{"data":info.height,"id":0xba}]}]}]},]}];var frameNumber=0;var clusterTimecode=0;while(frameNumber0x7fff)throw"Frame "+(i+1)+" has a weird duration (must be between 0 and 32767)";duration+=frames[i].duration;} -return{duration:duration,width:width,height:height};} -function numToBuffer(num){var parts=[];while(num>0){parts.push(num&0xff) -num=num>>8} -return new Uint8Array(parts.reverse());} -function strToBuffer(str){var arr=new Uint8Array(str.length);for(var i=0;i127){throw"TrackNumber > 127 not supported";} -var out=[data.trackNum|0x80,data.timecode>>8,data.timecode&0xff,flags].map(function(e){return String.fromCharCode(e)}).join('')+data.frame;return out;} -function parseWebP(riff){var VP8=riff.RIFF[0].WEBP[0];var frame_start=VP8.indexOf('\x9d\x01\x2a');for(var i=0,c=[];i<4;i++)c[i]=VP8.charCodeAt(frame_start+3+i);var width,horizontal_scale,height,vertical_scale,tmp;tmp=(c[1]<<8)|c[0];width=tmp&0x3FFF;horizontal_scale=tmp>>14;tmp=(c[3]<<8)|c[2];height=tmp&0x3FFF;vertical_scale=tmp>>14;return{width:width,height:height,data:VP8,riff:riff}} -function parseRIFF(string){var offset=0;var chunks={};while(offset - - - - - - -New Canvas - Drawception - -
-
-
- - New Canvas v43 -
-
- Play - + - - - 0 - 0/32 - 0 - 0 -
-
-
-
Fetching your Drawception game...
-
-
-
-
00:00
-
-
Normal
-
-
- - - - - - - - - - - - - - - - - - -
-
-
L
R
-
-
-
- - - - -
- - - - -
-
- -
Time's up!

Submit now or miss the game!
-
-
- -
- -
46
-
-
-
-
- Sandbox - Public safe for work game -
-
- - - - - - - - -
- - -
Guidelines -
    -
  • Simple drawings are welcome! Just give it your best shot
  • -
  • If the phrase is too difficult, use the skip button
  • -
  • Don't draw text - always draw a picture
  • -
  • Explicit (NSFW) drawings will get you banned
  • -
-
-
Tips -
    -
  • No explicit language allowed
  • -
  • Keep descriptions short and simple!
  • -
  • Avoid unnecessary detail, just describe the main subject
  • -
  • No metacommentary (e.g., don't comment on drawing quality)
  • -
  • No idea? Use the skip button
  • -
-
-
Sandbox -
    -
  • Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
  • -
  • Saved drawings will contain your drawing process in PNG format.
  • -
  • After loading a drawing you can continue to draw or watch its playback.
  • -
  • Note that editing the PNG with playback with other programs will likely destroy the playback data.
  • -
-
-
-
-
- - - - \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d1375b4 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "drawception-anbt", + "version": "1.156.2019.6", + "description": "ANBT is a script that will improve the comfort in drawing.", + "main": "index.js", + "scripts": { + "build": "rollup -c" + }, + "author": "GromPE", + "license": "ISC", + "devDependencies": { + "@babel/core": "^7.5.5", + "rollup": "^1.17.0", + "rollup-plugin-babel": "^4.3.3", + "rollup-plugin-cleanup": "^3.1.1", + "rollup-plugin-clear": "^2.0.7", + "rollup-plugin-commonjs": "^10.0.1", + "rollup-plugin-copy": "^3.0.0", + "rollup-plugin-eslint": "^6.0.0", + "rollup-plugin-node-resolve": "^5.2.0", + "rollup-plugin-prettier": "^0.6.0", + "rollup-plugin-replace-html-vars": "^1.0.3", + "rollup-plugin-scss": "^1.0.1", + "rollup-plugin-terser": "^5.1.1", + "rollup-pluginutils": "^2.8.1" + }, + "dependencies": { + "pako": "^1.0.10", + "pathseg": "^1.2.0" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..3a5df26 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,125 @@ +import fs from 'fs' +import cleanup from 'rollup-plugin-cleanup' +import clear from 'rollup-plugin-clear' +import commonjs from 'rollup-plugin-commonjs' +import copy from 'rollup-plugin-copy' +import { eslint } from 'rollup-plugin-eslint' +import resolve from 'rollup-plugin-node-resolve' +import prettier from 'rollup-plugin-prettier' +import replaceHtmlVars from 'rollup-plugin-replace-html-vars' +import scss from 'rollup-plugin-scss' +import { terser } from 'rollup-plugin-terser' +import banner from './src/extension/banner' + +const waitFile = path => { + return new Promise(resolve => { + const interval = setInterval(() => { + if (!fs.existsSync(path)) return + clearInterval(interval) + resolve(path) + }, 100) + }) +} + +export default [ + { + input: 'src/newcanvas/js/pako.js', + output: { + format: 'iife', + file: 'build/newcanvas/pako.js' + }, + plugins: [ + clear({ + targets: ['build/'], + watch: true + }), + commonjs(), + resolve(), + cleanup(), + eslint({ + exclude: ['/build/*.js'] + }), + terser() + ] + }, + { + input: 'src/newcanvas/js/pathseg.js', + output: { + format: 'iife', + file: 'build/newcanvas/pathseg.js' + }, + plugins: [ + commonjs(), + resolve(), + cleanup(), + eslint({ + exclude: ['/build/*.js'] + }), + terser() + ] + }, + { + input: 'src/newcanvas.js', + output: { + format: 'iife', + file: 'build/newcanvas/script.js' + }, + plugins: [ + scss({ + output: 'build/newcanvas/style.css', + outputStyle: 'compressed' + }), + commonjs(), + resolve(), + cleanup(), + eslint({ + exclude: ['/build/*.js'] + }), + terser(), + copy({ + targets: [ + { src: 'src/newcanvas/html/index.html', dest: 'build/newcanvas' } + ] + }) + ] + }, + { + input: 'src/extension.js', + output: { + format: 'iife', + file: 'build/drawception-anbt.user.js', + banner + }, + plugins: [ + commonjs(), + resolve(), + cleanup(), + eslint({ + exclude: ['/build/*.js'] + }), + prettier({ + cwd: __dirname + }), + copy({ + targets: [{ src: 'build/newcanvas/index.html', dest: 'build/' }] + }), + waitFile('build/index.html').then(file => { + replaceHtmlVars({ + files: file, + from: [ + '/*NEWCANVAS_STYLE*/', + '/*PAKO_SCRIPT*/', + '/*PATHSEG_SCRIPT*/', + '/*NEWCANVAS_SCRIPT*/' + ], + to: [ + fs.readFileSync('build/newcanvas/style.css', 'utf8'), + fs.readFileSync('build/newcanvas/pako.js', 'utf8'), + fs.readFileSync('build/newcanvas/pathseg.js', 'utf8'), + fs.readFileSync('build/newcanvas/script.js', 'utf8') + ] + }) + }) + ] + } +] diff --git a/src/extension.js b/src/extension.js new file mode 100644 index 0000000..2dd360c --- /dev/null +++ b/src/extension.js @@ -0,0 +1,25 @@ +import addDarkCSS from './extension/functions/darkMode/addDarkCSS' +import setDarkMode from './extension/functions/darkMode/setDarkMode' +import getLocalStorageItem from './extension/functions/getLocalStorageItem' +import wrapper from './extension/wrapper' + +addDarkCSS() +setDarkMode() +if (document && document.body) { + if (!document.getElementById('_anbt_')) wrapper() + if (window.opera && !getLocalStorageItem('gpe_operaWarning', 0)) { + const anbtTitle = document.createElement('h2') + anbtTitle.innerHTML = + 'ANBT speaking:
Rename your script file so it doesn\'t contain ".user." part for smoother loading!
This warning is only shown once.' + const mainSection = document.getElementById('main') + mainSection.insertBefore(anbtTitle, mainSection.firstChild) + localStorage.setItem('gpe_operaWarning', 1) + } +} +document.addEventListener( + 'DOMContentLoaded', + () => { + if (!document.getElementById('_anbt_')) wrapper() + }, + false +) diff --git a/src/extension/banner.js b/src/extension/banner.js new file mode 100644 index 0000000..81bb861 --- /dev/null +++ b/src/extension/banner.js @@ -0,0 +1,17 @@ +import versions from './versions' + +const banner = `// ==UserScript== +// @name Drawception ANBT +// @author Grom PE +// @namespace http://grompe.org.ru/ +// @version ${versions.scriptVersion} +// @description Enhancement script for Drawception.com - Artists Need Better Tools +// @downloadURL https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js +// @match http://drawception.com/* +// @match https://drawception.com/* +// @grant none +// @run-at document-start +// @license Public domain +// ==/UserScript==` + +export default banner diff --git a/src/extension/betterPages.js b/src/extension/betterPages.js new file mode 100644 index 0000000..0d78b02 --- /dev/null +++ b/src/extension/betterPages.js @@ -0,0 +1,17 @@ +import betterCreate from './functions/betterPages/betterCreate' +import betterForums from './functions/betterPages/betterForums' +import betterGame from './functions/betterPages/betterGame' +import betterPanel from './functions/betterPages/betterPanel' +import betterPlayer from './functions/betterPages/betterPlayer' +import betterSettings from './functions/betterPages/betterSettings' + +const betterPages = { + betterCreate, + betterForums, + betterGame, + betterPanel, + betterPlayer, + betterSettings +} + +export default betterPages diff --git a/src/extension/functions/addMarkdownTools.js b/src/extension/functions/addMarkdownTools.js new file mode 100644 index 0000000..ad4a68c --- /dev/null +++ b/src/extension/functions/addMarkdownTools.js @@ -0,0 +1,24 @@ +import markdown from '../markdown' +import getSelectedText from './getSelectedText' +import $ from './selector' + +const addMarkdownTools = () => { + const textarea = $('#input-comment') + if (!textarea) return + const markdownDiv = $('
') + Object.keys(markdown).forEach(toolName => + markdownDiv.appendChild( + $( + `
` + ) + ) + ) + textarea.insertAdjacentHTML('beforebegin', markdownDiv.outerHTML) + ;[...$('#markdown-editor').children].forEach(children => + children.addEventListener('click', getSelectedText) + ) +} + +export default addMarkdownTools diff --git a/src/extension/functions/addStyle.js b/src/extension/functions/addStyle.js new file mode 100644 index 0000000..61611cd --- /dev/null +++ b/src/extension/functions/addStyle.js @@ -0,0 +1,10 @@ +const addStyle = css => { + const parent = + document.getElementsByTagName('head')[0] || document.documentElement + const style = document.createElement('style') + style.type = 'text/css' + style.appendChild(document.createTextNode(css)) + parent.appendChild(style) +} + +export default addStyle diff --git a/src/extension/functions/base62ToDecimal.js b/src/extension/functions/base62ToDecimal.js new file mode 100644 index 0000000..4070cec --- /dev/null +++ b/src/extension/functions/base62ToDecimal.js @@ -0,0 +1,19 @@ +import globals from '../globals' + +const base62ToDecimal = number => { + number = number.toString() + const cachePosition = {} + let result = 0 + let pow = 1 + for (let i = number.length - 1; i >= 0; i--) { + const character = number[i] + if (typeof cachePosition[character] === 'undefined') { + cachePosition[character] = globals.alphabet.indexOf(character) + } + result += pow * cachePosition[character] + pow *= 62 + } + return result +} + +export default base62ToDecimal diff --git a/src/extension/functions/betterPages/betterCreate.js b/src/extension/functions/betterPages/betterCreate.js new file mode 100644 index 0000000..6f387ce --- /dev/null +++ b/src/extension/functions/betterPages/betterCreate.js @@ -0,0 +1,13 @@ +import options from '../../options' +import $ from '../selector' + +const betterCreate = () => { + if (!options.enterToCaption) { + if ($('#prompt')) + $('#prompt').addEventListener('keydown', event => { + if (event.keyCode === 13) event.preventDefault() + }) + } +} + +export default betterCreate diff --git a/src/extension/functions/betterPages/betterForums.js b/src/extension/functions/betterPages/betterForums.js new file mode 100644 index 0000000..2de7e3e --- /dev/null +++ b/src/extension/functions/betterPages/betterForums.js @@ -0,0 +1,151 @@ +import options from '../../options' +import addStyle from '../addStyle' +import getLocalStorageItem from '../getLocalStorageItem' +import linkifyDrawingPanels from '../linkifyDrawingPanels' +import linkifyNodeText from '../linkifyNodeText' +import $ from '../selector' +import setupNewCanvas from '../setupNewCanvas' +import formatTimestamp from '../time/formatTimestamp' + +const betterForums = () => { + // Linkify the links + $('.comment-body *', true).forEach(comment => linkifyNodeText(comment)) + + // Linkify drawing panels + $('img', true).forEach(img => linkifyDrawingPanels(img)) + + // Show posts IDs and link + if (document.location.pathname.match(/\/forums\/(\w+|-)\/.+/)) { + const hideUserIds = options.forumHiddenUsers + ? options.forumHiddenUsers.split(',') + : '' + if (hideUserIds) + addStyle( + '.anbt_hideUserPost:not(:target) {opacity: 0.4; margin-bottom: 10px}' + + '.anbt_hideUserPost:not(:target) .comment-body, .anbt_hideUserPost:not(:target) .avatar {display: none}' + + '' + ) + let lastid = 0 + $('.comment-avatar', true).forEach(({ parentNode }) => { + const commentHolder = parentNode.parentNode.parentNode + const anch = commentHolder.id || '' + commentHolder.classList.add('comment-holder') // No identification for these anymore, this is unhelpful! + const textMuted = commentHolder.querySelector('a.text-muted') + const vue = commentHolder.childNodes[0].__vue__ + if (vue) { + textMuted.textContent = `${textMuted.textContent.trim()}, ${formatTimestamp( + vue.comment_date * 1000 + )}` + if (vue.edit_date > 0) { + const el = textMuted.parentNode.querySelector('span[rel="tooltip"]') + const text = `${el.title}, ${formatTimestamp( + vue.edit_date * 1000 + ).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart + el.setAttribute('title', text) + } + } + if (anch) { + const id = parseInt(anch.substring(1), 10) + const text = textMuted.textContent.trim() + textMuted.textContent = `${text} #${id}` + textMuted.setAttribute('title', 'Link to post') + if (id < lastid) textMuted.classList.add('wrong-order') + try { + const { href } = commentHolder.querySelector('a[href^="/player/"]') + if (href) { + const userId = href.match(/\d+/)[0] + if (hideUserIds.includes(userId)) + commentHolder.classList.add('anbt_hideUserPost') + } + } catch (e) {} + lastid = id + } + }) + + // Warn about posting to another page + if ( + $('.comment-holder') && + $('.comment-holder').length === 20 && + $('#comment-form .btn-primary') + ) + $('#comment-form .btn-primary').insertAdjacentHTML( + 'afterend', + '
Note: posting to another page
' + ) + } + + if (options.proxyImgur) + $('img[src*="imgur.com/"]', true).forEach(img => + img.setAttribute( + 'src', + img.src.replace('imgur.com', 'filmot.com').replace('https', 'http') + ) + ) + + const pagination = $('.pagination', true) + if (pagination.length) + $('.breadcrumb').insertAdjacentHTML( + 'afterend', + `
${pagination[0].outerHTML}
` + ) + + // For the topic list pages only + if (document.location.pathname.match(/\/forums\/(\w+)\/$/)) { + const hiddenTopics = getLocalStorageItem('gpe_forumHiddenTopics', []) + let hidden = 0 + + const tempUnhideLink = $('') + + $('.forum-thread', true).forEach(thread => { + const href = thread + .querySelector('a:first-child') + .href.match(/\/forums\/\w+\/(\d+)\//) + // Don't let them hide the ANBT topic ;) + if (!href || !href[1] || href[1] === 11830) return + + const id = href[1] + if (hiddenTopics.includes(id)) { + thread.classList.add('anbt_hidden') + hidden++ + } + const hideLink = $('') + hideLink.addEventListener('click', () => { + const hiddenTopics = getLocalStorageItem('gpe_forumHiddenTopics', []) + if (hiddenTopics.includes(id)) { + if (hiddenTopics.includes(id)) + hiddenTopics.splice(hiddenTopics.indexOf(id), 1) + hiddenTopics.splice(hiddenTopics.indexOf(id), 1) + thread.classList.remove('anbt_hidden') + hidden-- + } else { + if (!hiddenTopics.includes(id)) hiddenTopics.push(id) + hiddenTopics.push(id) + thread.classList.add('anbt_hidden') + hidden++ + tempUnhideLink.style.display = '' + } + tempUnhideLink.textContent = hidden + localStorage.setItem( + 'gpe_forumHiddenTopics', + JSON.stringify(hiddenTopics) + ) + }) + thread.querySelector('p:nth-child(2)').appendChild(hideLink) + }) + tempUnhideLink.textContent = hidden + tempUnhideLink.addEventListener('click', () => { + $('#main').classList.toggle('anbt_showt') + }) + if (!hidden) tempUnhideLink.style.display = 'none' + if ($('#js-btn-toggle-thread')) + $('#js-btn-toggle-thread').parentNode.appendChild(tempUnhideLink) + } + $('.btn.btn-default', true).forEach(button => + button.addEventListener('click', () => { + if (button.textContent === 'Draw') + setupNewCanvas(true, document.location.href) + }) + ) +} + +export default betterForums diff --git a/src/extension/functions/betterPages/betterGame.js b/src/extension/functions/betterPages/betterGame.js new file mode 100644 index 0000000..ba30331 --- /dev/null +++ b/src/extension/functions/betterPages/betterGame.js @@ -0,0 +1,88 @@ +import options from '../../options' +import waitForComments from '../comment/waitForComments' +import getLocalStorageItem from '../getLocalStorageItem' +import addReplayButton from '../replay/addReplayButton' +import reversePanels from '../reversePanels' +import $ from '../selector' + +const betterGame = () => { + if (document.title === 'Not Safe For Work (18+) Gate') { + if (options.autoBypassNSFW) window.DrawceptionPlay.bypassNsfwGate() + return + } + const drawings = $( + 'img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]' + ) + + // Reverse panels button + if ($('#btn-copy-url')) + $('#btn-copy-url').insertAdjacentHTML( + 'afterend', + ' Reverse' + ) + + $('.reversePanels').addEventListener('click', reversePanels) + + // Panel favorite buttons + const favButton = $( + '' + ) + $('.panel-number', true).forEach(panelNumber => + panelNumber.insertAdjacentHTML('afterend', favButton.outerHTML) + ) + $('.gamepanel', true).forEach(({ parentNode }) => { + if (parentNode.querySelector('.gamepanel-tools>a:last-child') === null) + return + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + const id = parentNode + .querySelector('.gamepanel-tools>a:last-child') + .href.match(/\/panel\/[^/]+\/([^/]+)\/[^/]+\//)[1] + if (panels[id]) + parentNode + .querySelector('.anbt_favpanel') + .classList.add('anbt_favedpanel') + }) + $('.anbt_favpanel', true).forEach(favPanelButton => { + favPanelButton.addEventListener('click', () => { + if (favPanelButton.classList.contains('anbt_favedpanel')) return + const { parentNode } = favPanelButton + const id = parentNode + .querySelector('.gamepanel-tools>a:last-child') + .href.match(/\/panel\/[^/]+\/([^/]+)\/[^/]+\//)[1] + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + const panel = { + time: Date.now(), + by: parentNode.querySelector('.panel-user a').textContent + } + panel.userLink = parentNode + .querySelector('.panel-user a') + .href.match(/\/player\/[^/]+\/[^/]+\//)[0] + const img = parentNode.querySelector('.gamepanel img') + if (img) { + // Drawing panel + panel.image = img.src + panel.caption = img.alt + } else { + // Caption panel + panel.caption = parentNode + .querySelector('.gamepanel') + .textContent.trim() + } + panels[id] = panel + localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) + favPanelButton.classList.add('anbt_favedpanel') + }) + }) + + // Panel replay button + if (options.newCanvas) { + if (drawings) + drawings.forEach(drawing => + drawing.addEventListener('load', addReplayButton(drawing)) + ) + } + + setTimeout(waitForComments, 200) +} + +export default betterGame diff --git a/src/extension/functions/betterPages/betterPanel.js b/src/extension/functions/betterPages/betterPanel.js new file mode 100644 index 0000000..1962d23 --- /dev/null +++ b/src/extension/functions/betterPages/betterPanel.js @@ -0,0 +1,108 @@ +import options from '../../options' +import getCookie from '../cookie/getCookie' +import setCookie from '../cookie/setCookie' +import getLocalStorageItem from '../getLocalStorageItem' +import getPanelId from '../getPanelId' +import checkForRecording from '../replay/checkForRecording' +import $ from '../selector' +import setupNewCanvas from '../setupNewCanvas' +import unscrambleID from '../unscrambleID' + +const betterPanel = () => { + const favButton = $( + '' + ) + const gamePanel = $( + '.panel-caption-display>.flex,.gamepanel-holder>.gamepanel' + ) + if (gamePanel) gamePanel.insertAdjacentHTML('afterend', favButton.outerHTML) + const favBtn = $('.btn.btn-info') + if (favBtn) { + favBtn.addEventListener('click', event => { + event.preventDefault() + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + const panel = { + time: Date.now(), + by: $('.lead a', true)[0].textContent, + userLink: $('.lead a', true)[0].href.match( + /\/player\/[^/]+\/[^/]+\// + )[0] + } + const id = document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//)[1] + const img = $('.gamepanel img') + if (img) { + // Drawing panel + panel.image = img.src + panel.caption = img.alt + } else { + // Caption panel + panel.caption = $('.gamepanel').textContent.trim() + } + panels[id] = panel + localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) + favBtn.setAttribute('disabled', 'disabled') + favBtn.querySelector('b').textContent = 'Favorited!' + }) + } + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + if ( + document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//) && + panels[document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//)[1]] + ) { + favBtn.setAttribute('disabled', 'disabled') + favBtn.querySelector('b').textContent = 'Favorited!' + } + const panelId = getPanelId(location.pathname) + + // Only panels after 14924553 might have a recording + if (options.newCanvas && panelId && unscrambleID(panelId) >= 14924553) { + const img = $('.gamepanel img') + if (img) + checkForRecording(img.src, () => { + const replayLink = $( + ` Replay ` + ) + replayLink.addEventListener('click', event => { + if (event.which === 2) return + event.preventDefault() + setupNewCanvas(true, `/sandbox/#${panelId}`) + }) + $('.gamepanel').insertAdjacentHTML('afterend', replayLink.outerHTML) + }) + } + if ( + $('.btn-primary').length > 1 && + $('.btn-primary')[1].textContent === 'Play again' + ) { + // Allow adding to cover creator + const ccButton = $( + '' + ) + ccButton.addEventListener('click', event => { + event.preventDefault() + const id = unscrambleID(panelId) + const cookie = getCookie('covercreatorids') + const ids = cookie ? JSON.parse(cookie) : [] + if (!ids.includes(id)) { + if (ids.length > 98) { + window.apprise( + 'Max cover creator drawings selected. Please remove some before adding more.' + ) + return + } else ids.push(id.toString()) + } else { + ccButton + .setAttribute('disabled', 'disabled') + .querySelector('b').textContent = 'Already added!' + return + } + setCookie('covercreatorids', JSON.stringify(ids)) + ccButton + .setAttribute('disabled', 'disabled') + .querySelector('b').textContent = 'Added!' + }) + $('.gamepanel').insertAdjacentHTML('afterend', ccButton.outerHTML) + } +} + +export default betterPanel diff --git a/src/extension/functions/betterPages/betterPlayer.js b/src/extension/functions/betterPages/betterPlayer.js new file mode 100644 index 0000000..bae3157 --- /dev/null +++ b/src/extension/functions/betterPages/betterPlayer.js @@ -0,0 +1,118 @@ +import globals from '../../globals' +import options from '../../options' +import linkifyNodeText from '../linkifyNodeText' +import randomGreeting from '../randomGreeting' +import addReplaySign from '../replay/addReplaySign' +import $ from '../selector' +import formatTimestamp from '../time/formatTimestamp' +import viewMyGameBookmarks from '../viewMyGameBookmarks' +import viewMyPanelFavorites from '../viewMyPanelFavorites' + +const betterPlayer = () => { + // Linkify the links in location + const pubinfo = $('.profile-header-info .text-muted > span:last-child') + if (pubinfo) linkifyNodeText(pubinfo.parentNode) + const loc = document.location.href + // If it's user's homepage, add new buttons in there + if (loc.match(new RegExp(`/player/${globals.userId}/[^/]+/(?:$|#)`))) { + const anbtSection = $('

ANBT stuff:

') + const panelFavoritesButton = $( + 'Panel Favorites' + ) + const gameBookmarks = $( + 'Game Bookmarks' + ) + anbtSection.appendChild(panelFavoritesButton) + anbtSection.appendChild(gameBookmarks) + const profilemain = $('.profile-layout-content').firstChild + profilemain.insertAdjacentHTML( + 'afterbegin', + `
${randomGreeting()}
` + ) + profilemain.insertAdjacentHTML('afterbegin', anbtSection.outerHTML) + $('.viewFavorites').addEventListener('click', event => { + event.preventDefault() + viewMyPanelFavorites() + }) + $('.viewBookmarks').addEventListener('click', event => { + event.preventDefault() + viewMyGameBookmarks() + }) + + if (document.location.hash.includes('#anbt_panelfavorites')) + viewMyPanelFavorites() + if (document.location.hash.includes('#anbt_gamebookmarks')) + viewMyGameBookmarks() + + // Show your exact registration date + if (window.date) { + const pubinfo = $('.profile-user-header>div.row>div>h1+p') + if (pubinfo) + [...pubinfo.childNodes][4].nodeValue = ` ${formatTimestamp( + window.date + )} \xa0` + } + } else { + // Not the current user's profile or not profile homepage + const drawings = $( + 'img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]', + true + ) + // Show replayable panels; links are not straightforward to make since there's no panel ID + if (options.newCanvas) + drawings.forEach(drawing => + drawing.addEventListener('load', addReplaySign(drawing)) + ) + + // Detect Draw Firsts + drawings.forEach(({ src, parentNode }) => { + if (src.match(/-1\.png$/)) + parentNode.parentNode.appendChild( + $( + '' + ) + ) + }) + } + + // Convert timestamps in user profile's forum posts and game comments + if (loc.match(/player\/\d+\/[^/]+\/(posts)|(comments)\//)) { + // Show topic title at the top of the posts instead and display subforum + // Show game title at the top of the posts + $('.forum-thread-starter', true).forEach(threadStarter => { + const vue = threadStarter.childNodes[0].__vue__ + if (vue) { + const ts = threadStarter.querySelector('a.text-muted').firstChild + ts.textContent = `${ts.textContent.trim()}, ${formatTimestamp( + vue.comment_date * 1000 + )}` + if (vue.edit_date > 0) { + const el = ts.parentNode.parentNode.querySelector( + 'span[rel="tooltip"]' + ) + const text = `${el.title}, ${formatTimestamp( + vue.edit_date * 1000 + ).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart + el.setAttribute('title', text) + } + } + const postlink = threadStarter.querySelector( + '.add-margin-top small.text-muted' + ) + const created = postlink.textContent.match(/^\s*Created/) + const commented = postlink.textContent.match(/^\s*Commented/) + const prefix = commented + ? 'Comment in the game' + : created + ? 'New thread' + : 'Reply in' + const n = $(`

${prefix}:

`) + const thread = postlink.querySelector('a') + n.appendChild(thread) + threadStarter.insertAdjacentHTML('afterbegin', n.outerHTML) + postlink.parentNode.parentNode.removeChild(postlink.parentNode) + }) + } +} + +export default betterPlayer diff --git a/src/extension/functions/betterPages/betterSettings.js b/src/extension/functions/betterPages/betterSettings.js new file mode 100644 index 0000000..dc2ba32 --- /dev/null +++ b/src/extension/functions/betterPages/betterSettings.js @@ -0,0 +1,150 @@ +import $ from '../selector' +import addGroup from '../settings/addGroup' +import updateScriptSettings from '../settings/updateScriptSettings' + +const betterSettings = () => { + const theForm = $( + '
' + ) + theForm.appendChild($('ANBT script settings')) + theForm.appendChild( + addGroup('Pen Tablet (unavailable for the moment...)', [ + [ + 'enableWacom', + 'boolean', + 'Enable Wacom plugin / pressure sensitivity support' + ], + [ + 'fixTabletPluginGoingAWOL', + 'boolean', + 'Try to prevent Wacom plugin from disappearing' + ] + //["pressureExponent", "number", "Pressure exponent (smaller = softer tablet response, bigger = sharper)"], + ]) + ) + theForm.appendChild( + addGroup('Play (most settings are for the new canvas only)', [ + [ + 'newCanvas', + 'boolean', + 'New drawing canvas (also allows watching playback)' + ], + [ + 'submitConfirm', + 'boolean', + 'Confirm submitting if more than a minute is left' + ], + ['smoothening', 'boolean', 'Smoothing of strokes'], + ['hideCross', 'boolean', 'Hide the cross when drawing'], + [ + 'enterToCaption', + 'boolean', + 'Submit captions (and start games) by pressing Enter' + ], + [ + 'backup', + 'boolean', + 'Save the drawing in case of error and restore it in sandbox' + ], + [ + 'timeoutSound', + 'boolean', + 'Warning sound when only a minute is left (normal games)' + ], + [ + 'timeoutSoundBlitz', + 'boolean', + 'Warning sound when only 5 seconds left (blitz)' + ], + ['timeoutSoundVolume', 'number', 'Volume of the warning sound, in %'], + [ + 'rememberPosition', + 'boolean', + 'Show your panel position and track changes in unfinished games list' + ], + ['colorNumberShortcuts', 'boolean', 'Use 0-9 keys to select the color'], + [ + 'colorUnderCursorHint', + 'boolean', + 'Show the color under the cursor in the palette' + ], + [ + 'colorDoublePress', + 'boolean', + 'Double press 0-9 keys to select color without pressing shift' + ], + [ + 'bookmarkOwnCaptions', + 'boolean', + 'Automatically bookmark your own captions in case of dustcatchers' + ] + ]) + ) + theForm.appendChild( + addGroup('Miscellaneous', [ + [ + 'localeTimestamp', + 'boolean', + `Format timestamps as your system locale (${new Date().toLocaleString()})` + ], + [ + 'proxyImgur', + 'boolean', + 'Replace imgur.com links to filmot.com to load, in case your ISP blocks them' + ], + ['ajaxRetry', 'boolean', 'Retry failed AJAX requests'], + [ + 'autoplay', + 'boolean', + 'Automatically start replay when watching playback' + ], + ['autoBypassNSFW', 'boolean', 'Automatically bypass NSFW game warning'], + ['markStalePosts', 'boolean', 'Mark stale forum posts'], + [ + 'maxCommentHeight', + 'number', + 'Maximum comments and posts height until directly linked (px, 0 = no limit)' + ], + [ + 'useOldFont', + 'boolean', + 'Use old Nunito font (which is usually bolder and less wiggly)' + ], + ['useOldFontSize', 'boolean', 'Use old, smaller font size'], + ['markdownTools', 'boolean', 'Markdown tools for messages'], + [ + 'anbtDarkMode', + 'boolean', + "Switch between ANBT's and Drawception's dark mode" + ] + ]) + ) + theForm.appendChild( + addGroup('Advanced', [ + [ + 'newCanvasCSS', + 'longstr', + 'Custom CSS for new canvas (experimental, get styles here)' + ], + [ + 'forumHiddenUsers', + 'longstr', + 'Comma-separated list of user IDs whose forum posts are hidden' + ] + ]) + ) + $( + '
' + ).forEach(node => theForm.appendChild(node)) + $('#main').insertAdjacentHTML('afterbegin', theForm.outerHTML) + $('.settingsForm').addEventListener( + 'submit', + form => updateScriptSettings(form) && false + ) + + // Extend "location" input to max server-accepted 65 characters + if ($('input[name="location"]')) + $('input[name="location"]').setAttribute('maxlength', '65') +} + +export default betterSettings diff --git a/src/extension/functions/comment/betterComments.js b/src/extension/functions/comment/betterComments.js new file mode 100644 index 0000000..40f743e --- /dev/null +++ b/src/extension/functions/comment/betterComments.js @@ -0,0 +1,123 @@ +import options from '../../options' +import getLocalStorageItem from '../getLocalStorageItem' +import $ from '../selector' +import formatTimestamp from '../time/formatTimestamp' + +const betterComments = () => { + // Linkify the links and add ability to address comment holders again + const comments = [...$('#comments').nextElementSibling.children].slice(1) + comments.forEach(x => { + x.parentNode.parentNode.classList.add('comment-holder') + }) + // Interlink game panels and comments + const gamePlayers = [] + const playerdata = {} + $('.gamepanel-holder', true).forEach((gamePanel, index) => { + const detail = gamePanel.querySelector('.panel-details') + const gamepanel = gamePanel.querySelector('.gamepanel') + const playerLink = detail.querySelector('.panel-user a') + if (!playerLink) return + const id = playerLink.href.match(/\/player\/(\d+)\//)[1] + playerdata[id] = { + panel_number: index + 1, + player_anchor: playerLink, + panel_id: gamepanel.id, + drew: gamepanel.querySelector('img') !== null, + comments: 0 + } + gamePlayers.push(id) + }) + // Highlight new comments and remember seen comments + const seenComments = getLocalStorageItem('gpe_seenComments', {}) + const gameid = document.location.href.match(/game\/([^/]+)\//)[1] + if (comments) { + // Clear old tracked comments + const hour = Math.floor(Date.now() / (1000 * 60 * 60)) // timestamp with 1 hour precision + // Store game entry for up to a week after last tracked comment + for (const tempgame in seenComments) + if (seenComments[tempgame].h + 24 * 7 < hour) + delete seenComments[tempgame] + let maxseenid = 0 + comments.forEach(holder => { + const dateElement = holder.querySelector('a.text-muted') + const vue = holder.__vue__ + if (vue) { + const text = dateElement.textContent.trim() + dateElement.textContent = `${text}, ${formatTimestamp( + vue.comment_date * 1000 + )}` + if (vue.edit_date > 0) { + const element = dateElement.parentNode.querySelector( + 'span[rel="tooltip"]' + ) + const title = `${element.title}, ${formatTimestamp( + vue.edit_date * 1000 + ).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart + element.setAttribute('title', title) + } + } + const ago = dateElement.textContent + const commentid = parseInt(holder.id.slice(1), 10) + // Also allow linking to specific comment + dateElement.setAttribute('title', 'Link to comment') + dateElement.textContent = `${dateElement.textContent.trim()} #${commentid}` + // Track comments from up to week ago + if (ago.match(/just now|min|hour|a day| [1-7] day/)) { + if (!(seenComments[gameid] && seenComments[gameid].id >= commentid)) { + holder.classList.add('comment-new') + if (maxseenid < commentid) maxseenid = commentid + } + } + // Add game perticipation info + const link = holder.querySelector('.text-bold a') + ? holder.querySelector('.text-bold a').href.match(/\/player\/(\d+)\//) + : '' + if (link) { + const id = link[1] + if (gamePlayers.includes(id)) { + const drew = playerdata[id].drew ? 'drew' : 'wrote' + dateElement.insertAdjacentHTML( + 'beforebegin', + `(${drew} #${ + playerdata[id].panel_number + }) ` + ) + playerdata[id].comments++ + } + } + }) + if (maxseenid) + seenComments[gameid] = { + h: hour, + id: maxseenid + } + localStorage.setItem('gpe_seenComments', JSON.stringify(seenComments)) + } + for (const playerId in gamePlayers) { + const data = playerdata[playerId] + if (data && data.comments) { + const cmt2 = `Player left ${data.comments} comment${ + data.comments > 1 ? 's' : '' + }` + data.player_anchor.title = cmt2 + data.player_anchor.insertAdjacentHTML( + 'afterend', + `${data.comments}` + ) + } + } + if (options.maxCommentHeight) { + const h = options.maxCommentHeight + comments.forEach(comment => + comment.addEventListener('click', () => { + if ( + comment.clientHeight > h - 50 && + !$(location.hash).has(comment).length + ) + location.hash = `#${comment.parentNode.parentNode.id}` + }) + ) + } +} + +export default betterComments diff --git a/src/extension/functions/comment/waitForComments.js b/src/extension/functions/comment/waitForComments.js new file mode 100644 index 0000000..0435c96 --- /dev/null +++ b/src/extension/functions/comment/waitForComments.js @@ -0,0 +1,16 @@ +import $ from '../selector' +import betterComments from './betterComments' + +const waitForComments = () => { + const comments = $('#comments') + ? [...$('#comments').nextElementSibling.children].slice(1) + : '' + if (comments.length && !comments[0].classList.contains('spinner')) + betterComments() + else { + if (comments.length === 0) return + setTimeout(waitForComments, 1000) + } +} + +export default waitForComments diff --git a/src/extension/functions/cookie/getCookie.js b/src/extension/functions/cookie/getCookie.js new file mode 100644 index 0000000..f894099 --- /dev/null +++ b/src/extension/functions/cookie/getCookie.js @@ -0,0 +1,4 @@ +const getCookie = name => + document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`))[2] || null + +export default getCookie diff --git a/src/extension/functions/cookie/setCookie.js b/src/extension/functions/cookie/setCookie.js new file mode 100644 index 0000000..e29c6b2 --- /dev/null +++ b/src/extension/functions/cookie/setCookie.js @@ -0,0 +1,12 @@ +const setCookie = (name, value, expire) => { + if (expire) { + const time = new Date() + time.setTime(time.getTime() + 24 * expire * 60 * 60 * 1e3) + expire = time.toUTCString() + } + document.cookie = `${name}=${value ? JSON.stringify(value) : ''}; expires=${ + expire ? expire : 'Thu, 01 Jan 1970 00:00:00 UTC' + }; path=/` +} + +export default setCookie diff --git a/src/extension/functions/darkMode/addDarkCSS.js b/src/extension/functions/darkMode/addDarkCSS.js new file mode 100644 index 0000000..0d8e37e --- /dev/null +++ b/src/extension/functions/darkMode/addDarkCSS.js @@ -0,0 +1,53 @@ +const addDarkCSS = () => + localStorage.setItem( + 'gpe_darkCSS', + ( + 'a{color:#77c0ff$}.wrapper{~#444$}#nav-drag{~#353535$}.btn-default{~#7f7f7f$;border-bottom-color:#666$;border-left-color:#666$;border-right-color:#666$;border-top-color:#666$;color:#CCC$}' + + '.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{~#757575$;border-bottom-color:#565656$;border-left-color:#565656$;border-right-color:#565656$;border-top-color:#565656$;color:#DDD$}' + + '.btn-success{~#2e2e2e$;border-bottom-color:#262626$;border-left-color:#262626$;border-right-color:#262626$;border-top-color:#262626$;color:#CCC$}' + + '.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{~#232323$;border-bottom-color:#1c1c1c$;border-left-color:#1c1c1c$;border-right-color:#1c1c1c$;border-top-color:#1c1c1c$;color:#DDD$}' + + '.btn-primary{~#213184$;border-bottom-color:#1a1a68$;border-left-color:#1a1a68$;border-right-color:#1a1a68$;border-top-color:#1a1a68$;color:#CCC$}' + + '.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{~#191964$;border-bottom-color:#141450$;border-left-color:#141450$;border-right-color:#141450$;border-top-color:#141450$;color:#DDD$}' + + '.btn-info{~#2d7787$;border-bottom-color:#236969$;border-left-color:#236969$;border-right-color:#236969$;border-top-color:#236969$;color:#CCC$}' + + '.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{~#1c5454$;border-bottom-color:#133939$;border-left-color:#133939$;border-right-color:#133939$;border-top-color:#133939$;color:#DDD$}' + + '.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{~#3b3b3b$}.navbar-toggle{~#393939$}.navbar{border-bottom:1px solid #000$}.forum-thread-starter,.breadcrumb,.regForm{~#555$}' + + '.form-control{~#666$;border:1px solid #333$;color:#EEE$}code,pre{~#656$;color:#FCC$}body{color:#EEE$}footer{~#333$;border-top:1px solid #000$}' + + '.pagination>li:not(.disabled):not(.active),.pagination>li:not(.disabled):not(.active)>a:hover,.pagination>li:not(.disabled):not(.active)>span:hover,.pagination>li:not(.disabled):not(.active)>a:focus,.pagination>li:not(.disabled):not(.active)>span:focus{~#444$}.pagination>li>a,.pagination>li>span{~#555$;border:1px solid #000$}' + + '.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{~#666$;border-top:1px solid #444$;border-bottom:1px solid #444$}' + + '.drawingForm{~#555$}.well{~#666$;border:1px solid #333$}#timeleft{color:#AAA$}legend{border-bottom:1px solid #000$}.thumbpanel{color:#EEE;~#555$}.thumbpanel img{~#fffdc9$}.panel-number,.modal-content,.profile-user-header{~#555$}' + + '#commentForm{~#555$;border:1px solid #000$}.modal-header,.nav-tabs{border-bottom:1px solid #000$}hr,.modal-footer{border-top:1px solid #000$}' + + '.store-item{background:#666$;~-moz-linear-gradient(top,#666 0,#333 100%)$;~-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(100%,#333))$;~-webkit-linear-gradient(top,#666 0,#333 100%)$;~-o-linear-gradient(top,#666 0,#333 100%)$;~-ms-linear-gradient(top,#666 0,#333 100%)$;~linear-gradient(to bottom,#666 0,#333 100%)$;border:1px solid #222$}' + + '.store-item:hover{border:1px solid #000$}.store-item-title{~#222$;color:#DDD$}.store-title-link{color:#DDD$}.profile-award{~#222$}.profile-award-unlocked{~#888$}.progress-bar{color:#CCC$;~#214565$}.progress{~#333$}' + + '.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(0,0,0,0.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(0,0,0,0.15)),color-stop(.75,rgba(0,0,0,0.15)),color-stop(.75,transparent),to(transparent))$;background-image:-webkit-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:-moz-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$}' + + '.progress-bar-success{~#363$}.progress-bar-info{~#367$}.progress-bar-warning{~#863$}.progress-bar-danger{~#733$}' + + '.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#DDD$;~#555$;border:1px solid #222$}.nav>li>a:hover,.nav>li>a:focus{~#333$;border-bottom-color:#222$;border-left-color:#111$;border-right-color:#111$;border-top-color:#111$}' + + '.nav>li.disabled>a,.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#555$}.table-striped>tbody>tr:nth-child(2n+1)>td,.table-striped>tbody>tr:nth-child(2n+1)>th{~#333$}' + + '.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{~#555$}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{border-top:1px solid #333$}.news-alert{~#555$;border:2px solid #444$}' + + '.btn-menu{~#2e2e2e$}.btn-menu:hover{~#232323$}.btn-yellow{~#8a874e$}.btn-yellow:hover{~#747034$}' + + 'a.label{color:#fff$}.text-muted,a.text-muted{color:#999$}a.wrong-order{color:#F99$}div.comment-holder:target{~#454$}' + + '.popover{~#777$}.popover-title{~#666$;border-bottom:1px solid #444$}.popover.top .arrow:after{border-top-color:#777$}.popover.right .arrow:after{border-right-color:#777$}.popover.bottom .arrow:after{border-bottom-color:#777$}.popover.left .arrow:after{border-left-color:#777$}' + + '.label-fancy{~#444$;border-color:#333$;color:#FFF$}' + + '.avatar,.profile-avatar{~#444$;border:1px solid #777$;}' + + '.bg-lifesupport{~#444$}body{~#555$}.snap-content{~#333$}' + + 'select,textarea{color:#000$}.help-block{color:#ddd$}.jumbotron{~#444$}' + + '.navbar-dropdown{~#444$}a.list-group-item{~#444$;color:#fff$;border:1px solid #222$}a.list-group-item:hover,a.list-group-item:focus{~#222$}' + + '.likebutton.btn-success{color:#050$;~#5A5$}.likebutton.btn-success:hover{~#494$}' + + ".thumbnail[style*='background-color: rgb(255, 255, 255)']{~#555$}" + + '.popup,.v--modal{~#666$;border:1px solid #222$}.btn-reaction{~#666$;border:none$;color:#AAA$}@media(min-width:625px){.create-game-wrapper{~#555$}}' + + '.profile-header{~#555$}.profile-nav > li > a{~#333$}.profile-nav>li.active>a,.profile-nav>li>a:hover{~#555$}' + + '.gsc-control-cse{~#444$;border-color:#333$}.gsc-above-wrapper-area,.gsc-result{border:none$}.gs-snippet{color:#AAA$}.gs-visibleUrl{color:#8A8$}a.gs-title b,.gs-visibleUrl b{color:#EEE$}.gsc-adBlock{display:none$}.gsc-input{~#444$;border-color:#333$;color:#EEE$}' + + '.comment-highlight{border:none$;background:#454$}#header-emotes{~#555$}#header-bar-container{border:none$}.paypal-button-tag-content{color:#EEE$}.numlikes{color:#EEE$}.gsc-input-box{~#444$;border-color:#333$}.gsc-completion-container{~#333$;border-color:#000$}.gsc-completion-selected{~#222$}.gsc-completion-container b{color:#AAA$}.alert-nice{~#4a4a4a$}.store-buy-coins{~#777$}.store-buy-coins:hover{~#666$}.store-buy-coins>h2,.store-buy-coins>h2>small{color:#EEE$}.store-package-selector{~#888$}.store-package-selector>label{color:#EEE$}.label-stat{~#444$;color:#EEE$;border:1px solid #555$}.label-stat.disabled{~#333$}.option{padding:4px 8px$;~#666$;color:#EEE$;border-color:#333$}.option.selected{border-color:#EEE$}.sleek-select{~#666$;border-color:#333$}select{color:#EEE$}.modal-note{color:#EEE$}.vue-dialog-button{~#555$;border:none$}.vue-dialog-button:hover{~#5a5a5a$}.vue-dialog-buttons{border-top:1px solid #222$}.dashboard-item{~#333$}legend{color:#EEE$}.list-group-item{~#444$;color:#EEE$;border:1px solid #222$}.alert-warning{color:#EEE$;~#555$;border-color:#555$}.btn-reaction.active{border:1px solid #EEE$}.bg-shadow-box{~#333$}.btn-gray{~#222$;border:none$}.btn-gray:hover{color:#EEE$;~#1a1a1a$}.btn-bright{~#333$;color:#EEE$}' + + '.player-name-new{color:#33b73f$}.gsc-tabsArea>div{overflow:hidden$}.gs-image-popup-box{~#333$;border-color:#222$}.gs-size{color:#6f6f6f$}.gsc-result-info{color:#EEE$}.gsc-refinementsArea{border:none$}.gsc-tabsArea{border-bottom-color:#333$}.gsc-cursor-page{color:#EEE$}.gsc-cursor-current-page{color:#AAA$}.profile-nav>.disabled>a{color:#555$;~#3a3a3a$}.profile-nav>.disabled>a:hover{~#3a3a3a$}.sleek-select:hover{border-color:#EEE$}' + + '.btn-menu{border-color:#1e1e1e$;text-shadow:0px 0px 3px #777$}.btn-menu:hover{border-color:#1e1e1e$}.pagination>.active>span{color:#EEE$}#btn-notifications{color:#EEE$}.btn-warning{color:#EEE$}.alert-nice{color:#EEE$}.emotes-popup{~#2e2e2e$}.navbar-toggle{~#2e2e2e$;border-color:#1e1e1e$}.navbar-toggle .icon-bar{~#EEE$}' + + '.gamepanel-highlight{box-shadow:0 0 20px #111$;~#222$}' + + // We have entered specificity hell... + 'a.anbt_replaypanel:hover{color:#8af$}' + + '.anbt_favedpanel{color:#d9534f$}' + + // Some lamey compression method! + '' + ) + .replace(/~/g, 'background-color:') + .replace(/\$/g, ' !important') + ) + +export default addDarkCSS diff --git a/src/extension/functions/darkMode/setDarkMode.js b/src/extension/functions/darkMode/setDarkMode.js new file mode 100644 index 0000000..ddf6604 --- /dev/null +++ b/src/extension/functions/darkMode/setDarkMode.js @@ -0,0 +1,25 @@ +import getLocalStorageItem from '../getLocalStorageItem' + +const setDarkMode = () => { + const settings = getLocalStorageItem('gpe_anbtSettings', {}) + if (settings.anbtDarkMode || typeof settings.anbtDarkMode === 'undefined') { + if (getLocalStorageItem('gpe_inDark', 0)) { + const css = document.createElement('style') + css.id = 'darkgraycss' + css.type = 'text/css' + css.appendChild( + document.createTextNode(getLocalStorageItem('gpe_darkCSS')) + ) + if (document.head) document.head.appendChild(css) + else { + const darkLoad = setInterval(() => { + if (!document.head) return + document.head.appendChild(css) + clearInterval(darkLoad) + }, 100) + } + } + } +} + +export default setDarkMode diff --git a/src/extension/functions/darkMode/toggleLight.js b/src/extension/functions/darkMode/toggleLight.js new file mode 100644 index 0000000..c8d35d3 --- /dev/null +++ b/src/extension/functions/darkMode/toggleLight.js @@ -0,0 +1,31 @@ +import options from '../../options' +import setCookie from '../cookie/setCookie' +import getLocalStorageItem from '../getLocalStorageItem' + +const toggleLight = () => { + if (options.anbtDarkMode) { + const inDark = getLocalStorageItem('gpe_inDark', 0) + if (!inDark) { + const css = document.createElement('style') + css.id = 'darkgraycss' + css.type = 'text/css' + css.appendChild( + document.createTextNode(getLocalStorageItem('gpe_darkCSS')) + ) + document.head.appendChild(css) + } else { + document.head.removeChild(document.getElementById('darkgraycss')) + } + localStorage.setItem('gpe_inDark', `${inDark ? 0 : 1}`) + } else { + if (document.body.classList.contains('theme-night')) { + document.body.classList.remove('theme-night') + setCookie('theme-night') + } else { + document.body.classList.add('theme-night') + setCookie('theme-night', 1, 365) + } + } +} + +export default toggleLight diff --git a/src/extension/functions/decimalToBase62.js b/src/extension/functions/decimalToBase62.js new file mode 100644 index 0000000..e5ddefa --- /dev/null +++ b/src/extension/functions/decimalToBase62.js @@ -0,0 +1,13 @@ +import globals from '../globals' + +const decimalToBase62 = number => { + let result = '' + while (number !== 0) { + const quotient = number % 62 + result = globals.alphabet[quotient] + result + number = (number - quotient) / 62 + } + return result +} + +export default decimalToBase62 diff --git a/src/extension/functions/fade/fadeIn.js b/src/extension/functions/fade/fadeIn.js new file mode 100644 index 0000000..7ec62c0 --- /dev/null +++ b/src/extension/functions/fade/fadeIn.js @@ -0,0 +1,14 @@ +const fadeIn = (element, duration = 400) => { + element.style.display = 'inline' + duration = duration === 'slow' ? 600 : duration + element.style.opacity = element.style.opacity + ? parseFloat(element.style.opacity) + 0.1 + : 0.2 + if (parseFloat(element.style.opacity) > 1) element.style.opacity = 1 + else + setTimeout(() => { + fadeIn(element, duration) + }, duration / 10) +} + +export default fadeIn diff --git a/src/extension/functions/fade/fadeOut.js b/src/extension/functions/fade/fadeOut.js new file mode 100644 index 0000000..42e0aca --- /dev/null +++ b/src/extension/functions/fade/fadeOut.js @@ -0,0 +1,15 @@ +const fadeOut = (element, duration = 400) => { + duration = duration === 'slow' ? 600 : duration + element.style.opacity = element.style.opacity + ? parseFloat(element.style.opacity) - 0.1 + : 1 + if (parseFloat(element.style.opacity) < 0) { + element.style.opacity = 0 + element.style.display = 'none' + } else + setTimeout(() => { + fadeOut(element, duration) + }, duration / 10) +} + +export default fadeOut diff --git a/src/extension/functions/getLocalStorageItem.js b/src/extension/functions/getLocalStorageItem.js new file mode 100644 index 0000000..bba6b3a --- /dev/null +++ b/src/extension/functions/getLocalStorageItem.js @@ -0,0 +1,10 @@ +const getLocalStorageItem = (name, empty) => { + const item = localStorage.getItem(name) + try { + return item ? JSON.parse(item) : empty || '' + } catch (e) { + return item ? item : empty || '' + } +} + +export default getLocalStorageItem diff --git a/src/extension/functions/getNotifications.js b/src/extension/functions/getNotifications.js new file mode 100644 index 0000000..1c15fa3 --- /dev/null +++ b/src/extension/functions/getNotifications.js @@ -0,0 +1,22 @@ +import $ from './selector' + +const getNotifications = () => { + if (!window.notificationsOpened) { + $('#user-notify-list').innerHTML = + 'Loading....' + const xhr = new XMLHttpRequest() + xhr.open('GET', '/notification/view/') + xhr.onload = () => { + if (xhr.status === 200) { + $('#user-notify-list').innerHTML = xhr.responseText + $('#user-notify-count').textContent = '0' + window.notificationsOpened = true + } else { + $('#user-notify-list').innerHTML = xhr.responseText + window.notificationsOpened = true + } + } + } +} + +export default getNotifications diff --git a/src/extension/functions/getPanelId.js b/src/extension/functions/getPanelId.js new file mode 100644 index 0000000..b6fd02a --- /dev/null +++ b/src/extension/functions/getPanelId.js @@ -0,0 +1,6 @@ +const getPanelId = url => { + const match = url.match(/\/panel\/[^/]+\/(\w+)\//) + if (match) return match[1] +} + +export default getPanelId \ No newline at end of file diff --git a/src/extension/functions/getSelectedText.js b/src/extension/functions/getSelectedText.js new file mode 100644 index 0000000..e3f1373 --- /dev/null +++ b/src/extension/functions/getSelectedText.js @@ -0,0 +1,19 @@ +import markdown from '../markdown' +import $ from './selector' + +const getSelectedText = event => { + const textarea = $('#input-comment') + const { value, selectionStart, selectionEnd } = textarea + const { length } = value + const selection = value.substring(selectionStart, selectionEnd) + markdown[`${event.currentTarget.id}`].replaceFunc( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea + ) +} + +export default getSelectedText diff --git a/src/extension/functions/linkifyDrawingPanels.js b/src/extension/functions/linkifyDrawingPanels.js new file mode 100644 index 0000000..474256f --- /dev/null +++ b/src/extension/functions/linkifyDrawingPanels.js @@ -0,0 +1,33 @@ +import scrambleID from './scrambleID' + +const linkifyDrawingPanels = img => { + if (img.parentNode.nodeName !== 'A') { + if (img.src.match(/\/images\/panels\//) || img.src.match(/\/pub\/panels\//)) + img.outerHTML = `${img.outerHTML}` + + if (img.src.match(/\/drawings\//)) + img.outerHTML = `${img.outerHTML}` + + if (img.src.match(/\/panel\//)) + img.outerHTML = `${img.outerHTML}` + + if (img.src.match(/\/images\/games\//) || img.src.match(/\/pub\/games\//)) + img.outerHTML = `${img.outerHTML}` + + if (img.src.match(/\/display-panel.php?/)) { + const newsrc = `/panel/drawing/${scrambleID( + img.src.match(/x=(\d+)/)[1] + )}/` + img.setAttribute('src', newsrc) + img.outerHTML = `${img.outerHTML}` + } + } +} + +export default linkifyDrawingPanels diff --git a/src/extension/functions/linkifyNodeText.js b/src/extension/functions/linkifyNodeText.js new file mode 100644 index 0000000..6c22565 --- /dev/null +++ b/src/extension/functions/linkifyNodeText.js @@ -0,0 +1,9 @@ +const linkifyNodeText = node => { + if (node.textContent.includes('://')) + node.innerHTML = node.innerHTML.replace( + /([^"]|^)(https?:\/\/(?:(?:(?:[^\s<()]*\([^\s<()]*\))+)|(?:[^\s<()]+)))/g, + '$1$2' + ) +} + +export default linkifyNodeText diff --git a/src/extension/functions/markdown/bold.js b/src/extension/functions/markdown/bold.js new file mode 100644 index 0000000..ed1c6fa --- /dev/null +++ b/src/extension/functions/markdown/bold.js @@ -0,0 +1,58 @@ +const bold = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = new RegExp(`\\*\\*(${selection.replace(/\*/g, '')})\\*\\*`) + if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1') + else if (selectionStart > 0 && selectionEnd < length) { + if (value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)) { + selectionStart-- + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + value.substring(selectionStart - 2, selectionEnd + 2).match(selRegex) + ) { + selectionStart -= 2 + selectionEnd += 2 + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else + selection = selection.match(/\*\*.+\*\*/g) + ? selection.replace(/\*\*/g, '') + : `**${selection.replace(/\n/g, '**\n**')}**` + } else { + if ( + !selectionStart && + value.substring(selectionStart, selectionEnd + 1).match(selRegex) + ) { + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + selectionEnd === length && + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else + selection = selection.match(/\*\*.+\*\*/g) + ? selection.replace(/\*\*/g, '') + : `**${selection.replace(/\n/g, '**\n**')}**` + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) +} + +export default bold diff --git a/src/extension/functions/markdown/code.js b/src/extension/functions/markdown/code.js new file mode 100644 index 0000000..ff77a33 --- /dev/null +++ b/src/extension/functions/markdown/code.js @@ -0,0 +1,38 @@ +const code = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = /^ {4}(.*)/gm + if (selection.match(selRegex)) selection = selection.replace(/^ {4}/gm, '') + else if ( + selectionStart === 0 || + value.substring(selectionStart - 1, selectionEnd).match(/\n.*/gm) + ) { + if (selection.match(/^ {4}/gm)) selection = selection.replace(/^ {4}/gm, '') + else + selection = `${ + selectionStart === 0 + ? '' + : value.substring(selectionStart - 1, selectionEnd).match(/^\n/) + ? '\n' + : '\n\n' + } ${selection.replace(/\n/g, '\n ')}` + } else + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/^\n/) + ? '\n' + : '\n\n' + } ${selection.replace(/\n^(.*)/gm, '\n $1')}${ + value.substring(selectionEnd, selectionEnd + 1).match(/\n/) ? '' : '\n' + }` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) +} + +export default code diff --git a/src/extension/functions/markdown/heading.js b/src/extension/functions/markdown/heading.js new file mode 100644 index 0000000..4b157fe --- /dev/null +++ b/src/extension/functions/markdown/heading.js @@ -0,0 +1,42 @@ +const heading = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = /^#+ .*/gm + if (selection.match(selRegex)) { + selection = selection.replace(/^# /gm, '') + if (selection.match(/^#{2,} /gm)) selection.replace(/(^#*)# /gm, '$1 ') + } else if ( + !selectionStart || + value.substring(selectionStart - 1, selectionEnd).match(/\n.*/gm) + ) + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) || + !selectionStart + ? '' + : '\n' + }###### ${selection.replace(/\n/g, '\n###### ')}` + else if (value.substring(selectionStart - 1, selectionEnd).match(selRegex)) { + selectionStart -= 4 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/(^#*)# /gm, '$1 ') + } else if ( + value.substring(selectionStart - 2, selectionEnd).match(selRegex) + ) { + selectionStart -= 5 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/(^#*)# /gm, '$1 ') + } else selection = `\n###### ${selection.replace(/\n/g, '\n###### ')}` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) +} + +export default heading diff --git a/src/extension/functions/markdown/highlighter.js b/src/extension/functions/markdown/highlighter.js new file mode 100644 index 0000000..0b5c665 --- /dev/null +++ b/src/extension/functions/markdown/highlighter.js @@ -0,0 +1,52 @@ +const highlighter = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = new RegExp(`\`(${selection.replace(/`/g, '')})\``) + if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1') + else if (selectionStart > 0 && selectionEnd < length) { + if (value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)) { + selectionStart-- + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(/`.+`/g) + ? selection.replace(/`/g, '') + : `\`${selection.replace(/\n/g, '`\n`')}\`` + } + } else { + if ( + !selectionStart && + value.substring(selectionStart, selectionEnd + 1).match(selRegex) + ) { + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + selectionEnd === length && + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(/`.+`/g) + ? selection.replace(/`/g, '') + : `\`${selection.replace(/\n/g, '`\n`')}\`` + } + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) +} + +export default highlighter diff --git a/src/extension/functions/markdown/image.js b/src/extension/functions/markdown/image.js new file mode 100644 index 0000000..5448a6b --- /dev/null +++ b/src/extension/functions/markdown/image.js @@ -0,0 +1,57 @@ +import $ from '../selector' + +const image = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = /!\[(.*)\]\((\S*)( ".*")?\)/ + if (selection.match(selRegex)) + textarea.value = + value.substring(0, selectionStart) + + selection.replace(selRegex, '$1 $2') + + value.substring(selectionEnd, length) + else { + let link = '' + if (!selection.match(/\[(.*)\]\((\S*)( ".*")?\)/)) { + link = selection.match(/https?:\/\/\S*/) || '' + selection = selection + .replace(link[0], '') + .replace(/ +/g, ' ') + .trim() + } else selection = '' + const divModal = $( + `

Markdown informations box


Text:

Link:

Hover message:


` + ) + $('.navbar-header>div:last-child').append(divModal) + setTimeout(() => { + document.body.classList.add('v--modal-block-scroll') + $('#markdown').style.opacity = 1 + }, 1) + $('#markdown-text').value = selection ? selection : '' + $('#markdown-link').value = link ? link[0] : '' + $('.close').addEventListener('click', () => { + document.body.classList.remove('v--modal-block-scroll') + $('#markdown').outerHTML = '' + }) + $('#markdown-done').addEventListener('click', () => { + const tag = `![${$('#markdown-text').value}](${ + $('#markdown-link').value + }${$('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : ''})` + selection = value.substring(selectionStart, selectionEnd) + textarea.value = + value.substring(0, selectionStart) + + (selection.match(/\[(.*)\]\((\S*)( ".*")?\)/) + ? selection.replace(/\[.*\]/, `[${tag}]`) + : tag) + + value.substring(selectionEnd, length) + document.body.classList.remove('v--modal-block-scroll') + $('#markdown').outerHTML = '' + }) + } +} + +export default image diff --git a/src/extension/functions/markdown/italic.js b/src/extension/functions/markdown/italic.js new file mode 100644 index 0000000..3a200fe --- /dev/null +++ b/src/extension/functions/markdown/italic.js @@ -0,0 +1,57 @@ +const italic = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = new RegExp( + `\\*(?=\\S*${selection.replace( + /(.*)\*(.*)/g, + '' + )})((?:\\*\\*|\\\\[\\s\\S]|\\s+(?:\\\\[\\s\\S]|[^\\s\\*\\\\]|\\*\\*)|[^\\s\\*\\\\])+?)\\*(?!\\*)` + ) + const italicRegex = /\*(?=\S)((?:\*\*|\\[\s\S]|\s+(?:\\[\s\S]|[^\s\*\\]|\*\*)|[^\s\*\\])+?)\*(?!\*)/g + if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1') + else if (selectionStart > 0 && selectionEnd < length) { + if (value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)) { + selectionStart-- + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(italicRegex) + ? selection.replace(italicRegex, '$1') + : `*${selection.replace(/\n/g, '*\n*')}*` + } + } else { + if ( + !selectionStart && + value.substring(selectionStart, selectionEnd + 1).match(selRegex) + ) { + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + selectionEnd === length && + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else + selection = selection.match(italicRegex) + ? selection.replace(italicRegex, '$1') + : `*${selection.replace(/\n/g, '*\n*')}*` + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) +} + +export default italic diff --git a/src/extension/functions/markdown/link.js b/src/extension/functions/markdown/link.js new file mode 100644 index 0000000..df4cb0b --- /dev/null +++ b/src/extension/functions/markdown/link.js @@ -0,0 +1,54 @@ +import $ from '../selector' + +const link = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = /^(?!!)\[(.*)\]\((\S*)( ".*")?\)/ + if (selection.match(selRegex)) + textarea.value = + value.substring(0, selectionStart) + + selection.replace(selRegex, '$1 $2') + + value.substring(selectionEnd, length) + else { + let imageLink = '' + if (!selection.match(/!\[(.*)\]\((\S*)( ".*")?\)/)) { + imageLink = selection.match(/https?:\/\/\S*/) || '' + selection = selection + .replace(imageLink[0], '') + .replace(/ +/g, ' ') + .trim() + } + const divModal = $( + `

Markdown informations box


Text:

Link:

Hover message:


` + ) + $('.navbar-header>div:last-child').append(divModal) + setTimeout(() => { + document.body.classList.add('v--modal-block-scroll') + $('#markdown').style.opacity = 1 + }, 1) + $('#markdown-text').value = selection ? selection : '' + $('#markdown-link').value = imageLink ? imageLink[0] : '' + $('.close').addEventListener('click', () => { + document.body.classList.remove('v--modal-block-scroll') + $('#markdown').outerHTML = '' + }) + $('#markdown-done').addEventListener('click', () => { + selection = `[${$('#markdown-text').value}](${$('#markdown-link').value}${ + $('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : '' + })` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) + document.body.classList.remove('v--modal-block-scroll') + $('#markdown').outerHTML = '' + }) + } +} + +export default link diff --git a/src/extension/functions/markdown/listOl.js b/src/extension/functions/markdown/listOl.js new file mode 100644 index 0000000..dd43a3a --- /dev/null +++ b/src/extension/functions/markdown/listOl.js @@ -0,0 +1,68 @@ +const listOl = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = /^( {3})*\d+\. (.*)/gm + if (selection.match(selRegex)) { + selection = selection.match(/^ {3}/) + ? selection.replace(/^ {3}/gm, '') + : selection.replace(/^\d+\. /gm, '') + } else if ( + !selectionStart || + value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/) + ) { + let countOl = 0 + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) || + !selectionStart + ? '' + : '\n' + }0. ${selection.replace(/\n/g, () => { + countOl++ + return `\n${countOl}. ` + })}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + } else if ( + value.substring(selectionStart - 4, selectionEnd).match(/( {3})*\d+\. (.*)/) + ) { + selectionStart -= 4 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/( {3})*(\d+\.) /g, ' $1$2 ') + } else if ( + value.substring(selectionStart - 5, selectionEnd).match(/( {3})*\d+\. (.*)/) + ) { + selectionStart -= 5 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/( {3})*(\d+\.) /g, ' $1$2 ') + } else { + let countOl = 0 + selection = `\n0. ${selection.replace(/\n/g, () => { + countOl++ + return `\n${countOl}. ` + })}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) || + selectionEnd === length + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) +} + +export default listOl diff --git a/src/extension/functions/markdown/listUl.js b/src/extension/functions/markdown/listUl.js new file mode 100644 index 0000000..e616369 --- /dev/null +++ b/src/extension/functions/markdown/listUl.js @@ -0,0 +1,57 @@ +const listUl = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = /^( {3})*- (.*)/ + if (selection.match(selRegex)) + selection = selection.match(/^ {3}/) + ? selection.replace(/^ {3}/gm, '') + : selection.replace(/^- /gm, '') + else if ( + !selectionStart || + value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/) + ) + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) || + !selectionStart + ? '' + : '\n' + }- ${selection.replace(/\n/g, '\n- ')}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + else if (value.substring(selectionStart - 1, selectionEnd).match(selRegex)) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(/( {3})*- /g, '$1 - ') + } else if ( + value.substring(selectionStart - 2, selectionEnd).match(selRegex) + ) { + selectionStart -= 2 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/( {3})*- /g, '$1 - ') + } else + selection = `\n- ${selection.replace(/\n/g, '\n- ')}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) || + selectionEnd === length + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) +} + +export default listUl diff --git a/src/extension/functions/markdown/quoteRight.js b/src/extension/functions/markdown/quoteRight.js new file mode 100644 index 0000000..b8475f6 --- /dev/null +++ b/src/extension/functions/markdown/quoteRight.js @@ -0,0 +1,57 @@ +const quoteRight = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = /^>+\s.*/gm + if (selection.match(selRegex)) + selection = selection.match(/^> /gm) + ? selection.replace(/^> /gm, '') + : selection.replace(/(^>*)> /gm, '$1 ') + else if ( + !selectionStart || + value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/) + ) + selection = `${ + value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) || + !selectionStart + ? '' + : '\n' + }> ${selection.replace(/\n/g, '\n> ')}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + else if (value.substring(selectionStart - 1, selectionEnd).match(selRegex)) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(/(^>*)\s/gm, '$1> ') + } else if ( + value.substring(selectionStart - 2, selectionEnd).match(selRegex) + ) { + selectionStart -= 2 + selection = value + .substring(selectionStart, selectionEnd) + .replace(/(^>*)\s/gm, '$1> ') + } else + selection = `\n> ${selection.replace(/\n/g, '\n> ')}${ + value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) || + selectionEnd === length + ? '' + : value.substring(selectionEnd, selectionEnd + 1).match(/\n/) + ? '\n' + : '\n\n' + }` + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) +} + +export default quoteRight diff --git a/src/extension/functions/markdown/strikethrough.js b/src/extension/functions/markdown/strikethrough.js new file mode 100644 index 0000000..a2047d3 --- /dev/null +++ b/src/extension/functions/markdown/strikethrough.js @@ -0,0 +1,63 @@ +const strikethrough = ( + value, + length, + selectionStart, + selectionEnd, + selection, + textarea +) => { + const selRegex = /~~((.*\W?)*)~~/ + if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1') + else if (selectionStart > 0 && selectionEnd < length) { + if (selection.match(selRegex)) selection.replace(selRegex, '$1') + else if ( + value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex) + ) { + selectionStart-- + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + value.substring(selectionStart - 2, selectionEnd + 2).match(selRegex) + ) { + selectionStart -= 2 + selectionEnd += 2 + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(/~~.+~~/g) + ? selection.replace(/~~/g, '') + : `~~${selection}~~` + } + } else { + if ( + !selectionStart && + value.substring(selectionStart, selectionEnd + 1).match(selRegex) + ) { + selectionEnd++ + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else if ( + selectionEnd === length && + value.substring(selectionStart - 1, selectionEnd).match(selRegex) + ) { + selectionStart-- + selection = value + .substring(selectionStart, selectionEnd) + .replace(selRegex, '$1') + } else { + selection = selection.match(/~~.+~~/g) + ? selection.replace(/~~/g, '') + : `~~${selection}~~` + } + } + textarea.value = + value.substring(0, selectionStart) + + selection + + value.substring(selectionEnd, length) +} + +export default strikethrough diff --git a/src/extension/functions/pageEnhancements.js b/src/extension/functions/pageEnhancements.js new file mode 100644 index 0000000..cc206d7 --- /dev/null +++ b/src/extension/functions/pageEnhancements.js @@ -0,0 +1,197 @@ +import betterPages from '../betterPages' +import options from '../options' +import versions from '../versions' +import addMarkdownTools from './addMarkdownTools' +import addStyle from './addStyle' +import setCookie from './cookie/setCookie' +import getNotifications from './getNotifications' +import $ from './selector' +import loadScriptSettings from './settings/loadScriptSettings' +import setupNewCanvas from './setupNewCanvas' + +const pageEnhancements = () => { + loadScriptSettings() + if (typeof DrawceptionPlay === 'undefined') return // Firefox Greasemonkey seems to call pageEnhancements() after document.write... + if (document.getElementById('newcanvasyo')) return // Chrome, I'm looking at you too... + try { + const tmpuserlink = $('.player-dropdown a[href^="/player/"]') + const username = tmpuserlink.querySelector('strong').textContent + const userid = tmpuserlink.href.match(/\/player\/(\d+)\//)[1] + localStorage.setItem('gpe_lastSeenName', username) + localStorage.setItem('gpe_lastSeenId', userid) + } catch (e) {} + const currentPage = location.href.match(/drawception\.com\/([^/]+)/) + if (currentPage) { + const page = currentPage[1] + const functionName = `better${page.replace(page[0], page[0].toUpperCase())}` + if (betterPages[functionName]) betterPages[functionName]() + } + + addStyle( + '.panel-user {width: auto} .panel-details img.loading {display: none}' + + '.gpe-wide, .gpe-wide-block {display: none}' + + '.gpe-btn {padding: 5px 8px; height: 28px}' + + '.gpe-spacer {margin-right: 7px; float:left}' + + '@media (min-width:992px) {.navbar-toggle,.btn-menu-player {display: none} .gpe-wide {display: inline} .gpe-wide-block {display: block}}' + + '@media (min-width:1200px) {.gpe-btn {padding: 5px 16px;} .gpe-spacer {margin-right: 20px;} .panel-number {left: -30px}}' + + '#anbtver {font-size: 10px; position:absolute; opacity:0.3; right:10px; top: 0;}' + + '.anbt_paneldel {position:absolute; padding:1px 6px; color:#FFF; background:#d9534f; text-decoration: none !important; right: 18px; border-radius: 5px}' + + '.anbt_paneldel:hover {background:#d2322d}' + + '.anbt_favpanel {top: 20px; font-weight: normal; padding: 0 2px}' + + '.anbt_favpanel:hover {color: #d9534f; cursor:pointer}' + + '.anbt_favedpanel {color: #d9534f; border-color: #d9534f}' + + '.anbt_replaypanel {top: 55px; font-weight: normal; padding: 0 8px}' + + '.anbt_replaypanel:hover {color: #8af; text-decoration: none}' + + ".anbt_owncaption:before {content: ''; display: inline-block; background: #5C5; border: 1px solid #080; width: 10px; height: 10px; border-radius: 10px; margin-right: 10px;}" + + '.gamepanel, .thumbpanel, .comment-body {word-wrap: break-word}' + + '.comment-body img {max-width: 100%}' + + '.forum-thread.anbt_hidden {display: none}' + + '.anbt_showt .forum-thread.anbt_hidden {display: block; opacity: 0.6}' + + ".anbt_unhidet:after {content: ' threads hidden. Show'}" + + ".anbt_showt .anbt_unhidet:after {content: ' threads hidden. Hide'}" + + ".anbt_hft:after {content: '[hide]'}" + + '.anbt_hft, .anbt_unhidet {padding-left: 0.4em; cursor:pointer}' + + ".forum-thread.anbt_hidden .anbt_hft:after {content: '[show]'}" + + '.anbt_threadtitle {margin: 0 0 10px}' + + '.avatar {box-sizing: content-box}' + + '.pagination {margin: 0px}' + + '#nav-drag {position: fixed; width: 100%; z-index: 2000}' + + '#header-bar-container {position: relative; width: 100%; top: 6rem}' + + '.wrapper {position: relative; top: 6rem}' + + 'footer {position: relative; top: 6rem}' + + '.option span:first-child {display: flex; flex-direction: row; justify-content: space-between}' + + '.grid-settings div[class^="grid-"] label {display: inline-flex}' + + 'input[type="checkbox"], input[type="radio"] {margin:4px 4px 0 0}' + + '@-moz-document url-prefix() {input[type="checkbox"], input[type="radio"] {margin:0 4px 0 0}}' + + '.tooltip {z-index: 3000;}' + ) + if (options.maxCommentHeight) { + const maxHeight = options.maxCommentHeight + addStyle( + `.comment-holder[id]:not(:target) .comment-body {overflow-y: hidden; max-height: ${maxHeight}px; position:relative}.comment-holder[id]:not(:target) .comment-body:before{content: 'Click to read more'; position:absolute; width:100%; height:50px; left:0; top:${maxHeight - + 50}px;text-align: center; font-weight: bold; color: #fff; text-shadow: 0 0 2px #000; padding-top: 20px; background:linear-gradient(transparent, rgba(0,0,0,0.4))}` + ) + $('.comment-body', true).forEach(comment => + comment.addEventListener('click', () => { + if ( + comment.clientHeight > maxHeight - 50 && + location.hash.indexOf(comment) === -1 + ) + location.hash = `#${comment.parentNode.parentNode.id}` + }) + ) + } + if (options.useOldFontSize) document.body.style.fontSize = '15px' + if (options.useOldFont) { + const nunito = $("link[href*='Nunito']") + nunito.parentNode.removeChild(nunito) + addStyle( + "@import url('https://fonts.googleapis.com/css?family=Nunito&display=swap')" + ) + } + if (options.anbtDarkMode) { + if (document.body.classList.contains('theme-night')) { + document.body.classList.remove('theme-night') + setCookie('theme-night') + } + } + if (options.markdownTools) addMarkdownTools() + if (options.newCanvas) { + const inSandbox = location.href.match(/drawception\.com\/sandbox\/#?(.*)/) + const inPlay = location.href.match( + /drawception\.com\/(:?contests\/)?play\/(.*)/ + ) + const hasCanvas = document.getElementById('canvas-holder') + // If created a friend game, the link won't present playable canvas + const hasCanvasOrGameForm = document.querySelector('.playtimer') + const captionContest = location.href.match(/contests\/play\//) && !hasCanvas + if (!captionContest && (inSandbox || (inPlay && hasCanvasOrGameForm))) { + setTimeout(() => setupNewCanvas(inSandbox, location.href), 1) + return + } + $('a[href^="/sandbox/"]', true).forEach(sandboxButton => + sandboxButton.addEventListener('click', event => { + if (event.which === 2) return + event.preventDefault() + setupNewCanvas(true, event.currentTarget.href) + }) + ) + $('a[href="/play/"]', true).forEach(playButton => + playButton.addEventListener('click', event => { + if (event.which === 2) return + event.preventDefault() + setupNewCanvas(false, event.currentTarget.href) + }) + ) + } + // Enhance menu for higher resolutions + if ($('.navbar-toggle')) { + const navbarToggle = $('.navbar-toggle').parentNode + const navbarButtonsList = [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '' + ] + navbarButtonsList.forEach(button => navbarToggle.appendChild($(button))) + // Let users with screens narrow enough so top bar isn't visible still use toggle light function + $('#main-menu').insertAdjacentHTML( + 'afterbegin', + ' Toggle light' + ) + } + + const menuPlayer = $('.btn-menu-player') + if (menuPlayer) { + const userlink = $('.player-dropdown a[href^="/player/"]').href + const useravatar = $('.btn-menu-player').innerHTML + const element = $( + `${useravatar}` + ) + menuPlayer.parentNode.appendChild(element) + } + + // Make new notifications actually discernable from the old ones + const num = + $('#user-notify-count') && $('#user-notify-count').textContent.trim() + addStyle( + `#user-notify-list .list-group .list-group-item .fas {color: #888}#user-notify-list .list-group .list-group-item:nth-child(-n+${num}) .fas {color: #2F5}a.wrong-order {color: #F99} div.comment-holder:target {background-color: #DFD}.comment-new a.text-muted:last-child:after {content: 'New'; color: #2F5; font-weight: bold; background-color: #183; border-radius: 9px; display: inline-block; padding: 0px 6px; margin-left: 10px;}` + ) + + // Show an error if it occurs instead of "loading forever" + window.getNotifications = getNotifications + + let versionDisplay = `ANBT v${versions.scriptVersion}` + try { + const appver = $('script[src^="/build/app"]').src.match(/(\w+)\.js$/)[1] + const runtimever = $('script[src^="/build/runtime"]').src.match( + /(\w+)\.js$/ + )[1] + versionDisplay += ` | app ${appver}` + if (appver !== versions.siteVersion) versionDisplay += '*' + versionDisplay += ` | runtime ${runtimever}` + if (runtimever !== versions.runtimeVersion) versionDisplay += '*!!!' // didn't break with one update, hurray + } catch (e) {} + const wrapperSection = $('.wrapper') + if (wrapperSection) + wrapperSection.appendChild($(`
${versionDisplay}
`)) + + const linkList = [ + '
  • ANBT script
  • ', + '
  • Wiki
  • ', + '
  • Chat (Discord)
  • ' + ] + $('.footer-main .list-unstyled').forEach((list, index) => + list.appendChild($(linkList[index])) + ) +} + +export default pageEnhancements diff --git a/src/extension/functions/randomGreeting.js b/src/extension/functions/randomGreeting.js new file mode 100644 index 0000000..9a05618 --- /dev/null +++ b/src/extension/functions/randomGreeting.js @@ -0,0 +1,13 @@ +import globals from '../globals' +import rot13 from './rot13' +import simpleHash from './simpleHash' + +const randomGreeting = () => { + const change_every_half_day = Math.floor(Date.now() / (1000 * 60 * 60 * 12)) + const rnddata = simpleHash( + change_every_half_day + parseInt(globals.userid, 10) + 178889 + ) + return rot13(globals.greetings[rnddata % globals.greetings.length]) +} + +export default randomGreeting diff --git a/src/extension/functions/replay/addReplayButton.js b/src/extension/functions/replay/addReplayButton.js new file mode 100644 index 0000000..3a5af06 --- /dev/null +++ b/src/extension/functions/replay/addReplayButton.js @@ -0,0 +1,27 @@ +import scrambleID from '../scrambleID' +import $ from '../selector' +import setupNewCanvas from '../setupNewCanvas' +import checkForRecording from './checkForRecording' + +const addReplayButton = drawing => { + if (drawing.replayAdded) return + drawing.replayAdded = true + const { parentNode, src } = drawing + checkForRecording(src, () => { + const newid = $(`img[src='${src}']`) + .parentNode.querySelector('a[href^="/panel/"]') + .href.match(/\/panel\/[^/]+\/([^/]+)/)[1] + const id = newid.length >= 8 ? newid : scrambleID(parentNode.id.slice(6)) + const replayButton = $( + `` + ) + replayButton.addEventListener('click', event => { + if (event.which === 2) return + event.preventDefault() + setupNewCanvas(true, `/sandbox/#${id}`) + }) + parentNode.insertAdjacentHTML('beforebegin', replayButton.outerHTML) + }) +} + +export default addReplayButton diff --git a/src/extension/functions/replay/addReplaySign.js b/src/extension/functions/replay/addReplaySign.js new file mode 100644 index 0000000..49ec294 --- /dev/null +++ b/src/extension/functions/replay/addReplaySign.js @@ -0,0 +1,24 @@ +import $ from '../selector' +import checkForRecording from './checkForRecording' + +const addReplaySign = drawing => { + if (drawing.replayAdded) return + drawing.replayAdded = true + const panel = drawing.parentNode.parentNode + const { src } = drawing + checkForRecording(src, () => { + const newid = src.match(/(\w+).png$/)[1] + const replaySign = + newid.length >= 8 + ? $( + `` + ) + : $( + '' + ) + panel.appendChild(replaySign) + // replaySign.tooltip(); + }) +} + +export default addReplaySign diff --git a/src/extension/functions/replay/checkForRecording.js b/src/extension/functions/replay/checkForRecording.js new file mode 100644 index 0000000..3fd98ab --- /dev/null +++ b/src/extension/functions/replay/checkForRecording.js @@ -0,0 +1,31 @@ +const checkForRecording = (url, success, retrying) => { + const request = new XMLHttpRequest() + request.open('GET', `${url}?anbt`, true) + request.responseType = 'arraybuffer' + request.onload = () => { + const buffer = request.response + const dataView = new window.DataView(buffer) + const magic = dataView.getUint32(0) + if (magic != 0x89504e47) return request.onerror() // Drawception started hijacking XHR errors and putting HTML in there + for (let i = 8; i < buffer.byteLength; i += 4 /* Skip CRC */) { + const chunklen = dataView.getUint32(i) + i += 4 + const chunkname = dataView.getUint32(i) + i += 4 + if (chunkname === 0x73764762) return success() + else { + if (chunkname === 0x49454e44) break + i += chunklen + } + } + } + request.onerror = () => { + console.log( + 'checkForRecording fail (likely due to cache without CORS), retrying' + ) + if (!retrying) checkForRecording(`${url}?anbt`, success, true) + } + request.send() +} + +export default checkForRecording \ No newline at end of file diff --git a/src/extension/functions/reversePanels.js b/src/extension/functions/reversePanels.js new file mode 100644 index 0000000..9405750 --- /dev/null +++ b/src/extension/functions/reversePanels.js @@ -0,0 +1,10 @@ +import $ from './selector' + +const reversePanels = () => { + const element = $('.gamepanel-holder')[0].parentNode.parentNode + ;[...element.childNodes] + .reverse() + .forEach(child => element.appendChild(child)) +} + +export default reversePanels diff --git a/src/extension/functions/rot13.js b/src/extension/functions/rot13.js new file mode 100644 index 0000000..6f4bad6 --- /dev/null +++ b/src/extension/functions/rot13.js @@ -0,0 +1,11 @@ +const rot13 = number => + [...number.toString()] + .map(character => { + character = character.charCodeAt(0) + if (character >= 97 && character <= 122) character = ((character - 97 + 13) % 26) + 97 + if (character >= 65 && character <= 90) character = ((character - 65 + 13) % 26) + 65 + return String.fromCharCode(character) + }) + .join('') + +export default rot13 diff --git a/src/extension/functions/scrambleID.js b/src/extension/functions/scrambleID.js new file mode 100644 index 0000000..f27f872 --- /dev/null +++ b/src/extension/functions/scrambleID.js @@ -0,0 +1,10 @@ +import decimalToBase62 from './decimalToBase62' + +const scrambleID = number => { + if (isNaN(number)) throw new Error('Invalid panel ID') + return [...decimalToBase62(parseInt(number, 10) + 3521614606208)] + .reverse() + .join('') +} + +export default scrambleID diff --git a/src/extension/functions/selector.js b/src/extension/functions/selector.js new file mode 100644 index 0000000..040eab6 --- /dev/null +++ b/src/extension/functions/selector.js @@ -0,0 +1,8 @@ +const $ = (selector, array = false) => { + const elements = selector.startsWith('<') + ? [...new DOMParser().parseFromString(selector, 'text/html').body.children] + : [...document.querySelectorAll(selector)] + return elements.length > 1 || array ? elements : elements[0] +} + +export default $ diff --git a/src/extension/functions/settings/addGroup.js b/src/extension/functions/settings/addGroup.js new file mode 100644 index 0000000..1667bc6 --- /dev/null +++ b/src/extension/functions/settings/addGroup.js @@ -0,0 +1,44 @@ +import options from '../../options' +import $ from '../selector' +import escapeHTML from './escapeHTML' + +const addGroup = (name, settings) => { + const controlGroup = $('
    ') + controlGroup.appendChild($(``)) + settings.forEach(setting => { + const value = options[setting[0]] + const [name, type, description] = setting + const controls = $('
    ') + if (type === 'boolean') { + controls.appendChild( + $( + `` + ) + ) + } else if (type === 'number') { + $( + `${description}:` + ).forEach(node => controls.appendChild(node)) + } else if (type === 'longstr') { + $( + `${description}:` + ).forEach(node => controls.appendChild(node)) + } else { + $( + `${description}:` + ).forEach(node => controls.appendChild(node)) + } + controlGroup.appendChild(controls) + }) + return controlGroup +} + +export default addGroup diff --git a/src/extension/functions/settings/escapeHTML.js b/src/extension/functions/settings/escapeHTML.js new file mode 100644 index 0000000..309771b --- /dev/null +++ b/src/extension/functions/settings/escapeHTML.js @@ -0,0 +1,10 @@ +const escapeHTML = value => + value + .toString() + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + +export default escapeHTML \ No newline at end of file diff --git a/src/extension/functions/settings/loadScriptSettings.js b/src/extension/functions/settings/loadScriptSettings.js new file mode 100644 index 0000000..32171cb --- /dev/null +++ b/src/extension/functions/settings/loadScriptSettings.js @@ -0,0 +1,9 @@ +import getLocalStorageItem from '../getLocalStorageItem' + +const loadScriptSettings = () => { + const result = getLocalStorageItem('gpe_anbtSettings', null) + if (!result) return + for (const i in result) window.options[i] = result[i] +} + +export default loadScriptSettings diff --git a/src/extension/functions/settings/updateScriptSettings.js b/src/extension/functions/settings/updateScriptSettings.js new file mode 100644 index 0000000..ad8d4b9 --- /dev/null +++ b/src/extension/functions/settings/updateScriptSettings.js @@ -0,0 +1,23 @@ +import fadeIn from '../fade/fadeIn' +import fadeOut from '../fade/fadeOut' +import $ from '../selector' +import loadScriptSettings from './loadScriptSettings' + +const updateScriptSettings = ({ currentTarget: theForm }) => { + const result = {} + theForm.querySelectorAll('input,textarea').forEach(fromField => { + if (fromField.type === 'checkbox') + result[fromField.name] = fromField.checked ? 1 : 0 + else if (fromField.getAttribute('data-subtype') === 'number') + result[fromField.name] = parseFloat(fromField.value) || 0 + else result[fromField.name] = fromField.value + }) + localStorage.setItem('gpe_anbtSettings', JSON.stringify(result)) + loadScriptSettings() + fadeIn($('#anbtSettingsOK'), 'slow') + setTimeout(() => { + fadeOut($('#anbtSettingsOK'), 'slow') + }, 800) +} + +export default updateScriptSettings diff --git a/src/extension/functions/setupNewCanvas.js b/src/extension/functions/setupNewCanvas.js new file mode 100644 index 0000000..7968f1b --- /dev/null +++ b/src/extension/functions/setupNewCanvas.js @@ -0,0 +1,116 @@ +import options from '../options' +import versions from '../versions' +import getLocalStorageItem from './getLocalStorageItem' + +const setupNewCanvas = (insandbox, url) => { + const canvasHTML = localStorage.getItem('anbt_canvasHTML') + const canvasHTMLver = localStorage.getItem('anbt_canvasHTMLver') + if ( + !canvasHTML || + canvasHTMLver < versions.newCanvasVersion || + canvasHTML.length < 10000 + ) { + const request = new XMLHttpRequest() + request.open( + 'GET', + 'https://api.github.com/repos/EnderDragonneau/Drawception-ANBT/contents/build/index.html' + ) + request.setRequestHeader('Accept', 'application/vnd.github.3.raw') + request.onload = () => { + if (request.responseText.length < 10000) { + alert( + `Error: instead of new canvas code, got this response from GitHub:\n${ + request.responseText + }` + ) + location.pathname = '/' + } else { + localStorage.setItem('anbt_canvasHTML', request.responseText) + localStorage.setItem('anbt_canvasHTMLver', versions.newCanvasVersion) + setupNewCanvas(insandbox, url) + } + } + request.onerror = () => { + alert('Error loading the new canvas code. Please try again.') + location.pathname = '/' + } + request.send() + return + } + const inforum = url.match(/forums\//) + const friendgameid = url.match(/play\/(.+)\//) // Save friend game id if any + const panelid = url.match(/sandbox\/#?([^/]+)/) + const incontest = + url.match(/contests\/play\//) && document.getElementById('canvas-holder') // Handle drawing contests only + const vertitle = `ANBT v${versions.scriptVersion}` + + // Disable built-in safety warning + if (incontest) window.onbeforeunload = () => {} + + // Show normal address + const normalurl = + insandbox && !inforum + ? `/sandbox/${panelid ? `#${panelid[1]}` : ''}` + : incontest + ? '/contests/play/' + : inforum + ? url.match(/\/forums\/?.+/) + : `/play/${friendgameid ? `${friendgameid[1]}/` : ''}` + try { + if (location.pathname + location.hash !== normalurl) + history.pushState({}, document.title, normalurl) + } catch (e) {} + + const alarmSoundOgg = + 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAABnHAAAAAAAAHQUSFoBHgF2b3JiaXMAAAAAAUSsAAAAAAAAYG0AAAAAAADJAU9nZ1MAAAAAAAAAAAAAZxwAAAEAAABq35G0DxD/////////////////NQN2b3JiaXMAAAAAAAAAAAEFdm9yYmlzH0JDVgEAAAEAFGNWKWaZUpJbihlzmDFnGWPUWoolhBRCKKVzVlurKbWaWsq5xZxzzpViUilFmVJQW4oZY1IpBhlTEltpIYQUQgehcxJbaa2l2FpqObacc62VUk4ppBhTiEromFJMKaQYU4pK6Jxz0DnmnFOMSgg1lVpTyTGFlFtLKXROQgephM5SS7F0kEoHJXRQOms5lRJTKZ1jVkJquaUcU8qtpphzjIHQkFUAAAEAwEAQGrIKAFAAABCGoSiKAoSGrAIAMgAABOAojuIokiI5kmM5FhAasgoAAAIAEAAAwHAUSZEUy9EcTdIszdI8U5ZlWZZlWZZlWZZd13VdIDRkFQAAAQBAKAcZxRgQhJSyEggNWQUAIAAAAIIowxADQkNWAQAAAQAIUR4h5qGj3nvvEXIeIeYdg9577yG0XjnqoaTee++99x5777n33nvvkWFeIeehk9577xFiHBnFmXLee+8hpJwx6J2D3nvvvfeec+451957752j3kHpqdTee++Vk14x6Z2jXnvvJdUeQuqlpN5777333nvvvffee++9955777333nvvrefeau+9995777333nvvvffee++9995777333nvvgdCQVQAAEAAAYRg2iHHHpPfae2GYJ4Zp56T3nnvlqGcMegqx9557773X3nvvvffeeyA0ZBUAAAgAACGEEFJIIYUUUkghhhhiyCGHHIIIKqmkoooqqqiiiiqqLKOMMsook4wyyiyjjjrqqMPOQgoppNJKC620VFtvLdUehBBCCCGEEEIIIYQQvvceCA1ZBQCAAAAwxhhjjEEIIYQQQkgppZRiiimmmAJCQ1YBAIAAAAIAAAAsSZM0R3M8x3M8x1M8R3RER3RER5RESbRETfREUTRFVbRF3dRN3dRNXdVN27VVW7ZlXdddXddlXdZlXdd1Xdd1Xdd1Xdd1XbeB0JBVAAAIAABhkEEGGYQQQkghhZRSijHGGHPOOSA0ZBUAAAgAIAAAAEBxFEdxHMmRJMmyLM3yLM8SNVMzNVNzNVdzRVd1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' + + if (inforum) { + if (document.querySelector('.v--modal-overlay')) + document.querySelector('.v--modal-overlay').outerHTML = '' + const div = document.querySelector('.wrapper').children[1] + const iframe = document.createElement('iframe') + const modalOverlay = document.createElement('div') + modalOverlay.setAttribute('class', 'v--modal-overlay') + iframe.id = 'iframe' + iframe.setAttribute('class', 'v--modal-background-click') + modalOverlay.appendChild(iframe) + div.appendChild(modalOverlay) + iframe.contentWindow.document.open() + iframe.contentWindow.anbtReady = () => { + iframe.contentWindow.inforum = inforum + iframe.contentWindow.insandbox = insandbox + iframe.contentWindow.incontest = incontest + iframe.contentWindow.options = options + iframe.contentWindow.alarmSoundOgg = alarmSoundOgg + iframe.contentWindow.vertitle = vertitle + iframe.contentWindow.getLocalStorageItem = getLocalStorageItem + iframe.contentWindow.needToGoDeeper() + } + iframe.contentWindow.document.write(canvasHTML) + iframe.contentWindow.document.close() + return + } + document.open() + window.anbtReady = () => { + if (friendgameid) window.friendgameid = friendgameid[1] + if (panelid) window.panelid = panelid[1] + window.inforum = inforum + window.insandbox = insandbox + window.incontest = incontest + window.options = options + window.alarmSoundOgg = alarmSoundOgg + window.vertitle = vertitle + window.getLocalStorageItem = getLocalStorageItem + window.needToGoDeeper() + } + document.write(canvasHTML) + document.close() +} + +export default setupNewCanvas diff --git a/src/extension/functions/simpleHash.js b/src/extension/functions/simpleHash.js new file mode 100644 index 0000000..57cc5f6 --- /dev/null +++ b/src/extension/functions/simpleHash.js @@ -0,0 +1,10 @@ +const simpleHash = number => + number + .toString() + .split('') + .reduce((a, b) => { + a = (a << 5) - a + b.charCodeAt(0) + return a & a + }, 0) + +export default simpleHash diff --git a/src/extension/functions/time/convertForumTime.js b/src/extension/functions/time/convertForumTime.js new file mode 100644 index 0000000..aa85a30 --- /dev/null +++ b/src/extension/functions/time/convertForumTime.js @@ -0,0 +1,11 @@ +import formatTimestamp from './formatTimestamp' +import isFloridaDST from './isFloridaDST' + +const convertForumTime = (year, month, day, hours, minutes) => { + const date = new Date(year, month, day, hours, minutes) + const tzo = date.getTimezoneOffset() * 60 * 1000 + const dst = isFloridaDST() + return formatTimestamp(date.getTime() - tzo + (6 - dst) * 60 * 60 * 1000) +} + +export default convertForumTime diff --git a/src/extension/functions/time/formatTimestamp.js b/src/extension/functions/time/formatTimestamp.js new file mode 100644 index 0000000..bebcad9 --- /dev/null +++ b/src/extension/functions/time/formatTimestamp.js @@ -0,0 +1,14 @@ +import globals from '../../globals' +import options from '../../options' + +const formatTimestamp = date => { + if (typeof date === 'number') date = new Date(date) + if (options.localeTimestamp) return date.toLocaleString() + return `${('0' + date.getDate()).slice(-2)} ${ + globals.months[date.getMonth()] + } ${date.getFullYear()} ${('0' + date.getHours()).slice(-2)}:${( + '0' + date.getMinutes() + ).slice(-2)}` +} + +export default formatTimestamp diff --git a/src/extension/functions/time/isFloridaDST.js b/src/extension/functions/time/isFloridaDST.js new file mode 100644 index 0000000..8c72163 --- /dev/null +++ b/src/extension/functions/time/isFloridaDST.js @@ -0,0 +1,23 @@ +const isFloridaDST = () => { + const date = new Date(Date.now() - 6 * 60 * 60 * 1000) + const month = date.getUTCMonth() + const day = date.getUTCDate() + const hours = date.getUTCHours() + const dayOfWeef = date.getUTCDay() + + if (month < 2 || month > 10) return false + if (month > 2 && month < 10) return true + if (month === 2) { + if (day < 8) return false + if (day > 14) return true + if (dayOfWeef === 7) return hours > 1 + return day > dayOfWeef + 7 + } + if (month === 10) { + if (day > 7) return false + if (dayOfWeef === 7) return hours < 1 + return day <= dayOfWeef + } +} + +export default isFloridaDST \ No newline at end of file diff --git a/src/extension/functions/unscrambleID.js b/src/extension/functions/unscrambleID.js new file mode 100644 index 0000000..5ef306e --- /dev/null +++ b/src/extension/functions/unscrambleID.js @@ -0,0 +1,6 @@ +import base62ToDecimal from './base62ToDecimal' + +const unscrambleID = string => + base62ToDecimal([...string].reverse().join('')) - 3521614606208 + +export default unscrambleID diff --git a/src/extension/functions/viewMyGameBookmarks.js b/src/extension/functions/viewMyGameBookmarks.js new file mode 100644 index 0000000..87450de --- /dev/null +++ b/src/extension/functions/viewMyGameBookmarks.js @@ -0,0 +1,87 @@ +import fadeOut from './fade/fadeOut' +import getLocalStorageItem from './getLocalStorageItem' +import $ from './selector' +import formatTimestamp from './time/formatTimestamp' + +const viewMyGameBookmarks = () => { + const removeButtonHTML = + '' + const games = getLocalStorageItem('gpe_gameBookmarks', {}) + const result = [] + for (let id in games) { + const extraClass = games[id].own ? ' anbt_owncaption' : '' + if (id.length > 10) { + // token, seen lengths: 43, 32; just in case assuming everything > 10 is a token + result.push( + `

    ${id}${removeButtonHTML}

    ` + ) + const xhr = new XMLHttpRequest() + xhr.open('GET', `/play/${id}`) + xhr.onload = () => { + const { responseText, status } = xhr + if (status === 200) { + const m = + responseText.match(/Game is not private/) || + (responseText.match(/Problem loading game/) && 'del') + if (m) { + const gamename = + `${ + games[id].own + ? ` with your caption${ + games[id].caption ? ` ${games[id].caption}` : '' + }` + : '' + }${ + games[id].time + ? ` bookmarked on ${formatTimestamp(games[id].time)}` + : '' + }` || id + const status = m === 'del' ? 'Deleted' : 'Unfinished public' + $(`#${id}`).querySelector( + 'span' + ).textContent = `${status} game${gamename}` + return + } + const title = responseText.match(/(.+)<\/title>/)[1] + const [url, gameId] = responseText.match(/\/game\/([^/]+)\/[^/]+\//) + delete games[id] + games[gameId] = { + title, + url + } + $(`#${id}`).id = gameId + const spanId = $(`#${gameId}`).querySelector('span') + spanId.parentNode.replaceChild( + $(`<a href="${url}">${title}</a>`), + spanId + ) + localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) + } else { + $(`#${id}`).querySelector( + 'span' + ).textContent = `Error while retrieving game: ${responseText}` + } + } + xhr.send() + } else if (id.length === 10) + result.push( + `<p class="well${extraClass}" id="${id}"><a href="${games[id].url}">${ + games[id].title + }</a>${removeButtonHTML}</p>` + ) // game ID + } + $('#anbt_userpage').innerHTML = result.length + ? result.join('') + : "You don't have any bookmarked games." + $('#anbt_userpage .anbt_gamedel', true).forEach(gameDelete => + gameDelete.addEventListener('click', event => { + event.preventDefault() + const { id } = gameDelete.parentNode + fadeOut($(`#${id}`)) + delete games[id] + localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) + }) + ) +} + +export default viewMyGameBookmarks diff --git a/src/extension/functions/viewMyPanelFavorites.js b/src/extension/functions/viewMyPanelFavorites.js new file mode 100644 index 0000000..1e8b750 --- /dev/null +++ b/src/extension/functions/viewMyPanelFavorites.js @@ -0,0 +1,52 @@ +import fadeOut from './fade/fadeOut' +import getLocalStorageItem from './getLocalStorageItem' +import $ from './selector' +import formatTimestamp from './time/formatTimestamp' + +const viewMyPanelFavorites = () => { + const panels = getLocalStorageItem('gpe_panelFavorites', {}) + let result = '' + let needsUpdate = false + for (const id in panels) { + if (panels[id].image && panels[id].image.match(/^\/pub\/panels\//)) { + needsUpdate = true + panels[id].image = panels[id].image.replace( + '/pub/panels/', + 'https://cdn.drawception.com/images/panels/' + ) + } + result += `<div id="${id}" style="float: left; position: relative; min-width: 150px;"><div class="thumbpanel-holder" style="overflow:hidden"><a class="anbt_paneldel" href="#" title="Remove">X</a><a href="/panel/-/${id}/-/" class="thumbpanel" rel="tooltip" title="${ + panels[id].caption + }">${ + panels[id].image + ? `<img src="${panels[id].image}" width="125" height="104" alt="${ + panels[id].caption + }" />` + : panels[id].caption + }</a><span class="text-muted" style="white-space:nowrap">by <a href="${ + panels[id].userLink + }">${ + panels[id].by + }</a></span><br><small class="text-muted"><span class="fas fa-heart text-danger"></span> ${formatTimestamp( + panels[id].time + )}</small></div></div>` + } + + if (needsUpdate) + localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) + result = result + ? `${result}<div style="clear:left"></div>` + : "You don't have any favorited panels." + $('#anbt_userpage').innerHTML = result + $('#anbt_userpage .anbt_paneldel', true).forEach(panelDelete => + panelDelete.addEventListener('click', event => { + event.preventDefault() + const { id } = panelDelete.parentNode.parentNode + fadeOut($(`#${CSS.escape(id)}`)) + delete panels[id] + localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels)) + }) + ) +} + +export default viewMyPanelFavorites diff --git a/src/extension/globals.js b/src/extension/globals.js new file mode 100644 index 0000000..27e6d2b --- /dev/null +++ b/src/extension/globals.js @@ -0,0 +1,50 @@ +import getLocalStorageItem from './functions/getLocalStorageItem' +import $ from './functions/selector' + +const globals = { + userId: getLocalStorageItem( + 'gpe_lastSeenId', + $('.player-dropdown a[href^="/player/"]') && + $('.player-dropdown a[href^="/player/"]').href.match( + /\/player\/(\d+)\// + )[1] + ), + months: [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec' + ], + alphabet: '36QtfkmuFds0UjlvCGIXZ125bEMhz48JSYgipwKn7OVHRBPoy9DLWaceqxANTr', + greetings: [ + 'Oruvaq lbh!', + "Ubcr vg'f abg envavat gbqnl.", + 'Jurer vf lbhe tbq abj?', + 'Lbh fubhyq srry 5% zber cbjreshy abj.', + 'Fhqqrayl, abguvat unccrarq!', + '^_^', + 'Guvf gnxrf fb ybat gb svavfu...', + "Jungrire lbh qb, qba'g ernq guvf grkg.", + 'Pyvpx urer sbe 1 serr KC', + 'Or cngvrag.', + "Whfg qba'g fgneg nal qenzn nobhg vg.", + '47726S6Q2069732074686520677265617465737421', + 'Cynl fzneg.', + 'Cynl avpr.', + 'Fzvyr!', + "Qba'g sbetrg gb rng.", + "V xabj jung lbh'ir qbar.", + 'Fpvrapr!', + 'Gbqnl vf n tbbq qnl.' + ] +} + +export default globals diff --git a/src/extension/markdown.js b/src/extension/markdown.js new file mode 100644 index 0000000..29f8304 --- /dev/null +++ b/src/extension/markdown.js @@ -0,0 +1,60 @@ +import bold from './functions/markdown/bold' +import code from './functions/markdown/code' +import heading from './functions/markdown/heading' +import highlighter from './functions/markdown/highlighter' +import image from './functions/markdown/image' +import italic from './functions/markdown/italic' +import link from './functions/markdown/link' +import listOl from './functions/markdown/listOl' +import listUl from './functions/markdown/listUl' +import quoteRight from './functions/markdown/quoteRight' +import strikethrough from './functions/markdown/strikethrough' + +const markdown = { + bold: { + title: 'bold text', + replaceFunc: bold + }, + italic: { + title: 'italic text', + replaceFunc: italic + }, + heading: { + title: 'enlarges/reduces the text', + replaceFunc: heading + }, + strikethrough: { + title: 'strikethrough text', + replaceFunc: strikethrough + }, + highlighter: { + title: 'highlighted text', + replaceFunc: highlighter + }, + 'list-ul': { + title: 'unordered list', + replaceFunc: listUl + }, + 'list-ol': { + title: 'ordered list', + replaceFunc: listOl + }, + 'quote-right': { + title: 'quote', + replaceFunc: quoteRight + }, + code: { + title: 'block of code', + replaceFunc: code + }, + link: { + title: 'insert link', + replaceFunc: link + }, + image: { + title: 'insert image', + replaceFunc: image + } +} + +export default markdown diff --git a/src/extension/options.js b/src/extension/options.js new file mode 100644 index 0000000..aff30d0 --- /dev/null +++ b/src/extension/options.js @@ -0,0 +1,32 @@ +const options = { + enableWacom: 0, // Whether to enable Wacom plugin and thus pressure sensitivity support + fixTabletPluginGoingAWOL: 1, // Fix pressure sensitivity disappearing in case of stupid/old Wacom plugin + hideCross: 0, // Whether to hide the cross when drawing + enterToCaption: 0, // Whether to submit caption by pressing Enter + pressureExponent: 0.5, // Smaller = softer tablet response, bigger = sharper + backup: 1, + timeoutSound: 0, + timeoutSoundBlitz: 0, + timeoutSoundVolume: 100, + newCanvas: 1, + proxyImgur: 0, + ajaxRetry: 1, + localeTimestamp: 0, + autoplay: 1, // Whether to automatically start playback of a recorded drawing + submitConfirm: 1, + smoothening: 1, + autoBypassNSFW: 0, + colorNumberShortcuts: 1, + colorUnderCursorHint: 1, + bookmarkOwnCaptions: 0, + colorDoublePress: 0, + newCanvasCSS: '', + forumHiddenUsers: '', + maxCommentHeight: 1000, + useOldFont: true, + useOldFontSize: true, + markdownTools: 1, + anbtDarkMode: 1 +} + +export default options \ No newline at end of file diff --git a/src/extension/versions.js b/src/extension/versions.js new file mode 100644 index 0000000..f73286d --- /dev/null +++ b/src/extension/versions.js @@ -0,0 +1,7 @@ +const versions = { + scriptVersion: "1.156.2019.06", + newCanvasVersion: 53, // Increase to update the cached canvas + siteVersion: "f6b35cce", // Last seen site version + runtimeVersion: "1ba6bf05" // Last seen runtime version +} +export default versions diff --git a/src/extension/wrapper.js b/src/extension/wrapper.js new file mode 100644 index 0000000..4c3ff2f --- /dev/null +++ b/src/extension/wrapper.js @@ -0,0 +1,21 @@ +import pageEnhancements from './functions/pageEnhancements' +import options from './options' + +const wrapper = () => { + window.options = options + const mark = document.createElement('b') + mark.id = '_anbt_' + mark.style.display = 'none' + document.body.appendChild(mark) + + if (!window.DrawceptionPlay) { + // Fix for Chrome new loading algorithm, apparently + const loader = setInterval(() => { + if (!window.DrawceptionPlay) return + pageEnhancements() + clearInterval(loader) + }, 100) + } else pageEnhancements() +} + +export default wrapper diff --git a/src/newcanvas.js b/src/newcanvas.js new file mode 100644 index 0000000..839e8ba --- /dev/null +++ b/src/newcanvas.js @@ -0,0 +1,15 @@ +import './newcanvas/css/style.scss' +import anbt from './newcanvas/js/anbt' +import bindEvents from './newcanvas/js/functions/bindEvents' +import ID from './newcanvas/js/functions/idSelector' +import needToGoDeeper from './newcanvas/js/functions/needToGoDeeper' +import updateTimer from './newcanvas/js/functions/updateTimer' +import globals from './newcanvas/js/globals' + +window.needToGoDeeper = needToGoDeeper +if (!window.options) window.options = {} +anbt.bindContainer(ID('svgContainer')) +bindEvents() +globals.timerStart = Date.now() +setInterval(updateTimer, 500) +if (window.anbtReady) window.anbtReady() diff --git a/src/newcanvas/css/style.scss b/src/newcanvas/css/style.scss new file mode 100644 index 0000000..3f2811e --- /dev/null +++ b/src/newcanvas/css/style.scss @@ -0,0 +1,832 @@ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ""; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +body { + margin: 0; + background: #ccc; + font-family: "Nunito", sans-serif; + background: #555; +} +.noselect { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +#newcanvasyo { + background: #555; + &.play .onlysandbox, + &.play .onlyfriend, + &.friend .no-friend, + &.nsfw .no-nsfw, + &.sandbox .onlycaption, + &.drawing .onlycaption, + &.captioning .onlydraw, + &.locked .onlyunlocked, + &.sandbox .onlyplay { + display: none; + } +} +#myheader { + font-size: 15pt; + line-height: 49px; + height: 49px; + border-bottom: 1px solid black; + background: #333; + text-align: center; + color: #999; + overflow: hidden; + display: block; + img { + vertical-align: middle; + } + @media screen and (max-width: 999px) { + font-size: 11pt; + } +} +#headercont { + max-width: 1280px; + margin: auto; +} +.headerleft { + float: left; + margin-left: 50px; +} +.headerright { + float: right; + margin-right: 50px; +} +#infoavatar { + border: 1px solid #555; +} +.headerbutton { + font-size: 10.5pt; + font-weight: bold; + padding: 4px 10px; + background: #2e2e2e; + vertical-align: middle; + text-decoration: none; + cursor: pointer; + &.active { + background: #248; + color: #eee; + &:hover { + background: #236; + } + } + img { + margin: 0 3px; + } +} +a { + color: #999; + &.prominent { + color: #7af; + &:hover { + color: #9ff; + } + } +} +#menu { + display: none; + position: absolute; + background: #666; + color: #ddd; + z-index: 2; + border: 2px solid #777; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); + list-style-type: none; + padding: 0; + margin: 0; + font-size: 13pt; + text-align: left; + &.open { + display: block; + } + li { + line-height: 49px; + height: 49px; + padding: 0 10px; + cursor: pointer; + &:hover { + background: #777; + } + } + hr { + margin: 5px; + border: 1px solid #444; + } +} +hr { + border: 1px solid #888; +} +#noscriptwarn { + color: #800; + padding: 10px; + font-size: 15pt; + background: #faa; + text-align: center; +} +#drawthis { + padding: 4px; + color: #fff; + font-size: 26pt; + font-family: "Yanone Kaffeesatz"; + text-align: center; + vertical-align: middle; + display: table-cell; + width: 1%; + &::before { + color: #aaa; + font-size: 14pt; + font-family: "Nunito"; + content: "Draw this:"; + padding-right: 1em; + width: 1%; + text-align: right; + vertical-align: middle; + margin-left: -10%; + } +} +.captioning { + #drawthis:before { + content: "Describe this drawing:"; + padding-right: 0; + width: 100%; + text-align: center; + margin-left: 0; + } + #drawingtocaption { + display: block; + } +} +#emptytitle { + height: 10px; +} +#bl { + width: 600px; + margin: auto; + position: relative; +} +#toolpane { + text-align: center; + padding: 0 10px; + @media screen and (min-width: 1000px) { + width: 180px; + height: 500px; + position: absolute; + margin-left: -200px; + } + @media screen and (max-width: 999px) { + width: 580px; + margin: auto; + } + &:after { + @media screen and (max-width: 999px) { + display: block; + content: ""; + clear: both; + } + } +} +#infopanel { + text-align: center; + padding: 0 10px; + @media screen and (min-width: 1000px) { + width: 180px; + height: 500px; + position: absolute; + margin-left: 600px; + top: 0; + } + @media screen and (max-width: 999px) { + width: 580px; + margin: 40px 0; + } +} +.panel { + border: 2px solid #888; + border-radius: 15px; + border-top: none; + background: #444; + margin: 3px 0; +} +#timer { + font-size: 26pt; + color: #aaa; + width: 176px; + float: left; + @media screen and (max-width: 999px) { + float: right; + width: 260px; + } +} +.blitz #timer { + border-color: #dda; + color: #dda; + background: #982; +} +.locked { + #timer { + border-color: #faa; + color: #faa; + background: #744; + } + #lockwarn { + display: block; + } +} +#timeplus { + background: url("/img/icon-time_plus_60.png") center no-repeat; + width: 16px; + height: 16px; + vertical-align: middle; + padding: 10px; + margin-left: 3px; + border-radius: 3px; + cursor: pointer; + &:hover { + background-color: #222; + } +} +#palettechooser { + display: none; + position: absolute; + width: 450px; + background: #666; + color: #ddd; + z-index: 1; + -webkit-columns: 3; + -moz-columns: 3; + columns: 3; + -webkit-column-gap: 0; + -moz-column-gap: 0; + column-gap: 0; + border: 2px solid #777; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); + &.open { + display: block; + } + div { + padding: 10px 0 20px; + cursor: pointer; + break-inside: avoid; + &:hover { + background-color: #777; + color: #eee; + } + } +} +#colors { + width: 100px; + float: left; + b { + float: left; + display: block; + width: 50px; + height: 44px; + line-height: 42px; + cursor: pointer; + counter-increment: color; + &.hint { + &::after { + content: ""; + position: absolute; + border-radius: 50%; + margin: 32px 2px; + width: 5px; + height: 5px; + background-color: white; + border: 1px solid #000; + } + &::before { + opacity: 0.6; + } + } + &::before { + font-size: 10pt; + opacity: 0.3; + color: #000; + content: counter(color); + } + &:hover { + &::before { + opacity: 0.4; + color: #fff; + } + } + &:nth-child(10) { + counter-reset: color 1; + } + &:nth-child(n + 10) { + counter-increment: color; + &::before { + content: "\2191"counter(color); + } + } + } + #eraser { + &::before { + content: "eraser"; + } + } + &.setbackground { + b { + &::before { + content: "BG " counter(color); + } + &:nth-child(n + 10) { + &::before { + content: "BG\2191"counter(color); + } + } + } + #eraser { + &::before { + content: "cancel"; + } + } + } + div { + clear: both; + } + @media screen and (max-width: 999px) { + width: 300px; + margin-left: 5px; + clear: left; + float: left; + } +} +#palettename { + color: #aaa; + font-size: 10pt; + padding: 0 5px; + cursor: pointer; + &:hover { + color: #ccc; + } +} +#tools { + width: 68px; + float: right; + padding: 5px 0; + hr { + width: 93%; + margin: 8px auto; + } + button { + @media screen and (max-width: 999px) { + margin: 0; + } + } + @media screen and (max-width: 999px) { + width: 260px; + } +} +#tools button, +#openmenu { + width: 55px; + height: 44px; + vertical-align: top; + margin: 3px 0; + padding: 0; +} +#brush0, +#brush1, +#brush2, +#brush3, +#setbackground, +#undo, +#redo, +#trash, +#play, +#knob, +#eraser, +.eraser, +#openmenu { + background-image: url("data:image/png;base64,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"); +} +#brush0 { + background-position: -2px -8px; + &.sel { + background-position: -2px -68px; + } +} +#brush1 { + background-position: -62px -8px; + &.sel { + background-position: -62px -68px; + } +} +#brush2 { + background-position: -122px -8px; + &.sel { + background-position: -122px -68px; + } +} +#brush3 { + background-position: -182px -8px; + &.sel { + background-position: -182px -68px; + } +} +#setbackground { + background-position: -242px -8px; + &.sel { + background-position: -242px -68px; + } +} +#undo { + background-position: -302px -8px; +} +#redo { + background-position: -362px -8px; +} +#trash { + background-position: -422px -8px; +} +#eraser { + background-position: -365px -68px; + background-color: pink; +} +.eraser { + background-position: -425px -68px; + background-color: pink; +} +#openmenu { + background-position: -425px -68px; + background-color: #999; + &::after { + content: "\25BC"; + color: #fff; + opacity: 0.6; + } + &:hover { + background-color: #57a; + } + &:active { + background-color: #555; + } +} +button, +#play { + border: none; + border-radius: 4px; + background-color: #eee; + &:hover { + background-color: #acf; + } + &:active { + background-color: #aaa; + } +} +button:disabled { + background-color: #aaa; +} +#primary, +#secondary { + display: inline-block; + width: 50px; + border-radius: 0 0 0 13px; + color: #fff; + @media screen and (max-width: 999px) { + width: 150px; + } +} +#secondary { + border-radius: 0 0 13px 0; +} +#indicator { + display: flex; +} +#gamemode { + font-size: 13pt; + color: #aaa; + width: 176px; + float: left; +} +.nsfw { + #gamemode { + border-color: #faa; + color: #faa; + background: #744; + } + #drawthis { + background: #744; + } +} +#gamebuttons { + font-size: 13pt; + color: #aaa; + width: 176px; + float: left; + padding: 5px 0; + button { + padding: 10px; + margin: 3px 0; + font-size: 10pt; + } + @media screen and (max-width: 999px) { + width: 390px; + margin-left: 5px; + } +} +.submit { + font-size: 16pt; + width: 180px; + height: 50px; + background-color: #6d6; + &:hover { + background-color: #1b7; + } + @media screen and (min-width: 1000px) { + position: absolute; + bottom: 10px; + left: 10px; + width: 180px; + height: 50px; + } +} +.guidelines { + color: #fff; + font-size: 10pt; + ul { + margin: 0; + padding: 0 1em; + text-align: left; + } + li { + color: #bbb; + } +} +#seekbar { + width: 530px; + height: 0px; + margin: 20px 50px 0; + background: #333; + border: 10px solid #333; + border-radius: 10px; + cursor: pointer; +} +#knob { + position: absolute; + width: 48px; + height: 38px; + margin-top: -19px; + margin-left: 492px; /* -10px min */ + border-radius: 10px; + background-color: #fbb; + background-position: -305px -71px; + &.smooth { + transition: margin-left 0.1s ease-out; + } + &:hover { + background-color: #fdd; + } +} +#play { + background-position: -486px -11px; + position: absolute; + width: 48px; + height: 38px; + margin: -19px -59px; + border-radius: 4px; + &.pause { + background-position: -546px -11px; + } +} +#wacomContainer { + position: absolute; + top: -10px; + left: -10px; +} +.hideonsmall { + @media screen and (max-width: 999px) { + display: none; + } +} +.loadingbg { + background: #fffdc9 + url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAAAVCAQAAAAJQsNPAAAAkUlEQVR4Xu2WgQbAMBBD9/8/vQ14KpC4UneqYurZ2eNGnnfouWLdsjwce59ikFCdk75ricGIQSEmhekcvRfE8s8jMJ9CdI7camLGYoQ0XzNjJukgRsaJkWFiKqhiLf8x2DAx6EExTV0Mns/sIPZnnBinJJb1h4p21BXrzSOpTZVF3VGCN4lB07U53+6H5Yr1zAdssNOdvbG/lgAAAABJRU5ErkJggg==") + center no-repeat; +} +#svgContainer { + width: 600px; + height: 500px; + margin: auto; + cursor: crosshair; + touch-action: pinch-zoom; + &.hidecursor { + cursor: none; + } + &.loading * { + display: none; + } + canvas, + svg { + position: absolute; + } +} +#drawingtocaption { + display: none; + width: 550px; + height: 400px; + padding: 50px 25px; + margin: auto; + background: #484848; + text-align: center; +} +#tocaption { + width: 300px; + height: 250px; +} +#caption { + font-family: "Nunito", sans-serif; + margin-top: 100px; + width: 100%; + font-size: 20pt; +} +#usedchars { + text-align: right; + opacity: 0.4; +} +#lockwarn { + display: none; + position: absolute; + width: 500px; + height: 400px; + padding: 50px; + opacity: 0.8; + background: #aaa; + color: #a00; + text-align: center; + z-index: 1; + font-size: 35pt; + text-shadow: 1px 1px 1px #fff, -1px -1px 1px #fff, -1px 1px 1px #fff, 1px -1px 1px #fff; +} +#popup { + display: none; + position: absolute; + width: 400px; + height: 300px; + margin: 50px; + padding: 50px; + opacity: 0.95; + background: #444; + color: #ddd; + border-radius: 15px; + text-align: center; + z-index: 1; + font-size: 20pt; + &.show { + display: block; + } +} +#popuptitle { + color: #999; + font-size: 25pt; + margin: 10px; +} +#popupclose { + display: block; + position: absolute; + width: 50px; + height: 50px; + background: #c44; + color: #ddd; + right: 0; + top: 0; + text-align: center; + border-radius: 13px; + cursor: pointer; + &:hover { + background: #e77; + color: #eee; + } + &::before { + content: "\d7"; + font-size: 30pt; + line-height: 40px; + } +} diff --git a/src/newcanvas/html/index.html b/src/newcanvas/html/index.html new file mode 100644 index 0000000..3140894 --- /dev/null +++ b/src/newcanvas/html/index.html @@ -0,0 +1,170 @@ +<!doctype html> +<html lang="en"> + +<head> + <link rel="author" href="http://grompe.org.ru/"> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <link href="https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz" rel="stylesheet" type="text/css"> + <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css"> + <title>New Canvas - Drawception + + + + +
    +
    +
    + + New Canvas v53 +
    + +
    +
    +
    Fetching your Drawception game...
    +
    +
    +
    +
    00:00 + +
    +
    +
    Normal
    +
    +
    + + + + + + + + + + + + + + + + + + +
    +
    +
    L
    +
    R
    +
    +
    +
    + + + + +
    + + + + +
    +
    + +
    Time's up!

    Submit now or miss the game!
    +
    +
    + +
    + +
    46
    +
    +
    +
    +
    +
    +
    +
    + Sandbox + Public safe for work game +
    +
    + + + + + + + + +
    + + +
    Guidelines +
      +
    • Simple drawings are welcome! Just give it your best shot
    • +
    • If the phrase is too difficult, use the skip button
    • +
    • Don't draw text - always draw a picture
    • +
    • Explicit (NSFW) drawings will get you banned
    • +
    +
    +
    Tips +
      +
    • No explicit language allowed
    • +
    • Keep descriptions short and simple!
    • +
    • Avoid unnecessary detail, just describe the main subject
    • +
    • No metacommentary (e.g., don't comment on drawing quality)
    • +
    • No idea? Use the skip button
    • +
    +
    +
    Sandbox +
      +
    • Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
    • +
    • Saved drawings will contain your drawing process in PNG format.
    • +
    • After loading a drawing you can continue to draw or watch its playback.
    • +
    • Note that editing the PNG with playback with other programs will likely destroy the playback data.
    • +
    +
    +
    +
    +
    + + + + + + \ No newline at end of file diff --git a/src/newcanvas/js/anbt.js b/src/newcanvas/js/anbt.js new file mode 100644 index 0000000..172211c --- /dev/null +++ b/src/newcanvas/js/anbt.js @@ -0,0 +1,120 @@ +import addToSvg from './functions/anbt/addToSvg' +import bindContainer from './functions/anbt/bindContainer' +import clearWithColor from './functions/anbt/clearWithColor' +import cutHistoryBeforeClearAndAfterPosition from './functions/anbt/cutHistoryBeforeClearAndAfterPosition' +import drawDispLine from './functions/anbt/drawDispLine' +import drawDispLinePresto from './functions/anbt/drawDispLinePresto' +import drawSvgElement from './functions/anbt/drawSvgElement' +import eyedropper from './functions/anbt/eyedropper' +import findLastRect from './functions/anbt/findLastRect' +import fromLocalFile from './functions/anbt/fromLocalFile' +import fromPng from './functions/anbt/fromPng' +import fromUrl from './functions/anbt/fromUrl' +import getSeekMax from './functions/anbt/getSeekMax' +import lock from './functions/anbt/lock' +import makePng from './functions/anbt/makePng' +import moveCursor from './functions/anbt/moveCursor' +import moveSeekbar from './functions/anbt/moveSeekbar' +import packPlayback from './functions/anbt/packPlayback' +import pause from './functions/anbt/pause' +import play from './functions/anbt/play' +import playTimer from './functions/anbt/playTimer' +import redo from './functions/anbt/redo' +import requestSave from './functions/anbt/requestSave' +import seek from './functions/anbt/seek' +import setBackground from './functions/anbt/setBackground' +import setColor from './functions/anbt/setColor' +import setSeekbarMove from './functions/anbt/setSeekbarMove' +import setSize from './functions/anbt/setSize' +import showEyedropperCursor from './functions/anbt/showEyedropperCursor' +import strokeAdd from './functions/anbt/strokeAdd' +import strokeBegin from './functions/anbt/strokeBegin' +import strokeEnd from './functions/anbt/strokeEnd' +import undo from './functions/anbt/undo' +import unlock from './functions/anbt/unlock' +import unpackPlayback from './functions/anbt/unpackPlayback' +import updateView from './functions/anbt/updateView' +import uploadToImgur from './functions/anbt/uploadToImgur' +import createSvgElement from './functions/createSvgElement' +import palettes from './palettes' + +const anbt = { + container: null, + svg: createSvgElement('svg', { + // Even though Opera complains to have failed to set xmlns attribute: + // > Failed attribute on svg element: xmlns="http://www.w3.org/2000/svg". + // this is necessary for loading a saved SVG which otherwise wouldn't + // bind correct prototypes for functions such as path.pathSegList + xmlns: 'http://www.w3.org/2000/svg', + version: '1.1', + width: '600', + height: '500' + }), + canvas: document.createElement('canvas'), + canvasDisp: document.createElement('canvas'), + svgDisp: createSvgElement('svg', { + //xmlns: "http://www.w3.org/2000/svg", + version: '1.1', + width: '600', + height: '500', + 'pointer-events': 'none' + }), + svgHist: null, + path: null, + points: null, + pngBase64: null, + lastrect: 0, + position: 0, + isStroking: false, + isPlaying: false, + size: 14, + smoothening: 1, + palette: palettes.Normal, + patternCache: {}, + delay: 100, + unsaved: false, + background: '#fffdc9', + transparent: false, + colors: ['#000000', 'eraser'], + fastUndoLevels: 10, + rewindCache: [], + bindContainer, + packPlayback, + unpackPlayback, + findLastRect, + cutHistoryBeforeClearAndAfterPosition, + makePng, + fromPng, + fromUrl, + fromLocalFile, + setBackground, + setColor, + setSize, + drawSvgElement, + updateView, + drawDispLinePresto, + drawDispLine, + strokeBegin, + strokeEnd, + strokeAdd, + clearWithColor, + addToSvg, + undo, + redo, + moveSeekbar, + setSeekbarMove, + getSeekMax, + seek, + play, + playTimer, + pause, + moveCursor, + showEyedropperCursor, + eyedropper, + requestSave, + uploadToImgur, + lock, + unlock +} + +export default anbt diff --git a/src/newcanvas/js/functions/anbt/addToSvg.js b/src/newcanvas/js/functions/anbt/addToSvg.js new file mode 100644 index 0000000..8648f23 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/addToSvg.js @@ -0,0 +1,19 @@ +import anbt from '../../anbt' +import drawSvgElement from './drawSvgElement' +import moveSeekbar from './moveSeekbar' + +const addToSvg = element => { + if (anbt.rewindCache.length >= anbt.fastUndoLevels) anbt.rewindCache.pop() + anbt.rewindCache.unshift(anbt.ctx.getImageData(0, 0, 600, 500)) + drawSvgElement(element) + if (!anbt.timeedit || anbt.position === anbt.svg.childNodes.length - 1) { + // Remove everything past current position + for (let i = anbt.svg.childNodes.length - 1; i > anbt.position; i--) + anbt.svg.removeChild(anbt.svg.childNodes[i]) + anbt.svg.appendChild(element) + anbt.position = anbt.svg.childNodes.length - 1 + moveSeekbar(1) + } else anbt.svg.insertBefore(element, anbt.svg.childNodes[anbt.position + 1]) +} + +export default addToSvg diff --git a/src/newcanvas/js/functions/anbt/bindContainer.js b/src/newcanvas/js/functions/anbt/bindContainer.js new file mode 100644 index 0000000..388f37d --- /dev/null +++ b/src/newcanvas/js/functions/anbt/bindContainer.js @@ -0,0 +1,31 @@ +import anbt from '../../anbt' +import createSvgElement from '../createSvgElement' + +const bindContainer = element => { + anbt.container = element + anbt.canvas.width = 600 + anbt.canvas.height = 500 + anbt.canvas.style.background = anbt.background + anbt.ctx = anbt.canvas.getContext('2d') + anbt.ctx.lineJoin = anbt.ctx.lineCap = 'round' + anbt.container.appendChild(anbt.canvas) + if (!navigator.userAgent.match(/\bPresto\b/)) { + anbt.canvasDisp.width = 600 + anbt.canvasDisp.height = 500 + anbt.ctxDisp = anbt.canvasDisp.getContext('2d') + anbt.ctxDisp.lineJoin = anbt.ctxDisp.lineCap = 'round' + anbt.container.appendChild(anbt.canvasDisp) + } else anbt.DrawDispLine = anbt.DrawDispLinePresto // Opera Presto is faster with SVG redrawing + anbt.container.appendChild(anbt.svgDisp) + const rect = createSvgElement('rect', { + class: 'eraser', + x: 0, + y: 0, + width: 600, + height: 500, + fill: anbt.background + }) + anbt.svg.appendChild(rect) +} + +export default bindContainer diff --git a/src/newcanvas/js/functions/anbt/clearWithColor.js b/src/newcanvas/js/functions/anbt/clearWithColor.js new file mode 100644 index 0000000..3ad60c0 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/clearWithColor.js @@ -0,0 +1,19 @@ +import anbt from '../../anbt' +import createSvgElement from '../createSvgElement' +import addToSvg from './addToSvg' + +const clearWithColor = color => { + addToSvg( + createSvgElement('rect', { + class: color, + x: 0, + y: 0, + width: 600, + height: 500, + fill: anbt.background + }) + ) + anbt.lastrect = anbt.position +} + +export default clearWithColor diff --git a/src/newcanvas/js/functions/anbt/cutHistoryBeforeClearAndAfterPosition.js b/src/newcanvas/js/functions/anbt/cutHistoryBeforeClearAndAfterPosition.js new file mode 100644 index 0000000..aa5cb33 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/cutHistoryBeforeClearAndAfterPosition.js @@ -0,0 +1,17 @@ +import anbt from '../../anbt' + +const cutHistoryBeforeClearAndAfterPosition = () => { + let removing = false + for (let i = anbt.svg.childNodes.length - 1; i > 0; i--) { + const element = anbt.svg.childNodes[i] + if (removing || i > anbt.position) anbt.svg.removeChild(element) + else if (element.nodeName === 'rect' && i <= anbt.position) { + removing = true + // Optimize out two eraser rectangles next to each other + if (element.getAttribute('class') === 'eraser') + anbt.svg.removeChild(element) + } + } +} + +export default cutHistoryBeforeClearAndAfterPosition diff --git a/src/newcanvas/js/functions/anbt/drawDispLine.js b/src/newcanvas/js/functions/anbt/drawDispLine.js new file mode 100644 index 0000000..3f69f70 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/drawDispLine.js @@ -0,0 +1,15 @@ +import anbt from '../../anbt' + +const drawDispLine = (x1, y1, x2, y2) => { + const { ctxDisp } = anbt + // var c = this.lastcolor; + // ctx.strokeStyle = anbt.pattern ? anbt.MakePattern(c, anbt.pattern) : c; + ctxDisp.strokeStyle = anbt.lastcolor + ctxDisp.lineWidth = anbt.size + ctxDisp.beginPath() + ctxDisp.moveTo(x1, y1) + ctxDisp.lineTo(x2, y2) + ctxDisp.stroke() +} + +export default drawDispLine diff --git a/src/newcanvas/js/functions/anbt/drawDispLinePresto.js b/src/newcanvas/js/functions/anbt/drawDispLinePresto.js new file mode 100644 index 0000000..66c4370 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/drawDispLinePresto.js @@ -0,0 +1,7 @@ +import anbt from '../../anbt'; + +const drawDispLinePresto = first => { + if (first) anbt.svgDisp.insertBefore(anbt.path, anbt.svgDisp.firstChild) +} + +export default drawDispLinePresto diff --git a/src/newcanvas/js/functions/anbt/drawSvgElement.js b/src/newcanvas/js/functions/anbt/drawSvgElement.js new file mode 100644 index 0000000..3b3cd5d --- /dev/null +++ b/src/newcanvas/js/functions/anbt/drawSvgElement.js @@ -0,0 +1,35 @@ +import anbt from '../../anbt' + +const drawSvgElement = (element, ctx) => { + if (!ctx) ctx = anbt.ctx + ctx.globalCompositeOperation = + element.getAttribute('class') === 'eraser' + ? 'destination-out' + : 'source-over' + if (element.nodeName === 'path') { + //var c = el.getAttribute("stroke"); + //ctx.strokeStyle = el.pattern ? anbt.MakePattern(c, el.pattern) : c; + ctx.strokeStyle = element.getAttribute('stroke') + ctx.lineWidth = element.getAttribute('stroke-width') + ctx.beginPath() + for (let i = 0; i < element.pathSegList.numberOfItems; i++) { + const seg = element.pathSegList.getItem(i) + if (seg.pathSegTypeAsLetter === 'M') ctx.moveTo(seg.x, seg.y) + else if (seg.pathSegTypeAsLetter === 'L') ctx.lineTo(seg.x, seg.y) + else if (seg.pathSegTypeAsLetter === 'Q') + ctx.quadraticCurveTo(seg.x1, seg.y1, seg.x, seg.y) + else if (seg.pathSegTypeAsLetter === 'C') + ctx.bezierCurveTo(seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y) + } + ctx.stroke() + } else if (element.nodeName === 'rect') { + ctx.fillStyle = element.getAttribute('fill') + const x = element.getAttribute('x') + const y = element.getAttribute('y') + const width = element.getAttribute('width') + const height = element.getAttribute('height') + ctx.fillRect(x, y, width, height) + } +} + +export default drawSvgElement diff --git a/src/newcanvas/js/functions/anbt/eyedropper.js b/src/newcanvas/js/functions/anbt/eyedropper.js new file mode 100644 index 0000000..5ae88b6 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/eyedropper.js @@ -0,0 +1,11 @@ +import anbt from '../../anbt' +import getClosestColor from '../getClosestColor' + +const eyedropper = (x, y) => { + const pixelColor = anbt.ctx.getImageData(x, y, 1, 1).data + return pixelColor[3] > 0 + ? getClosestColor(pixelColor, anbt.palette) + : anbt.background +} + +export default eyedropper diff --git a/src/newcanvas/js/functions/anbt/findLastRect.js b/src/newcanvas/js/functions/anbt/findLastRect.js new file mode 100644 index 0000000..3998f84 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/findLastRect.js @@ -0,0 +1,12 @@ +import anbt from '../../anbt' + +const findLastRect = endPosition => { + if (!endPosition) endPosition = anbt.svg.childNodes.length - 1 + for (let i = endPosition; i > 0; i--) { + const element = anbt.svg.childNodes[i] + if (element.nodeName === 'rect') return i + } + return 0 +} + +export default findLastRect diff --git a/src/newcanvas/js/functions/anbt/fromLocalFile.js b/src/newcanvas/js/functions/anbt/fromLocalFile.js new file mode 100644 index 0000000..3f9081b --- /dev/null +++ b/src/newcanvas/js/functions/anbt/fromLocalFile.js @@ -0,0 +1,26 @@ +import anbt from '../../anbt' +import fromPng from './fromPng' + +const fromLocalFile = () => { + if (!anbt.fileInput) { + anbt.fileInput = document.createElement('input') + anbt.fileInput.style.position = 'absolute' + anbt.fileInput.style.top = '-1000px' + anbt.fileInput.type = 'file' + anbt.fileInput.accept = '.png' + document.body.appendChild(anbt.fileInput) + anbt.fileInput.addEventListener( + 'change', + event => { + const reader = new FileReader() + reader.onload = () => fromPng(reader.result) + if (event.currentTarget.files[0]) + reader.readAsArrayBuffer(event.currentTarget.files[0]) + }, + false + ) + } + anbt.fileInput.click() +} + +export default fromLocalFile diff --git a/src/newcanvas/js/functions/anbt/fromPng.js b/src/newcanvas/js/functions/anbt/fromPng.js new file mode 100644 index 0000000..2be46a2 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/fromPng.js @@ -0,0 +1,36 @@ +import anbt from '../../anbt' +import packUint32be from '../pack/packUint32be' +import moveSeekbar from './moveSeekbar' +import setBackground from './setBackground' +import unpackPlayback from './unpackPlayback' +import updateView from './updateView' + +const fromPng = buffer => { + const dv = new DataView(buffer) + const magic = dv.getUint32(0) + if (magic !== 0x89504e47) + throw new Error(`Invalid PNG format: ${packUint32be(magic)}`) + for (let i = 8; i < buffer.byteLength; i += 4 /* Skip CRC */) { + const chunklen = dv.getUint32(i) + i += 4 + const chunkname = packUint32be(dv.getUint32(i)) + i += 4 + if (chunkname === 'svGb') { + anbt.svg = unpackPlayback(new Uint8Array(buffer, i, chunklen)) + anbt.lastrect = 0 + anbt.rewindCache.length = 0 + anbt.position = anbt.svg.childNodes.length - 1 + updateView() + moveSeekbar(1) + // Here we assume first element of svg is background rect + setBackground(anbt.svg.background) + return + } else { + if (chunkname === 'IEND') break + i += chunklen + } + } + throw new Error('No vector data found!') +} + +export default fromPng diff --git a/src/newcanvas/js/functions/anbt/fromUrl.js b/src/newcanvas/js/functions/anbt/fromUrl.js new file mode 100644 index 0000000..c058f6a --- /dev/null +++ b/src/newcanvas/js/functions/anbt/fromUrl.js @@ -0,0 +1,12 @@ +import fromPng from './fromPng' + +const fromUrl = url => { + const xhr = new XMLHttpRequest() + xhr.open('GET', url, true) + if ('responseType' in xhr) xhr.responseType = 'arraybuffer' + else return alert('Your browser is too old for this') + xhr.onload = () => fromPng(xhr.response) + xhr.send() +} + +export default fromUrl diff --git a/src/newcanvas/js/functions/anbt/getSeekMax.js b/src/newcanvas/js/functions/anbt/getSeekMax.js new file mode 100644 index 0000000..6519bc9 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/getSeekMax.js @@ -0,0 +1,5 @@ +import anbt from '../../anbt' + +const getSeekMax = () => anbt.svg.childNodes.length - 1 + +export default getSeekMax diff --git a/src/newcanvas/js/functions/anbt/lock.js b/src/newcanvas/js/functions/anbt/lock.js new file mode 100644 index 0000000..4df8e28 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/lock.js @@ -0,0 +1,11 @@ +import anbt from '../../anbt' +import moveCursor from './moveCursor' +import strokeEnd from './strokeEnd' + +const lock = () => { + if (anbt.isStroking) strokeEnd() + anbt.locked = true + moveCursor(-100, -100) +} + +export default lock diff --git a/src/newcanvas/js/functions/anbt/makePng.js b/src/newcanvas/js/functions/anbt/makePng.js new file mode 100644 index 0000000..8fee57d --- /dev/null +++ b/src/newcanvas/js/functions/anbt/makePng.js @@ -0,0 +1,54 @@ +import anbt from '../../anbt' +import crc32 from '../crc32' +import packUint32be from '../pack/packUint32be' +import cutHistoryBeforeClearAndAfterPosition from './cutHistoryBeforeClearAndAfterPosition' +import drawSvgElement from './drawSvgElement' +import moveSeekbar from './moveSeekbar' +import packPlayback from './packPlayback' + +const makePng = (width, height, fromBuffer) => { + // Cut all needless SVG data that comes before clearing whole canvas + cutHistoryBeforeClearAndAfterPosition() + moveSeekbar(1) + + const canvas = document.createElement('canvas') + canvas.width = width + canvas.height = height + const context = canvas.getContext('2d') + if (!anbt.transparent) { + context.fillStyle = anbt.background + context.fillRect(0, 0, width, height) + } + if (fromBuffer) context.drawImage(anbt.canvas, 0, 0, width, height) + else { + context.lineJoin = context.lineCap = 'round' + context.scale(width / 600, height / 500) + for (let i = 0; i < anbt.svg.childNodes.length; i++) { + drawSvgElement(anbt.svg.childNodes[i], context) + } + } + anbt.pngBase64 = canvas.toDataURL('image/png') + + const version = 'svGb' + const svgString = packPlayback(anbt.svg) + const padding = anbt.pngBase64.substr(-2) + // To append the custom chunk, we need to decode the end of the base64-encoded PNG + // and then reattach as btoa((prepend) + (custom data) + (iend)). + // As base64 encoding chunks are 3 bytes, iend chunk can start in the middle of those, + // so (prepend) contains the data before iend chunk that we had to cut. + const cut = padding === '==' ? 1 : padding[1] === '=' ? 2 : 3 + const indexEnd = atob(anbt.pngBase64.substr(-20)).substr(cut) + const prepend = atob(anbt.pngBase64.substr(-20)).substr(0, cut) + const custom = [ + prepend, + packUint32be(svgString.length), + version, + svgString, + packUint32be(crc32(version, svgString)), + indexEnd + ].join('') + anbt.pngBase64 = + anbt.pngBase64.substr(0, anbt.pngBase64.length - 20) + btoa(custom) +} + +export default makePng diff --git a/src/newcanvas/js/functions/anbt/moveCursor.js b/src/newcanvas/js/functions/anbt/moveCursor.js new file mode 100644 index 0000000..5376e49 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/moveCursor.js @@ -0,0 +1,44 @@ +import anbt from '../../anbt' +import createSvgElement from '../createSvgElement' + +const moveCursor = (x, y) => { + if (anbt.locked) return + if (!anbt.brushCursor) { + anbt.brushCursor = createSvgElement('circle', { + 'stroke-width': '1', + stroke: '#000', + fill: 'none' + }) + anbt.svgDisp.appendChild(anbt.brushCursor) + anbt.brushCursor2 = createSvgElement('circle', { + 'stroke-width': '1', + stroke: '#fff', + fill: 'none' + }) + anbt.svgDisp.appendChild(anbt.brushCursor2) + anbt.eyedropperCursor = createSvgElement('image', { + width: 16, + height: 16, + visibility: 'hidden' + }) + anbt.eyedropperCursor.setAttributeNS( + 'http://www.w3.org/1999/xlink', + 'href', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARklEQVR4XoXRwQoAIAgEUf//pzeGDgq5G3PrCQqVbIAqsDz9WM2qhTX4GZgPV+JpSFxAC0PwbeVZZIpMgXvAMwoj4U9B3wGySxvzk6ZjvwAAAABJRU5ErkJggg==' + ) + anbt.svgDisp.appendChild(anbt.eyedropperCursor) + } + // Assume just size change if called with no parameters + if (typeof x !== 'undefined') { + anbt.brushCursor.setAttribute('cx', x) + anbt.brushCursor.setAttribute('cy', y) + anbt.brushCursor2.setAttribute('cx', x) + anbt.brushCursor2.setAttribute('cy', y) + anbt.eyedropperCursor.setAttribute('x', x - 1) + anbt.eyedropperCursor.setAttribute('y', y - 15) + } + anbt.brushCursor.setAttribute('r', anbt.size / 2 + 0.5) + anbt.brushCursor2.setAttribute('r', anbt.size / 2 - 0.5) +} + +export default moveCursor diff --git a/src/newcanvas/js/functions/anbt/moveSeekbar.js b/src/newcanvas/js/functions/anbt/moveSeekbar.js new file mode 100644 index 0000000..20c19d4 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/moveSeekbar.js @@ -0,0 +1,7 @@ +import anbt from '../../anbt' + +const moveSeekbar = position => { + if (anbt.seekbarMove) anbt.seekbarMove(position) +} + +export default moveSeekbar diff --git a/src/newcanvas/js/functions/anbt/packPlayback.js b/src/newcanvas/js/functions/anbt/packPlayback.js new file mode 100644 index 0000000..31bb3e3 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/packPlayback.js @@ -0,0 +1,63 @@ +import anbt from '../../anbt'; +import bytesToStrings from '../conversions/bytesToStrings'; +import colorToDword from '../conversions/colorToDword'; +import stringToBytes from '../conversions/stringToBytes'; +import packUint16be from '../pack/packUint16be'; + +const packPlayback = svg => { + const { pako } = window + const array = [colorToDword(anbt.background)] + const last = { + color: colorToDword('#000000'), + size: 14, + x: -1, + y: -1, + pattern: 0 + } + svg.childNodes.forEach(element => { + if (element.nodeName === 'path') { + const color = + element.getAttribute('class') === 'eraser' + ? '\xFF\xFF\xFF\x00' + : colorToDword(element.getAttribute('stroke')) + const size = element.getAttribute('stroke-width') + const pattern = element.pattern || 0 + if (color !== last.color || size !== last.size) { + array.push(packUint16be(-1)) + array.push(packUint16be(size * 100)) + array.push(color) + last.color = color + last.size = size + } + if (pattern !== last.pattern) { + array.push(packUint16be(-3)) + array.push(packUint16be(pattern)) + array.push('\x00\x00\x00\x00') // reserved for the future + last.pattern = pattern + } + last.x = element.orig[0].x + last.y = element.orig[0].y + array.push(packUint16be(last.x)) + array.push(packUint16be(last.y)) + for (let j = 1; j < element.orig.length; j++) { + const dx = element.orig[j].x - last.x + const dy = element.orig[j].y - last.y + // Ignore repeating points + if (!dx && !dy) continue + array.push(packUint16be(dx)) + array.push(packUint16be(dy)) + last.x = element.orig[j].x + last.y = element.orig[j].y + } + array.push('\x00\x00\x00\x00') + } else if (element.nodeName === 'rect') { + const color = colorToDword(element.getAttribute('fill')) + array.push(packUint16be(-2)) + array.push(packUint16be(0)) // reserved for the future + array.push(color) + } else throw new Error('Unknown node name: ' + element.nodeName) + }) + return '\x04' + bytesToStrings(pako.deflate(stringToBytes(array.join('')))) +} + +export default packPlayback diff --git a/src/newcanvas/js/functions/anbt/pause.js b/src/newcanvas/js/functions/anbt/pause.js new file mode 100644 index 0000000..83654be --- /dev/null +++ b/src/newcanvas/js/functions/anbt/pause.js @@ -0,0 +1,19 @@ +import anbt from '../../anbt' +import drawSvgElement from './drawSvgElement' +import moveSeekbar from './moveSeekbar' + +const pause = noSeekbar => { + if (anbt.isPlaying) { + if (anbt.isAnimating) { + anbt.isAnimating = false + anbt.svgDisp.removeChild(anbt.path) + drawSvgElement(anbt.animatePath) + anbt.position++ + if (!noSeekbar) + moveSeekbar(anbt.position / (anbt.svg.childNodes.length - 1)) + } + anbt.isPlaying = false + } +} + +export default pause diff --git a/src/newcanvas/js/functions/anbt/play.js b/src/newcanvas/js/functions/anbt/play.js new file mode 100644 index 0000000..89b6dbc --- /dev/null +++ b/src/newcanvas/js/functions/anbt/play.js @@ -0,0 +1,20 @@ +import anbt from '../../anbt' +import drawSvgElement from './drawSvgElement' +import moveSeekbar from './moveSeekbar' +import playTimer from './playTimer' + +const play = () => { + if (anbt.locked) return + anbt.rewindCache.length = 0 // TODO: make rewind data remember its position + if (anbt.position === anbt.svg.childNodes.length - 1) { + if (anbt.position === 0) return moveSeekbar(1) // To make button revert to play + anbt.position = 0 + moveSeekbar(0) + // Assume first svg child is background rect + drawSvgElement(anbt.svg.childNodes[0]) + } + anbt.isPlaying = true + playTimer() +} + +export default play diff --git a/src/newcanvas/js/functions/anbt/playTimer.js b/src/newcanvas/js/functions/anbt/playTimer.js new file mode 100644 index 0000000..4351470 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/playTimer.js @@ -0,0 +1,71 @@ +import anbt from '../../anbt' +import drawSvgElement from './drawSvgElement' +import moveSeekbar from './moveSeekbar' +import pause from './pause' + +const playTimer = () => { + if (!anbt.isPlaying) return + const posmax = anbt.svg.childNodes.length - 1 + let { delay } = anbt + let maxidx = 0 + if (anbt.position < posmax || anbt.isAnimating) { + if (anbt.isAnimating) { + maxidx = anbt.animatePath.pathSegList.numberOfItems - 1 + if (anbt.animateIndex < maxidx) { + // There doesn't seem to be a simplier way to copy the pathSeg + const segment = anbt.animatePath.pathSegList.getItem(anbt.animateIndex) + const newSegment = + segment.pathSegTypeAsLetter === 'L' + ? anbt.path.createSVGPathSegLinetoAbs(segment.x, segment.y) + : segment.pathSegTypeAsLetter === 'Q' + ? anbt.path.createSVGPathSegCurvetoQuadraticAbs( + segment.x, + segment.y, + segment.x1, + segment.y1 + ) + : segment.pathSegTypeAsLetter === 'C' && + anbt.path.createSVGPathSegCurvetoCubicAbs( + segment.x, + segment.y, + segment.x1, + segment.y1, + segment.x2, + segment.y2 + ) + anbt.path.pathSegList.appendItem(newSegment) + anbt.animateIndex++ + } else { + anbt.isAnimating = false + anbt.svgDisp.removeChild(anbt.path) + drawSvgElement(anbt.animatePath) + anbt.position++ + anbt.animateIndex = 0 + } + delay /= 6 + } else { + const element = anbt.svg.childNodes[anbt.position + 1] + if (element.nodeName === 'path') { + anbt.isAnimating = true + anbt.animatePath = element + anbt.animateIndex = 1 + anbt.path = element.cloneNode(true) + const segment = element.pathSegList.getItem(0) + anbt.path.pathSegList.initialize( + anbt.path.createSVGPathSegMovetoAbs(segment.x, segment.y) + ) + anbt.svgDisp.insertBefore(anbt.path, anbt.svgDisp.firstChild) + } else { + drawSvgElement(element) + anbt.position++ + } + } + } + moveSeekbar( + (anbt.position + (maxidx ? anbt.animateIndex / maxidx : 0)) / posmax + ) + if (anbt.position < posmax) setTimeout(anbt.playTimer, delay) + else pause() +} + +export default playTimer diff --git a/src/newcanvas/js/functions/anbt/redo.js b/src/newcanvas/js/functions/anbt/redo.js new file mode 100644 index 0000000..185fd36 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/redo.js @@ -0,0 +1,14 @@ +import anbt from '../../anbt' +import moveSeekbar from './moveSeekbar' +import seek from './seek' + +const redo = () => { + if (anbt.locked) return + var posmax = anbt.svg.childNodes.length - 1 + if (anbt.position < posmax) { + seek(anbt.position + 1) + moveSeekbar(anbt.position / posmax) + } +} + +export default redo diff --git a/src/newcanvas/js/functions/anbt/requestSave.js b/src/newcanvas/js/functions/anbt/requestSave.js new file mode 100644 index 0000000..c5772ee --- /dev/null +++ b/src/newcanvas/js/functions/anbt/requestSave.js @@ -0,0 +1,33 @@ +import anbt from '../../anbt' + +const requestSave = (dataUrl, extension) => { + if (!dataUrl) { + dataUrl = anbt.pngBase64 + extension = '.png' + anbt.unsaved = false + } + if (!anbt.saveLink) { + anbt.saveLink = document.createElement('a') + document.body.appendChild(anbt.saveLink) + } + if ('download' in anbt.saveLink) { + anbt.saveLink.href = dataUrl + const date = new Date() + anbt.saveLink.download = [ + 'DrawingInTime_', + date.getFullYear(), + '_', + (101 + date.getMonth() + '').slice(-2), + (100 + date.getDate() + '').slice(-2), + '_', + (100 + date.getHours() + '').slice(-2), + (100 + date.getMinutes() + '').slice(-2), + (100 + date.getSeconds() + '').slice(-2), + extension + ].join('') + anbt.saveLink.click() + } else window.open(dataUrl) + return true +} + +export default requestSave diff --git a/src/newcanvas/js/functions/anbt/seek.js b/src/newcanvas/js/functions/anbt/seek.js new file mode 100644 index 0000000..48d761b --- /dev/null +++ b/src/newcanvas/js/functions/anbt/seek.js @@ -0,0 +1,46 @@ +import anbt from '../../anbt'; +import drawSvgElement from './drawSvgElement'; +import findLastRect from './findLastRect'; +import pause from './pause'; + +const seek = newPosition => { + if (anbt.locked) return + let start = -1 + pause(true) + if (newPosition === anbt.position) return + if (newPosition < anbt.position) { + const rewindSteps = anbt.position - newPosition + if (rewindSteps <= anbt.rewindCache.length) { + // Draw from cached + anbt.ctx.putImageData(anbt.rewindCache[rewindSteps - 1], 0, 0) + anbt.rewindCache.splice(0, rewindSteps) + } else { + // Not cached; rebuild cache + start = 0 + if (anbt.lastrect <= newPosition) start = anbt.lastrect + else start = findLastRect(newPosition) + drawSvgElement(anbt.svg.childNodes[start]) + } + } else if (newPosition > anbt.position) start = anbt.position + if (start !== -1) { + const forwardSteps = newPosition - start + if (forwardSteps >= anbt.fastUndoLevels) anbt.rewindCache.length = 0 + else { + // Ex: 3 cached, 10 max, 8 steps to play => delete 1 from the end + const { length } = anbt.rewindCache + const numRemove = Math.min( + length, + newPosition - start + length - anbt.fastUndoLevels + ) + anbt.rewindCache.splice(length - numRemove, numRemove) + } + for (let i = start + 1; i <= newPosition; i++) { + if (newPosition - i < anbt.fastUndoLevels) + anbt.rewindCache.unshift(anbt.ctx.getImageData(0, 0, 600, 500)) + drawSvgElement(anbt.svg.childNodes[i]) + } + } + anbt.position = newPosition +} + +export default seek diff --git a/src/newcanvas/js/functions/anbt/setBackground.js b/src/newcanvas/js/functions/anbt/setBackground.js new file mode 100644 index 0000000..ecefed8 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/setBackground.js @@ -0,0 +1,18 @@ +import anbt from '../../anbt' +import colorToHex from '../conversions/colorToHex' + +const setBackground = color => { + const transparent = color === 'eraser' + anbt.transparent = transparent + anbt.canvas.style.background = transparent ? 'none' : color + // Normalize the color representation + color = transparent ? '#ffffff' : colorToHex(color) + anbt.background = color + anbt.svg + .querySelectorAll('.eraser') + .forEach(erased => + erased.setAttribute(erased.nodeName === 'path' ? 'stroke' : 'fill', color) + ) +} + +export default setBackground diff --git a/src/newcanvas/js/functions/anbt/setColor.js b/src/newcanvas/js/functions/anbt/setColor.js new file mode 100644 index 0000000..d6a0cf7 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/setColor.js @@ -0,0 +1,5 @@ +import anbt from '../../anbt' + +const setColor = (number, color) => (anbt.colors[number] = color) + +export default setColor diff --git a/src/newcanvas/js/functions/anbt/setSeekbarMove.js b/src/newcanvas/js/functions/anbt/setSeekbarMove.js new file mode 100644 index 0000000..4663225 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/setSeekbarMove.js @@ -0,0 +1,5 @@ +import anbt from '../../anbt'; + +const setSeekbarMove = func => (anbt.seekbarMove = func) + +export default setSeekbarMove diff --git a/src/newcanvas/js/functions/anbt/setSize.js b/src/newcanvas/js/functions/anbt/setSize.js new file mode 100644 index 0000000..92dec7e --- /dev/null +++ b/src/newcanvas/js/functions/anbt/setSize.js @@ -0,0 +1,9 @@ +import anbt from '../../anbt' +import moveCursor from './moveCursor' + +const setSize = size => { + anbt.size = size + moveCursor() +} + +export default setSize diff --git a/src/newcanvas/js/functions/anbt/showEyedropperCursor.js b/src/newcanvas/js/functions/anbt/showEyedropperCursor.js new file mode 100644 index 0000000..f6366f9 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/showEyedropperCursor.js @@ -0,0 +1,12 @@ +import anbt from '../../anbt' + +const showEyedropperCursor = isEyedropper => { + if (!anbt.brushCursor) return + const vis = isEyedropper ? 'hidden' : 'visible' + const vis2 = isEyedropper ? 'visible' : 'hidden' + anbt.brushCursor.setAttribute('visibility', vis) + anbt.brushCursor2.setAttribute('visibility', vis) + anbt.eyedropperCursor.setAttribute('visibility', vis2) +} + +export default showEyedropperCursor diff --git a/src/newcanvas/js/functions/anbt/strokeAdd.js b/src/newcanvas/js/functions/anbt/strokeAdd.js new file mode 100644 index 0000000..2064c59 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/strokeAdd.js @@ -0,0 +1,20 @@ +import anbt from '../../anbt' +import drawDispLine from './drawDispLine' +import drawDispLinePresto from './drawDispLinePresto' + +const strokeAdd = (x, y) => { + if (anbt.locked) return + if (!anbt.isStroking) throw new Error('StrokeAdd without StrokeBegin!') + const point = anbt.points[anbt.points.length - 1] + if (point.x === x && point.y === y) return + if (anbt.blot) { + anbt.path.pathSegList.removeItem(1) + anbt.blot = false + } + anbt.path.pathSegList.appendItem(anbt.path.createSVGPathSegLinetoAbs(x, y)) + if (navigator.userAgent.match(/\bPresto\b/)) drawDispLinePresto(false) + else drawDispLine(point.x, point.y, x, y) + anbt.points.push({ x, y }) +} + +export default strokeAdd diff --git a/src/newcanvas/js/functions/anbt/strokeBegin.js b/src/newcanvas/js/functions/anbt/strokeBegin.js new file mode 100644 index 0000000..c625f10 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/strokeBegin.js @@ -0,0 +1,36 @@ +import anbt from '../../anbt' +import createSvgElement from '../createSvgElement' +import drawDispLine from './drawDispLine' +import drawDispLinePresto from './drawDispLinePresto' + +const strokeBegin = (x, y, left) => { + if (anbt.locked) return + if (!left) left = anbt.lastleft + else anbt.lastleft = left + let color = left ? anbt.colors[0] : anbt.colors[1] + const cls = color === 'eraser' ? color : null + color = color === 'eraser' ? anbt.background : color + anbt.path = createSvgElement('path', { + class: cls, + stroke: color, + 'stroke-width': anbt.size, + 'stroke-linejoin': 'round', + 'stroke-linecap': 'round', + fill: 'none' + }) + anbt.lastcolor = color + anbt.path.pattern = anbt.pattern + //anbt.svgDisp.insertBefore(anbt.path, anbt.svgDisp.firstChild); + anbt.path.pathSegList.appendItem(anbt.path.createSVGPathSegMovetoAbs(x, y)) + anbt.path.pathSegList.appendItem( + anbt.path.createSVGPathSegLinetoAbs(x, y + 0.001) + ) + if (navigator.userAgent.match(/\bPresto\b/)) drawDispLinePresto(true) + else drawDispLine(x, y, x, y + 0.001) + anbt.points = [] + anbt.points.push({ x, y }) + anbt.blot = true + anbt.isStroking = true +} + +export default strokeBegin diff --git a/src/newcanvas/js/functions/anbt/strokeEnd.js b/src/newcanvas/js/functions/anbt/strokeEnd.js new file mode 100644 index 0000000..debbfd5 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/strokeEnd.js @@ -0,0 +1,18 @@ +import anbt from '../../anbt' +import buildSmoothPath from '../buildSmoothPath' +import simplifyDouglasPeucker from '../simplifyDouglasPeucker' +import addToSvg from './addToSvg' + +const strokeEnd = () => { + if (anbt.locked) return + anbt.unsaved = true + const points = + anbt.points.length > 2 ? simplifyDouglasPeucker(anbt) : anbt.points + buildSmoothPath(points, anbt.path) + anbt.path.orig = points + addToSvg(anbt.path) + anbt.ctxDisp && anbt.ctxDisp.clearRect(0, 0, 600, 500) + anbt.isStroking = false +} + +export default strokeEnd diff --git a/src/newcanvas/js/functions/anbt/undo.js b/src/newcanvas/js/functions/anbt/undo.js new file mode 100644 index 0000000..7121876 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/undo.js @@ -0,0 +1,14 @@ +import anbt from '../../anbt' +import moveSeekbar from './moveSeekbar' +import seek from './seek' + +const undo = () => { + if (anbt.locked) return + // Prevent "undoing" the background rectangle + if (anbt.position > 0) { + seek(anbt.position - 1) + moveSeekbar(anbt.position / (anbt.svg.childNodes.length - 1)) + } +} + +export default undo diff --git a/src/newcanvas/js/functions/anbt/unlock.js b/src/newcanvas/js/functions/anbt/unlock.js new file mode 100644 index 0000000..6354086 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/unlock.js @@ -0,0 +1,5 @@ +import anbt from '../../anbt' + +const unlock = () => (anbt.locked = false) + +export default unlock diff --git a/src/newcanvas/js/functions/anbt/unpackPlayback.js b/src/newcanvas/js/functions/anbt/unpackPlayback.js new file mode 100644 index 0000000..eb1eccc --- /dev/null +++ b/src/newcanvas/js/functions/anbt/unpackPlayback.js @@ -0,0 +1,117 @@ +import buildSmoothPath from '../buildSmoothPath' +import stringToBytes from '../conversions/stringToBytes' +import createSvgElement from '../createSvgElement' +import int16be from '../int16be' + +const unpackPlayback = bytes => { + const { pako } = window + const version = bytes[0] + let start + if (version === 4) { + bytes = pako.inflate(bytes.subarray(1)) + start = 0 + } else if (version === 3) { + bytes = stringToBytes(pako.inflate(bytes.subarray(1), { to: 'string' })) + start = 0 + } else if (version === 2) start = 1 + else throw new Error(`Unsupported version: ${version}`) + const svg = createSvgElement('svg', { + xmlns: 'http://www.w3.org/2000/svg', + version: '1.1', + width: 600, + height: 500 + }) + const last = { + color: '#000000', + size: 14, + x: 0, + y: 0, + pattern: 0 + } + const points = [] + // Ignore background alpha + const background = `rgb(${bytes[start]}, ${bytes[start + 1]}, ${ + bytes[start + 2] + })` + svg.background = background + + svg.appendChild( + createSvgElement('rect', { + class: 'eraser', + x: 0, + y: 0, + width: 600, + height: 500, + fill: background + }) + ) + + for (let i = start + 4; i < bytes.length; ) { + let x = int16be(bytes[i], bytes[i + 1]) + i += 2 + let y = int16be(bytes[i], bytes[i + 1]) + i += 2 + if (points.length) { + if (!x && !y) { + const path = createSvgElement('path', { + class: last.color === 'eraser' ? last.color : null, + stroke: last.color === 'eraser' ? background : last.color, + 'stroke-width': last.size, + 'stroke-linejoin': 'round', + 'stroke-linecap': 'round', + fill: 'none' + }) + // Restore blots + if (points.length === 1) { + path.pathSegList.appendItem( + path.createSVGPathSegMovetoAbs(last.x, last.y) + ) + path.pathSegList.appendItem( + path.createSVGPathSegLinetoAbs(last.x, last.y + 0.001) + ) + } else buildSmoothPath(points, path) + path.orig = points + path.pattern = last.pattern + svg.appendChild(path) + points.length = 0 + } else { + last.x = x += last.x + last.y = y += last.y + points.push({ x, y }) + } + } else { + if (x < 0) { + if (x === -1 || x === -2) { + last.color = `rgba(${bytes[i]}, ${bytes[i + 1]}, ${ + bytes[i + 2] + }, ${bytes[i + 3] / 255}` + // TODO: fix ugly code + if (last.color === 'rgba(255,255,255,0)') last.color = 'eraser' + i += 4 + if (x === -1) last.size = y / 100 + else + svg.appendChild( + createSvgElement('rect', { + class: last.color === 'eraser' ? last.color : null, + x: 0, + y: 0, + width: 600, + height: 500, + fill: last.color === 'eraser' ? background : last.color + }) + ) + } else if (x === -3) { + last.pattern = y + i += 4 + } + } else { + points.push({ x, y }) + last.x = x + last.y = y + } + } + } + return svg +} + +export default unpackPlayback diff --git a/src/newcanvas/js/functions/anbt/updateView.js b/src/newcanvas/js/functions/anbt/updateView.js new file mode 100644 index 0000000..ae47023 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/updateView.js @@ -0,0 +1,9 @@ +import anbt from '../../anbt' +import drawSvgElement from './drawSvgElement' + +const updateView = () => + [...anbt.svg.childNodes] + .splice(anbt.lastrect < anbt.position ? anbt.lastrect : 0) + .forEach(child => drawSvgElement(child)) + +export default updateView diff --git a/src/newcanvas/js/functions/anbt/uploadToImgur.js b/src/newcanvas/js/functions/anbt/uploadToImgur.js new file mode 100644 index 0000000..16f4dc2 --- /dev/null +++ b/src/newcanvas/js/functions/anbt/uploadToImgur.js @@ -0,0 +1,44 @@ +import anbt from '../../anbt' +import base64ToBytes from '../conversions/base64ToBytes' + +const uploadToImgur = callback => { + const request = new XMLHttpRequest() + request.open('POST', 'https://api.imgur.com/3/image') + request.onload = () => { + let response = request.responseText + try { + response = JSON.parse(response) + } catch (e) {} + if (response.success) { + // To set description + const request2 = new XMLHttpRequest() + request2.open( + 'POST', + 'https://api.imgur.com/3/image/' + response.data.deletehash + ) + request2.setRequestHeader('Authorization', 'Client-ID 4809db83c8897af') + const formData = new FormData() + formData.append( + 'description', + 'Playback: http://grompe.org.ru/drawit/#' + response.data.id + ) + request2.send(formData) + } + callback(response) + } + request.onerror = error => callback(`error: ${error}`) + request.setRequestHeader('Authorization', 'Client-ID 4809db83c8897af') + const formData = new FormData() + formData.append( + 'image', + new Blob([base64ToBytes(anbt.pngBase64.substr(22)).buffer], { + type: 'image/png' + }) + ) + formData.append('type', 'file') + formData.append('title', 'Made with Drawing in Time') + formData.append('description', 'http://grompe.org.ru/drawit/') + request.send(formData) +} + +export default uploadToImgur diff --git a/src/newcanvas/js/functions/bindEvents.js b/src/newcanvas/js/functions/bindEvents.js new file mode 100644 index 0000000..de70b7a --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents.js @@ -0,0 +1,72 @@ +import globals from '../globals' +import setSeekbarMove from './anbt/setSeekbarMove' +import changeBrushSize from './bindEvents/changeBrushSize' +import clickRedo from './bindEvents/clickRedo' +import clickSetBackground from './bindEvents/clickSetBackground' +import clickTrash from './bindEvents/clickTrash' +import clickUndo from './bindEvents/clickUndo' +import colorClick from './bindEvents/colorClick' +import keyDown from './bindEvents/documentEvents/keyDown' +import keyUp from './bindEvents/documentEvents/keyUp' +import doExport from './bindEvents/doExport' +import doImport from './bindEvents/doImport' +import exportToImgur from './bindEvents/exportToImgur' +import knobCommonDown from './bindEvents/knob/knobCommomDown' +import knobMove from './bindEvents/knob/knobMove' +import noDefault from './bindEvents/noDefault' +import openPaletteList from './bindEvents/palette/openPaletteList' +import playCommonDown from './bindEvents/playCommonDown' +import popupClose from './bindEvents/popupClose' +import svgContextMenu from './bindEvents/svgContainerEvents/contextMenu' +import mouseDown from './bindEvents/svgContainerEvents/mouseDown' +import mouseLeave from './bindEvents/svgContainerEvents/mouseLeave' +import svgMouseMove from './bindEvents/svgContainerEvents/mouseMove' +import touchStart from './bindEvents/svgContainerEvents/touchStart' +import beforeUnload from './bindEvents/windowEvents/beforeUnload' +import windowContextMenu from './bindEvents/windowEvents/contextMenu' +import error from './bindEvents/windowEvents/error' +import ID from './idSelector' + +const bindEvents = () => { + ID('svgContainer').addEventListener('mousedown', mouseDown) + ID('svgContainer').addEventListener('mousemove', svgMouseMove) + ID('svgContainer').addEventListener('touchstart', touchStart) + ID('svgContainer').addEventListener('mouseleave', mouseLeave) + ID('svgContainer').addEventListener('contextmenu', svgContextMenu) + ID('import').addEventListener('click', doImport) + ID('export').addEventListener('click', doExport) + ID('imgur').addEventListener('click', exportToImgur) + document.querySelectorAll('.brush').forEach((brush, index) => { + brush.classList.add(`size-${globals.brushSizes[index]}`) + brush.addEventListener('mousedown', changeBrushSize) + brush.addEventListener('click', changeBrushSize) + }) + ID('colors') + .querySelectorAll('b') + .forEach(color => { + color.addEventListener('mousedown', colorClick) + color.addEventListener('touchend', colorClick) + color.addEventListener('contextmenu', noDefault) + }) + ID('setbackground').addEventListener('click', clickSetBackground) + ID('undo').addEventListener('click', clickUndo) + ID('redo').addEventListener('click', clickRedo) + ID('trash').addEventListener('click', clickTrash) + setSeekbarMove(knobMove) + ID('knob').addEventListener('mousedown', knobCommonDown) + ID('knob').addEventListener('touchstart', knobCommonDown) + ID('seekbar').addEventListener('mousedown', knobCommonDown) + ID('seekbar').addEventListener('touchstart', knobCommonDown) + ID('play').addEventListener('mousedown', playCommonDown) + ID('play').addEventListener('touchstart', playCommonDown) + ID('palettename').addEventListener('mousedown', openPaletteList) + ID('palettename').addEventListener('touchend', openPaletteList) + ID('popupclose').addEventListener('click', popupClose) + document.addEventListener('keyup', keyUp) + document.addEventListener('keydown', keyDown) + window.addEventListener('contextmenu', windowContextMenu) + window.addEventListener('error', error) + window.addEventListener('beforeunload', beforeUnload) +} + +export default bindEvents diff --git a/src/newcanvas/js/functions/bindEvents/changeBrushSize.js b/src/newcanvas/js/functions/bindEvents/changeBrushSize.js new file mode 100644 index 0000000..5bccda5 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/changeBrushSize.js @@ -0,0 +1,22 @@ +import anbt from '../../anbt' +import setSize from '../anbt/setSize' +import strokeBegin from '../anbt/strokeBegin' +import strokeEnd from '../anbt/strokeEnd' +import ID from '../idSelector' + +const changeBrushSize = event => { + event.preventDefault() + const size = [...event.currentTarget.classList] + .filter(htmlClass => htmlClass.startsWith('size-'))[0] + .match(/\d+/)[0] + setSize(size) + const element = ID('tools').querySelector('.sel') + if (element) element.classList.remove('sel') + event.currentTarget.classList.add('sel') + if (!anbt.isStroking) return + strokeEnd() + const lastPoint = anbt.points[anbt.points.length - 1] + strokeBegin(lastPoint.x, lastPoint.y) +} + +export default changeBrushSize diff --git a/src/newcanvas/js/functions/bindEvents/checkPlayingAndStop.js b/src/newcanvas/js/functions/bindEvents/checkPlayingAndStop.js new file mode 100644 index 0000000..2188b85 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/checkPlayingAndStop.js @@ -0,0 +1,13 @@ +import anbt from '../../anbt' +import ID from '../idSelector' + +const checkPlayingAndStop = () => { + if (anbt.isPlaying) { + anbt.Pause() + ID('play').classList.remove('pause') + return true + } + return false +} + +export default checkPlayingAndStop diff --git a/src/newcanvas/js/functions/bindEvents/clickRedo.js b/src/newcanvas/js/functions/bindEvents/clickRedo.js new file mode 100644 index 0000000..2a8a05a --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/clickRedo.js @@ -0,0 +1,10 @@ +import redo from '../anbt/redo' +import ID from '../idSelector' + +const clickRedo = event => { + event.preventDefault() + ID('play').classList.remove('pause') + redo() +} + +export default clickRedo diff --git a/src/newcanvas/js/functions/bindEvents/clickSetBackground.js b/src/newcanvas/js/functions/bindEvents/clickSetBackground.js new file mode 100644 index 0000000..c050c2f --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/clickSetBackground.js @@ -0,0 +1,9 @@ +import globals from '../../globals' +import updateChooseBackground from './updateChooseBackground' + +const clickSetBackground = event => { + event.preventDefault() + updateChooseBackground(!globals.chooseBackground) +} + +export default clickSetBackground diff --git a/src/newcanvas/js/functions/bindEvents/clickTrash.js b/src/newcanvas/js/functions/bindEvents/clickTrash.js new file mode 100644 index 0000000..0641dd1 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/clickTrash.js @@ -0,0 +1,12 @@ +import globals from '../../globals' +import clearWithColor from '../anbt/clearWithColor' +import ID from '../idSelector' + +const clickTrash = event => { + event.preventDefault() + clearWithColor('eraser') + if (ID('newcanvasyo').classList.contains('sandbox')) + globals.timerStart = Date.now() +} + +export default clickTrash diff --git a/src/newcanvas/js/functions/bindEvents/clickUndo.js b/src/newcanvas/js/functions/bindEvents/clickUndo.js new file mode 100644 index 0000000..b4fe608 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/clickUndo.js @@ -0,0 +1,10 @@ +import undo from '../anbt/undo' +import ID from '../idSelector' + +const clickUndo = event => { + event.preventDefault() + ID('play').classList.remove('pause') + undo() +} + +export default clickUndo diff --git a/src/newcanvas/js/functions/bindEvents/colorClick.js b/src/newcanvas/js/functions/bindEvents/colorClick.js new file mode 100644 index 0000000..4e23327 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/colorClick.js @@ -0,0 +1,26 @@ +import globals from '../../globals'; +import setBackground from '../anbt/setBackground'; +import setColor from '../anbt/setColor'; +import getPointerType from './getPointerType'; +import updateChooseBackground from './updateChooseBackground'; +import updateColorIndicators from './updateColorIndicators'; + +const colorClick = event => { + if (event.touches || event.button === 0 || event.button === 2) { + event.preventDefault() + const colorButton = event.currentTarget + let color = colorButton.style.backgroundColor + if (globals.chooseBackground) { + if (colorButton.id !== 'eraser') setBackground(color) + updateChooseBackground(false) + } else { + if (colorButton.id === 'eraser') color = 'eraser' + // PointerType == 3 is pen tablet eraser + if (event.button === 2 || getPointerType() === 3) setColor(1, color) + else setColor(0, color) + updateColorIndicators() + } + } +} + +export default colorClick diff --git a/src/newcanvas/js/functions/bindEvents/doExport.js b/src/newcanvas/js/functions/bindEvents/doExport.js new file mode 100644 index 0000000..4d03049 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/doExport.js @@ -0,0 +1,12 @@ +import makePng from '../anbt/makePng' +import requestSave from '../anbt/requestSave' +import warnStrokesAfterPosition from './warnStrokesAfterPosition' + +const doExport = event => { + event.preventDefault() + if (warnStrokesAfterPosition()) return + makePng(600, 500, true) + requestSave() +} + +export default doExport diff --git a/src/newcanvas/js/functions/bindEvents/doImport.js b/src/newcanvas/js/functions/bindEvents/doImport.js new file mode 100644 index 0000000..0ca30ad --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/doImport.js @@ -0,0 +1,11 @@ +import fromLocalFile from '../anbt/fromLocalFile' +import ID from '../idSelector' + +const doImport = event => { + event.preventDefault() + ID('svgContainer').classList.add('loading') + fromLocalFile() + ID('svgContainer').classList.remove('loading') +} + +export default doImport diff --git a/src/newcanvas/js/functions/bindEvents/documentEvents/keyDown.js b/src/newcanvas/js/functions/bindEvents/documentEvents/keyDown.js new file mode 100644 index 0000000..091a2f1 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/documentEvents/keyDown.js @@ -0,0 +1,140 @@ +import anbt from '../../../anbt' +import globals from '../../../globals' +import redo from '../../anbt/redo' +import setBackground from '../../anbt/setBackground' +import setColor from '../../anbt/setColor' +import showEyedropperCursor from '../../anbt/showEyedropperCursor' +import strokeBegin from '../../anbt/strokeBegin' +import strokeEnd from '../../anbt/strokeEnd' +import undo from '../../anbt/undo' +import ID from '../../idSelector' +import playCommonDown from '../playCommonDown' +import removeEyedropper from '../removeEyedropper' +import updateChooseBackground from '../updateChooseBackground' +import updateColorIndicators from '../updateColorIndicators' + +const keyDown = event => { + const { options } = window + if (document.activeElement instanceof HTMLInputElement) return true + // Alt + if (event.keyCode === 18) { + // Opera Presto refuses to hide cursor =( + if (!navigator.userAgent.match(/\bPresto\b/)) + ID('svgContainer').classList.add('hidecursor') + showEyedropperCursor(true) + // The following is needed in case of Alt+Tab causing eyedropper to be stuck + ID('svgContainer').addEventListener('mousemove', removeEyedropper) + } else if (event.keyCode === 'Q'.charCodeAt(0)) { + event.preventDefault() + options.colorDoublePress = !options.colorDoublePress + } else if ( + event.keyCode === 'Z'.charCodeAt(0) || + (event.keyCode === 8 && anbt.unsaved) + ) { + event.preventDefault() + ID('play').classList.remove('pause') + undo() + } else if (event.keyCode === 'Y'.charCodeAt(0)) { + event.preventDefault() + ID('play').classList.remove('pause') + redo() + } else if (event.keyCode === 'X'.charCodeAt(0)) { + event.preventDefault() + const [color0, color1] = anbt.color + setColor(0, color1) + setColor(1, color0) + updateColorIndicators() + } else if (event.keyCode === 'B'.charCodeAt(0)) { + if (ID('setbackground').hidden) return + event.preventDefault() + updateChooseBackground(!globals.chooseBackground) + } else if ( + event.keyCode === 'E'.charCodeAt(0) && + !event.ctrlKey && + !event.metaKey + ) { + event.preventDefault() + setColor(0, 'eraser') + updateColorIndicators() + } else if ( + event.keyCode >= 48 && + event.keyCode <= 57 && + !event.ctrlKey && + !event.metaKey && + options.colorNumberShortcuts + ) { + event.preventDefault() + let index = event.keyCode === 48 ? 9 : event.keyCode - 49 + if ( + event.shiftKey || + (options.colorDoublePress && anbt.prevColorKey === index) + ) + index += 8 + anbt.prevColorKey = index + if (options.colorDoublePress) { + if (anbt.prevColorKeyTimer) clearTimeout(anbt.prevColorKeyTimer) + anbt.prevColorKeyTimer = setTimeout(() => (anbt.prevColorKey = -1), 500) + } + const elements = ID('colors').querySelectorAll('b') + if (index < elements.length) { + const color = + elements[index].id === 'eraser' + ? 'eraser' + : elements[index].style.backgroundColor + if (globals.chooseBackground) { + if (color !== 'eraser') setBackground(color) + updateChooseBackground(false) + } else { + setColor(0, color) + updateColorIndicators() + } + } + if (anbt.isStroking) { + strokeEnd() + const lastPoint = anbt.points[anbt.points.length - 1] + strokeBegin(lastPoint.x, lastPoint.y) + } + } else if ( + (event.keyCode === 189 || event.keyCode === 219 || event.keyCode === 188) && + !event.ctrlKey && + !event.metaKey + ) { + // - or [ or , + event.preventDefault() + for (let i = 1; i < globals.brushSizes.length; i++) { + if (anbt.size - globals.brushSizes[i] < 0.01) { + ID('brush' + (i - 1)).click() + break + } + } + } else if ( + (event.keyCode === 187 || event.keyCode === 221 || event.keyCode === 190) && + !event.ctrlKey && + !event.metaKey + ) { + // = or ] or . + event.preventDefault() + for (let i = 0; i < globals.brushSizes.length - 1; i++) { + if (anbt.size - globals.brushSizes[i] < 0.01) { + ID('brush' + (i + 1)).click() + break + } + } + } else if ( + event.keyCode >= 49 && + event.keyCode <= 52 && + (event.ctrlKey || event.metaKey) + ) { + // Ctrl+1,2,3,4 + event.preventDefault() + ID('brush' + (event.keyCode - 49)).click() + } else if ( + event.keyCode === 32 && + (event.ctrlKey || event.metaKey) && + !event.altKey && + !event.shiftKey + ) + playCommonDown(event) +} + +export default keyDown diff --git a/src/newcanvas/js/functions/bindEvents/documentEvents/keyUp.js b/src/newcanvas/js/functions/bindEvents/documentEvents/keyUp.js new file mode 100644 index 0000000..7fb77df --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/documentEvents/keyUp.js @@ -0,0 +1,10 @@ +import showEyedropperCursor from '../../anbt/showEyedropperCursor' +import ID from '../../idSelector' + +const keyUp = event => { + if (event.keyCode !== 18) return // return if not Alt + ID('svgContainer').classList.remove('hidecursor') + showEyedropperCursor(false) +} + +export default keyUp diff --git a/src/newcanvas/js/functions/bindEvents/exportToImgur.js b/src/newcanvas/js/functions/bindEvents/exportToImgur.js new file mode 100644 index 0000000..acc1ffd --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/exportToImgur.js @@ -0,0 +1,40 @@ +import anbt from '../../anbt' +import makePng from '../anbt/makePng' +import uploadToImgur from '../anbt/uploadToImgur' +import ID from '../idSelector' +import warnStrokesAfterPosition from './warnStrokesAfterPosition' + +const exportToImgur = event => { + event.preventDefault() + if (warnStrokesAfterPosition()) return + ID('imgur').childNodes[0].nodeValue = 'Uploading...' + ID('imgur').disabled = true + makePng(600, 500, true) + uploadToImgur(request => { + ID('imgur').childNodes[0].nodeValue = 'Upload to imgur' + ID('popup').classList.add('show') + ID('popuptitle').childNodes[0].nodeValue = 'Imgur upload result' + if (request && request.success) { + anbt.unsaved = false + //history.replaceState(null, null, "#" + r.data.id); + ID('imgururl').href = `http://imgur.com/${request.data.id}` + ID('imgururl').childNodes[0].nodeValue = 'Uploaded image' + ID('imgurdelete').href = `http://imgur.com/delete/${ + request.data.deletehash + }` + ID('imgurerror').childNodes[0].nodeValue = '' + if (window.inforum) + window.frameElement.ownerDocument.getElementById( + 'input-comment' + ).value += `![](http://i.imgur.com/${request.data.id}.png)` + } else { + const error = request.data + ? `Imgur error: ${request.data.error}` + : `Error: ${request}` + ID('imgurerror').childNodes[0].nodeValue = error + } + ID('imgur').disabled = false + }) +} + +export default exportToImgur diff --git a/src/newcanvas/js/functions/bindEvents/getPointerType.js b/src/newcanvas/js/functions/bindEvents/getPointerType.js new file mode 100644 index 0000000..84f84d3 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/getPointerType.js @@ -0,0 +1,8 @@ +import ID from '../idSelector'; + +const getPointerType = () => + ID('wacom') && ID('wacom').penAPI && ID('wacom').penAPI.isWacom + ? ID('wacom').penAPI.pointerType + : 0 + +export default getPointerType diff --git a/src/newcanvas/js/functions/bindEvents/knob/knobCommoUp.js b/src/newcanvas/js/functions/bindEvents/knob/knobCommoUp.js new file mode 100644 index 0000000..4eaca37 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/knob/knobCommoUp.js @@ -0,0 +1,13 @@ +import knobCommonMove from './knobCommonMove' + +const knobCommonUp = event => { + if (!event.button || (!event.touches && !event.touches.length)) { + event.preventDefault() + window.removeEventListener('mouseup', knobCommonUp) + window.removeEventListener('touchend', knobCommonUp) + window.removeEventListener('mousemove', knobCommonMove) + window.removeEventListener('touchmove', knobCommonMove) + } +} + +export default knobCommonUp diff --git a/src/newcanvas/js/functions/bindEvents/knob/knobCommomDown.js b/src/newcanvas/js/functions/bindEvents/knob/knobCommomDown.js new file mode 100644 index 0000000..320e520 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/knob/knobCommomDown.js @@ -0,0 +1,17 @@ +import globals from '../../../globals' +import ID from '../../idSelector' +import knobCommonMove from './knobCommonMove' +import knobCommonUp from './knobCommoUp' + +const knobCommonDown = event => { + if (event.button === 0 || (event.touches && event.touches.length === 1)) { + globals.rectangle = ID('seekbar').getBoundingClientRect() + knobCommonMove(event) + window.addEventListener('mouseup', knobCommonUp) + window.addEventListener('touchend', knobCommonUp) + window.addEventListener('mousemove', knobCommonMove) + window.addEventListener('touchmove', knobCommonMove) + } +} + +export default knobCommonDown diff --git a/src/newcanvas/js/functions/bindEvents/knob/knobCommonMove.js b/src/newcanvas/js/functions/bindEvents/knob/knobCommonMove.js new file mode 100644 index 0000000..05a6549 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/knob/knobCommonMove.js @@ -0,0 +1,21 @@ +import globals from '../../../globals' +import getSeekMax from '../../anbt/getSeekMax' +import seek from '../../anbt/seek' +import ID from '../../idSelector' + +const knobCommonMove = event => { + event.preventDefault() + const length = getSeekMax() + let x = event.touches + ? event.touches[0].pageX - globals.rectangle.left /*- pageXOffset*/ - 34 + : event.pageX - globals.rectangle.left - pageXOffset - 34 + x = Math.min(Math.max(-10, x), 492) + const position = Math.round(((x + 10) / 502) * length) + x = (position / length) * 502 - 10 + ID('knob').classList.add('smooth') + ID('knob').style.marginLeft = x + 'px' + seek(position) + ID('play').classList.remove('pause') +} + +export default knobCommonMove diff --git a/src/newcanvas/js/functions/bindEvents/knob/knobMove.js b/src/newcanvas/js/functions/bindEvents/knob/knobMove.js new file mode 100644 index 0000000..be003d7 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/knob/knobMove.js @@ -0,0 +1,16 @@ +import ID from '../../idSelector' + +const knobMove = fraction => { + const x = Math.floor(fraction * 502 - 10) + if (fraction > 0) { + ID('knob').classList.add('smooth') + } else { + ID('knob').classList.remove('smooth') + } + ID('knob').style.marginLeft = x + 'px' + if (fraction >= 1) { + ID('play').classList.remove('pause') + } +} + +export default knobMove diff --git a/src/newcanvas/js/functions/bindEvents/noDefault.js b/src/newcanvas/js/functions/bindEvents/noDefault.js new file mode 100644 index 0000000..09c2790 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/noDefault.js @@ -0,0 +1,3 @@ +const noDefault = event => event.preventDefault() + +export default noDefault diff --git a/src/newcanvas/js/functions/bindEvents/palette/choosePalette.js b/src/newcanvas/js/functions/bindEvents/palette/choosePalette.js new file mode 100644 index 0000000..c1e8995 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/palette/choosePalette.js @@ -0,0 +1,11 @@ +import setPaletteByName from './setPaletteByName' + +const choosePalette = event => { + if (event.touches || event.button === 0) { + event.preventDefault() + const name = event.currentTarget.childNodes[0].nodeValue + setPaletteByName(name) + } +} + +export default choosePalette diff --git a/src/newcanvas/js/functions/bindEvents/palette/closePaletteList.js b/src/newcanvas/js/functions/bindEvents/palette/closePaletteList.js new file mode 100644 index 0000000..8d2c85c --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/palette/closePaletteList.js @@ -0,0 +1,11 @@ +import ID from '../../idSelector' + +const closePaletteList = event => { + if (event.touches || event.button === 0) { + ID('palettechooser').classList.remove('open') + window.removeEventListener('mousedown', closePaletteList) + window.removeEventListener('touchend', closePaletteList) + } +} + +export default closePaletteList diff --git a/src/newcanvas/js/functions/bindEvents/palette/openPaletteList.js b/src/newcanvas/js/functions/bindEvents/palette/openPaletteList.js new file mode 100644 index 0000000..f637455 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/palette/openPaletteList.js @@ -0,0 +1,45 @@ +import palettes from '../../../palettes' +import ID from '../../idSelector' +import choosePalette from './choosePalette' +import closePaletteList from './closePaletteList' + +const openPaletteList = event => { + if (event.touches || event.button === 0) { + event.preventDefault() + const chooser = ID('palettechooser') + chooser.classList.toggle('open') + if (chooser.classList.contains('open')) { + setTimeout(() => { + window.addEventListener('mousedown', closePaletteList) + window.addEventListener('touchend', closePaletteList) + }, 1) + } + const keys = Object.keys(palettes) + if (chooser.childNodes.length < keys.length) { + const canvas = document.createElement('canvas') + canvas.height = 10 + const ctx = canvas.getContext('2d') + for (let i = chooser.childNodes.length; i < keys.length; i++) { + canvas.width = 8 * palettes[keys[i]].length + 2 + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.globalAlpha = 0.5 + ctx.fillRect(0, 0, canvas.width, canvas.height) + ctx.globalAlpha = 1 + palettes[keys[i]].forEach((color, index) => { + ctx.fillStyle = color + ctx.fillRect(index * 8 + 1, 1, 8, 8) + }) + const div = document.createElement('div') + div.appendChild(document.createTextNode(keys[i])) + div.style.backgroundImage = `url("${canvas.toDataURL()}")` + div.style.backgroundRepeat = 'no-repeat' + div.style.backgroundPosition = 'center 35px' + div.addEventListener('mousedown', choosePalette) + div.addEventListener('touchend', choosePalette) + chooser.appendChild(div) + } + } + } +} + +export default openPaletteList diff --git a/src/newcanvas/js/functions/bindEvents/palette/setPaletteByName.js b/src/newcanvas/js/functions/bindEvents/palette/setPaletteByName.js new file mode 100644 index 0000000..0f32d59 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/palette/setPaletteByName.js @@ -0,0 +1,28 @@ +import anbt from '../../../anbt' +import palettes from '../../../palettes' +import ID from '../../idSelector' +import colorClick from '../colorClick' +import noDefault from '../noDefault' + +const setPaletteByName = name => { + ID('palettename').childNodes[0].nodeValue = name + const colors = palettes[name] + anbt.palette = colors + const palette = ID('palette') + const elements = palette.querySelectorAll('b') + // Remove all current colors except for the eraser + elements.forEach(element => palette.removeChild(element)) + const eraser = elements[elements.length - 1] + colors.forEach(color => { + const bElement = document.createElement('b') + bElement.style.backgroundColor = color + bElement.addEventListener('mousedown', colorClick) + bElement.addEventListener('touchend', colorClick) + bElement.addEventListener('contextmenu', noDefault) + palette.appendChild(bElement) + // Eraser got on the front, put it on the back + palette.appendChild(eraser) + }) +} + +export default setPaletteByName diff --git a/src/newcanvas/js/functions/bindEvents/playCommonDown.js b/src/newcanvas/js/functions/bindEvents/playCommonDown.js new file mode 100644 index 0000000..e7f9b9f --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/playCommonDown.js @@ -0,0 +1,18 @@ +import anbt from '../../anbt' +import pause from '../anbt/pause' +import play from '../anbt/play' +import ID from '../idSelector' + +const playCommonDown = event => { + event.stopPropagation() + event.preventDefault() + if (anbt.isPlaying) { + ID('play').classList.remove('pause') + pause() + } else { + ID('play').classList.add('pause') + play() + } +} + +export default playCommonDown diff --git a/src/newcanvas/js/functions/bindEvents/popupClose.js b/src/newcanvas/js/functions/bindEvents/popupClose.js new file mode 100644 index 0000000..5b1b1f7 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/popupClose.js @@ -0,0 +1,8 @@ +import ID from '../idSelector' + +const popupClose = event => { + event.preventDefault() + ID('popup').classList.remove('show') +} + +export default popupClose diff --git a/src/newcanvas/js/functions/bindEvents/removeEyedropper.js b/src/newcanvas/js/functions/bindEvents/removeEyedropper.js new file mode 100644 index 0000000..f58a9c5 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/removeEyedropper.js @@ -0,0 +1,10 @@ +import showEyedropperCursor from '../anbt/showEyedropperCursor' + +const removeEyedropper = event => { + if (event.altKey) return + event.currentTarget.classList.remove('hidecursor') + showEyedropperCursor(false) + event.currentTarget.removeEventListener('mousemove', removeEyedropper) +} + +export default removeEyedropper diff --git a/src/newcanvas/js/functions/bindEvents/simulateSingleTouchStart.js b/src/newcanvas/js/functions/bindEvents/simulateSingleTouchStart.js new file mode 100644 index 0000000..bedcd8d --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/simulateSingleTouchStart.js @@ -0,0 +1,12 @@ +import globals from '../../globals' +import strokeBegin from '../anbt/strokeBegin' + +const simulateSingleTouchStart = () => { + if (!globals.touchSingle) return + const x = globals.lastTouch.pageX - globals.rectangle.left /*- pageXOffset*/ + const y = globals.lastTouch.pageY - globals.rectangle.top /*- pageYOffset*/ + strokeBegin(x, y, true) + globals.touchSingle = false +} + +export default simulateSingleTouchStart diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/contextMenu.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/contextMenu.js new file mode 100644 index 0000000..2726d80 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/contextMenu.js @@ -0,0 +1,3 @@ +const svgContextMenu = event => event.preventDefault() + +export default svgContextMenu \ No newline at end of file diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseDown.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseDown.js new file mode 100644 index 0000000..03bcb40 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseDown.js @@ -0,0 +1,37 @@ +import anbt from '../../../anbt' +import globals from '../../../globals' +import eyedropper from '../../anbt/eyedropper' +import setColor from '../../anbt/setColor' +import strokeBegin from '../../anbt/strokeBegin' +import ID from '../../idSelector' +import checkPlayingAndStop from '../checkPlayingAndStop' +import getPointerType from '../getPointerType' +import updateColorIndicators from '../updateColorIndicators' +import windowMouseMove from '../windowEvents/mouseMove' +import mouseUp from './mouseUp' + +const mouseDown = event => { + const { options } = window + if (event.button === 0 || event.button === 2) { + if (anbt.isStroking) return mouseUp(event) + if (checkPlayingAndStop()) return + event.preventDefault() + globals.rectangle = event.currentTarget.getBoundingClientRect() + const x = event.pageX - globals.rectangle.left - pageXOffset + const y = event.pageY - globals.rectangle.top - pageYOffset + + if (event.altKey) { + setColor(event.button ? 1 : 0, eyedropper(x, y)) + updateColorIndicators() + } else { + // PointerType == 3 is pen tablet eraser + const left = event.button === 0 && getPointerType() !== 3 + if (options.hideCross) ID('svgContainer').classList.add('hidecursor') + strokeBegin(x, y, left) + window.addEventListener('mouseup', mouseUp) + window.addEventListener('mousemove', windowMouseMove) + } + } +} + +export default mouseDown diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseLeave.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseLeave.js new file mode 100644 index 0000000..a77f887 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseLeave.js @@ -0,0 +1,5 @@ +import moveCursor from '../../anbt/moveCursor' + +const mouseLeave = () => moveCursor(-100, -100) // Hide brush cursor + +export default mouseLeave diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseMove.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseMove.js new file mode 100644 index 0000000..9d4c409 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseMove.js @@ -0,0 +1,29 @@ +import anbt from '../../../anbt' +import globals from '../../../globals' +import eyedropper from '../../anbt/eyedropper' +import moveCursor from '../../anbt/moveCursor' +import ID from '../../idSelector' + +const svgMouseMove = event => { + const { options } = window + globals.rectangle = event.currentTarget.getBoundingClientRect() + const x = event.pageX - globals.rectangle.left - pageXOffset + const y = event.pageY - globals.rectangle.top - pageYOffset + moveCursor(x, y) + // Highlight color we're pointing at + if (options.colorUnderCursorHint && !anbt.isStroking) { + const color = eyedropper(x, y) + if (globals.stSeenColorToHighlight !== color) { + const element = ID('colors').querySelector('b.hint') + if (element) element.classList.remove('hint') + const colorIndex = anbt.palette.indexOf(color) + if (colorIndex >= 0) { + const elements = ID('colors').querySelectorAll('b') + elements[colorIndex].classList.add('hint') + } + } + globals.lastSeenColorToHighlight = color + } +} + +export default svgMouseMove diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseUp.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseUp.js new file mode 100644 index 0000000..b7c9910 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseUp.js @@ -0,0 +1,17 @@ +import anbt from '../../../anbt' +import strokeEnd from '../../anbt/strokeEnd' +import ID from '../../idSelector' +import windowMouseMove from '../windowEvents/mouseMove' + +const mouseUp = event => { + const { options } = window + if (event.button === 0 || event.button === 2) { + event.preventDefault() + if (anbt.isStroking) strokeEnd() + if (options.hideCross) ID('svgContainer').classList.remove('hidecursor') + window.removeEventListener('mouseup', mouseUp) + window.removeEventListener('mousemove', windowMouseMove) + } +} + +export default mouseUp diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/touchStart.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/touchStart.js new file mode 100644 index 0000000..6472810 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/touchStart.js @@ -0,0 +1,36 @@ +import anbt from '../../../anbt' +import globals from '../../../globals' +import strokeEnd from '../../anbt/strokeEnd' +import checkPlayingAndStop from '../checkPlayingAndStop' +import touchEnd from '../windowEvents/touchEnd' +import touchMove from '../windowEvents/touchMove' +import touchUndoRedo from '../windowEvents/touchUndoRedo' + +const touchStart = event => { + if (event.touches.length === 1) { + if (checkPlayingAndStop()) return + // Let two-finger scrolling, pinching, etc. work. + // This requires moving dot-drawing to simulateSingleTouchStart() + globals.rectangle = event.currentTarget.getBoundingClientRect() + globals.touchSingle = true + globals.lastTouch = event.touches[0] + window.addEventListener('touchend', touchEnd) + window.addEventListener('touchmove', touchMove) + } else { + // Enable two-finger undo and redo: + // 1 o o + // 2 o o o o + // 3 . o o . + // Undo Redo + if (globals.touchSingle && event.touches.length === 3) { + globals.lastTouch = event.touches[1] + window.addEventListener('touchend', touchUndoRedo) + } + globals.touchSingle = false + window.removeEventListener('touchend', touchEnd) + window.removeEventListener('touchmove', touchMove) + if (anbt.isStroking) strokeEnd() + } +} + +export default touchStart diff --git a/src/newcanvas/js/functions/bindEvents/updateChooseBackground.js b/src/newcanvas/js/functions/bindEvents/updateChooseBackground.js new file mode 100644 index 0000000..817f48c --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/updateChooseBackground.js @@ -0,0 +1,15 @@ +import globals from '../../globals'; +import ID from '../idSelector'; + +const updateChooseBackground = chooseBackground => { + globals.chooseBackground = chooseBackground + if (chooseBackground) { + ID('colors').classList.add('setbackground') + ID('setbackground').classList.add('sel') + } else { + ID('colors').classList.remove('setbackground') + ID('setbackground').classList.remove('sel') + } +} + +export default updateChooseBackground diff --git a/src/newcanvas/js/functions/bindEvents/updateColorIndicators.js b/src/newcanvas/js/functions/bindEvents/updateColorIndicators.js new file mode 100644 index 0000000..76eeecc --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/updateColorIndicators.js @@ -0,0 +1,17 @@ +import anbt from '../../anbt'; +import ID from '../idSelector'; + +const updateColorIndicators = () => { + const { colors } = anbt + ;['primary', 'secondary'].forEach((id, index) => { + if (colors[index] === 'eraser') { + ID(id).style.backgroundColor = 'pink' + ID(id).classList.add('eraser') + } else { + ID(id).style.backgroundColor = colors[index] + ID(id).classList.remove('eraser') + } + }) +} + +export default updateColorIndicators diff --git a/src/newcanvas/js/functions/bindEvents/warnStrokesAfterPosition.js b/src/newcanvas/js/functions/bindEvents/warnStrokesAfterPosition.js new file mode 100644 index 0000000..b4e7e9c --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/warnStrokesAfterPosition.js @@ -0,0 +1,11 @@ +import anbt from '../../anbt' +import getSeekMax from '../anbt/getSeekMax' + +const warnStrokesAfterPosition = () => { + if (anbt.position < getSeekMax()) + return !confirm( + 'Strokes after current position wi)ll be discarded. Continue?' + ) +} + +export default warnStrokesAfterPosition diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/beforeUnload.js b/src/newcanvas/js/functions/bindEvents/windowEvents/beforeUnload.js new file mode 100644 index 0000000..f3147a0 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/windowEvents/beforeUnload.js @@ -0,0 +1,10 @@ +import anbt from '../../../anbt' + +const beforeUnload = event => { + if (!anbt.unsaved) return + const message = "You haven't saved the drawing. Abandon?" + event.returnValue = message + return message +} + +export default beforeUnload diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/contextMenu.js b/src/newcanvas/js/functions/bindEvents/windowEvents/contextMenu.js new file mode 100644 index 0000000..60791ec --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/windowEvents/contextMenu.js @@ -0,0 +1,7 @@ +import anbt from '../../../anbt' + +const windowContextMenu = event => { + if (anbt.isStroking) event.preventDefault() +} + +export default windowContextMenu diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/error.js b/src/newcanvas/js/functions/bindEvents/windowEvents/error.js new file mode 100644 index 0000000..bd262f5 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/windowEvents/error.js @@ -0,0 +1,3 @@ +const error = event => alert(event) + +export default error diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/mouseMove.js b/src/newcanvas/js/functions/bindEvents/windowEvents/mouseMove.js new file mode 100644 index 0000000..85f156e --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/windowEvents/mouseMove.js @@ -0,0 +1,13 @@ +import anbt from '../../../anbt' +import globals from '../../../globals' +import strokeAdd from '../../anbt/strokeAdd' + +const windowMouseMove = event => { + event.preventDefault() + if (!anbt.isStroking) return + const x = event.pageX - globals.rectangle.left - pageXOffset + const y = event.pageY - globals.rectangle.top - pageYOffset + strokeAdd(x, y) +} + +export default windowMouseMove diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/touchEnd.js b/src/newcanvas/js/functions/bindEvents/windowEvents/touchEnd.js new file mode 100644 index 0000000..7c33dba --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/windowEvents/touchEnd.js @@ -0,0 +1,14 @@ +import strokeEnd from '../../anbt/strokeEnd' +import simulateSingleTouchStart from '../simulateSingleTouchStart' +import touchMove from './touchMove' + +const touchEnd = event => { + if (event.touches.length !== 0) return + simulateSingleTouchStart() + event.preventDefault() + window.removeEventListener('touchend', touchEnd) + window.removeEventListener('touchmove', touchMove) + strokeEnd() +} + +export default touchEnd diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/touchMove.js b/src/newcanvas/js/functions/bindEvents/windowEvents/touchMove.js new file mode 100644 index 0000000..d4b5184 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/windowEvents/touchMove.js @@ -0,0 +1,15 @@ +import anbt from '../../../anbt' +import globals from '../../../globals' +import simulateSingleTouchStart from '../simulateSingleTouchStart' + +const touchMove = event => { + if (event.touches.length !== 1) return + simulateSingleTouchStart() + event.preventDefault() + if (!anbt.isStroking) return + const x = event.touches[0].pageX - globals.rectangle.left /*- pageXOffset*/ + const y = event.touches[0].pageY - globals.rectangle.top /*- pageYOffset*/ + anbt.StrokeAdd(x, y) +} + +export default touchMove diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/touchUndoRedo.js b/src/newcanvas/js/functions/bindEvents/windowEvents/touchUndoRedo.js new file mode 100644 index 0000000..c426074 --- /dev/null +++ b/src/newcanvas/js/functions/bindEvents/windowEvents/touchUndoRedo.js @@ -0,0 +1,21 @@ +import globals from '../../../globals' +import redo from '../../anbt/redo' +import undo from '../../anbt/undo' +import ID from '../../idSelector' + +const touchUndoRedo = event => { + if (event.changedTouches.length === 1 && event.touches.length === 1) { + const { pageX, pageY } = event.changedTouches[0] + if ( + Math.abs(pageX - globals.lastTouch.pageX) < 10 && + Math.abs(pageY - globals.lastTouch.pageY) < 10 + ) { + ID('play').classList.remove('pause') + if (pageX < event.touches[0].pageX) undo() + else redo() + } + } + window.removeEventListener('touchend', touchUndoRedo) +} + +export default touchUndoRedo diff --git a/src/newcanvas/js/functions/buildSmoothPath.js b/src/newcanvas/js/functions/buildSmoothPath.js new file mode 100644 index 0000000..9c0e284 --- /dev/null +++ b/src/newcanvas/js/functions/buildSmoothPath.js @@ -0,0 +1,72 @@ +const buildSmoothPath = (points, path) => { + const { length } = points + if (length < 2) return + path.pathSegList.initialize( + path.createSVGPathSegMovetoAbs(points[0].x, points[0].y) + ) + if (!window.options.smoothening) { + for (let i = 1; i < points.length; i++) + path.pathSegList.appendItem( + path.createSVGPathSegLinetoAbs(points[i].x, points[i].y) + ) + return + } + path.pathSegList.appendItem( + path.createSVGPathSegLinetoAbs(points[1].x, points[1].y) + ) + if (length < 3) return + let prevtangent + for (let i = 1; i < length - 1; i++) { + const previousPoint = points[i - 1] + const currentPoint = points[i] + const nextPoint = points[i + 1] + const dx1 = currentPoint.x - previousPoint.x + const dy1 = currentPoint.y - previousPoint.y + const angle1 = Math.atan2(dy1, dx1) + const dist1 = Math.sqrt(dx1 ** 2 + dy1 ** 2) + const dx2 = nextPoint.x - currentPoint.x + const dy2 = nextPoint.y - currentPoint.y + const angle2 = Math.atan2(dy2, dx2) + const dist2 = Math.sqrt(dx2 ** 2 + dy2 ** 2) + const tangent = (angle1 + angle2) / 2 + if (i > 1) { + let good = false + if (Math.abs(angle2 - angle1) >= Math.PI / 4) { + path.pathSegList.appendItem( + path.createSVGPathSegLinetoAbs(currentPoint.x, currentPoint.y) + ) + } else { + if (good && dist1 / dist2 >= 0.4 && dist1 / dist2 <= 2.5) { + const t1 = { + x: previousPoint.x + Math.cos(prevtangent) * dist1 * 0.4, + y: previousPoint.y + Math.sin(prevtangent) * dist1 * 0.4 + } + const t2 = { + x: currentPoint.x - Math.cos(tangent) * dist2 * 0.4, + y: currentPoint.y - Math.sin(tangent) * dist2 * 0.4 + } + path.pathSegList.appendItem( + path.createSVGPathSegCurvetoCubicAbs( + currentPoint.x, + currentPoint.y, + t1.x, + t1.y, + t2.x, + t2.y + ) + ) + } else { + path.pathSegList.appendItem( + path.createSVGPathSegLinetoAbs(currentPoint.x, currentPoint.y) + ) + good = true + } + } + } + prevtangent = tangent + } + const c = points[length - 1] + path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y)) +} + +export default buildSmoothPath diff --git a/src/newcanvas/js/functions/conversions/base64ToBytes.js b/src/newcanvas/js/functions/conversions/base64ToBytes.js new file mode 100644 index 0000000..019131e --- /dev/null +++ b/src/newcanvas/js/functions/conversions/base64ToBytes.js @@ -0,0 +1,5 @@ +import stringToBytes from './stringToBytes' + +const base64ToBytes = base64 => stringToBytes(atob(base64)) + +export default base64ToBytes diff --git a/src/newcanvas/js/functions/conversions/bytesToStrings.js b/src/newcanvas/js/functions/conversions/bytesToStrings.js new file mode 100644 index 0000000..d97d28f --- /dev/null +++ b/src/newcanvas/js/functions/conversions/bytesToStrings.js @@ -0,0 +1,4 @@ +const bytesToString = bytes => + [...bytes].map(byte => String.fromCharCode(byte)).join('') + +export default bytesToString \ No newline at end of file diff --git a/src/newcanvas/js/functions/conversions/colorToDword.js b/src/newcanvas/js/functions/conversions/colorToDword.js new file mode 100644 index 0000000..1838904 --- /dev/null +++ b/src/newcanvas/js/functions/conversions/colorToDword.js @@ -0,0 +1,8 @@ +import colorToRgba from './colorToRgba' + +const colorToDword = color => + colorToRgba(color) + .map(value => String.fromCharCode(value)) + .join('') + +export default colorToDword diff --git a/src/newcanvas/js/functions/conversions/colorToHex.js b/src/newcanvas/js/functions/conversions/colorToHex.js new file mode 100644 index 0000000..e6d1286 --- /dev/null +++ b/src/newcanvas/js/functions/conversions/colorToHex.js @@ -0,0 +1,6 @@ +import colorToRgba from './colorToRgba' +import rgbToHex from './rgbToHex' + +const colorToHex = color => rgbToHex(colorToRgba(color)) + +export default colorToHex diff --git a/src/newcanvas/js/functions/conversions/colorToRgba.js b/src/newcanvas/js/functions/conversions/colorToRgba.js new file mode 100644 index 0000000..890ac0f --- /dev/null +++ b/src/newcanvas/js/functions/conversions/colorToRgba.js @@ -0,0 +1,19 @@ +const colorToRgba = color => + color[0] === '#' + ? color.length === 4 + ? [...(color.substr(1, 3) + 'F')].map(rgb => parseInt(rgb + rgb, 16)) + : (color + 'FF') + .substr(1, 8) + .match(/.{2}/g) + .map(rgb => parseInt(rgb, 16)) + : color.substr(0, 4) === 'rgba' + ? color + .match(/[\d\.]+/g) + .map((rgba, index) => + index === 3 ? Math.floor(parseFloat(rgba) * 255) : parseInt(rgba, 10) + ) + : color.substr(0, 3) === 'rgb' + ? (color + 255).match(/[\d\.]+/g).map(rgba => parseInt(rgba, 10)) + : [0, 0, 0, 255] + +export default colorToRgba diff --git a/src/newcanvas/js/functions/conversions/rgbToHex.js b/src/newcanvas/js/functions/conversions/rgbToHex.js new file mode 100644 index 0000000..6a421fe --- /dev/null +++ b/src/newcanvas/js/functions/conversions/rgbToHex.js @@ -0,0 +1,4 @@ +const rgbToHex = rgb => + '#' + rgb.map((value, index) => index < 3 ? ('0' + value.toString(16)).slice(-2) : '').join('') + +export default rgbToHex diff --git a/src/newcanvas/js/functions/conversions/rgbToLab.js b/src/newcanvas/js/functions/conversions/rgbToLab.js new file mode 100644 index 0000000..783863e --- /dev/null +++ b/src/newcanvas/js/functions/conversions/rgbToLab.js @@ -0,0 +1,17 @@ +const rgbToLab = rgb => { + const [r, g, b] = rgb.map(value => + value > 10 + ? Math.pow((value / 255 + 0.055) / 1.055, 2.4) + : value / 255 / 12.92 + ) + const [x, y, z] = [ + (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047, + r * 0.2126 + g * 0.7152 + b * 0.0722, + (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883 + ].map(value => + value > 0.008856 ? Math.pow(value, 1 / 3) : 7.787 * value + 16 / 116 + ) + return [116 * y - 16, 500 * (x - y), 200 * (y - z)] +} + +export default rgbToLab diff --git a/src/newcanvas/js/functions/conversions/stringToBytes.js b/src/newcanvas/js/functions/conversions/stringToBytes.js new file mode 100644 index 0000000..c2585ab --- /dev/null +++ b/src/newcanvas/js/functions/conversions/stringToBytes.js @@ -0,0 +1,4 @@ +const stringToBytes = binaryString => + new Uint8Array([...binaryString].map(character => character.charCodeAt(0))) + +export default stringToBytes diff --git a/src/newcanvas/js/functions/crc32.js b/src/newcanvas/js/functions/crc32.js new file mode 100644 index 0000000..1c45d4c --- /dev/null +++ b/src/newcanvas/js/functions/crc32.js @@ -0,0 +1,15 @@ +import makeCRCTable from './makeCRCTable' + +const crc32 = (string, string2) => { + const crcTable = makeCRCTable() + let crc = -1 + for (let i = 0; i < string.length; i++) + crc = (crc >>> 8) ^ crcTable[(crc ^ string.charCodeAt(i)) & 0xff] + if (string2) { + for (let i = 0; i < string2.length; i++) + crc = (crc >>> 8) ^ crcTable[(crc ^ string2.charCodeAt(i)) & 0xff] + } + return (crc ^ -1) >>> 0 +} + +export default crc32 diff --git a/src/newcanvas/js/functions/createSvgElement.js b/src/newcanvas/js/functions/createSvgElement.js new file mode 100644 index 0000000..f232454 --- /dev/null +++ b/src/newcanvas/js/functions/createSvgElement.js @@ -0,0 +1,11 @@ +const createSvgElement = (name, attributs) => { + const element = document.createElementNS('http://www.w3.org/2000/svg', name) + if (attributs) + Object.keys(attributs).forEach(attribut => { + if (attributs[attribut]) + element.setAttribute(attribut, attributs[attribut]) + }) + return element +} + +export default createSvgElement \ No newline at end of file diff --git a/src/newcanvas/js/functions/fixTabletPluginGoingAwol.js b/src/newcanvas/js/functions/fixTabletPluginGoingAwol.js new file mode 100644 index 0000000..8f6c957 --- /dev/null +++ b/src/newcanvas/js/functions/fixTabletPluginGoingAwol.js @@ -0,0 +1,14 @@ +import ID from './idSelector' + +const fixTabletPluginGoingAwol = () => { + const stupidPlugin = ID('wacom') + const container = ID('wacomContainer') + window.onblur = () => { + if (container.childNodes.length === 1) container.removeChild(stupidPlugin) + } + window.onfocus = () => { + if (container.childNodes.length === 0) container.appendChild(stupidPlugin) + } +} + +export default fixTabletPluginGoingAwol diff --git a/src/newcanvas/js/functions/getClosestColor.js b/src/newcanvas/js/functions/getClosestColor.js new file mode 100644 index 0000000..33c2282 --- /dev/null +++ b/src/newcanvas/js/functions/getClosestColor.js @@ -0,0 +1,22 @@ +import colorToHex from './conversions/colorToHex' +import colorToRgba from './conversions/colorToRgba' +import rgbToHex from './conversions/rgbToHex' +import getColorDistance from './getColorDistance' +import ID from './idSelector' + +const getClosestColor = (rgb, palette) => { + // Allow any color in sandbox and friend games + if ( + ID('newcanvasyo').classList.contains('sandbox') || + (window.gameInfo && window.gameInfo.friend) + ) + return rgbToHex([...rgb]) + const distances = palette + .slice(0) + .map(color => getColorDistance([...rgb], colorToRgba(color))) + const minimum = Math.min(...distances) + const closestColor = palette[distances.indexOf(minimum)] + return colorToHex(closestColor) +} + +export default getClosestColor diff --git a/src/newcanvas/js/functions/getColorDistance.js b/src/newcanvas/js/functions/getColorDistance.js new file mode 100644 index 0000000..fb93c32 --- /dev/null +++ b/src/newcanvas/js/functions/getColorDistance.js @@ -0,0 +1,12 @@ +import rgbToLab from './conversions/rgbToLab' + +const getColorDistance = (rgb1, rgb2) => { + const lab1 = rgbToLab(rgb1) + const lab2 = rgbToLab(rgb2) + const l = lab2[0] - lab1[0] + const a = lab2[1] - lab1[1] + const b = lab2[2] - lab1[2] + return Math.sqrt(l ** 2 * 2 + a ** 2 + b ** 2) +} + +export default getColorDistance diff --git a/src/newcanvas/js/functions/getSqSegDist.js b/src/newcanvas/js/functions/getSqSegDist.js new file mode 100644 index 0000000..6f0d622 --- /dev/null +++ b/src/newcanvas/js/functions/getSqSegDist.js @@ -0,0 +1,20 @@ +const getSqSegDist = (point, point1, point2) => { + let { x, y } = point1 + let dx = point2.x - x + let dy = point2.y - y + if (dx !== 0 || dy !== 0) { + var t = ((point.x - x) * dx + (point.y - y) * dy) / (dx * dx + dy * dy) + if (t > 1) { + x = point2.x + y = point2.y + } else if (t > 0) { + x += dx * t + y += dy * t + } + } + dx = point.x - x + dy = point.y - y + return dx * dx + dy * dy +} + +export default getSqSegDist diff --git a/src/newcanvas/js/functions/idSelector.js b/src/newcanvas/js/functions/idSelector.js new file mode 100644 index 0000000..93b06f6 --- /dev/null +++ b/src/newcanvas/js/functions/idSelector.js @@ -0,0 +1,3 @@ +const ID = id => document.getElementById(id) + +export default ID \ No newline at end of file diff --git a/src/newcanvas/js/functions/int16be.js b/src/newcanvas/js/functions/int16be.js new file mode 100644 index 0000000..5a78861 --- /dev/null +++ b/src/newcanvas/js/functions/int16be.js @@ -0,0 +1,6 @@ +const int16be = (byte1, byte2) => { + const v = (byte1 << 8) | byte2 + return v > 32767 ? v - 65536 : v +} + +export default int16be diff --git a/src/newcanvas/js/functions/makeCRCTable.js b/src/newcanvas/js/functions/makeCRCTable.js new file mode 100644 index 0000000..9208fef --- /dev/null +++ b/src/newcanvas/js/functions/makeCRCTable.js @@ -0,0 +1,11 @@ +const makeCRCTable = () => { + const crcTable = [] + for (let n = 0; n < 256; n++) { + let c = n + for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1 + crcTable.push(c) + } + return crcTable +} + +export default makeCRCTable diff --git a/src/newcanvas/js/functions/needToGoDeeper.js b/src/newcanvas/js/functions/needToGoDeeper.js new file mode 100644 index 0000000..01e499b --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper.js @@ -0,0 +1,85 @@ +import anbt from '../anbt'; +import fromPng from './anbt/fromPng'; +import fromUrl from './anbt/fromUrl'; +import base64ToBytes from './conversions/base64ToBytes'; +import fixTabletPluginGoingAwol from './fixTabletPluginGoingAwol'; +import ID from './idSelector'; +import ajax from './needToGoDeeper/ajax'; +import bindCanvasEvents from './needToGoDeeper/bindCanvasEvents'; +import extractInfoFromHTML from './needToGoDeeper/extractInfoFromHTML'; +import getParametersFromPlay from './needToGoDeeper/getParametersFromPlay'; +import handleSandboxParameters from './needToGoDeeper/handleSandboxParameters'; + +const needToGoDeeper = () => { + const { options, insandbox, panelid } = window + window.onerror = (error, file, line) => { + // Silence the bogus error message from the overwritten page's timer + if (error.toString().includes('periodsToSeconds')) return + // Silence the useless error message + if (error.toString().match(/script error/i)) return + alert(line ? `${error}\nline: ${line}` : error) + } + if (options.newCanvasCSS) { + const parent = + document.getElementsByTagName('head')[0] || document.documentElement + const style = document.createElement('style') + style.type = 'text/css' + const textNode = document.createTextNode(options.newCanvasCSS) + style.appendChild(textNode) + parent.appendChild(style) + } + if (options.enableWacom) { + const stupidPlugin = document.createElement('object') + const container = ID('wacomContainer') + stupidPlugin.setAttribute('id', 'wacom') + stupidPlugin.setAttribute('type', 'application/x-wacomtabletplugin') + stupidPlugin.setAttribute('width', '1') + stupidPlugin.setAttribute('height', '1') + container.appendChild(stupidPlugin) + if (options.fixTabletPluginGoingAWOL) fixTabletPluginGoingAwol() + } + bindCanvasEvents() + if (insandbox) { + if (panelid) + ajax('GET', `/panel/drawing/${panelid}/-/`, { + load: response => { + window.gameInfo = extractInfoFromHTML(response) + fromUrl(`${window.gameInfo.drawinglink}?anbt`) // workaround for non-CORS cached image + handleSandboxParameters() + }, + error: () => { + alert('Error loading the panel page. Please try again.') + } + }) + else { + ajax('GET', '/sandbox/', { + load: response => { + window.gameInfo = extractInfoFromHTML(response) + handleSandboxParameters() + }, + error: () => {} + }) + if (options.backup) { + const pngdata = localStorage.getItem('anbt_drawingbackup_newcanvas') + if (pngdata) { + fromPng(base64ToBytes(pngdata.substr(22)).buffer) + localStorage.removeItem('anbt_drawingbackup_newcanvas') + } + } + } + } else { + ID('newcanvasyo').className = 'play' + getParametersFromPlay() + } + // Poor poor memory devices, let's save on memory to avoid them "crashing"... + if (/iPad|iPhone/.test(navigator.userAgent)) anbt.fastUndoLevels = 3 + window.$ = () => { + alert( + 'Some additional script conflicts with ANBT new canvas, please disable it.' + ) + window.$ = null + throw new Error('Script conflict with ANBT new canvas') + } +} + +export default needToGoDeeper diff --git a/src/newcanvas/js/functions/needToGoDeeper/ajax.js b/src/newcanvas/js/functions/needToGoDeeper/ajax.js new file mode 100644 index 0000000..40357fa --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/ajax.js @@ -0,0 +1,47 @@ +const ajax = (type, url, params) => { + const { options } = window + const request = new XMLHttpRequest() + request.open(type, url) + if (params.header) + request.setRequestHeader(params.header[0], params.header[1]) + params.retry = 5 + request.timeout = 15000 + request.ontimeout = () => { + if (params.retry > 0) { + if (!options.retryEnabled) return + document.body.style.cursor = 'progress' + params.retry-- + ajax(type, url, params) + } else { + document.body.style.cursor = '' + params.error() + } + } + request.onload = () => { + if ( + url === '/play/skip.json' && + request.error === 'Sorry, but we couldn\u0027t find your current game.' + ) { + location.reload() + return + } + if ( + url === '/play/exit.json' && + request.error === 'Sorry, but we couldn\u0027t find your current game.' + ) { + location.pathname = '/' + return + } + params.load(request.responseText) + } + request.onerror = () => { + if (params.error) params.error(request) + else params.load(request) + } + if (params.obj) request.send(JSON.stringify(params.obj)) + else request.send() + document.body.style.cursor = '' + return +} + +export default ajax diff --git a/src/newcanvas/js/functions/needToGoDeeper/bindCanvasEvents.js b/src/newcanvas/js/functions/needToGoDeeper/bindCanvasEvents.js new file mode 100644 index 0000000..0d8b64b --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/bindCanvasEvents.js @@ -0,0 +1,41 @@ +import ID from '../idSelector' +import backToForum from './events/backToForum' +import bookmark from './events/bookmark' +import caption from './events/caption' +import exit from './events/exit' +import quit from './events/quit' +import report from './events/report' +import skip from './events/skip' +import start from './events/start' +import submitCaption from './events/submitCaption' +import submitDrawing from './events/submitDrawing' +import timePlus from './events/timePlus' +import updateUsedChars from './events/updateUsedChars' + +const bindCanvasEvents = () => { + const { options, inforum } = window + if (inforum) { + ID('quit').addEventListener('click', quit) + const backForum = document.createElement('button') + backForum.href = '/' + backForum.setAttribute('class', 'submit exit') + backForum.title = 'Exit' + backForum.textContent = 'Exit' + backForum.addEventListener('click', backToForum) + ID('submit').parentNode.insertBefore(backForum, ID('submit').nextSibling) + } + ID('exit').addEventListener('click', exit) + ID('skip').addEventListener('click', skip) + ID('start').addEventListener('click', start) + ID('report').addEventListener('click', report) + ID('bookmark').addEventListener('click', bookmark) + ID('submit').addEventListener('click', submitDrawing) + ID('submitcaption').addEventListener('click', submitCaption) + if (options.enterToCaption) ID('caption').addEventListener('keydown', caption) + ID('caption').addEventListener('change', updateUsedChars) + ID('caption').addEventListener('keydown', updateUsedChars) + ID('caption').addEventListener('input', updateUsedChars) + ID('timeplus').addEventListener('click', timePlus) +} + +export default bindCanvasEvents diff --git a/src/newcanvas/js/functions/needToGoDeeper/decodeHTML.js b/src/newcanvas/js/functions/needToGoDeeper/decodeHTML.js new file mode 100644 index 0000000..061b98e --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/decodeHTML.js @@ -0,0 +1,7 @@ +const decodeHTML = html => { + const txt = document.createElement('textarea') + txt.innerHTML = html + return txt.value +} + +export default decodeHTML diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/backToForum.js b/src/newcanvas/js/functions/needToGoDeeper/events/backToForum.js new file mode 100644 index 0000000..e7ec940 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/backToForum.js @@ -0,0 +1,8 @@ +const backToForum = event => { + event.preventDefault() + window.frameElement.ownerDocument.querySelector( + '.v--modal-overlay' + ).outerHTML = '' +} + +export default backToForum diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/bookmark.js b/src/newcanvas/js/functions/needToGoDeeper/events/bookmark.js new file mode 100644 index 0000000..e224cca --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/bookmark.js @@ -0,0 +1,16 @@ +import decodeHTML from '../decodeHTML' +import ID from '../../idSelector' + +const bookmark = () => { + const { getLocalStorageItem } = window + ID('bookmark').disabled = true + const games = getLocalStorageItem('gpe_gameBookmarks', {}) + const caption = window.gameInfo.caption + games[window.gameInfo.gameid] = { + time: Date.now(), + caption: caption ? decodeHTML(caption) : '' + } + localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) +} + +export default bookmark diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/caption.js b/src/newcanvas/js/functions/needToGoDeeper/events/caption.js new file mode 100644 index 0000000..f9a5631 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/caption.js @@ -0,0 +1,9 @@ +import ID from '../../idSelector' + +const caption = event => { + if (event.keyCode !== 13) return + event.preventDefault() + ID('submitcaption').click() +} + +export default caption diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/exit.js b/src/newcanvas/js/functions/needToGoDeeper/events/exit.js new file mode 100644 index 0000000..689808c --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/exit.js @@ -0,0 +1,57 @@ +import ID from '../../idSelector' +import ajax from '../ajax' +import exitToSandbox from '../exitToSandbox' + +const exit = () => { + const { gameInfo, incontest } = window + if (incontest) { + if (!confirm('Quit the contest? Entry coins will be lost!')) return + ID('exit').disabled = true + ajax('POST', '/contests/exit.json', { + load: () => { + ID('exit').disabled = false + window.drawing_aborted = true + exitToSandbox() + document.location.pathname = '/contests/' + }, + error: () => { + ID('exit').disabled = false + alert('Server error. :( Try again?') + } + }) + return + } + if (gameInfo.drawfirst) { + if (!confirm('Abort creating a draw first game?')) return + ID('exit').disabled = true + ajax('POST', '/play/abort-start.json', { + obj: { + game_token: gameInfo.gameid + }, + load: () => { + ID('exit').disabled = false + window.drawing_aborted = true + exitToSandbox() + document.location.pathname = '/create/' + }, + error: () => { + ID('exit').disabled = false + alert('Server error. :( Try again?') + } + }) + return + } + if (!confirm('Really exit?')) return + ID('exit').disabled = true + ajax('POST', '/play/exit.json', { + obj: { + game_token: gameInfo.gameid + }, + load: () => { + ID('exit').disabled = false + exitToSandbox() + } + }) +} + +export default exit diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/quit.js b/src/newcanvas/js/functions/needToGoDeeper/events/quit.js new file mode 100644 index 0000000..7315f92 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/quit.js @@ -0,0 +1,6 @@ +const quit = event => { + event.preventDefault() + window.top.location.href = 'https://drawception.com/' +} + +export default quit diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/report.js b/src/newcanvas/js/functions/needToGoDeeper/events/report.js new file mode 100644 index 0000000..36492fb --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/report.js @@ -0,0 +1,18 @@ +import ID from '../../idSelector' +import ajax from '../ajax' +import getParametersFromPlay from '../getParametersFromPlay' + +const report = () => { + if (!confirm('Report this panel?')) return + ajax('POST', '/play/flag.json', { + obj: { + game_token: window.gameInfo.gameid + }, + load: () => { + ID('report').disabled = false + getParametersFromPlay() + } + }) +} + +export default report diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/skip.js b/src/newcanvas/js/functions/needToGoDeeper/events/skip.js new file mode 100644 index 0000000..6bd2f50 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/skip.js @@ -0,0 +1,21 @@ +import ID from '../../idSelector' +import ajax from '../ajax' +import getParametersFromPlay from '../getParametersFromPlay' +import unsavedStopAction from '../unsavedStopAction' + +const skip = () => { + if (unsavedStopAction()) return + ID('skip').disabled = true + ajax('POST', '/play/skip.json', { + obj: { + game_token: window.gameInfo.gameid + }, + load: () => getParametersFromPlay(), // Postpone enabling skip until we get game info + error: () => { + ID('skip').disabled = false + getParametersFromPlay() + } + }) +} + +export default skip diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/start.js b/src/newcanvas/js/functions/needToGoDeeper/events/start.js new file mode 100644 index 0000000..9ec53c6 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/start.js @@ -0,0 +1,11 @@ +import ID from '../../idSelector' +import getParametersFromPlay from '../getParametersFromPlay' +import unsavedStopAction from '../unsavedStopAction' + +const start = () => { + if (unsavedStopAction()) return + ID('start').disabled = true + getParametersFromPlay() +} + +export default start diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/submitCaption.js b/src/newcanvas/js/functions/needToGoDeeper/events/submitCaption.js new file mode 100644 index 0000000..77b17a4 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/submitCaption.js @@ -0,0 +1,54 @@ +import ID from '../../idSelector' +import ajax from '../ajax' +import onCaptionSuccess from '../onCaptionSuccess' + +const submitCaption = () => { + const { incontest, gameInfo } = window + const title = ID('caption').value + if (!title) { + ID('caption').focus() + return alert("You haven't entered a caption!") + } + window.submitting = true + ID('submitcaption').disabled = true + const url = incontest + ? '/contests/submit-caption.json' + : '/play/describe.json' + ajax('POST', url, { + obj: { + game_token: gameInfo.gameid, + title + }, + load: response => { + try { + response = JSON.parse(response) + } catch (e) { + response = { + error: response + } + } + if (response.error) { + ID('submitcaption').disabled = false + if (typeof response.error === 'object') + alert( + `Error! Please report this data:\ngame: ${ + gameInfo.gameid + }\n\nresponse: \n${JSON.stringify(response.error)}` + ) + else alert(response.error) + } else if (response.message) { + ID('submitcaption').disabled = false + alert(response.message) + } else if (response.url) { + onCaptionSuccess(title) + location.replace(response.url) + } + }, + error: () => { + ID('submitcaption').disabled = false + alert('Server error. :( Try again?') + } + }) +} + +export default submitCaption diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/submitDrawing.js b/src/newcanvas/js/functions/needToGoDeeper/events/submitDrawing.js new file mode 100644 index 0000000..15f65b0 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/submitDrawing.js @@ -0,0 +1,59 @@ +import anbt from '../../../anbt' +import globals from '../../../globals' +import makePng from '../../anbt/makePng' +import ID from '../../idSelector' +import ajax from '../ajax' + +const submitDrawing = () => { + const { incontest, gameInfo, options } = window + const moreThanMinuteLeft = globals.timerStart - Date.now() > 60000 + if ( + options.submitConfirm && + moreThanMinuteLeft && + !confirm('Ready to submit this drawing?') + ) + return + ID('submit').disabled = true + makePng(300, 250, true) + if (options.backup) + localStorage.setItem('anbt_drawingbackup_newcanvas', anbt.pngBase64) + window.submitting = true + const url = incontest ? '/contests/submit-drawing.json' : '/play/draw.json' + ajax('POST', url, { + obj: { + game_token: gameInfo.gameid, + panel: anbt.pngBase64 + }, + load: response => { + try { + response = JSON.parse(response) + } catch (e) { + response = { + error: response + } + } + if (response.error) { + ID('submit').disabled = false + if (typeof response.error === 'object') + alert( + `Error! Please report this data:\ngame: ${ + gameInfo.gameid + }\n\nresponse:\n${JSON.stringify(response.error)}` + ) + else alert(response.error) + } else if (response.message) { + ID('submit').disabled = false + alert(response.message) + } else if (response.url) { + window.onbeforeunload = () => {} + location.replace(response.url) + } + }, + error: () => { + ID('submit').disabled = false + alert('Server error. :( Try again?') + } + }) +} + +export default submitDrawing diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/timePlus.js b/src/newcanvas/js/functions/needToGoDeeper/events/timePlus.js new file mode 100644 index 0000000..636e0ea --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/timePlus.js @@ -0,0 +1,38 @@ +import globals from '../../../globals' +import ID from '../../idSelector' +import ajax from '../ajax' +import extractInfoFromHTML from '../extractInfoFromHTML' + +const timePlus = () => { + let { gameInfo } = window + if (!gameInfo.friend) return + ID('timeplus').disabled = true + ajax('POST', '/play/exit.json', { + obj: { + game_token: gameInfo.gameid + }, + load: () => { + ajax('GET', `/play/${gameInfo.gameid}/?${Date.now()}`, { + load: response => { + ID('timeplus').disabled = false + gameInfo = response + ? extractInfoFromHTML(response) + : { + error: 'Server returned a blank response :(' + } + globals.timerStart = Date.now() + 1000 * gameInfo.timeleft + }, + error: () => { + ID('timeplus').disabled = false + alert('Server error. :( Try again?') + } + }) + }, + error: () => { + ID('timeplus').disabled = false + alert('Server error. :( Try again?') + } + }) +} + +export default timePlus diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/updateUsedChars.js b/src/newcanvas/js/functions/needToGoDeeper/events/updateUsedChars.js new file mode 100644 index 0000000..f3c5279 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/events/updateUsedChars.js @@ -0,0 +1,7 @@ +import ID from '../../idSelector' + +const updateUsedChars = () => { + ID('usedchars').textContent = 45 - ID('caption').value.length +} + +export default updateUsedChars diff --git a/src/newcanvas/js/functions/needToGoDeeper/exitToSandbox.js b/src/newcanvas/js/functions/needToGoDeeper/exitToSandbox.js new file mode 100644 index 0000000..fb3b0ba --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/exitToSandbox.js @@ -0,0 +1,39 @@ +import globals from '../../globals' +import unlock from '../anbt/unlock' +import ID from '../idSelector' +import updateTimer from '../updateTimer' +import ajax from './ajax' + +const exitToSandbox = () => { + const { incontest, gameInfo, drawing_aborted, vertitle } = window + if (incontest && !drawing_aborted) + ajax('POST', '/contests/exit.json', { + load: () => alert('You have missed your contest.') + }) + if (gameInfo.drawfirst && !drawing_aborted) { + ajax('POST', '/play/abort-start.json', { + obj: { + game_token: gameInfo.gameid + }, + load: () => + alert('You have missed your Draw First game.\nIt has been aborted.'), + error: () => + alert( + 'You have missed your Draw First game.\nI tried aborting it, but an error occured. :(' + ) + }) + } + globals.timerStart = Date.now() + ID('newcanvasyo').className = 'sandbox' + window.timerCallback = () => {} + updateTimer() + document.title = 'Sandbox - Drawception' + ID('gamemode').innerHTML = 'Sandbox' + ID('headerinfo').innerHTML = `Sandbox with ${vertitle}` + try { + history.replaceState({}, null, '/sandbox/') + } catch (e) {} + unlock() +} + +export default exitToSandbox diff --git a/src/newcanvas/js/functions/needToGoDeeper/extractInfoFromHTML.js b/src/newcanvas/js/functions/needToGoDeeper/extractInfoFromHTML.js new file mode 100644 index 0000000..2a0127f --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/extractInfoFromHTML.js @@ -0,0 +1,43 @@ +const extractInfoFromHTML = html => { + const doc = document.implementation.createHTMLDocument('') + doc.body.innerHTML = html + const drawapp = doc.querySelector('draw-app') || + doc.querySelector('describe') || { + getAttribute: () => false + } + const getElement = query => doc.querySelector(query) + return { + error: (element => (element ? element.src : false))(getElement('.error')), + gameid: drawapp.getAttribute('game_token'), + blitz: drawapp.getAttribute(':blitz_mode') === 'true', + nsfw: drawapp.getAttribute(':nsfw') === 'true', + friend: drawapp.getAttribute(':game_public') !== 'true', + drawfirst: drawapp.getAttribute(':draw_first') === 'true', + timeleft: drawapp.getAttribute(':seconds') * 1, + caption: drawapp.getAttribute('phrase'), + image: drawapp.getAttribute('img_url'), + palette: drawapp.getAttribute('theme_id'), + bgbutton: drawapp.getAttribute(':bg_layer') === 'true', + playerurl: '/profile/', + avatar: null, + coins: '-', + pubgames: '-', + friendgames: '-', + notifications: '-', + drawinglink: (element => (element ? element.src : false))( + getElement('.gamepanel img') + ), + drawingbylink: (element => + element ? [element.textContent.trim(), element.href] : false)( + getElement('#main p a') + ), + drawncaption: (element => (element ? element.src : false))( + getElement('h1.game-title') + ), + notloggedin: getElement('form.form-login') !== null, + limitreached: false, // ??? appears to be redirecting to /play/limit/ which gives "game not found" error + html + } +} + +export default extractInfoFromHTML diff --git a/src/newcanvas/js/functions/needToGoDeeper/getPalData.js b/src/newcanvas/js/functions/needToGoDeeper/getPalData.js new file mode 100644 index 0000000..e42be65 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/getPalData.js @@ -0,0 +1,21 @@ +import palettemap from '../../palettemap'; +import palettes from '../../palettes'; + +const getPalData = palette => { + if (palette === 'theme_roulette') { + // Since site update, the game reports already chosen palette, + // but apparently this still happens sometimes. ??? + alert( + "Warning: Drawception roulette didn't give a theme. ANBT will choose a random palette." + ) + delete palettes.Roulette + const keys = Object.keys(palettemap) + const paletteName = keys[(keys.length * Math.random()) << 0] + palettes.Roulette = palettes[palettemap[paletteName][0]] + return ['Roulette', palettemap[paletteName][1]] + } else { + if (palette) return palettemap[palette.toLowerCase()] + } +} + +export default getPalData diff --git a/src/newcanvas/js/functions/needToGoDeeper/getParametersFromPlay.js b/src/newcanvas/js/functions/needToGoDeeper/getParametersFromPlay.js new file mode 100644 index 0000000..e2ebcf5 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/getParametersFromPlay.js @@ -0,0 +1,33 @@ +import ajax from './ajax' +import extractInfoFromHTML from './extractInfoFromHTML' +import handlePlayParameters from './handlePlayParameters' + +const getParametersFromPlay = () => { + const { incontest, friendgameid } = window + const url = incontest + ? '/contests/play/' + : `/play/${friendgameid ? `${friendgameid}/` : ''}` + try { + if (location.pathname !== url) history.replaceState({}, null, url) + } catch (e) {} + // On Firefox, requesting "/play/" url would immediately return a cached error. + // Firefox, WTF? So we use cache-busting here. + ajax('GET', `${url}?${Date.now()}`, { + load: response => { + window.gameInfo = response + ? extractInfoFromHTML(response) + : { + error: 'Server returned a blank response :(' + } + handlePlayParameters() + }, + error: response => { + window.gameInfo = { + error: `Server error: ${response.statusText}` + } + handlePlayParameters() + } + }) +} + +export default getParametersFromPlay diff --git a/src/newcanvas/js/functions/needToGoDeeper/handleCommonParameters.js b/src/newcanvas/js/functions/needToGoDeeper/handleCommonParameters.js new file mode 100644 index 0000000..fc82056 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/handleCommonParameters.js @@ -0,0 +1,17 @@ +import ID from '../idSelector' + +const handleCommonParameters = () => { + const { gameInfo, inforum } = window + if (gameInfo.notloggedin) + return (ID('start').parentNode.innerHTML = + 'Login Register') + if (gameInfo.avatar) ID('infoavatar').src = gameInfo.avatar + ID('infoprofile').href = gameInfo.playerurl + ID('infocoins').innerHTML = gameInfo.coins + ID('infogames').innerHTML = gameInfo.pubgames + ID('infofriendgames').innerHTML = gameInfo.friendgames || 0 + ID('infonotifications').innerHTML = gameInfo.notifications + if (inforum) document.querySelector('.headerright').hidden = true +} + +export default handleCommonParameters diff --git a/src/newcanvas/js/functions/needToGoDeeper/handlePlayParameters.js b/src/newcanvas/js/functions/needToGoDeeper/handlePlayParameters.js new file mode 100644 index 0000000..3d54b7e --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/handlePlayParameters.js @@ -0,0 +1,113 @@ +import anbt from '../../anbt' +import globals from '../../globals' +import palettes from '../../palettes' +import lock from '../anbt/lock' +import moveSeekbar from '../anbt/moveSeekbar' +import seek from '../anbt/seek' +import setBackground from '../anbt/setBackground' +import strokeEnd from '../anbt/strokeEnd' +import unlock from '../anbt/unlock' +import setPaletteByName from '../bindEvents/palette/setPaletteByName' +import updateColorIndicators from '../bindEvents/updateColorIndicators' +import ID from '../idSelector' +import updateTimer from '../updateTimer' +import exitToSandbox from './exitToSandbox' +import getPalData from './getPalData' +import handleCommonParameters from './handleCommonParameters' +import timerCallback from './timerCallback' + +const handlePlayParameters = () => { + const { options, gameInfo, incontest, vertitle } = window + ID('skip').disabled = gameInfo.drawfirst || incontest + ID('report').disabled = gameInfo.drawfirst || incontest + ID('exit').disabled = false + ID('start').disabled = false + ID('bookmark').disabled = gameInfo.drawfirst || incontest + ID('options').disabled = true // Not implemented yet! + ID('timeplus').disabled = incontest + ID('submit').disabled = false + ID('headerinfo').innerHTML = `Playing with ${vertitle}` + ID('drawthis').classList.add('onlyplay') + ID('emptytitle').classList.remove('onlyplay') + window.submitting = false + window.drawing_aborted = false + if (gameInfo.error) { + alert(`Play Error:\n${gameInfo.error}`) + return exitToSandbox() + } + if (gameInfo.limitreached) { + alert('Play limit reached!') + return exitToSandbox() + } + ID('gamemode').innerHTML = incontest + ? 'Contest' + : `${(gameInfo.friend ? 'Friend ' : 'Public ') + + (gameInfo.nsfw ? 'Not Safe For Work (18+) ' : 'safe for work ') + + (gameInfo.blitz ? 'BLITZ ' : '')}Game` + ID('drawthis').innerHTML = + gameInfo.caption || (gameInfo.drawfirst && '(Start your game!)') || '' + ID('tocaption').src = '' + const newcanvas = ID('newcanvasyo') + newcanvas.className = 'play' + if (gameInfo.friend) newcanvas.classList.add('friend') + ID('palettechooser').className = gameInfo.friend ? '' : 'onlysandbox' + if (gameInfo.nsfw) newcanvas.classList.add('nsfw') + if (gameInfo.blitz) newcanvas.classList.add('blitz') + newcanvas.classList.add(gameInfo.image ? 'captioning' : 'drawing') + // Clear + if (anbt.isStroking) strokeEnd() + unlock() + for (let i = anbt.svg.childNodes.length - 1; i > 0; i--) + anbt.svg.removeChild(anbt.svg.childNodes[i]) + seek(0) + moveSeekbar(1) + anbt.unsaved = false + const { palette } = gameInfo + if (!gameInfo.image) { + const paletteData = getPalData(palette) + if (!paletteData) { + if (!palette) + alert( + 'Error, please report! Failed to extract the palette.\nAre you using the latest ANBT version?' + ) + else + alert( + `Error, please report! Unknown palette: '${palette}'.\nAre you using the latest ANBT version?` + ) + // Prevent from drawing with a wrong palette + lock + ID('submit').disabled = true + } else { + setPaletteByName(paletteData[0]) + setBackground(paletteData[1]) + anbt.color = [palettes[paletteData[0]][0], 'eraser'] + updateColorIndicators() + } + ID('setbackground').hidden = !gameInfo.bgbutton + } else { + // Check broken drawing + ID('tocaption').src = + gameInfo.image.length <= 30 + ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAD6AQMAAAAho+iwAAAABlBMVEWAQED///94jotxAAABiklEQVR4Xu3W0UrCUBjA8eOO5CLK7VxLzDWFrjK6Eaha8FHuppfwBRJvdjlMIK/K3qA3OZBBd/UIm9UL2O2inMJBptNuog/6/h4Q2Y8J387Y2KIoiqIoiqIoiuIxXnbI5cmXSiJjD3LmFyrGY46PqVAx/HPDv9/w3wsJTTgapuDkcEIQMFxzo937S8+F5OkWI2IKymQl3yiZ6j8zYsRY6vUYDcOfGkuMknE5/aQAMczX9O+iKIrKJWuSxliQqT61hOmMucsYK6uzLWfDenF34EXhOX+s377KLCZcs1bxhNXQqnAvrExWM8vvY3amORCNsplu2nZPWKdj1tecTHZZLA97ZnjBB/XrkWIZWT+bsmTowp+7FHSnyMi7CpuMrWcwNsMMxnJzrCUbwwq/2/MLJb8lP4L2zVHJ35Bp1rE8Uc2bALoNHQvcoNG3Yf5Pm6EnHG50Ye0YmiG4V08LmWD7wmF9gJwFgoHbnZzNSDE/Co3orSB2YGsbovAgaD9vlkB/WbkbdQVWMNxR1Ddnf4eSZpHZYAAAAABJRU5ErkJggg==' + : gameInfo.image + ID('caption').value = '' + ID('caption').focus() + ID('caption').setAttribute('maxlength', 45) + ID('usedchars').textContent = '45' + } + if ( + (options.timeoutSound && !gameInfo.blitz) || + (options.timeoutSoundBlitz && gameInfo.blitz) + ) { + window.playedWarningSound = false + window.alarm = new Audio(window.alarmSoundOgg) + window.alarm.volume = options.timeoutSoundVolume / 100 + } + globals.timerStart = Date.now() + 1000 * gameInfo.timeleft + window.timerCallback = timerCallback + handleCommonParameters() + window.timesup = false + updateTimer() +} + +export default handlePlayParameters diff --git a/src/newcanvas/js/functions/needToGoDeeper/handleSandboxParameters.js b/src/newcanvas/js/functions/needToGoDeeper/handleSandboxParameters.js new file mode 100644 index 0000000..6101d61 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/handleSandboxParameters.js @@ -0,0 +1,29 @@ +import play from '../anbt/play' +import ID from '../idSelector' +import handleCommonParameters from './handleCommonParameters' + +const handleSandboxParameters = () => { + const { gameInfo, vertitle, options } = window + if (gameInfo.drawingbylink) { + const [playername, playerlink] = gameInfo.drawingbylink + const replaylink = `Drawing` + ID( + 'headerinfo' + ).innerHTML = `${replaylink} by ${playername}` + document.title = `${playername}'s drawing - Drawception` + if (gameInfo.drawncaption) { + ID('drawthis').innerHTML = `"${gameInfo.drawncaption}"` + ID('drawthis').classList.remove('onlyplay') + ID('emptytitle').classList.add('onlyplay') + } + if (options.autoplay) play() + } else { + ID('headerinfo').innerHTML = `Sandbox with ${vertitle}` + ID('drawthis').classList.add('onlyplay') + } + handleCommonParameters() +} + +export default handleSandboxParameters diff --git a/src/newcanvas/js/functions/needToGoDeeper/onCaptionSuccess.js b/src/newcanvas/js/functions/needToGoDeeper/onCaptionSuccess.js new file mode 100644 index 0000000..29d2467 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/onCaptionSuccess.js @@ -0,0 +1,13 @@ +const onCaptionSuccess = title => { + const { options, gameInfo } = window + if (!options.bookmarkOwnCaptions) return + const games = window.getLocalStorageItem('gpe_gameBookmarks', {}) + games[gameInfo.gameid] = { + time: Date.now(), + caption: `"${title}"`, + own: true + } + localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games)) +} + +export default onCaptionSuccess diff --git a/src/newcanvas/js/functions/needToGoDeeper/timerCallback.js b/src/newcanvas/js/functions/needToGoDeeper/timerCallback.js new file mode 100644 index 0000000..337fac2 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/timerCallback.js @@ -0,0 +1,40 @@ +import globals from '../../globals' +import lock from '../anbt/lock' +import ID from '../idSelector' +import updateTimer from '../updateTimer' +import exitToSandbox from './exitToSandbox' +import getParametersFromPlay from './getParametersFromPlay' + +const timerCallback = seconds => { + const { gameInfo } = window + if (seconds < 1) { + document.title = "[TIME'S UP!] Playing Drawception" + if (gameInfo.image || window.timesup) { + // If pressed submit before timer expired, let it process or retry in case of error + if (!window.submitting) { + if (gameInfo.image) getParametersFromPlay() + else exitToSandbox() // Allow to save the drawing after time's up + } + } else { + ID('newcanvasyo').classList.add('locked') + lock() + globals.timerStart += 15000 // 15 seconds to submit + updateTimer() + window.timesup = true + } + } else + document.title = `[${`0${Math.floor(seconds / 60)}`.slice( + -2 + )}:${`0${Math.floor(seconds % 60)}`.slice(-2)}] Playing Drawception` + if ( + window.alarm && + !window.playedWarningSound && + seconds <= (gameInfo.blitz ? 5 : 61) && + seconds > 0 + ) { + window.alarm.play() + window.playedWarningSound = true + } +} + +export default timerCallback diff --git a/src/newcanvas/js/functions/needToGoDeeper/unsavedStopAction.js b/src/newcanvas/js/functions/needToGoDeeper/unsavedStopAction.js new file mode 100644 index 0000000..c4dcd61 --- /dev/null +++ b/src/newcanvas/js/functions/needToGoDeeper/unsavedStopAction.js @@ -0,0 +1,6 @@ +import anbt from '../../anbt' + +const unsavedStopAction = () => + anbt.unsaved && !confirm("You haven't saved the drawing. Abandon?") + +export default unsavedStopAction diff --git a/src/newcanvas/js/functions/pack/packUint16be.js b/src/newcanvas/js/functions/pack/packUint16be.js new file mode 100644 index 0000000..9765fd0 --- /dev/null +++ b/src/newcanvas/js/functions/pack/packUint16be.js @@ -0,0 +1,4 @@ +const packUint16be = number => + String.fromCharCode((number >> 8) & 0xff, number & 0xff) + +export default packUint16be \ No newline at end of file diff --git a/src/newcanvas/js/functions/pack/packUint32be.js b/src/newcanvas/js/functions/pack/packUint32be.js new file mode 100644 index 0000000..504dcbd --- /dev/null +++ b/src/newcanvas/js/functions/pack/packUint32be.js @@ -0,0 +1,9 @@ +const packUint32be = number => + String.fromCharCode( + (number >> 24) & 0xff, + (number >> 16) & 0xff, + (number >> 8) & 0xff, + number & 0xff + ) + +export default packUint32be diff --git a/src/newcanvas/js/functions/randomBase.js b/src/newcanvas/js/functions/randomBase.js new file mode 100644 index 0000000..148a6ad --- /dev/null +++ b/src/newcanvas/js/functions/randomBase.js @@ -0,0 +1,15 @@ +import random from '../random' +import randomItem from './randomItem' + +const randomBase = () => { + const randomNumber = Math.floor(Math.random() * 3) + return `${randomItem(random.description)} ${randomItem(random.things)}${ + randomNumber >= 1 + ? ` ${randomItem(random.acts)} ${ + randomNumber === 2 ? `${randomItem(random.description)} ` : '' + }${randomItem(random.things)}` + : '' + }` +} + +export default randomBase diff --git a/src/newcanvas/js/functions/randomItem.js b/src/newcanvas/js/functions/randomItem.js new file mode 100644 index 0000000..eb67d00 --- /dev/null +++ b/src/newcanvas/js/functions/randomItem.js @@ -0,0 +1,3 @@ +const randomItem = list => list[Math.floor(Math.random() * list.length)] + +export default randomItem diff --git a/src/newcanvas/js/functions/randomPhrase.js b/src/newcanvas/js/functions/randomPhrase.js new file mode 100644 index 0000000..d0637b2 --- /dev/null +++ b/src/newcanvas/js/functions/randomPhrase.js @@ -0,0 +1,8 @@ +import randomBase from './randomBase' + +const randomPhrase = () => { + const s = randomBase() + return s.charAt(0).toUpperCase() + s.substr(1) +} + +export default randomPhrase diff --git a/src/newcanvas/js/functions/simplifyDouglasPeucker.js b/src/newcanvas/js/functions/simplifyDouglasPeucker.js new file mode 100644 index 0000000..1b8e5c6 --- /dev/null +++ b/src/newcanvas/js/functions/simplifyDouglasPeucker.js @@ -0,0 +1,33 @@ +import getSqSegDist from './getSqSegDist' + +const simplifyDouglasPeucker = ({ points, smoothening: sqTolerance }) => { + const length = points.length + const MarkerArray = typeof Uint8Array !== 'undefined' ? Uint8Array : Array + const markers = new MarkerArray(length) + let first = 0 + let last = length - 1 + const stack = [] + const newPoints = [] + markers[first] = markers[last] = 1 + while (last) { + let maxSqDist = 0 + let index + for (let i = first + 1; i < last; i++) { + let sqDist = getSqSegDist(points[i], points[first], points[last]) + if (sqDist > maxSqDist) { + index = i + maxSqDist = sqDist + } + } + if (maxSqDist > sqTolerance) { + markers[index] = 1 + stack.push(first, index, index, last) + } + last = stack.pop() + first = stack.pop() + } + for (let i = 0; i < length; i++) if (markers[i]) newPoints.push(points[i]) + return newPoints +} + +export default simplifyDouglasPeucker diff --git a/src/newcanvas/js/functions/updateTimer.js b/src/newcanvas/js/functions/updateTimer.js new file mode 100644 index 0000000..4dd9d60 --- /dev/null +++ b/src/newcanvas/js/functions/updateTimer.js @@ -0,0 +1,15 @@ +import globals from '../globals' +import ID from './idSelector' + +const updateTimer = () => { + let seconds = (globals.timerStart - Date.now()) / 1000 + try { + if (window.timerCallback) window.timerCallback(seconds) + } catch (e) {} + seconds = Math.abs(seconds) + const minutes = `0${Math.floor(seconds / 60)}`.slice(-2) + seconds = `0${Math.floor(seconds % 60)}`.slice(-2) + ID('timer').childNodes[0].nodeValue = `${minutes}:${seconds}` +} + +export default updateTimer diff --git a/src/newcanvas/js/globals.js b/src/newcanvas/js/globals.js new file mode 100644 index 0000000..2142b3f --- /dev/null +++ b/src/newcanvas/js/globals.js @@ -0,0 +1,12 @@ +import anbt from './anbt' + +const globals = { + rectangle: {}, + touchSingle: false, + lastTouch: {}, + lastSeenColorToHighlight: anbt.background, + brushSizes: [2, 6, 14, 42], + timerStart: 0 +} + +export default globals diff --git a/src/newcanvas/js/pako.js b/src/newcanvas/js/pako.js new file mode 100644 index 0000000..01a24e1 --- /dev/null +++ b/src/newcanvas/js/pako.js @@ -0,0 +1,2 @@ +import pako from 'pako' +window.pako = pako diff --git a/src/newcanvas/js/palettemap.js b/src/newcanvas/js/palettemap.js new file mode 100644 index 0000000..2d9b800 --- /dev/null +++ b/src/newcanvas/js/palettemap.js @@ -0,0 +1,36 @@ +const palettemap = { + default: ['Normal', '#fffdc9'], + theme_thanksgiving: ['Thanksgiving', '#f5e9ce'], + halloween: ['Halloween', '#444444'], + theme_cga: ['CGA', '#ffff55'], + shades_of_grey: ['Grayscale', '#e9e9e9'], + theme_bw: ['Black and white', '#ffffff'], + theme_gameboy: ['Gameboy', '#9bbc0f'], + theme_neon: ['Neon', '#00abff'], + theme_sepia: ['Sepia', '#ffe2c4'], + theme_valentines: ["Valentine's", '#ffccdf'], + theme_blues: ['the blues', '#295c6f'], + theme_spring: ['Spring', '#ffffff'], + theme_beach: ['Beach', '#f7dca2'], + theme_beach_2: ['Tide Pool', '#2271a2'], + theme_coty_2016: ['Colors of 2016', '#648589'], + theme_bee: ['Bee', '#ffffff'], + theme_coty_2017: ['Colors of 2017', '#5f7278'], + theme_fire_ice: ['Fire and Ice', '#040526'], + theme_coty_2018: ['Canyon Sunset', '#2e1b50'], + theme_juice: ['Juice', '#fced95'], + theme_tropical: ['Tropical', '#2f0946'], + theme_grimby_grays: ['Grimby Grays', '#f0efeb'], + theme_fury_road: ['Fury Road', '#893f1d'], + theme_candy: ['Candy', '#793abd'], + theme_holiday_2: ['Holiday', '#f6f6f6'], + theme_blues_2: ['Blues', '#0f1328'], + theme_sin_city: ['Sin City', '#000000'], + theme_lucky_clover: ['Lucky Clover', '#0c442c'], + theme_drawception: ["D's Exclusive", '#0ee446'], + theme_retina_burn: ['Retina Burn', '#ff0b11'], + theme_easter: ['Easter', '#ddf7a8'], + theme_neapolitan: ['Neapolitan', '#fff7e1'] +} + +export default palettemap diff --git a/src/newcanvas/js/palettes.js b/src/newcanvas/js/palettes.js new file mode 100644 index 0000000..51d1971 --- /dev/null +++ b/src/newcanvas/js/palettes.js @@ -0,0 +1,68 @@ +// prettier-ignore +const palettes = { + "Normal": ['#000000', '#444444', '#999999', '#ffffff', '#603913', '#c69c6d', + '#ffdab9', '#ff0000', '#ffd700', '#ff6600', '#16ff00', '#0fad00', + '#00ffff', '#0247fe', '#ec008c', '#8601af', '#fffdc9'], + "Sepia": ['#402305', '#503315', '#604325', '#705335', '#806345', '#907355', + '#a08365', '#b09375', '#bfa284', '#cfb294', '#dfc2a4', '#ffe2c4'], + "Grayscale": ['#000000', '#ffffff', '#151515', '#2a2a2a', '#3f3f3f', '#555555', '#6a6a6a', + '#7f7f7f', '#949494', '#aaaaaa', '#bfbfbf', '#d4d4d4', '#e9e9e9'], + "Black and white": ['#ffffff', '#000000'], + "CGA": ['#555555', '#000000', '#0000aa', '#5555ff', '#00aa00', '#55ff55', '#00aaaa', '#55ffff', + '#aa0000', '#ff5555', '#aa00aa', '#ff55ff', '#aa5500', '#ffff55', '#aaaaaa', '#ffffff'], + "Gameboy": ['#8bac0f', '#9bbc0f', '#306230', '#0f380f'], + "Neon": ['#ffffff', '#000000', '#adfd09', '#f3f315', '#feac09', '#fe0bab', '#ad0bfb', '#00abff'], + "Thanksgiving": ['#673718', '#3c2d27', '#c23322', '#850005', '#c67200', '#77785b', + '#5e6524', '#cfb178', '#f5e9ce'], + "Holiday_old": ['#3d9949', '#7bbd82', '#7d1a0c', '#bf2a23', + '#fdd017', '#00b7f1', '#bababa', '#ffffff'], + "Valentine's": ['#2d1014', '#ffffff', '#600d17', '#c2113a', '#b71d1d', '#e54d5a', + '#ff7d63', '#fd8647', '#fed067', '#ffe4b7', '#fdc0c6'], + "Halloween": ['#444444', '#000000', '#999999', '#ffffff', '#603913', '#c69c6d', + '#7a0e0e', '#b40528', '#fd2119', '#fa5b11', '#faa611', '#ffd700', + '#602749', '#724b97', '#bef202', '#519548', '#b2bb1e'], + "the blues": ['#b6cbe4', '#618abc', '#d0d5ce', '#82a2a1', '#92b8c1', '#607884', + '#c19292', '#8c2c2c', '#295c6f'], + "Spring": ['#9ed396', '#57b947', '#4d7736', '#365431', '#231302', + '#3e2409', '#a66621', '#a67e21', '#ebbb49', '#ffc0cb', '#ffffff'], + "Beach": ['#1ca4d2', '#65bbe2', '#6ab7bf', '#94cbda', '#9cbf80', '#d2e1ab', + '#b8a593', '#d7cfb9', '#dc863e', '#f7dca2'], + "Tide Pool": ["#ffe8b9", "#fad489", "#ffb44c", "#d6b1de", "#b197a8", "#e5f2ff", + "#a1ffb8", "#53e6ef", "#3ad3a8", "#1ca4d2", "#2271a2"], + "Colors of 2016": ['#91a7d0', '#f6cac9', '#eb9587', '#776a5f', + '#d1c2ab', '#a39d9d', '#648589'], + "Bee": ["#000000", "#7a5c00", "#b58800", "#eab618", "#f6de97", "#ffffff"], + "Colors of 2017": ['#86af49', '#44883d', '#1f4478', '#0062a3', + '#00939a', '#59c9d5', '#8a9a9a', '#5f7278'], + "Fire and Ice": ['#520909', '#b40528', '#fd2119', '#faa611', '#ffe96a', + '#ffffff', '#69ddff', '#1c8ae5', '#0a3fa9', '#040526'], + "Canyon Sunset": ['#fce3ca', '#feb789', '#f27c8a', '#af5081', + '#8e6dae', '#5f4a8b', '#2e1b50'], + "Juice": ["#f3ab54", "#ec5e66", "#ab5871", "#f2a19b", "#f9f4d4", "#fadfb7", + "#869e3c", "#cbdd7e", "#fced95"], + "Tropical": ["#f68357", "#fbc040", "#fefa56", "#fef0f5", "#90fc51", "#07f182", + "#1d6ab2", "#12041b", "#2f0946"], + "Grimby Grays": ['#000000', '#ffffff', '#2f3032', '#252422', '#545758', '#4b4a46', '#797d80', + '#71706c', '#9ea1a4', '#979692', '#c4c8cb', '#d7d6d2', '#dee1e4', '#f0efeb'], + "DawnBringer 16": ['#140c1c', '#442434', '#30346d', '#4e4a4e', '#854c30', '#346524', + '#d04648', '#757161', '#597dce', '#d27d2c', '#8595a1', '#6daa2c', + '#d2aa99', '#6dc2ca', '#dad45e', '#deeed6'], + "Fury Road": ['#020c16', '#023745', '#08616d', '#36d4b6', '#0afef6', + '#fce173', '#e29f30', '#b56942', '#ad3f16', '#893f1d'], + "Candy": ['#06063c', '#4f95ff', '#68f9ff', '#fffef9', '#ff96f8', '#ff44d3', '#793abd'], + "Holiday": ['#e91434', '#97200a', '#c66a20', '#fdbe30', '#688625', '#004f28', + '#112825', '#1c69bf', '#6096d3', '#a5c4e6', '#f7d9f0', '#f6f6f6'], + "Blues": ['#929aa8', '#896868', '#546c7d', '#633d3d', '#284660', + '#421f29', '#232e3f', '#0f1328'], + "Sin City": ['#ffffff', '#ff0000', '#000000'], + "Lucky Clover": ['#ffffff', '#fcf4c4', '#f7b307', '#fc8404', '#cd7a14', + '#9bf23e', '#40d910', '#34900b', '#0c442c'], + "D's Exclusive": ['#000000', '#717474', '#ffffff', '#f25b99', '#e4965e', '#ffc416', + '#ffe38f', '#0074d9', '#09a3ec', '#12d1ff', '#bcf5ff', '#0ee446'], + "Retina Burn": ['#bc0bff', '#ff0b11'], + "Easter": ['#9678ba', '#bc9ff0', '#e4ccff', '#ffa1f1', '#fbd0ee', '#e6f2ff', + '#aaedfb', '#f4dc7b', '#fdfabd', '#a1ef85', '#ddf7a8'], + "Neapolitan": ['#3f3245', '#ff5c98', '#ecb2a4', '#fff7e1'] +} + +export default palettes diff --git a/src/newcanvas/js/pathseg.js b/src/newcanvas/js/pathseg.js new file mode 100644 index 0000000..8d395dd --- /dev/null +++ b/src/newcanvas/js/pathseg.js @@ -0,0 +1 @@ +import 'pathseg' diff --git a/src/newcanvas/js/random.js b/src/newcanvas/js/random.js new file mode 100644 index 0000000..d3274db --- /dev/null +++ b/src/newcanvas/js/random.js @@ -0,0 +1,34 @@ +// prettier-ignore +const random = { + things: [ + 'hobo', 'shoe', 'log', 'bun', 'sandwich', 'bull', 'beer', 'hair', + 'hill', 'beans', 'man', 'sofa', 'dinosaur', 'road', 'plank', 'hole', 'food', + 'hedgehog', 'pine', 'toad', 'tooth', 'candy', 'rock', 'drop', 'book', 'button', 'carpet', + 'wheel', 'computer', 'box', 'cat', 'rat', 'hook', 'chunk', 'boat', 'spade', 'sack', + 'hammer', 'face', 'soap', 'nose', 'finger', 'steam', 'spring', 'hand', 'fish', + 'elephant', 'dog', 'chair', 'bag', 'phone', 'robot', 'axe', 'grass', 'crack', 'teacher', + 'breadcrumb', 'fridge', 'worm', 'nut', 'cloth', 'apple', 'tongue', 'jar' + ], + acts: [ + 'crazy from', 'thanks', 'hits', 'lies around on', 'sees', 'grows in', + 'attaches to', 'flies from', 'crawls from', 'chews', 'walks on', 'squishes', 'pecks', + 'wobbles in', 'smokes from', 'smokes', 'rides', 'eats', 'squeals from under', + 'is lost in', 'spins in', 'stuck in', 'hooks', 'angry at', 'bends', 'drips on', + 'rolls on', 'digs', 'crawls in', 'flies at', 'massages', 'dreams of', 'kills', 'pulls', + "doesn't want", 'licks', 'shoots', 'falls off', 'falls in', 'crawls on', 'turns into', + 'stuck to', 'jumps on', 'hides', 'hides in', 'disassembles', 'rips', 'dissolves', + 'stretches', 'crushes', 'pushes', 'drowns in', 'pokes', 'runs away from', 'wants', + 'scratches', 'throws', 'and', 'confused by', 'unimpressed by' + ], + description: [ + 'white', 'concrete', 'shiny', 'ill', 'big', 'ex', 'fast', 'happy', + 'inside-out', 'hot', 'burning', 'thick', 'wooden', 'long', 'good', 'tattered', 'iron', + 'liquid', 'frozen', 'green', 'evil', 'bent', 'rough', 'pretty', 'red', 'round', + 'shaggy', 'bald', 'slow', 'wet', 'wrinkly', 'meaty', 'impudent', 'real', 'distraught', + 'sharp', 'plastic', 'gift', 'squished', 'chubby', 'crumbling', 'horned', 'angry', + 'sitting', 'stranded', 'dry', 'hard', 'thin', 'killer', 'walking', 'cold', 'wheezing', + 'grunting', 'chirping', 'wide', 'electric', 'nuclear', 'confused', 'unimpressed' + ] +} + +export default random