Updated npm
BIN
PREVIEW.jpg
Before Width: | Height: | Size: 544 KiB |
39
README.md
@ -1,39 +0,0 @@
|
||||
# Dotgrid
|
||||
|
||||
<img src="https://raw.githubusercontent.com/hundredrabbits/100r.co/master/media/content/characters/dotgrid.hello.png" width="300"/>
|
||||
|
||||
Dotgrid is a <strong>grid-based vector drawing software</strong> designed to create logos, icons and type. It supports layers, the full SVG specs and additional effects such as mirroring and radial drawing. Dotgrid exports to both PNG and SVG files.
|
||||
|
||||
The <a href="http://github.com/hundredrabbits/Dotgrid" target="_blank" rel="noreferrer" class="external ">application</a> was initially created for internal use, and later made available as a free and <a href="https://github.com/hundredrabbits/Dotgrid" target="_blank" rel="noreferrer" class="external ">open source</a> software.
|
||||
|
||||
Learn more by reading the <a href="https://github.com/Hundredrabbits/Dotgrid" target="_blank" rel="noreferrer" class="external ">manual</a>, or have a look at a <a href="https://www.youtube.com/watch?v=Xt1zYHhpypk" target="_blank" rel="noreferrer" class="external ">tutorial video</a>. If you need <b>help</b>, visit the <a href="https://hundredrabbits.itch.io/dotgrid/community" target="_blank" rel="noreferrer" class="external ">Community</a>.
|
||||
|
||||
<img src='https://raw.githubusercontent.com/hundredrabbits/Dotgrid/master/PREVIEW.jpg' width="600"/>
|
||||
|
||||
## Guide
|
||||
|
||||
View the [guide](https://100r.co/site/dotgrid.html).
|
||||
|
||||
## Electron Build
|
||||
|
||||
```
|
||||
cd desktop
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## UDP Controls
|
||||
|
||||
Dotgrid can be controlled by UDP via the port `49161`. It expects messages up to 6 characters.
|
||||
|
||||
- **layer** `0/1/2`
|
||||
- **type** `l/c/r/z` (`l`:line, `c`:clock-wise arc, `r`: cc-wise arc, `z`: close, `.`:clear, `*`: draw)
|
||||
- **from** `0-z``0-z`
|
||||
- **to** `0-z``0-z`
|
||||
|
||||
## Extras
|
||||
|
||||
- This application supports the [Ecosystem Theme](https://github.com/hundredrabbits/Themes).
|
||||
- Support this project through [Patreon](https://patreon.com/100).
|
||||
- See the [License](LICENSE.md) file for license rights and limitations (MIT).
|
||||
- Pull Requests are welcome!
|
BIN
desktop/icon.ico
Before Width: | Height: | Size: 102 KiB |
@ -1,32 +0,0 @@
|
||||
<svg class="vector" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" style="width: 300px; height: 300px; stroke: rgb(0, 0, 0); stroke-width: 0; fill: none; stroke-linecap: round;">
|
||||
<rect width="300" height="300" style="fill:rgb(0,0,0)" />
|
||||
<circle cx="60" cy="60" r="5" fill="white"/>
|
||||
<circle cx="105" cy="60" r="5" fill="white"/>
|
||||
<circle cx="150" cy="60" r="5" fill="white"/>
|
||||
<circle cx="195" cy="60" r="5" fill="white"/>
|
||||
<circle cx="240" cy="60" r="5" fill="white"/>
|
||||
|
||||
<circle cx="60" cy="105" r="5" fill="white"/>
|
||||
<circle cx="105" cy="105" r="5" fill="white"/>
|
||||
<circle cx="150" cy="105" r="5" fill="white"/>
|
||||
<circle cx="195" cy="105" r="5" fill="white"/>
|
||||
<circle cx="240" cy="105" r="5" fill="white"/>
|
||||
|
||||
<circle cx="60" cy="150" r="5" fill="white"/>
|
||||
<circle cx="105" cy="150" r="5" fill="white"/>
|
||||
<circle cx="150" cy="150" r="5" fill="white"/>
|
||||
<circle cx="195" cy="150" r="5" fill="white"/>
|
||||
<circle cx="240" cy="150" r="5" fill="white"/>
|
||||
|
||||
<circle cx="60" cy="195" r="5" fill="white"/>
|
||||
<circle cx="105" cy="195" r="5" fill="white"/>
|
||||
<circle cx="150" cy="195" r="5" fill="white"/>
|
||||
<circle cx="195" cy="195" r="5" fill="white"/>
|
||||
<circle cx="240" cy="195" r="5" fill="white"/>
|
||||
|
||||
<circle cx="60" cy="240" r="5" fill="white"/>
|
||||
<circle cx="105" cy="240" r="5" fill="white"/>
|
||||
<circle cx="150" cy="240" r="5" fill="white"/>
|
||||
<circle cx="195" cy="240" r="5" fill="white"/>
|
||||
<circle cx="240" cy="240" r="5" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,81 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* global createWindow */
|
||||
|
||||
const { app, BrowserWindow, Menu } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
let isShown = true
|
||||
|
||||
app.win = null
|
||||
|
||||
app.on('ready', () => {
|
||||
app.win = new BrowserWindow({
|
||||
width: 780,
|
||||
height: 462,
|
||||
minWidth: 380,
|
||||
minHeight: 360,
|
||||
backgroundColor: '#000',
|
||||
icon: path.join(__dirname, { darwin: 'icon.icns', linux: 'icon.png', win32: 'icon.ico' }[process.platform] || 'icon.ico'),
|
||||
resizable: true,
|
||||
frame: process.platform !== 'darwin',
|
||||
skipTaskbar: process.platform === 'darwin',
|
||||
autoHideMenuBar: process.platform === 'darwin',
|
||||
webPreferences: { zoomFactor: 1.0, nodeIntegration: true, backgroundThrottling: false }
|
||||
})
|
||||
|
||||
app.win.loadURL(`file://${__dirname}/sources/index.html`)
|
||||
// app.inspect()
|
||||
|
||||
app.win.on('closed', () => {
|
||||
app.quit()
|
||||
})
|
||||
|
||||
app.win.on('hide', function () {
|
||||
isShown = false
|
||||
})
|
||||
|
||||
app.win.on('show', function () {
|
||||
isShown = true
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit()
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (app.win === null) {
|
||||
createWindow()
|
||||
} else {
|
||||
app.win.show()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
app.inspect = function () {
|
||||
app.win.toggleDevTools()
|
||||
}
|
||||
|
||||
app.toggleFullscreen = function () {
|
||||
app.win.setFullScreen(!app.win.isFullScreen())
|
||||
}
|
||||
|
||||
app.toggleMenubar = function () {
|
||||
app.win.setMenuBarVisibility(!app.win.isMenuBarVisible())
|
||||
}
|
||||
|
||||
app.toggleVisible = function () {
|
||||
if (process.platform !== 'darwin') {
|
||||
if (!app.win.isMinimized()) { app.win.minimize() } else { app.win.restore() }
|
||||
} else {
|
||||
if (isShown && !app.win.isFullScreen()) { app.win.hide() } else { app.win.show() }
|
||||
}
|
||||
}
|
||||
|
||||
app.injectMenu = function (menu) {
|
||||
try {
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(menu))
|
||||
} catch (err) {
|
||||
console.warn('Cannot inject menu.')
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
{
|
||||
"name": "Dotgrid",
|
||||
"version": "0.1.0",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron . --disable-gpu",
|
||||
"fix": "standard --fix",
|
||||
"clean": "rm -r ~/Documents/Dotgrid-darwin-x64/ ; rm -r ~/Documents/Dotgrid-linux-x64/ ; rm -r ~/Documents/Dotgrid-win32-x64/ ; echo 'cleaned build location'",
|
||||
"build_osx": "electron-packager . Dotgrid --platform=darwin --arch=x64 --out ~/Documents/ --overwrite --icon=icon.icns ; echo 'Built for OSX'",
|
||||
"build_linux": "electron-packager . Dotgrid --platform=linux --arch=x64 --out ~/Documents/ --overwrite --icon=icon.ico ; echo 'Built for LINUX'",
|
||||
"build_win": "electron-packager . Dotgrid --platform=win32 --arch=x64 --out ~/Documents/ --overwrite --icon=icon.ico ; echo 'Built for WIN'",
|
||||
"build": "npm run clean ; npm run build_osx ; npm run build_linux ; npm run build_win",
|
||||
"push_osx": "~/Applications/butler push ~/Documents/Dotgrid-darwin-x64/ hundredrabbits/dotgrid:osx-64",
|
||||
"push_linux": "~/Applications/butler push ~/Documents/Dotgrid-linux-x64/ hundredrabbits/dotgrid:linux-64",
|
||||
"push_win": "~/Applications/butler push ~/Documents/Dotgrid-win32-x64/ hundredrabbits/dotgrid:windows-64",
|
||||
"status": "~/Applications/butler status hundredrabbits/dotgrid",
|
||||
"push": "npm run build ; npm run push_osx ; npm run push_linux ; npm run push_win ; npm run clean ; npm run status"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^8.1.1",
|
||||
"electron-packager": "^14.2.1"
|
||||
},
|
||||
"standard": {
|
||||
"globals": [
|
||||
"localStorage",
|
||||
"DOMParser",
|
||||
"onMessage",
|
||||
"postMessage",
|
||||
"FileReader",
|
||||
"performance",
|
||||
"Worker"
|
||||
],
|
||||
"ignore": [
|
||||
"/node_modules/*"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:site" content="@neauoire">
|
||||
<meta name="twitter:title" content="Dotgrid">
|
||||
<meta name="twitter:description" content="A minimalist vector graphics tool.">
|
||||
<meta name="twitter:creator" content="@neauoire">
|
||||
<meta name="twitter:image" content="https://hundredrabbits.github.io/Dotgrid/media/services/meta.jpg">
|
||||
<!-- Facebook -->
|
||||
<meta property="og:title" content="Dotgrid" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:url" content="http://wiki.xxiivv.com/Dotgrid" />
|
||||
<meta property="og:image" content="https://hundredrabbits.github.io/Dotgrid/media/services/meta.jpg" />
|
||||
<meta property="og:description" content="A minimalist vector graphics tool." />
|
||||
<meta property="og:site_name" content="Dotgrid" />
|
||||
<!-- Generics -->
|
||||
<script type="text/javascript" src="scripts/lib/acels.js"></script>
|
||||
<script type="text/javascript" src="scripts/lib/theme.js"></script>
|
||||
<script type="text/javascript" src="scripts/lib/history.js"></script>
|
||||
<script type="text/javascript" src="scripts/lib/source.js"></script>
|
||||
<!-- Dotgrid -->
|
||||
<script type="text/javascript" src="scripts/listener.js"></script>
|
||||
<script type="text/javascript" src="scripts/client.js"></script>
|
||||
<script type="text/javascript" src="scripts/manager.js"></script>
|
||||
<script type="text/javascript" src="scripts/renderer.js"></script>
|
||||
<script type="text/javascript" src="scripts/cursor.js"></script>
|
||||
<script type="text/javascript" src="scripts/interface.js"></script>
|
||||
<script type="text/javascript" src="scripts/tool.js"></script>
|
||||
<script type="text/javascript" src="scripts/generator.js"></script>
|
||||
<script type="text/javascript" src="scripts/picker.js"></script>
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" type="text/css" href="links/reset.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="links/fonts.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="links/main.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="links/theme.css"/>
|
||||
<title>Dotgrid</title>
|
||||
</head>
|
||||
<body class='web'>
|
||||
<script>
|
||||
'use strict'
|
||||
|
||||
const client = new Client()
|
||||
|
||||
client.install(document.body)
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
client.start()
|
||||
client.acels.inject('Dotgrid')
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,15 +0,0 @@
|
||||
/* Input */
|
||||
|
||||
@font-face {
|
||||
font-family: 'input_mono_regular';
|
||||
src: url('../media/fonts/input_mono_regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'input_mono_medium';
|
||||
src: url('../media/fonts/input_mono_medium.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
body { padding: 0px; font-family: 'input_mono_regular'; -webkit-user-select: none; overflow: hidden; transition: background-color 500ms; -webkit-app-region: drag; padding: 30px;width:calc(100vw - 60px);height:calc(100vh - 60px)}
|
||||
|
||||
/* Core */
|
||||
|
||||
#guide { position: absolute;width: 300px;height: 300px; transition: opacity 150ms; -webkit-app-region: no-drag; border-radius: 3px;}
|
||||
#render { display: none }
|
||||
#vector { z-index: 1000;position: relative;width:300px; height:300px; }
|
||||
|
||||
/* Interface */
|
||||
|
||||
#interface { font-size: 11px;line-height: 30px;text-transform: uppercase;-webkit-app-region: no-drag; transition: all 150ms; width: 100%; position:fixed; bottom:30px; left:40px; height:30px; max-width:calc(100vw - 75px); overflow: hidden;}
|
||||
#interface.hidden { bottom:10px !important;opacity: 0 !important }
|
||||
#interface.visible { bottom:28px !important; opacity: 1 !important}
|
||||
#interface #menu { opacity: 1; position: absolute; top:0px; transition: all 250ms; z-index: 900; overflow: hidden; height:30px; width:100%;}
|
||||
#interface #menu svg.icon { width:30px; height:30px; margin-right:-9px; opacity: 0.6; transition: opacity 250ms; }
|
||||
#interface #menu svg.icon.inactive { opacity: 0.2 }
|
||||
#interface #menu svg.icon:hover { cursor: pointer; opacity: 1.0 }
|
||||
#interface #menu svg.icon:last-child { margin-right: 0; }
|
||||
#interface #menu svg.icon path { fill:none; stroke-linecap: round; stroke-linejoin: round; stroke-width:12px; }
|
||||
#interface #menu svg.icon.source { float:right; margin-left:-2px; margin-right:0px; }
|
||||
#interface #menu svg.icon#option_color { opacity: 1.0; z-index:1001; position: relative; }
|
||||
#interface #menu svg.icon#option_color:hover { opacity: 0.8 }
|
||||
|
||||
#interface #picker { position: absolute; line-height: 20px; z-index: 0; width: 30px; opacity: 0; transition: all 250ms; font-size: 11px; border-radius: 3px; left: 200px; top: 0px; text-transform: uppercase; font-family: 'input_mono_medium';height:20px; padding:5px 0px;left:280px; overflow:hidden;}
|
||||
#interface #picker:before { content:"#"; position: absolute; left:10px; opacity: 0; transition: opacity 500ms}
|
||||
#interface #picker input { background:transparent; position: absolute; left: 20px; height: 20px; width: 60px; line-height: 20px; opacity: 0; transition: opacity 500ms; text-transform: uppercase;}
|
||||
#interface #color_path { transition: all 500ms; }
|
||||
#interface.picker #menu { z-index: 0 }
|
||||
|
||||
#interface.picker #picker { width:30px; padding: 5px 15px; padding-right: 45px; opacity: 1; z-index: 900; width: 50px; left:200px; opacity: 1}
|
||||
#interface.picker #picker:before { opacity: 1; }
|
||||
#interface.picker #picker input { opacity: 1 }
|
||||
#interface.picker #option_thickness { opacity: 0 !important }
|
||||
#interface.picker #option_mirror { opacity: 0 !important }
|
||||
#interface.picker #option_fill { opacity: 0 !important }
|
||||
|
||||
/* Web Specific */
|
||||
|
||||
body.web #interface #menu #option_open { display: none; }
|
||||
|
||||
/* Ready */
|
||||
|
||||
body #guide { opacity: 0; transition: opacity 500ms; }
|
||||
body.ready #guide { opacity: 1 }
|
||||
body #interface { opacity: 0; transition: opacity 250ms, bottom 500ms; bottom:15px; }
|
||||
body.ready #interface { opacity: 1; bottom:30px; }
|
||||
|
||||
@media (max-width: 560px) {
|
||||
#interface #menu svg.icon.source { opacity: 0; }
|
||||
}
|
@ -1 +0,0 @@
|
||||
* { margin:0;padding:0;border:0;outline:0;text-decoration:none;font-weight:inherit;font-style:inherit;color:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;list-style:none;border-collapse:collapse;border-spacing:0; -webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}
|
@ -1,13 +0,0 @@
|
||||
body { background:var(--background) !important; }
|
||||
#picker { background-color:var(--b_inv) !important; color:var(--f_inv) !important; }
|
||||
#picker:before { color:var(--f_med) !important; }
|
||||
#picker input::placeholder { color:var(--f_med) !important; }
|
||||
.fh { color:var(--f_high) !important; stroke:var(--f_high) !important; }
|
||||
.fm { color:var(--f_med) !important; stroke:var(--f_med) !important; }
|
||||
.fl { color:var(--f_low) !important; stroke:var(--f_low) !important; }
|
||||
.f_inv { color:var(--f_inv) !important; stroke:var(--f_inv) !important; }
|
||||
.bh { background:var(--b_high) !important; }
|
||||
.bm { background:var(--b_med) !important; }
|
||||
.bl { background:var(--b_low) !important; }
|
||||
.b_inv { background:var(--b_inv) !important; }
|
||||
.icon { color:var(--f_high) !important; stroke:var(--f_high) !important; }
|
@ -1 +0,0 @@
|
||||
<svg class="vector" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" style="width: 300px; height: 300px; stroke: rgb(0, 0, 0); stroke-width: 10px; fill: none; stroke-linecap: square;"><path d="M50,50 A200,200 0 0,1 250,250 "></path></svg>
|
Before Width: | Height: | Size: 286 B |
@ -1 +0,0 @@
|
||||
<svg class="vector" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" style="width: 300px; height: 300px; stroke: rgb(0, 0, 0); stroke-width: 10px; fill: none; stroke-linecap: square;"><path d="M50,50 A200,200 0 0,0 250,250 "></path></svg>
|
Before Width: | Height: | Size: 286 B |
@ -1 +0,0 @@
|
||||
<svg class="vector" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" style="width: 300px; height: 300px; stroke: rgb(0, 0, 0); stroke-width: 10px; fill: none; stroke-linecap: square;"><path d="M50,50 Q50,150 150,150 Q250,150 250,250 "></path></svg>
|
Before Width: | Height: | Size: 296 B |
@ -1 +0,0 @@
|
||||
<svg class="vector" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" style="width: 300px; height: 300px; stroke: rgb(0, 0, 0); stroke-width: 10px; fill: none; stroke-linecap: square;"><path d="M50,50 L120,120 M250,250 L180,180 "></path></svg>
|
Before Width: | Height: | Size: 290 B |
@ -1 +0,0 @@
|
||||
<svg class="vector" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" style="width: 300px; height: 300px; stroke: rgb(0, 0, 0); stroke-width: 10px; fill: none; stroke-linecap: square;"><path d="M150,50 L50,150 L150,250 L250,150 L150,50 "></path></svg>
|
Before Width: | Height: | Size: 298 B |
@ -1 +0,0 @@
|
||||
<svg class="vector" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" style="width: 300px; height: 300px; stroke: rgb(0, 0, 0); stroke-width: 10px; fill: none; stroke-linecap: square;"><path d="M50,50 L250,250 "></path></svg>
|
Before Width: | Height: | Size: 272 B |
@ -1 +0,0 @@
|
||||
<svg class="vector" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" style="width: 300px; height: 300px; stroke: rgb(0, 0, 0); stroke-width: 10px; fill: none; stroke-linecap: square;"><path d="M150,50 L150,250 M50,150 L100,150 M200,150 L250,150 "></path></svg>
|
Before Width: | Height: | Size: 308 B |
@ -1,267 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* global Acels */
|
||||
/* global Theme */
|
||||
/* global Source */
|
||||
/* global History */
|
||||
|
||||
/* global Manager */
|
||||
/* global Renderer */
|
||||
/* global Tool */
|
||||
/* global Interface */
|
||||
/* global Picker */
|
||||
/* global Cursor */
|
||||
|
||||
/* global FileReader */
|
||||
|
||||
function Client () {
|
||||
this.install = function (host) {
|
||||
console.info('Client', 'Installing..')
|
||||
|
||||
this.acels = new Acels(this)
|
||||
this.theme = new Theme(this)
|
||||
this.history = new History(this)
|
||||
this.source = new Source(this)
|
||||
|
||||
this.listener = new Listener(this)
|
||||
this.manager = new Manager(this)
|
||||
this.renderer = new Renderer(this)
|
||||
this.tool = new Tool(this)
|
||||
this.interface = new Interface(this)
|
||||
this.picker = new Picker(this)
|
||||
this.cursor = new Cursor(this)
|
||||
|
||||
host.appendChild(this.renderer.el)
|
||||
|
||||
document.addEventListener('mousedown', (e) => { this.cursor.down(e) }, false)
|
||||
document.addEventListener('mousemove', (e) => { this.cursor.move(e) }, false)
|
||||
document.addEventListener('contextmenu', (e) => { this.cursor.alt(e) }, false)
|
||||
document.addEventListener('mouseup', (e) => { this.cursor.up(e) }, false)
|
||||
document.addEventListener('copy', (e) => { this.copy(e) }, false)
|
||||
document.addEventListener('cut', (e) => { this.cut(e) }, false)
|
||||
document.addEventListener('paste', (e) => { this.paste(e) }, false)
|
||||
window.addEventListener('resize', (e) => { this.onResize() }, false)
|
||||
window.addEventListener('dragover', (e) => { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = 'copy' })
|
||||
window.addEventListener('drop', this.onDrop)
|
||||
|
||||
this.acels.set('File', 'New', 'CmdOrCtrl+N', () => { this.source.new() })
|
||||
this.acels.set('File', 'Open', 'CmdOrCtrl+O', () => { this.source.open('grid', this.whenOpen) })
|
||||
this.acels.set('File', 'Save', 'CmdOrCtrl+S', () => { this.source.write('dotgrid', 'grid', this.tool.export(), 'text/plain') })
|
||||
this.acels.set('File', 'Export Vector', 'CmdOrCtrl+E', () => { this.source.write('dotgrid', 'svg', this.manager.toString(), 'image/svg+xml') })
|
||||
this.acels.set('File', 'Export Image', 'CmdOrCtrl+Shift+E', () => { this.manager.toPNG(this.tool.settings.size, (dataUrl) => { this.source.write('dotgrid', 'png', dataUrl, 'image/png') }) })
|
||||
this.acels.set('History', 'Undo', 'CmdOrCtrl+Z', () => { this.tool.undo() })
|
||||
this.acels.set('History', 'Redo', 'CmdOrCtrl+Shift+Z', () => { this.tool.redo() })
|
||||
this.acels.set('Stroke', 'Line', 'A', () => { this.tool.cast('line') })
|
||||
this.acels.set('Stroke', 'Arc', 'S', () => { this.tool.cast('arc_c') })
|
||||
this.acels.set('Stroke', 'Arc Rev', 'D', () => { this.tool.cast('arc_r') })
|
||||
this.acels.set('Stroke', 'Bezier', 'F', () => { this.tool.cast('bezier') })
|
||||
this.acels.set('Stroke', 'Close', 'Z', () => { this.tool.cast('close') })
|
||||
this.acels.set('Stroke', 'Arc(full)', 'T', () => { this.tool.cast('arc_c_full') })
|
||||
this.acels.set('Stroke', 'Arc Rev(full)', 'Y', () => { this.tool.cast('arc_r_full') })
|
||||
this.acels.set('Stroke', 'Clear Selection', 'Escape', () => { this.tool.clear() })
|
||||
this.acels.set('Effect', 'Linecap', 'Q', () => { this.tool.toggle('linecap') })
|
||||
this.acels.set('Effect', 'Linejoin', 'W', () => { this.tool.toggle('linejoin') })
|
||||
this.acels.set('Effect', 'Mirror', 'E', () => { this.tool.toggle('mirror') })
|
||||
this.acels.set('Effect', 'Fill', 'R', () => { this.tool.toggle('fill') })
|
||||
this.acels.set('Effect', 'Thicker', '}', () => { this.tool.toggle('thickness', 1) })
|
||||
this.acels.set('Effect', 'Thinner', '{', () => { this.tool.toggle('thickness', -1) })
|
||||
this.acels.set('Effect', 'Thicker +5', ']', () => { this.tool.toggle('thickness', 5) })
|
||||
this.acels.set('Effect', 'Thinner -5', '[', () => { this.tool.toggle('thickness', -5) })
|
||||
this.acels.set('Manual', 'Add Point', 'Enter', () => { this.tool.addVertex(this.cursor.pos); this.renderer.update() })
|
||||
this.acels.set('Manual', 'Move Up', 'Up', () => { this.cursor.pos.y -= 15; this.renderer.update() })
|
||||
this.acels.set('Manual', 'Move Right', 'Right', () => { this.cursor.pos.x += 15; this.renderer.update() })
|
||||
this.acels.set('Manual', 'Move Down', 'Down', () => { this.cursor.pos.y += 15; this.renderer.update() })
|
||||
this.acels.set('Manual', 'Move Left', 'Left', () => { this.cursor.pos.x -= 15; this.renderer.update() })
|
||||
this.acels.set('Manual', 'Remove Point', 'Shift+Backspace', () => { this.tool.removeSegmentsAt(this.cursor.pos) })
|
||||
this.acels.set('Manual', 'Remove Segment', 'Backspace', () => { this.tool.removeSegment() })
|
||||
this.acels.set('Layers', 'Foreground', 'CmdOrCtrl+1', () => { this.tool.selectLayer(0) })
|
||||
this.acels.set('Layers', 'Middleground', 'CmdOrCtrl+2', () => { this.tool.selectLayer(1) })
|
||||
this.acels.set('Layers', 'Background', 'CmdOrCtrl+3', () => { this.tool.selectLayer(2) })
|
||||
this.acels.set('Layers', 'Merge Layers', 'CmdOrCtrl+M', () => { this.tool.merge() })
|
||||
this.acels.set('View', 'Color Picker', 'G', () => { this.picker.start() })
|
||||
this.acels.set('View', 'Toggle Grid', 'H', () => { this.renderer.toggle() })
|
||||
this.acels.install(window)
|
||||
this.acels.pipe(this)
|
||||
|
||||
this.manager.install()
|
||||
this.interface.install(host)
|
||||
this.theme.install(host, () => { this.update() })
|
||||
}
|
||||
|
||||
this.start = () => {
|
||||
console.log('Client', 'Starting..')
|
||||
console.info(`${this.acels}`)
|
||||
|
||||
this.theme.start()
|
||||
this.tool.start()
|
||||
this.renderer.start()
|
||||
this.interface.start()
|
||||
|
||||
this.source.new()
|
||||
this.onResize()
|
||||
|
||||
setTimeout(() => { document.body.className += ' ready' }, 250)
|
||||
}
|
||||
|
||||
this.update = () => {
|
||||
this.manager.update()
|
||||
this.interface.update()
|
||||
this.renderer.update()
|
||||
}
|
||||
|
||||
this.clear = () => {
|
||||
this.history.clear()
|
||||
this.tool.reset()
|
||||
this.reset()
|
||||
this.renderer.update()
|
||||
this.interface.update(true)
|
||||
}
|
||||
|
||||
this.reset = () => {
|
||||
this.tool.clear()
|
||||
this.update()
|
||||
}
|
||||
|
||||
this.whenOpen = (file, data) => {
|
||||
this.tool.replace(JSON.parse(data))
|
||||
this.onResize()
|
||||
}
|
||||
|
||||
// Resize Tools
|
||||
|
||||
this.fitSize = () => {
|
||||
if (this.requireResize() === false) { return }
|
||||
console.log('Client', `Will resize to: ${printSize(this.getRequiredSize())}`)
|
||||
this.update()
|
||||
}
|
||||
|
||||
this.getPadding = () => {
|
||||
return { x: 60, y: 90 }
|
||||
}
|
||||
|
||||
this.getWindowSize = () => {
|
||||
return { width: window.innerWidth, height: window.innerHeight }
|
||||
}
|
||||
|
||||
this.getProjectSize = () => {
|
||||
return this.tool.settings.size
|
||||
}
|
||||
|
||||
this.getPaddedSize = () => {
|
||||
const rect = this.getWindowSize()
|
||||
const pad = this.getPadding()
|
||||
return { width: step(rect.width - pad.x, 15), height: step(rect.height - pad.y, 15) }
|
||||
}
|
||||
|
||||
this.getRequiredSize = () => {
|
||||
const rect = this.getProjectSize()
|
||||
const pad = this.getPadding()
|
||||
return { width: step(rect.width, 15) + pad.x, height: step(rect.height, 15) + pad.y }
|
||||
}
|
||||
|
||||
this.requireResize = () => {
|
||||
const _window = this.getWindowSize()
|
||||
const _required = this.getRequiredSize()
|
||||
const offset = sizeOffset(_window, _required)
|
||||
if (offset.width !== 0 || offset.height !== 0) {
|
||||
console.log('Client', `Require ${printSize(_required)}, but window is ${printSize(_window)}(${printSize(offset)})`)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
this.onResize = () => {
|
||||
const _project = this.getProjectSize()
|
||||
const _padded = this.getPaddedSize()
|
||||
const offset = sizeOffset(_padded, _project)
|
||||
if (offset.width !== 0 || offset.height !== 0) {
|
||||
console.log('Client', `Resize project to ${printSize(_padded)}`)
|
||||
this.tool.settings.size = _padded
|
||||
}
|
||||
this.update()
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
this.drag = function (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const file = e.dataTransfer.files[0]
|
||||
const filename = file.path ? file.path : file.name ? file.name : ''
|
||||
|
||||
if (filename.indexOf('.grid') < 0) { console.warn('Client', 'Not a .grid file'); return }
|
||||
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = function (e) {
|
||||
const data = e.target && e.target.result ? e.target.result : ''
|
||||
this.source.load(filename, data)
|
||||
this.fitSize()
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
this.onDrop = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const file = e.dataTransfer.files[0]
|
||||
|
||||
if (file.name.indexOf('.grid') > -1) {
|
||||
this.source.read(e.dataTransfer.files[0], this.whenOpen)
|
||||
}
|
||||
}
|
||||
|
||||
this.copy = function (e) {
|
||||
this.renderer.update()
|
||||
|
||||
if (e.target !== this.picker.input) {
|
||||
e.clipboardData.setData('text/source', this.tool.export(this.tool.layer()))
|
||||
e.clipboardData.setData('text/plain', this.tool.path())
|
||||
e.clipboardData.setData('text/html', this.manager.el.outerHTML)
|
||||
e.clipboardData.setData('text/svg+xml', this.manager.el.outerHTML)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
this.renderer.update()
|
||||
}
|
||||
|
||||
this.cut = function (e) {
|
||||
this.renderer.update()
|
||||
|
||||
if (e.target !== this.picker.input) {
|
||||
e.clipboardData.setData('text/source', this.tool.export(this.tool.layer()))
|
||||
e.clipboardData.setData('text/plain', this.tool.export(this.tool.layer()))
|
||||
e.clipboardData.setData('text/html', this.manager.el.outerHTML)
|
||||
e.clipboardData.setData('text/svg+xml', this.manager.el.outerHTML)
|
||||
this.tool.layers[this.tool.index] = []
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
this.renderer.update()
|
||||
}
|
||||
|
||||
this.paste = function (e) {
|
||||
if (e.target !== this.picker.el) {
|
||||
let data = e.clipboardData.getData('text/source')
|
||||
if (isJson(data)) {
|
||||
data = JSON.parse(data.trim())
|
||||
this.tool.import(data)
|
||||
}
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
this.renderer.update()
|
||||
}
|
||||
|
||||
this.onKeyDown = (e) => {
|
||||
}
|
||||
|
||||
this.onKeyUp = (e) => {
|
||||
}
|
||||
|
||||
function sizeOffset (a, b) { return { width: a.width - b.width, height: a.height - b.height } }
|
||||
function printSize (size) { return `${size.width}x${size.height}` }
|
||||
function isJson (text) { try { JSON.parse(text); return true } catch (error) { return false } }
|
||||
function step (v, s) { return Math.round(v / s) * s }
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
function Cursor (client) {
|
||||
this.pos = { x: 0, y: 0 }
|
||||
this.lastPos = { x: 0, y: 0 }
|
||||
this.translation = null
|
||||
this.operation = null
|
||||
|
||||
this.translate = function (from = null, to = null, multi = false, copy = false, layer = false) {
|
||||
if ((from || to) && this.translation === null) { this.translation = { multi: multi, copy: copy, layer: layer } }
|
||||
if (from) { this.translation.from = from }
|
||||
if (to) { this.translation.to = to }
|
||||
if (!from && !to) {
|
||||
this.translation = null
|
||||
}
|
||||
}
|
||||
|
||||
this.down = function (e) {
|
||||
this.pos = this.atEvent(e)
|
||||
if (client.tool.vertexAt(this.pos)) {
|
||||
this.translate(this.pos, this.pos, e.shiftKey, e.ctrlKey || e.metaKey, e.altKey)
|
||||
}
|
||||
client.renderer.update()
|
||||
client.interface.update()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
this.move = function (e) {
|
||||
this.pos = this.atEvent(e)
|
||||
if (this.translation) {
|
||||
this.translate(null, this.pos)
|
||||
}
|
||||
if (this.lastPos.x !== this.pos.x || this.lastPos.y !== this.pos.y) {
|
||||
client.renderer.update()
|
||||
}
|
||||
client.interface.update()
|
||||
this.lastPos = this.pos
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
this.up = function (e) {
|
||||
this.pos = this.atEvent(e)
|
||||
if (this.translation && !isEqual(this.translation.from, this.translation.to)) {
|
||||
if (this.translation.layer === true) { client.tool.translateLayer(this.translation.from, this.translation.to) } else if (this.translation.copy) { client.tool.translateCopy(this.translation.from, this.translation.to) } else if (this.translation.multi) { client.tool.translateMulti(this.translation.from, this.translation.to) } else { client.tool.translate(this.translation.from, this.translation.to) }
|
||||
} else if (e.target.id === 'guide') {
|
||||
client.tool.addVertex({ x: this.pos.x, y: this.pos.y })
|
||||
client.picker.stop()
|
||||
}
|
||||
this.translate()
|
||||
client.interface.update()
|
||||
client.renderer.update()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
this.alt = function (e) {
|
||||
this.pos = this.atEvent(e)
|
||||
client.tool.removeSegmentsAt(this.pos)
|
||||
e.preventDefault()
|
||||
setTimeout(() => {
|
||||
client.tool.clear()
|
||||
}, 150)
|
||||
}
|
||||
|
||||
this.atEvent = function (e) {
|
||||
return this.snapPos(this.relativePos({ x: e.clientX, y: e.clientY }))
|
||||
}
|
||||
|
||||
this.relativePos = function (pos) {
|
||||
return {
|
||||
x: pos.x - client.renderer.el.offsetLeft,
|
||||
y: pos.y - client.renderer.el.offsetTop
|
||||
}
|
||||
}
|
||||
|
||||
this.snapPos = function (pos) {
|
||||
return {
|
||||
x: clamp(step(pos.x, 15), 15, client.tool.settings.size.width - 15),
|
||||
y: clamp(step(pos.y, 15), 15, client.tool.settings.size.height - 15)
|
||||
}
|
||||
}
|
||||
|
||||
function isEqual (a, b) { return a.x === b.x && a.y === b.y }
|
||||
function clamp (v, min, max) { return v < min ? min : v > max ? max : v }
|
||||
function step (v, s) { return Math.round(v / s) * s }
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* global client */
|
||||
|
||||
function Generator (layer, style) {
|
||||
this.layer = layer
|
||||
this.style = style
|
||||
|
||||
function operate (layer, offset, scale, mirror = 0, angle = 0) {
|
||||
const l = copy(layer)
|
||||
|
||||
for (const k1 in l) {
|
||||
const seg = l[k1]
|
||||
for (const k2 in seg.vertices) {
|
||||
if (mirror === 1 || mirror === 3) { seg.vertices[k2].x = (client.tool.settings.size.width) - seg.vertices[k2].x }
|
||||
if (mirror === 2 || mirror === 3) { seg.vertices[k2].y = (client.tool.settings.size.height) - seg.vertices[k2].y }
|
||||
// Offset
|
||||
seg.vertices[k2].x += offset.x
|
||||
seg.vertices[k2].y += offset.y
|
||||
// Rotate
|
||||
const center = { x: (client.tool.settings.size.width / 2) + offset.x + (7.5), y: (client.tool.settings.size.height / 2) + offset.y + 30 }
|
||||
seg.vertices[k2] = rotatePoint(seg.vertices[k2], center, angle)
|
||||
// Scale
|
||||
seg.vertices[k2].x *= scale
|
||||
seg.vertices[k2].y *= scale
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
this.render = function (prev, segment, mirror = 0) {
|
||||
const type = segment.type
|
||||
const vertices = segment.vertices
|
||||
let html = ''
|
||||
let skip = 0
|
||||
|
||||
for (const id in vertices) {
|
||||
if (skip > 0) { skip -= 1; continue }
|
||||
|
||||
const vertex = vertices[parseInt(id)]
|
||||
const next = vertices[parseInt(id) + 1]
|
||||
const afterNext = vertices[parseInt(id) + 2]
|
||||
|
||||
if (parseInt(id) === 0 && !prev) {
|
||||
html += `M${vertex.x},${vertex.y} `
|
||||
} else if (parseInt(id) === 0 && prev && (prev.x !== vertex.x || prev.y !== vertex.y)) {
|
||||
html += `M${vertex.x},${vertex.y} `
|
||||
}
|
||||
|
||||
if (type === 'line') {
|
||||
html += this._line(vertex)
|
||||
} else if (type === 'arc_c') {
|
||||
const clock = mirror > 0 && mirror < 3 ? '0,0' : '0,1'
|
||||
html += this._arc(vertex, next, clock)
|
||||
} else if (type === 'arc_r') {
|
||||
const clock = mirror > 0 && mirror < 3 ? '0,1' : '0,0'
|
||||
html += this._arc(vertex, next, clock)
|
||||
} else if (type === 'arc_c_full') {
|
||||
const clock = mirror > 0 ? '1,0' : '1,1'
|
||||
html += this._arc(vertex, next, clock)
|
||||
} else if (type === 'arc_r_full') {
|
||||
const clock = mirror > 0 ? '1,1' : '1,0'
|
||||
html += this._arc(vertex, next, clock)
|
||||
} else if (type === 'bezier') {
|
||||
html += this._bezier(next, afterNext)
|
||||
skip = 1
|
||||
}
|
||||
}
|
||||
|
||||
if (segment.type === 'close') {
|
||||
html += 'Z '
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
this._line = function (a) {
|
||||
return `L${a.x},${a.y} `
|
||||
}
|
||||
|
||||
this._arc = function (a, b, c) {
|
||||
if (!a || !b || !c) { return '' }
|
||||
|
||||
const offset = { x: b.x - a.x, y: b.y - a.y }
|
||||
|
||||
if (offset.x === 0 || offset.y === 0) { return this._line(b) }
|
||||
return `A${Math.abs(b.x - a.x)},${Math.abs(b.y - a.y)} 0 ${c} ${b.x},${b.y} `
|
||||
}
|
||||
|
||||
this._bezier = function (a, b) {
|
||||
if (!a || !b) { return '' }
|
||||
return `Q${a.x},${a.y} ${b.x},${b.y} `
|
||||
}
|
||||
|
||||
this.convert = function (layer, mirror, angle) {
|
||||
let s = ''
|
||||
let prev = null
|
||||
for (const id in layer) {
|
||||
const seg = layer[parseInt(id)]
|
||||
s += `${this.render(prev, seg, mirror)}`
|
||||
prev = seg.vertices ? seg.vertices[seg.vertices.length - 1] : null
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
this.toString = function (offset = { x: 0, y: 0 }, scale = 1, mirror = this.style && this.style.mirror_style ? this.style.mirror_style : 0) {
|
||||
let s = this.convert(operate(this.layer, offset, scale))
|
||||
|
||||
if (mirror === 1 || mirror === 2 || mirror === 3) {
|
||||
s += this.convert(operate(this.layer, offset, scale, mirror), mirror)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
function copy (data) { return data ? JSON.parse(JSON.stringify(data)) : [] }
|
||||
function rotatePoint (point, origin, angle) { angle = angle * Math.PI / 180.0; return { x: (Math.cos(angle) * (point.x - origin.x) - Math.sin(angle) * (point.y - origin.y) + origin.x).toFixed(1), y: (Math.sin(angle) * (point.x - origin.x) + Math.cos(angle) * (point.y - origin.y) + origin.y).toFixed(1) } }
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
function Interface (client) {
|
||||
this.el = document.createElement('div')
|
||||
this.el.id = 'interface'
|
||||
|
||||
this.el.appendChild(this.menu_el = document.createElement('div'))
|
||||
this.menu_el.id = 'menu'
|
||||
|
||||
this.isVisible = true
|
||||
this.zoom = false
|
||||
|
||||
this.install = function (host) {
|
||||
host.appendChild(this.el)
|
||||
}
|
||||
|
||||
this.start = function (host) {
|
||||
let html = ''
|
||||
const options = {
|
||||
cast: {
|
||||
line: { key: 'A', icon: 'M60,60 L240,240' },
|
||||
arc_c: { key: 'S', icon: 'M60,60 A180,180 0 0,1 240,240' },
|
||||
arc_r: { key: 'D', icon: 'M60,60 A180,180 0 0,0 240,240' },
|
||||
bezier: { key: 'F', icon: 'M60,60 Q60,150 150,150 Q240,150 240,240' },
|
||||
close: { key: 'Z', icon: 'M60,60 A180,180 0 0,1 240,240 M60,60 A180,180 0 0,0 240,240' }
|
||||
},
|
||||
toggle: {
|
||||
linecap: { key: 'Q', icon: 'M60,60 L60,60 L180,180 L240,180 L240,240 L180,240 L180,180' },
|
||||
linejoin: { key: 'W', icon: 'M60,60 L120,120 L180,120 M120,180 L180,180 L240,240' },
|
||||
thickness: { key: '', icon: 'M120,90 L120,90 L90,120 L180,210 L210,180 Z M105,105 L105,105 L60,60 M195,195 L195,195 L240,240' },
|
||||
mirror: { key: 'E', icon: 'M60,60 L60,60 L120,120 M180,180 L180,180 L240,240 M210,90 L210,90 L180,120 M120,180 L120,180 L90,210' },
|
||||
fill: { key: 'R', icon: 'M60,60 L60,150 L150,150 L240,150 L240,240 Z' }
|
||||
},
|
||||
misc: {
|
||||
color: { key: 'G', icon: 'M150,60 A90,90 0 0,1 240,150 A-90,90 0 0,1 150,240 A-90,-90 0 0,1 60,150 A90,-90 0 0,1 150,60' }
|
||||
},
|
||||
source: {
|
||||
open: { key: 'c-O', icon: 'M155,65 A90,90 0 0,1 245,155 A90,90 0 0,1 155,245 A90,90 0 0,1 65,155 A90,90 0 0,1 155,65 M155,95 A60,60 0 0,1 215,155 A60,60 0 0,1 155,215 A60,60 0 0,1 95,155 A60,60 0 0,1 155,95 ' },
|
||||
render: { key: 'c-R', icon: 'M155,65 A90,90 0 0,1 245,155 A90,90 0 0,1 155,245 A90,90 0 0,1 65,155 A90,90 0 0,1 155,65 M110,155 L110,155 L200,155 ' },
|
||||
export: { key: 'c-E', icon: 'M155,65 A90,90 0 0,1 245,155 A90,90 0 0,1 155,245 A90,90 0 0,1 65,155 A90,90 0 0,1 155,65 M110,140 L110,140 L200,140 M110,170 L110,170 L200,170' },
|
||||
save: { key: 'c-S', icon: 'M155,65 A90,90 0 0,1 245,155 A90,90 0 0,1 155,245 A90,90 0 0,1 65,155 A90,90 0 0,1 155,65 M110,155 L110,155 L200,155 M110,185 L110,185 L200,185 M110,125 L110,125 L200,125' },
|
||||
grid: { key: 'H', icon: 'M65,155 Q155,245 245,155 M65,155 Q155,65 245,155 M155,125 A30,30 0 0,1 185,155 A30,30 0 0,1 155,185 A30,30 0 0,1 125,155 A30,30 0 0,1 155,125 ' }
|
||||
}
|
||||
}
|
||||
|
||||
for (const type in options) {
|
||||
const tools = options[type]
|
||||
for (const name in tools) {
|
||||
const tool = tools[name]
|
||||
html += `
|
||||
<svg
|
||||
id="option_${name}"
|
||||
title="${capitalize(name)}"
|
||||
onmouseout="client.interface.out('${type}','${name}')"
|
||||
onmouseup="client.interface.up('${type}','${name}')"
|
||||
onmousedown="client.interface.down('${type}','${name}', event)"
|
||||
onmouseover="client.interface.over('${type}','${name}')"
|
||||
viewBox="0 0 300 300"
|
||||
class="icon ${type}">
|
||||
<path id="${name}_path" class="icon_path" d="${tool.icon}"/>${name === 'depth' ? '<path class="icon_path inactive" d=""/>' : ''}
|
||||
<rect ar="${name}" width="300" height="300" opacity="0">
|
||||
<title>${capitalize(name)}${tool.key ? '(' + tool.key + ')' : ''}</title>
|
||||
</rect>
|
||||
</svg>`
|
||||
}
|
||||
}
|
||||
this.menu_el.innerHTML = html
|
||||
this.menu_el.appendChild(client.picker.el)
|
||||
}
|
||||
|
||||
this.over = function (type, name) {
|
||||
client.cursor.operation = {}
|
||||
client.cursor.operation[type] = name
|
||||
this.update(true)
|
||||
client.renderer.update(true)
|
||||
}
|
||||
|
||||
this.out = function (type, name) {
|
||||
client.cursor.operation = ''
|
||||
client.renderer.update(true)
|
||||
}
|
||||
|
||||
this.up = function (type, name) {
|
||||
if (!client.tool[type]) { console.warn(`Unknown option(type): ${type}.${name}`, client.tool); return }
|
||||
|
||||
this.update(true)
|
||||
client.renderer.update(true)
|
||||
}
|
||||
|
||||
this.down = function (type, name, event) {
|
||||
if (!client.tool[type]) { console.warn(`Unknown option(type): ${type}.${name}`, client.tool); return }
|
||||
const mod = event.button === 2 ? -1 : 1
|
||||
client.tool[type](name, mod)
|
||||
this.update(true)
|
||||
client.renderer.update(true)
|
||||
}
|
||||
|
||||
this.prev_operation = null
|
||||
|
||||
this.update = function (force = false, id) {
|
||||
if (this.prev_operation === client.cursor.operation && force === false) { return }
|
||||
|
||||
let multiVertices = null
|
||||
const segments = client.tool.layer()
|
||||
const sumSegments = client.tool.length()
|
||||
|
||||
for (const i in segments) {
|
||||
if (segments[i].vertices.length > 2) { multiVertices = true; break }
|
||||
}
|
||||
|
||||
document.getElementById('option_line').className.baseVal = !client.tool.canCast('line') ? 'icon inactive' : 'icon'
|
||||
document.getElementById('option_arc_c').className.baseVal = !client.tool.canCast('arc_c') ? 'icon inactive' : 'icon'
|
||||
document.getElementById('option_arc_r').className.baseVal = !client.tool.canCast('arc_r') ? 'icon inactive' : 'icon'
|
||||
document.getElementById('option_bezier').className.baseVal = !client.tool.canCast('bezier') ? 'icon inactive' : 'icon'
|
||||
document.getElementById('option_close').className.baseVal = !client.tool.canCast('close') ? 'icon inactive' : 'icon'
|
||||
|
||||
document.getElementById('option_thickness').className.baseVal = client.tool.layer().length < 1 ? 'icon inactive' : 'icon'
|
||||
document.getElementById('option_linecap').className.baseVal = client.tool.layer().length < 1 ? 'icon inactive' : 'icon'
|
||||
document.getElementById('option_linejoin').className.baseVal = client.tool.layer().length < 1 || !multiVertices ? 'icon inactive' : 'icon'
|
||||
document.getElementById('option_mirror').className.baseVal = client.tool.layer().length < 1 ? 'icon inactive' : 'icon'
|
||||
document.getElementById('option_fill').className.baseVal = client.tool.layer().length < 1 ? 'icon inactive' : 'icon'
|
||||
|
||||
document.getElementById('option_color').children[0].style.fill = client.tool.style().color
|
||||
document.getElementById('option_color').children[0].style.stroke = client.tool.style().color
|
||||
document.getElementById('option_color').className.baseVal = 'icon'
|
||||
|
||||
// Source
|
||||
|
||||
document.getElementById('option_save').className.baseVal = sumSegments < 1 ? 'icon inactive source' : 'icon source'
|
||||
document.getElementById('option_export').className.baseVal = sumSegments < 1 ? 'icon inactive source' : 'icon source'
|
||||
document.getElementById('option_render').className.baseVal = sumSegments < 1 ? 'icon inactive source' : 'icon source'
|
||||
|
||||
document.getElementById('option_grid').className.baseVal = client.renderer.showExtras ? 'icon inactive source' : 'icon source'
|
||||
|
||||
// Grid
|
||||
if (client.renderer.showExtras) { document.getElementById('grid_path').setAttribute('d', 'M65,155 Q155,245 245,155 M65,155 Q155,65 245,155 M155,125 A30,30 0 0,1 185,155 A30,30 0 0,1 155,185 A30,30 0 0,1 125,155 A30,30 0 0,1 155,125 ') } else { document.getElementById('grid_path').setAttribute('d', 'M65,155 Q155,245 245,155 M65,155 ') }
|
||||
|
||||
// Mirror
|
||||
if (client.tool.style().mirror_style === 0) { document.getElementById('mirror_path').setAttribute('d', 'M60,60 L60,60 L120,120 M180,180 L180,180 L240,240 M210,90 L210,90 L180,120 M120,180 L120,180 L90,210') } else if (client.tool.style().mirror_style === 1) { document.getElementById('mirror_path').setAttribute('d', 'M60,60 L240,240 M180,120 L210,90 M120,180 L90,210') } else if (client.tool.style().mirror_style === 2) { document.getElementById('mirror_path').setAttribute('d', 'M210,90 L210,90 L90,210 M60,60 L60,60 L120,120 M180,180 L180,180 L240,240') } else if (client.tool.style().mirror_style === 3) { document.getElementById('mirror_path').setAttribute('d', 'M60,60 L60,60 L120,120 L180,120 L210,90 M240,240 L240,240 L180,180 L120,180 L90,210') } else if (client.tool.style().mirror_style === 4) { document.getElementById('mirror_path').setAttribute('d', 'M120,120 L120,120 L120,120 L180,120 M120,150 L120,150 L180,150 M120,180 L120,180 L180,180 L180,180 L180,180 L240,240 M120,210 L120,210 L180,210 M120,90 L120,90 L180,90 M60,60 L60,60 L120,120 ') }
|
||||
|
||||
this.prev_operation = client.cursor.operation
|
||||
}
|
||||
|
||||
this.toggle = function () {
|
||||
this.isVisible = !this.isVisible
|
||||
this.el.className = this.isVisible ? 'visible' : 'hidden'
|
||||
}
|
||||
|
||||
document.onkeydown = function (e) {
|
||||
if (e.key === 'Tab') {
|
||||
client.interface.toggle()
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
function capitalize (str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
function Acels (client) {
|
||||
this.all = {}
|
||||
this.roles = {}
|
||||
this.pipe = null
|
||||
|
||||
this.install = (host = window) => {
|
||||
host.addEventListener('keydown', this.onKeyDown, false)
|
||||
host.addEventListener('keyup', this.onKeyUp, false)
|
||||
}
|
||||
|
||||
this.set = (cat, name, accelerator, downfn, upfn) => {
|
||||
if (this.all[accelerator]) { console.warn('Acels', `Trying to overwrite ${this.all[accelerator].name}, with ${name}.`) }
|
||||
this.all[accelerator] = { cat, name, downfn, upfn, accelerator }
|
||||
}
|
||||
|
||||
this.add = (cat, role) => {
|
||||
this.all[':' + role] = { cat, name: role, role }
|
||||
}
|
||||
|
||||
this.get = (accelerator) => {
|
||||
return this.all[accelerator]
|
||||
}
|
||||
|
||||
this.sort = () => {
|
||||
const h = {}
|
||||
for (const item of Object.values(this.all)) {
|
||||
if (!h[item.cat]) { h[item.cat] = [] }
|
||||
h[item.cat].push(item)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
this.convert = (event) => {
|
||||
const accelerator = event.key === ' ' ? 'Space' : event.key.substr(0, 1).toUpperCase() + event.key.substr(1)
|
||||
if ((event.ctrlKey || event.metaKey) && event.shiftKey) {
|
||||
return `CmdOrCtrl+Shift+${accelerator}`
|
||||
}
|
||||
if (event.shiftKey && event.key.toUpperCase() !== event.key) {
|
||||
return `Shift+${accelerator}`
|
||||
}
|
||||
if (event.altKey && event.key.length !== 1) {
|
||||
return `Alt+${accelerator}`
|
||||
}
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
return `CmdOrCtrl+${accelerator}`
|
||||
}
|
||||
return accelerator
|
||||
}
|
||||
|
||||
this.pipe = (obj) => {
|
||||
this.pipe = obj
|
||||
}
|
||||
|
||||
this.onKeyDown = (e) => {
|
||||
const target = this.get(this.convert(e))
|
||||
if (!target || !target.downfn) { return this.pipe ? this.pipe.onKeyDown(e) : null }
|
||||
target.downfn()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
this.onKeyUp = (e) => {
|
||||
const target = this.get(this.convert(e))
|
||||
if (!target || !target.upfn) { return this.pipe ? this.pipe.onKeyUp(e) : null }
|
||||
target.upfn()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
this.toMarkdown = () => {
|
||||
const cats = this.sort()
|
||||
let text = ''
|
||||
for (const cat in cats) {
|
||||
text += `\n### ${cat}\n\n`
|
||||
for (const item of cats[cat]) {
|
||||
text += item.accelerator ? `- \`${item.accelerator.replace('`', 'tilde')}\`: ${item.name}\n` : ''
|
||||
}
|
||||
}
|
||||
return text.trim()
|
||||
}
|
||||
|
||||
this.toString = () => {
|
||||
const cats = this.sort()
|
||||
let text = ''
|
||||
for (const cat in cats) {
|
||||
text += `\n${cat}\n\n`
|
||||
for (const item of cats[cat]) {
|
||||
text += item.accelerator ? `${item.name.padEnd(25, '.')} ${item.accelerator}\n` : ''
|
||||
}
|
||||
}
|
||||
return text.trim()
|
||||
}
|
||||
|
||||
// Electron specifics
|
||||
|
||||
this.inject = (name = 'Untitled') => {
|
||||
const app = require('electron').remote.app
|
||||
const injection = []
|
||||
|
||||
injection.push({
|
||||
label: name,
|
||||
submenu: [
|
||||
{ label: 'About', click: () => { require('electron').shell.openExternal('https://github.com/hundredrabbits/' + name) } },
|
||||
{
|
||||
label: 'Theme',
|
||||
submenu: [
|
||||
{ label: 'Download Themes', click: () => { require('electron').shell.openExternal('https://github.com/hundredrabbits/Themes') } },
|
||||
{ label: 'Open Theme', click: () => { client.theme.open() } },
|
||||
{ label: 'Reset Theme', accelerator: 'CmdOrCtrl+Escape', click: () => { client.theme.reset() } }
|
||||
]
|
||||
},
|
||||
{ label: 'Fullscreen', accelerator: 'CmdOrCtrl+Enter', click: () => { app.toggleFullscreen() } },
|
||||
{ label: 'Hide', accelerator: 'CmdOrCtrl+H', click: () => { app.toggleVisible() } },
|
||||
{ label: 'Toggle Menubar', accelerator: 'Alt+H', click: () => { app.toggleMenubar() } },
|
||||
{ label: 'Inspect', accelerator: 'CmdOrCtrl+Tab', click: () => { app.inspect() } },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
})
|
||||
|
||||
const sorted = this.sort()
|
||||
for (const cat of Object.keys(sorted)) {
|
||||
const submenu = []
|
||||
for (const option of sorted[cat]) {
|
||||
if (option.role) {
|
||||
submenu.push({ role: option.role })
|
||||
} else if (option.type) {
|
||||
submenu.push({ type: option.type })
|
||||
} else {
|
||||
submenu.push({ label: option.name, accelerator: option.accelerator, click: option.downfn })
|
||||
}
|
||||
}
|
||||
injection.push({ label: cat, submenu: submenu })
|
||||
}
|
||||
app.injectMenu(injection)
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
function History () {
|
||||
this.index = 0
|
||||
this.a = []
|
||||
|
||||
this.clear = function () {
|
||||
this.a = []
|
||||
this.index = 0
|
||||
}
|
||||
|
||||
this.push = function (data) {
|
||||
if (this.index < this.a.length - 1) {
|
||||
this.fork()
|
||||
}
|
||||
this.index = this.a.length
|
||||
this.a = this.a.slice(0, this.index)
|
||||
this.a.push(copy(data))
|
||||
|
||||
if (this.a.length > 20) {
|
||||
this.a.shift()
|
||||
}
|
||||
}
|
||||
|
||||
this.fork = function () {
|
||||
this.a = this.a.slice(0, this.index + 1)
|
||||
}
|
||||
|
||||
this.pop = function () {
|
||||
return this.a.pop()
|
||||
}
|
||||
|
||||
this.prev = function () {
|
||||
this.index = clamp(this.index - 1, 0, this.a.length - 1)
|
||||
return copy(this.a[this.index])
|
||||
}
|
||||
|
||||
this.next = function () {
|
||||
this.index = clamp(this.index + 1, 0, this.a.length - 1)
|
||||
return copy(this.a[this.index])
|
||||
}
|
||||
|
||||
function copy (data) { return data ? JSON.parse(JSON.stringify(data)) : [] }
|
||||
function clamp (v, min, max) { return v < min ? min : v > max ? max : v }
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* global FileReader */
|
||||
/* global MouseEvent */
|
||||
|
||||
function Source (client) {
|
||||
this.cache = {}
|
||||
|
||||
this.install = () => {
|
||||
}
|
||||
|
||||
this.start = () => {
|
||||
this.new()
|
||||
}
|
||||
|
||||
this.new = () => {
|
||||
console.log('Source', 'New file..')
|
||||
this.cache = {}
|
||||
}
|
||||
|
||||
this.open = (ext, callback, store = false) => {
|
||||
console.log('Source', 'Open file..')
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0]
|
||||
if (file.name.indexOf('.' + ext) < 0) { console.warn('Source', `Skipped ${file.name}`); return }
|
||||
this.read(file, callback, store)
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
|
||||
this.load = (ext, callback) => {
|
||||
console.log('Source', 'Load files..')
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.setAttribute('multiple', 'multiple')
|
||||
input.onchange = (e) => {
|
||||
for (const file of e.target.files) {
|
||||
if (file.name.indexOf('.' + ext) < 0) { console.warn('Source', `Skipped ${file.name}`); return }
|
||||
this.read(file, this.store)
|
||||
}
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
|
||||
this.store = (file, content) => {
|
||||
console.info('Source', 'Stored ' + file.name)
|
||||
this.cache[file.name] = content
|
||||
}
|
||||
|
||||
this.save = (name, content, type = 'text/plain', callback) => {
|
||||
this.saveAs(name, content, type, callback)
|
||||
}
|
||||
|
||||
this.saveAs = (name, ext, content, type = 'text/plain', callback) => {
|
||||
console.log('Source', 'Save new file..')
|
||||
this.write(name, ext, content, type, callback)
|
||||
}
|
||||
|
||||
// I/O
|
||||
|
||||
this.read = (file, callback, store = false) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
const res = event.target.result
|
||||
if (callback) { callback(file, res) }
|
||||
if (store) { this.store(file, res) }
|
||||
}
|
||||
reader.readAsText(file, 'UTF-8')
|
||||
}
|
||||
|
||||
this.write = (name, ext, content, type, settings = 'charset=utf-8') => {
|
||||
const link = document.createElement('a')
|
||||
link.setAttribute('download', `${name}-${timestamp()}.${ext}`)
|
||||
if (type === 'image/png' || type === 'image/jpeg') {
|
||||
link.setAttribute('href', content)
|
||||
} else {
|
||||
link.setAttribute('href', 'data:' + type + ';' + settings + ',' + encodeURIComponent(content))
|
||||
}
|
||||
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }))
|
||||
}
|
||||
|
||||
function timestamp (d = new Date(), e = new Date(d)) {
|
||||
return `${arvelie()}-${neralie()}`
|
||||
}
|
||||
|
||||
function arvelie (date = new Date()) {
|
||||
const start = new Date(date.getFullYear(), 0, 0)
|
||||
const diff = (date - start) + ((start.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000)
|
||||
const doty = Math.floor(diff / 86400000) - 1
|
||||
const y = date.getFullYear().toString().substr(2, 2)
|
||||
const m = doty === 364 || doty === 365 ? '+' : String.fromCharCode(97 + Math.floor(doty / 14)).toUpperCase()
|
||||
const d = `${(doty === 365 ? 1 : doty === 366 ? 2 : (doty % 14)) + 1}`.padStart(2, '0')
|
||||
return `${y}${m}${d}`
|
||||
}
|
||||
|
||||
function neralie (d = new Date(), e = new Date(d)) {
|
||||
const ms = e - d.setHours(0, 0, 0, 0)
|
||||
return (ms / 8640 / 10000).toFixed(6).substr(2, 6)
|
||||
}
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* global localStorage */
|
||||
/* global FileReader */
|
||||
/* global DOMParser */
|
||||
|
||||
function Theme (client) {
|
||||
this.el = document.createElement('style')
|
||||
this.el.type = 'text/css'
|
||||
|
||||
this.active = {}
|
||||
this.default = {
|
||||
background: '#eeeeee',
|
||||
f_high: '#0a0a0a',
|
||||
f_med: '#4a4a4a',
|
||||
f_low: '#6a6a6a',
|
||||
f_inv: '#111111',
|
||||
b_high: '#a1a1a1',
|
||||
b_med: '#c1c1c1',
|
||||
b_low: '#ffffff',
|
||||
b_inv: '#ffb545'
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
this.onLoad = () => {}
|
||||
|
||||
this.install = (host = document.body) => {
|
||||
window.addEventListener('dragover', this.drag)
|
||||
window.addEventListener('drop', this.drop)
|
||||
host.appendChild(this.el)
|
||||
}
|
||||
|
||||
this.start = () => {
|
||||
console.log('Theme', 'Starting..')
|
||||
if (isJson(localStorage.theme)) {
|
||||
const storage = JSON.parse(localStorage.theme)
|
||||
if (isValid(storage)) {
|
||||
console.log('Theme', 'Loading theme in localStorage..')
|
||||
this.load(storage)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.load(this.default)
|
||||
}
|
||||
|
||||
this.open = () => {
|
||||
console.log('Theme', 'Open theme..')
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.onchange = (e) => {
|
||||
this.read(e.target.files[0], this.load)
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
|
||||
this.load = (data) => {
|
||||
const theme = this.parse(data)
|
||||
if (!isValid(theme)) { console.warn('Theme', 'Invalid format'); return }
|
||||
console.log('Theme', 'Loaded theme!')
|
||||
this.el.innerHTML = `:root {
|
||||
--background: ${theme.background};
|
||||
--f_high: ${theme.f_high};
|
||||
--f_med: ${theme.f_med};
|
||||
--f_low: ${theme.f_low};
|
||||
--f_inv: ${theme.f_inv};
|
||||
--b_high: ${theme.b_high};
|
||||
--b_med: ${theme.b_med};
|
||||
--b_low: ${theme.b_low};
|
||||
--b_inv: ${theme.b_inv};
|
||||
}`
|
||||
localStorage.setItem('theme', JSON.stringify(theme))
|
||||
this.active = theme
|
||||
if (this.onLoad) {
|
||||
this.onLoad(data)
|
||||
}
|
||||
}
|
||||
|
||||
this.reset = () => {
|
||||
this.load(this.default)
|
||||
}
|
||||
|
||||
this.set = (key, val) => {
|
||||
if (!val) { return }
|
||||
const hex = (`${val}`.substr(0, 1) !== '#' ? '#' : '') + `${val}`
|
||||
if (!isColor(hex)) { console.warn('Theme', `${hex} is not a valid color.`); return }
|
||||
this.active[key] = hex
|
||||
}
|
||||
|
||||
this.read = (key) => {
|
||||
return this.active[key]
|
||||
}
|
||||
|
||||
this.parse = (any) => {
|
||||
if (isValid(any)) { return any }
|
||||
if (isJson(any)) { return JSON.parse(any) }
|
||||
if (isHtml(any)) { return extract(any) }
|
||||
}
|
||||
|
||||
// Drag
|
||||
|
||||
this.drag = (e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
}
|
||||
|
||||
this.drop = (e) => {
|
||||
e.preventDefault()
|
||||
const file = e.dataTransfer.files[0]
|
||||
if (file.name.indexOf('.svg') > -1) {
|
||||
this.read(file, this.load)
|
||||
}
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
this.read = (file, callback) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
callback(event.target.result)
|
||||
}
|
||||
reader.readAsText(file, 'UTF-8')
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
function extract (xml) {
|
||||
const svg = new DOMParser().parseFromString(xml, 'text/xml')
|
||||
try {
|
||||
return {
|
||||
background: svg.getElementById('background').getAttribute('fill'),
|
||||
f_high: svg.getElementById('f_high').getAttribute('fill'),
|
||||
f_med: svg.getElementById('f_med').getAttribute('fill'),
|
||||
f_low: svg.getElementById('f_low').getAttribute('fill'),
|
||||
f_inv: svg.getElementById('f_inv').getAttribute('fill'),
|
||||
b_high: svg.getElementById('b_high').getAttribute('fill'),
|
||||
b_med: svg.getElementById('b_med').getAttribute('fill'),
|
||||
b_low: svg.getElementById('b_low').getAttribute('fill'),
|
||||
b_inv: svg.getElementById('b_inv').getAttribute('fill')
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Theme', 'Incomplete SVG Theme', err)
|
||||
}
|
||||
}
|
||||
|
||||
function isValid (json) {
|
||||
if (!json) { return false }
|
||||
if (!json.background || !isColor(json.background)) { return false }
|
||||
if (!json.f_high || !isColor(json.f_high)) { return false }
|
||||
if (!json.f_med || !isColor(json.f_med)) { return false }
|
||||
if (!json.f_low || !isColor(json.f_low)) { return false }
|
||||
if (!json.f_inv || !isColor(json.f_inv)) { return false }
|
||||
if (!json.b_high || !isColor(json.b_high)) { return false }
|
||||
if (!json.b_med || !isColor(json.b_med)) { return false }
|
||||
if (!json.b_low || !isColor(json.b_low)) { return false }
|
||||
if (!json.b_inv || !isColor(json.b_inv)) { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
function isColor (hex) {
|
||||
return /^#([0-9A-F]{3}){1,2}$/i.test(hex)
|
||||
}
|
||||
|
||||
function isJson (text) {
|
||||
try { JSON.parse(text); return true } catch (error) { return false }
|
||||
}
|
||||
|
||||
function isHtml (text) {
|
||||
try { new DOMParser().parseFromString(text, 'text/xml'); return true } catch (error) { return false }
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
// Dotgrid UDP Listener
|
||||
// Ex: 1a0156(6 characters 0-9a-z)
|
||||
|
||||
// 0 layer[0-2]
|
||||
// 1 type[lcrd*]
|
||||
// 2 from[0-z][0-z]
|
||||
// 4 to[0-z][0-z]
|
||||
|
||||
const dgram = require('dgram')
|
||||
|
||||
function Listener (dotgrid) {
|
||||
this.server = dgram ? dgram.createSocket('udp4') : { on: () => {}, bind: () => {} }
|
||||
|
||||
function base36 (c) {
|
||||
return clamp(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y'].indexOf(c.toLowerCase()), 0, 36) * 15 + 15
|
||||
}
|
||||
|
||||
function clear () {
|
||||
dotgrid.tool.erase()
|
||||
}
|
||||
|
||||
function operate (data) {
|
||||
if (!data) { return }
|
||||
if (!dotgrid.tool.layers[data.layer]) { return }
|
||||
dotgrid.tool.addSegment(data.type, [data.from, data.to], data.layer)
|
||||
}
|
||||
|
||||
function draw () {
|
||||
dotgrid.renderer.update()
|
||||
}
|
||||
|
||||
function parse (msg) {
|
||||
if (msg === '') {
|
||||
return draw()
|
||||
}
|
||||
if (['0', '1', '2'].indexOf(msg) > -1) {
|
||||
return clear()
|
||||
}
|
||||
const layer = parseInt(msg.substr(0, 1))
|
||||
const type = { l: 'line', c: 'arc_c', r: 'arc_r', '*': 'draw' }[msg.substr(1, 1).toLowerCase()]
|
||||
const from = { x: base36(msg.substr(2, 1)), y: base36(msg.substr(3, 1)) }
|
||||
const to = { x: base36(msg.substr(4, 1)), y: base36(msg.substr(5, 1)) }
|
||||
return { layer: layer, type: type, from: from, to: to }
|
||||
}
|
||||
|
||||
function clamp (v, min, max) {
|
||||
return v < min ? min : v > max ? max : v
|
||||
}
|
||||
|
||||
// Server
|
||||
|
||||
this.server.on('message', (msg, rinfo) => {
|
||||
operate(parse(`${msg}`))
|
||||
})
|
||||
|
||||
this.server.on('listening', () => {
|
||||
const address = this.server.address()
|
||||
console.log(`Server listening for UDP:\n ${address.address}:${address.port}`)
|
||||
})
|
||||
|
||||
this.server.on('error', (err) => {
|
||||
console.log(`Server error:\n ${err.stack}`)
|
||||
this.server.close()
|
||||
})
|
||||
|
||||
this.server.bind(49161)
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* global XMLSerializer */
|
||||
/* global btoa */
|
||||
/* global Image */
|
||||
/* global Blob */
|
||||
|
||||
function Manager (client) {
|
||||
// Create SVG parts
|
||||
this.el = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
this.el.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
|
||||
this.el.setAttribute('baseProfile', 'full')
|
||||
this.el.setAttribute('version', '1.1')
|
||||
this.el.style.fill = 'none'
|
||||
|
||||
this.layers = []
|
||||
|
||||
this.install = function () {
|
||||
this.el.appendChild(this.layers[2] = document.createElementNS('http://www.w3.org/2000/svg', 'path'))
|
||||
this.el.appendChild(this.layers[1] = document.createElementNS('http://www.w3.org/2000/svg', 'path'))
|
||||
this.el.appendChild(this.layers[0] = document.createElementNS('http://www.w3.org/2000/svg', 'path'))
|
||||
}
|
||||
|
||||
this.update = function () {
|
||||
this.el.setAttribute('width', (client.tool.settings.size.width) + 'px')
|
||||
this.el.setAttribute('height', (client.tool.settings.size.height) + 'px')
|
||||
this.el.style.width = (client.tool.settings.size.width)
|
||||
this.el.style.height = client.tool.settings.size.height
|
||||
|
||||
const styles = client.tool.styles
|
||||
const paths = client.tool.paths()
|
||||
|
||||
for (const id in this.layers) {
|
||||
const style = styles[id]
|
||||
const path = paths[id]
|
||||
const layer = this.layers[id]
|
||||
|
||||
layer.style.strokeWidth = style.thickness
|
||||
layer.style.strokeLinecap = style.strokeLinecap
|
||||
layer.style.strokeLinejoin = style.strokeLinejoin
|
||||
layer.style.stroke = style.color
|
||||
layer.style.fill = style.fill
|
||||
|
||||
layer.setAttribute('d', path)
|
||||
}
|
||||
}
|
||||
|
||||
this.svg64 = function () {
|
||||
const xml = new XMLSerializer().serializeToString(this.el)
|
||||
const svg64 = btoa(xml)
|
||||
const b64Start = 'data:image/svg+xml;base64,'
|
||||
return b64Start + svg64
|
||||
}
|
||||
|
||||
// Exporters
|
||||
|
||||
this.toPNG = function (size = client.tool.settings.size, callback) {
|
||||
this.update()
|
||||
|
||||
const image64 = this.svg64()
|
||||
const img = new Image()
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = (size.width) * 2
|
||||
canvas.height = (size.height) * 2
|
||||
img.onload = function () {
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, (size.width) * 2, (size.height) * 2)
|
||||
callback(canvas.toDataURL('image/png'))
|
||||
}
|
||||
img.src = image64
|
||||
}
|
||||
|
||||
this.toSVG = function (callback) {
|
||||
this.update()
|
||||
|
||||
const image64 = this.svg64()
|
||||
callback(image64, 'export.svg')
|
||||
}
|
||||
|
||||
this.toGRID = function (callback) {
|
||||
this.update()
|
||||
|
||||
const text = client.tool.export()
|
||||
const file = new Blob([text], { type: 'text/plain' })
|
||||
callback(URL.createObjectURL(file), 'export.grid')
|
||||
}
|
||||
|
||||
this.toString = () => {
|
||||
return new XMLSerializer().serializeToString(this.el)
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
function Picker (client) {
|
||||
this.memory = ''
|
||||
this.el = document.createElement('div')
|
||||
this.el.id = 'picker'
|
||||
this.isActive = false
|
||||
this.input = document.createElement('input')
|
||||
this.input.id = 'picker_input'
|
||||
|
||||
this.el.appendChild(this.input)
|
||||
|
||||
this.start = function () {
|
||||
if (this.isActive) { return }
|
||||
|
||||
this.isActive = true
|
||||
|
||||
this.input.setAttribute('placeholder', `${client.tool.style().color.replace('#', '').trim()}`)
|
||||
this.input.setAttribute('maxlength', 6)
|
||||
|
||||
this.input.addEventListener('keydown', this.onKeyDown, false)
|
||||
this.input.addEventListener('keyup', this.onKeyUp, false)
|
||||
|
||||
client.interface.el.className = 'picker'
|
||||
this.input.focus()
|
||||
this.input.value = ''
|
||||
|
||||
try { client.controller.set('picker') } catch (err) { }
|
||||
}
|
||||
|
||||
this.update = function () {
|
||||
if (!this.isActive) { return }
|
||||
if (!isColor(this.input.value)) { return }
|
||||
|
||||
const hex = `#${this.input.value}`
|
||||
|
||||
document.getElementById('option_color').children[0].style.fill = hex
|
||||
document.getElementById('option_color').children[0].style.stroke = hex
|
||||
}
|
||||
|
||||
this.stop = function () {
|
||||
if (!this.isActive) { return }
|
||||
|
||||
this.isActive = false
|
||||
|
||||
client.interface.el.className = ''
|
||||
this.input.blur()
|
||||
this.input.value = ''
|
||||
|
||||
try { client.controller.set() } catch (err) { console.log('No controller') }
|
||||
|
||||
setTimeout(() => { client.interface.update(true); client.renderer.update() }, 250)
|
||||
}
|
||||
|
||||
this.validate = function () {
|
||||
if (!isColor(this.input.value)) { return }
|
||||
|
||||
const hex = `#${this.input.value}`
|
||||
|
||||
client.tool.style().color = hex
|
||||
client.tool.style().fill = client.tool.style().fill !== 'none' ? hex : 'none'
|
||||
|
||||
this.stop()
|
||||
}
|
||||
|
||||
function isColor (val) {
|
||||
if (val.length !== 3 && val.length !== 6) {
|
||||
return false
|
||||
}
|
||||
|
||||
const re = /[0-9A-Fa-f]/g
|
||||
return re.test(val)
|
||||
}
|
||||
|
||||
this.onKeyDown = (e) => {
|
||||
e.stopPropagation()
|
||||
if (e.key === 'Enter') {
|
||||
this.validate()
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
this.stop()
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
this.onKeyUp = (e) => {
|
||||
e.stopPropagation()
|
||||
this.update()
|
||||
}
|
||||
}
|
@ -1,263 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* global Image */
|
||||
/* global Path2D */
|
||||
/* global Generator */
|
||||
|
||||
function Renderer (client) {
|
||||
this.el = document.createElement('canvas')
|
||||
this.el.id = 'guide'
|
||||
this.el.width = 640
|
||||
this.el.height = 640
|
||||
this.el.style.width = '320px'
|
||||
this.el.style.height = '320px'
|
||||
this.context = this.el.getContext('2d')
|
||||
this.showExtras = true
|
||||
|
||||
this.scale = 2 // window.devicePixelRatio
|
||||
|
||||
this.start = function () {
|
||||
this.update()
|
||||
}
|
||||
|
||||
this.update = function (force = false) {
|
||||
this.resize()
|
||||
client.manager.update()
|
||||
const render = new Image()
|
||||
render.onload = () => {
|
||||
this.draw(render)
|
||||
}
|
||||
render.src = client.manager.svg64()
|
||||
}
|
||||
|
||||
this.draw = function (render) {
|
||||
this.clear()
|
||||
this.drawMirror()
|
||||
this.drawGrid()
|
||||
this.drawRulers()
|
||||
this.drawRender(render) //
|
||||
this.drawVertices()
|
||||
this.drawHandles()
|
||||
this.drawTranslation()
|
||||
this.drawCursor()
|
||||
this.drawPreview()
|
||||
}
|
||||
|
||||
this.clear = function () {
|
||||
this.context.clearRect(0, 0, this.el.width * this.scale, this.el.height * this.scale)
|
||||
}
|
||||
|
||||
this.toggle = function () {
|
||||
this.showExtras = !this.showExtras
|
||||
this.update()
|
||||
client.interface.update(true)
|
||||
}
|
||||
|
||||
this.resize = function () {
|
||||
const _target = client.getPaddedSize()
|
||||
const _current = { width: this.el.width / this.scale, height: this.el.height / this.scale }
|
||||
const offset = sizeOffset(_target, _current)
|
||||
if (offset.width === 0 && offset.height === 0) {
|
||||
return
|
||||
}
|
||||
console.log('Renderer', `Require resize: ${printSize(_target)}, from ${printSize(_current)}`)
|
||||
this.el.width = (_target.width) * this.scale
|
||||
this.el.height = (_target.height) * this.scale
|
||||
this.el.style.width = (_target.width) + 'px'
|
||||
this.el.style.height = (_target.height) + 'px'
|
||||
}
|
||||
|
||||
// Collections
|
||||
|
||||
this.drawMirror = function () {
|
||||
if (!this.showExtras) { return }
|
||||
|
||||
if (client.tool.style().mirror_style === 0) { return }
|
||||
|
||||
const middle = { x: client.tool.settings.size.width, y: client.tool.settings.size.height }
|
||||
|
||||
if (client.tool.style().mirror_style === 1 || client.tool.style().mirror_style === 3) {
|
||||
this.drawRule({ x: middle.x, y: 15 * this.scale }, { x: middle.x, y: (client.tool.settings.size.height) * this.scale })
|
||||
}
|
||||
if (client.tool.style().mirror_style === 2 || client.tool.style().mirror_style === 3) {
|
||||
this.drawRule({ x: 15 * this.scale, y: middle.y }, { x: (client.tool.settings.size.width) * this.scale, y: middle.y })
|
||||
}
|
||||
}
|
||||
|
||||
this.drawHandles = function () {
|
||||
if (!this.showExtras) { return }
|
||||
|
||||
for (const segmentId in client.tool.layer()) {
|
||||
const segment = client.tool.layer()[segmentId]
|
||||
for (const vertexId in segment.vertices) {
|
||||
const vertex = segment.vertices[vertexId]
|
||||
this.drawHandle(vertex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.drawVertices = function () {
|
||||
for (const id in client.tool.vertices) {
|
||||
this.drawVertex(client.tool.vertices[id])
|
||||
}
|
||||
}
|
||||
|
||||
this.drawGrid = function () {
|
||||
if (!this.showExtras) { return }
|
||||
|
||||
const markers = { w: parseInt(client.tool.settings.size.width / 15), h: parseInt(client.tool.settings.size.height / 15) }
|
||||
|
||||
for (let x = markers.w - 1; x >= 0; x--) {
|
||||
for (let y = markers.h - 1; y >= 0; y--) {
|
||||
const isStep = x % 4 === 0 && y % 4 === 0
|
||||
// Don't draw margins
|
||||
if (x === 0 || y === 0) { continue }
|
||||
this.drawMarker({
|
||||
x: parseInt(x * 15),
|
||||
y: parseInt(y * 15)
|
||||
}, isStep ? 2.5 : 1.5, client.theme.active.b_med)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.drawRulers = function () {
|
||||
if (!client.cursor.translation) { return }
|
||||
|
||||
const pos = client.cursor.translation.to
|
||||
const bottom = (client.tool.settings.size.height * this.scale)
|
||||
const right = (client.tool.settings.size.width * this.scale)
|
||||
|
||||
this.drawRule({ x: pos.x * this.scale, y: 0 }, { x: pos.x * this.scale, y: bottom })
|
||||
this.drawRule({ x: 0, y: pos.y * this.scale }, { x: right, y: pos.y * this.scale })
|
||||
}
|
||||
|
||||
this.drawPreview = function () {
|
||||
const operation = client.cursor.operation && client.cursor.operation.cast ? client.cursor.operation.cast : null
|
||||
|
||||
if (!client.tool.canCast(operation)) { return }
|
||||
if (operation === 'close') { return }
|
||||
|
||||
const path = new Generator([{ vertices: client.tool.vertices, type: operation }]).toString({ x: 0, y: 0 }, 2)
|
||||
const style = {
|
||||
color: client.theme.active.f_med,
|
||||
thickness: 2,
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
strokeLineDash: [5, 15]
|
||||
}
|
||||
this.drawPath(path, style)
|
||||
}
|
||||
|
||||
// Elements
|
||||
|
||||
this.drawMarker = function (pos, radius = 1, color) {
|
||||
this.context.beginPath()
|
||||
this.context.lineWidth = 2
|
||||
this.context.arc(pos.x * this.scale, pos.y * this.scale, radius, 0, 2 * Math.PI, false)
|
||||
this.context.fillStyle = color
|
||||
this.context.fill()
|
||||
this.context.closePath()
|
||||
}
|
||||
|
||||
this.drawVertex = function (pos, radius = 5) {
|
||||
this.context.beginPath()
|
||||
this.context.lineWidth = 2
|
||||
this.context.arc((pos.x * this.scale), (pos.y * this.scale), radius, 0, 2 * Math.PI, false)
|
||||
this.context.fillStyle = client.theme.active.f_low
|
||||
this.context.fill()
|
||||
this.context.closePath()
|
||||
}
|
||||
|
||||
this.drawRule = function (from, to) {
|
||||
this.context.beginPath()
|
||||
this.context.moveTo(from.x, from.y)
|
||||
this.context.lineTo(to.x, to.y)
|
||||
this.context.lineCap = 'round'
|
||||
this.context.lineWidth = 3
|
||||
this.context.strokeStyle = client.theme.active.b_low
|
||||
this.context.stroke()
|
||||
this.context.closePath()
|
||||
}
|
||||
|
||||
this.drawHandle = function (pos, radius = 6) {
|
||||
this.context.beginPath()
|
||||
this.context.arc(Math.abs(pos.x * -this.scale), Math.abs(pos.y * this.scale), radius + 3, 0, 2 * Math.PI, false)
|
||||
this.context.fillStyle = client.theme.active.f_high
|
||||
this.context.fill()
|
||||
this.context.closePath()
|
||||
this.context.beginPath()
|
||||
this.context.arc((pos.x * this.scale), (pos.y * this.scale), radius - 3, 0, 2 * Math.PI, false)
|
||||
this.context.fillStyle = client.theme.active.b_low
|
||||
this.context.fill()
|
||||
this.context.closePath()
|
||||
}
|
||||
|
||||
this.drawPath = function (path, style) {
|
||||
const p = new Path2D(path)
|
||||
|
||||
this.context.strokeStyle = style.color
|
||||
this.context.lineWidth = style.thickness * this.scale
|
||||
this.context.lineCap = style.strokeLinecap
|
||||
this.context.lineJoin = style.strokeLinejoin
|
||||
|
||||
if (style.fill && style.fill !== 'none') {
|
||||
this.context.fillStyle = style.color
|
||||
this.context.fill(p)
|
||||
}
|
||||
|
||||
// Dash
|
||||
this.context.save()
|
||||
if (style.strokeLineDash) { this.context.setLineDash(style.strokeLineDash) } else { this.context.setLineDash([]) }
|
||||
this.context.stroke(p)
|
||||
this.context.restore()
|
||||
}
|
||||
|
||||
this.drawTranslation = function () {
|
||||
if (!client.cursor.translation) { return }
|
||||
|
||||
this.context.save()
|
||||
|
||||
this.context.beginPath()
|
||||
this.context.moveTo((client.cursor.translation.from.x * this.scale), (client.cursor.translation.from.y * this.scale))
|
||||
this.context.lineTo((client.cursor.translation.to.x * this.scale), (client.cursor.translation.to.y * this.scale))
|
||||
this.context.lineCap = 'round'
|
||||
this.context.lineWidth = 5
|
||||
this.context.strokeStyle = client.cursor.translation.multi === true ? client.theme.active.b_inv : client.cursor.translation.copy === true ? client.theme.active.f_med : client.theme.active.f_low
|
||||
this.context.setLineDash([5, 10])
|
||||
this.context.stroke()
|
||||
this.context.closePath()
|
||||
|
||||
this.context.setLineDash([])
|
||||
this.context.restore()
|
||||
}
|
||||
|
||||
this.drawCursor = function (pos = client.cursor.pos, radius = client.tool.style().thickness - 1) {
|
||||
this.context.save()
|
||||
|
||||
this.context.beginPath()
|
||||
this.context.lineWidth = 3
|
||||
this.context.lineCap = 'round'
|
||||
this.context.arc(Math.abs(pos.x * -this.scale), Math.abs(pos.y * this.scale), 5, 0, 2 * Math.PI, false)
|
||||
this.context.strokeStyle = client.theme.active.background
|
||||
this.context.stroke()
|
||||
this.context.closePath()
|
||||
|
||||
this.context.beginPath()
|
||||
this.context.lineWidth = 3
|
||||
this.context.lineCap = 'round'
|
||||
this.context.arc(Math.abs(pos.x * -this.scale), Math.abs(pos.y * this.scale), clamp(radius, 5, 100), 0, 2 * Math.PI, false)
|
||||
this.context.strokeStyle = client.theme.active.f_med
|
||||
this.context.stroke()
|
||||
this.context.closePath()
|
||||
|
||||
this.context.restore()
|
||||
}
|
||||
|
||||
this.drawRender = function (render) {
|
||||
this.context.drawImage(render, 0, 0, this.el.width, this.el.height)
|
||||
}
|
||||
|
||||
function printSize (size) { return `${size.width}x${size.height}` }
|
||||
function sizeOffset (a, b) { return { width: a.width - b.width, height: a.height - b.height } }
|
||||
function clamp (v, min, max) { return v < min ? min : v > max ? max : v }
|
||||
}
|
@ -1,366 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* global Generator */
|
||||
|
||||
function Tool (client) {
|
||||
this.index = 0
|
||||
this.settings = { size: { width: 600, height: 300 } }
|
||||
this.layers = [[], [], []]
|
||||
this.styles = [
|
||||
{ thickness: 15, strokeLinecap: 'round', strokeLinejoin: 'round', color: '#f00', fill: 'none', mirror_style: 0, transform: 'rotate(45)' },
|
||||
{ thickness: 15, strokeLinecap: 'round', strokeLinejoin: 'round', color: '#0f0', fill: 'none', mirror_style: 0, transform: 'rotate(45)' },
|
||||
{ thickness: 15, strokeLinecap: 'round', strokeLinejoin: 'round', color: '#00f', fill: 'none', mirror_style: 0, transform: 'rotate(45)' }
|
||||
]
|
||||
this.vertices = []
|
||||
this.reqs = { line: 2, arc_c: 2, arc_r: 2, arc_c_full: 2, arc_r_full: 2, bezier: 3, close: 0 }
|
||||
|
||||
this.start = function () {
|
||||
this.styles[0].color = client.theme.active.f_high
|
||||
this.styles[1].color = client.theme.active.f_med
|
||||
this.styles[2].color = client.theme.active.f_low
|
||||
}
|
||||
|
||||
this.erase = function () {
|
||||
this.layers = [[], [], []]
|
||||
}
|
||||
|
||||
this.reset = function () {
|
||||
this.styles[0].mirror_style = 0
|
||||
this.styles[1].mirror_style = 0
|
||||
this.styles[2].mirror_style = 0
|
||||
this.styles[0].fill = 'none'
|
||||
this.styles[1].fill = 'none'
|
||||
this.styles[2].fill = 'none'
|
||||
this.erase()
|
||||
this.vertices = []
|
||||
this.index = 0
|
||||
}
|
||||
|
||||
this.clear = function () {
|
||||
this.vertices = []
|
||||
client.renderer.update()
|
||||
client.interface.update(true)
|
||||
}
|
||||
|
||||
this.undo = function () {
|
||||
this.layers = client.history.prev()
|
||||
client.renderer.update()
|
||||
client.interface.update(true)
|
||||
}
|
||||
|
||||
this.redo = function () {
|
||||
this.layers = client.history.next()
|
||||
client.renderer.update()
|
||||
client.interface.update(true)
|
||||
}
|
||||
|
||||
this.length = function () {
|
||||
return this.layers[0].length + this.layers[1].length + this.layers[2].length
|
||||
}
|
||||
|
||||
// I/O
|
||||
|
||||
this.export = function (target = { settings: this.settings, layers: this.layers, styles: this.styles }) {
|
||||
return JSON.stringify(copy(target), null, 2)
|
||||
}
|
||||
|
||||
this.import = function (layer) {
|
||||
this.layers[this.index] = this.layers[this.index].concat(layer)
|
||||
client.history.push(this.layers)
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
client.interface.update(true)
|
||||
}
|
||||
|
||||
this.replace = function (dot) {
|
||||
if (!dot.layers || dot.layers.length !== 3) { console.warn('Incompatible version'); return }
|
||||
|
||||
if (dot.settings.width && dot.settings.height) {
|
||||
dot.settings.size = { width: dot.settings.width, height: dot.settings.height }
|
||||
}
|
||||
|
||||
this.layers = dot.layers
|
||||
this.styles = dot.styles
|
||||
this.settings = dot.settings
|
||||
|
||||
this.clear()
|
||||
client.fitSize()
|
||||
client.renderer.update()
|
||||
client.interface.update(true)
|
||||
client.history.push(this.layers)
|
||||
}
|
||||
|
||||
// EDIT
|
||||
|
||||
this.removeSegment = function () {
|
||||
if (this.vertices.length > 0) { this.clear(); return }
|
||||
|
||||
this.layer().pop()
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
client.interface.update(true)
|
||||
}
|
||||
|
||||
this.removeSegmentsAt = function (pos) {
|
||||
for (const segmentId in this.layer()) {
|
||||
const segment = this.layer()[segmentId]
|
||||
for (const vertexId in segment.vertices) {
|
||||
const vertex = segment.vertices[vertexId]
|
||||
if (Math.abs(pos.x) === Math.abs(vertex.x) && Math.abs(pos.y) === Math.abs(vertex.y)) {
|
||||
segment.vertices.splice(vertexId, 1)
|
||||
}
|
||||
}
|
||||
if (segment.vertices.length < 2) {
|
||||
this.layers[this.index].splice(segmentId, 1)
|
||||
}
|
||||
}
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
client.interface.update(true)
|
||||
}
|
||||
|
||||
this.selectSegmentAt = function (pos, source = this.layer()) {
|
||||
for (const segmentId in source) {
|
||||
const segment = source[segmentId]
|
||||
for (const vertexId in segment.vertices) {
|
||||
const vertex = segment.vertices[vertexId]
|
||||
if (vertex.x === Math.abs(pos.x) && vertex.y === Math.abs(pos.y)) {
|
||||
return segment
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
this.addVertex = function (pos) {
|
||||
pos = { x: Math.abs(pos.x), y: Math.abs(pos.y) }
|
||||
this.vertices.push(pos)
|
||||
client.interface.update(true)
|
||||
}
|
||||
|
||||
this.vertexAt = function (pos) {
|
||||
for (const segmentId in this.layer()) {
|
||||
const segment = this.layer()[segmentId]
|
||||
for (const vertexId in segment.vertices) {
|
||||
const vertex = segment.vertices[vertexId]
|
||||
if (vertex.x === Math.abs(pos.x) && vertex.y === Math.abs(pos.y)) {
|
||||
return vertex
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
this.addSegment = function (type, vertices, index = this.index) {
|
||||
const appendTarget = this.canAppend({ type: type, vertices: vertices }, index)
|
||||
if (appendTarget) {
|
||||
this.layer(index)[appendTarget].vertices = this.layer(index)[appendTarget].vertices.concat(vertices)
|
||||
} else {
|
||||
this.layer(index).push({ type: type, vertices: vertices })
|
||||
}
|
||||
}
|
||||
|
||||
this.cast = function (type) {
|
||||
if (!this.layer()) { this.layers[this.index] = [] }
|
||||
if (!this.canCast(type)) { console.warn('Cannot cast'); return }
|
||||
|
||||
this.addSegment(type, this.vertices.slice())
|
||||
|
||||
client.history.push(this.layers)
|
||||
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
client.interface.update(true)
|
||||
|
||||
console.log(`Casted ${type} -> ${this.layer().length} elements`)
|
||||
}
|
||||
|
||||
this.i = { linecap: 0, linejoin: 0, thickness: 5 }
|
||||
|
||||
this.toggle = function (type, mod = 1) {
|
||||
if (type === 'linecap') {
|
||||
const a = ['butt', 'square', 'round']
|
||||
this.i.linecap += mod
|
||||
this.style().strokeLinecap = a[this.i.linecap % a.length]
|
||||
} else if (type === 'linejoin') {
|
||||
const a = ['miter', 'round', 'bevel']
|
||||
this.i.linejoin += mod
|
||||
this.style().strokeLinejoin = a[this.i.linejoin % a.length]
|
||||
} else if (type === 'fill') {
|
||||
this.style().fill = this.style().fill === 'none' ? this.style().color : 'none'
|
||||
} else if (type === 'thickness') {
|
||||
this.style().thickness = clamp(this.style().thickness + mod, 1, 100)
|
||||
} else if (type === 'mirror') {
|
||||
this.style().mirror_style = this.style().mirror_style > 2 ? 0 : this.style().mirror_style + 1
|
||||
} else {
|
||||
console.warn('Unknown', type)
|
||||
}
|
||||
client.interface.update(true)
|
||||
client.renderer.update()
|
||||
}
|
||||
|
||||
this.misc = function (type) {
|
||||
client.picker.start()
|
||||
}
|
||||
|
||||
this.source = function (type) {
|
||||
if (type === 'grid') { client.renderer.toggle() }
|
||||
if (type === 'open') { client.source.open('grid', client.whenOpen) }
|
||||
if (type === 'save') { client.source.write('dotgrid', 'grid', client.tool.export(), 'text/plain') }
|
||||
if (type === 'export') { client.source.write('dotgrid', 'svg', client.manager.toString(), 'image/svg+xml') }
|
||||
if (type === 'render') { client.manager.toPNG(client.tool.settings.size, (dataUrl) => { client.source.write('dotgrid', 'png', dataUrl, 'image/png') }) }
|
||||
}
|
||||
|
||||
this.canAppend = function (content, index = this.index) {
|
||||
for (const id in this.layer(index)) {
|
||||
const stroke = this.layer(index)[id]
|
||||
if (stroke.type !== content.type) { continue }
|
||||
if (!stroke.vertices) { continue }
|
||||
if (!stroke.vertices[stroke.vertices.length - 1]) { continue }
|
||||
if (stroke.vertices[stroke.vertices.length - 1].x !== content.vertices[0].x) { continue }
|
||||
if (stroke.vertices[stroke.vertices.length - 1].y !== content.vertices[0].y) { continue }
|
||||
return id
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
this.canCast = function (type) {
|
||||
if (!type) { return false }
|
||||
// Cannot cast close twice
|
||||
if (type === 'close') {
|
||||
const prev = this.layer()[this.layer().length - 1]
|
||||
if (!prev || prev.type === 'close') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (type === 'bezier') {
|
||||
if (this.vertices.length !== 3 && this.vertices.length !== 5 && this.vertices.length !== 7 && this.vertices.length !== 9) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return this.vertices.length >= this.reqs[type]
|
||||
}
|
||||
|
||||
this.paths = function () {
|
||||
const l1 = new Generator(client.tool.layers[0], client.tool.styles[0]).toString({ x: 0, y: 0 }, 1)
|
||||
const l2 = new Generator(client.tool.layers[1], client.tool.styles[1]).toString({ x: 0, y: 0 }, 1)
|
||||
const l3 = new Generator(client.tool.layers[2], client.tool.styles[2]).toString({ x: 0, y: 0 }, 1)
|
||||
|
||||
return [l1, l2, l3]
|
||||
}
|
||||
|
||||
this.path = function () {
|
||||
return new Generator(client.tool.layer(), client.tool.style()).toString({ x: 0, y: 0 }, 1)
|
||||
}
|
||||
|
||||
this.translate = function (a, b) {
|
||||
for (const segmentId in this.layer()) {
|
||||
const segment = this.layer()[segmentId]
|
||||
for (const vertexId in segment.vertices) {
|
||||
const vertex = segment.vertices[vertexId]
|
||||
if (vertex.x === Math.abs(a.x) && vertex.y === Math.abs(a.y)) {
|
||||
segment.vertices[vertexId] = { x: Math.abs(b.x), y: Math.abs(b.y) }
|
||||
}
|
||||
}
|
||||
}
|
||||
client.history.push(this.layers)
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
}
|
||||
|
||||
this.translateMulti = function (a, b) {
|
||||
const offset = { x: a.x - b.x, y: a.y - b.y }
|
||||
const segment = this.selectSegmentAt(a)
|
||||
|
||||
if (!segment) { return }
|
||||
|
||||
for (const vertexId in segment.vertices) {
|
||||
const vertex = segment.vertices[vertexId]
|
||||
segment.vertices[vertexId] = { x: vertex.x - offset.x, y: vertex.y - offset.y }
|
||||
}
|
||||
|
||||
client.history.push(this.layers)
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
}
|
||||
|
||||
this.translateLayer = function (a, b) {
|
||||
const offset = { x: a.x - b.x, y: a.y - b.y }
|
||||
for (const segmentId in this.layer()) {
|
||||
const segment = this.layer()[segmentId]
|
||||
for (const vertexId in segment.vertices) {
|
||||
const vertex = segment.vertices[vertexId]
|
||||
segment.vertices[vertexId] = { x: vertex.x - offset.x, y: vertex.y - offset.y }
|
||||
}
|
||||
}
|
||||
client.history.push(this.layers)
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
}
|
||||
|
||||
this.translateCopy = function (a, b) {
|
||||
const offset = { x: a.x - b.x, y: a.y - b.y }
|
||||
const segment = this.selectSegmentAt(a, copy(this.layer()))
|
||||
|
||||
if (!segment) { return }
|
||||
|
||||
for (const vertexId in segment.vertices) {
|
||||
const vertex = segment.vertices[vertexId]
|
||||
segment.vertices[vertexId] = { x: vertex.x - offset.x, y: vertex.y - offset.y }
|
||||
}
|
||||
this.layer().push(segment)
|
||||
|
||||
client.history.push(this.layers)
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
}
|
||||
|
||||
this.merge = function () {
|
||||
const merged = [].concat(this.layers[0]).concat(this.layers[1]).concat(this.layers[2])
|
||||
this.erase()
|
||||
this.layers[this.index] = merged
|
||||
|
||||
client.history.push(this.layers)
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
}
|
||||
|
||||
// Style
|
||||
|
||||
this.style = function () {
|
||||
if (!this.styles[this.index]) {
|
||||
this.styles[this.index] = []
|
||||
}
|
||||
return this.styles[this.index]
|
||||
}
|
||||
|
||||
// Layers
|
||||
|
||||
this.layer = function (index = this.index) {
|
||||
if (!this.layers[index]) {
|
||||
this.layers[index] = []
|
||||
}
|
||||
return this.layers[index]
|
||||
}
|
||||
|
||||
this.selectLayer = function (id) {
|
||||
this.index = clamp(id, 0, 2)
|
||||
this.clear()
|
||||
client.renderer.update()
|
||||
client.interface.update(true)
|
||||
console.log(`layer:${this.index}`)
|
||||
}
|
||||
|
||||
this.selectNextLayer = function () {
|
||||
this.index = this.index >= 2 ? 0 : this.index++
|
||||
this.selectLayer(this.index)
|
||||
}
|
||||
|
||||
this.selectPrevLayer = function () {
|
||||
this.index = this.index >= 0 ? 2 : this.index--
|
||||
this.selectLayer(this.index)
|
||||
}
|
||||
|
||||
function copy (data) { return data ? JSON.parse(JSON.stringify(data)) : [] }
|
||||
function clamp (v, min, max) { return v < min ? min : v > max ? max : v }
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
{
|
||||
"settings": {
|
||||
"size": {
|
||||
"width": 300,
|
||||
"height": 285
|
||||
},
|
||||
"crest": false
|
||||
},
|
||||
"layers": [
|
||||
[
|
||||
{
|
||||
"type": "arc_r",
|
||||
"vertices": [
|
||||
{
|
||||
"x": 120,
|
||||
"y": 210
|
||||
},
|
||||
{
|
||||
"x": 180,
|
||||
"y": 150
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "arc_c",
|
||||
"vertices": [
|
||||
{
|
||||
"x": 120,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 180,
|
||||
"y": 90
|
||||
},
|
||||
{
|
||||
"x": 180,
|
||||
"y": 90
|
||||
},
|
||||
{
|
||||
"x": 240,
|
||||
"y": 150
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "arc_c",
|
||||
"vertices": [
|
||||
{
|
||||
"x": 120,
|
||||
"y": 210
|
||||
},
|
||||
{
|
||||
"x": 60,
|
||||
"y": 150
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "arc_c",
|
||||
"vertices": [
|
||||
{
|
||||
"x": 120,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 150,
|
||||
"y": 120
|
||||
},
|
||||
{
|
||||
"x": 180,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 150,
|
||||
"y": 180
|
||||
},
|
||||
{
|
||||
"x": 120,
|
||||
"y": 150
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "arc_c",
|
||||
"vertices": [
|
||||
{
|
||||
"x": 150,
|
||||
"y": 60
|
||||
},
|
||||
{
|
||||
"x": 240,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 150,
|
||||
"y": 240
|
||||
},
|
||||
{
|
||||
"x": 60,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 150,
|
||||
"y": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "close",
|
||||
"vertices": []
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "arc_r",
|
||||
"vertices": [
|
||||
{
|
||||
"x": 240,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 150,
|
||||
"y": 60
|
||||
},
|
||||
{
|
||||
"x": 60,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 120,
|
||||
"y": 210
|
||||
},
|
||||
{
|
||||
"x": 180,
|
||||
"y": 150
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "arc_c",
|
||||
"vertices": [
|
||||
{
|
||||
"x": 180,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 180,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 150,
|
||||
"y": 180
|
||||
},
|
||||
{
|
||||
"x": 120,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 120,
|
||||
"y": 150
|
||||
},
|
||||
{
|
||||
"x": 180,
|
||||
"y": 90
|
||||
},
|
||||
{
|
||||
"x": 240,
|
||||
"y": 150
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "close",
|
||||
"vertices": []
|
||||
}
|
||||
],
|
||||
[]
|
||||
],
|
||||
"styles": [
|
||||
{
|
||||
"thickness": 2,
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"color": "#000",
|
||||
"fill": "none",
|
||||
"mirror_style": 0
|
||||
},
|
||||
{
|
||||
"thickness": 2,
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"color": "#000",
|
||||
"fill": "#000",
|
||||
"mirror_style": 0
|
||||
},
|
||||
{
|
||||
"thickness": 2,
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"color": "#000",
|
||||
"fill": "none",
|
||||
"mirror_style": 0
|
||||
}
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 45 KiB |