Working toward PWA.

This commit is contained in:
neauoire 2019-11-02 14:55:38 -04:00
parent ee1cdfa167
commit 707f056649
16 changed files with 322 additions and 451 deletions

View File

@ -16,9 +16,6 @@
"push_status": "~/butler status hundredrabbits/ronin", "push_status": "~/butler status hundredrabbits/ronin",
"push": "npm run build ; npm run push_theme ; npm run push_osx ; npm run push_linux ; npm run push_win ; npm run clean ; npm run push_status" "push": "npm run build ; npm run push_theme ; npm run push_osx ; npm run push_linux ; npm run push_win ; npm run clean ; npm run push_status"
}, },
"dependencies": {
"node-osc": "^4.1.0"
},
"devDependencies": { "devDependencies": {
"electron": "^5.0.8", "electron": "^5.0.8",
"electron-packager": "^13.1.1" "electron-packager": "^13.1.1"

View File

@ -10,7 +10,6 @@
<script type="text/javascript" src="scripts/surface.js"></script> <script type="text/javascript" src="scripts/surface.js"></script>
<script type="text/javascript" src="scripts/lisp.js"></script> <script type="text/javascript" src="scripts/lisp.js"></script>
<script type="text/javascript" src="scripts/library.js"></script> <script type="text/javascript" src="scripts/library.js"></script>
<script type="text/javascript" src="scripts/osc.js"></script>
<link rel="stylesheet" type="text/css" href="links/reset.css"/> <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/fonts.css"/>
<link rel="stylesheet" type="text/css" href="links/main.css"/> <link rel="stylesheet" type="text/css" href="links/main.css"/>
@ -19,6 +18,7 @@
</head> </head>
<body> <body>
<script type="text/javascript"> <script type="text/javascript">
const remote = require('electron').remote
const {dialog,app} = require('electron').remote; const {dialog,app} = require('electron').remote;
const fs = require('fs') const fs = require('fs')
const ronin = new Ronin() const ronin = new Ronin()

View File

@ -8,7 +8,6 @@ body { margin:0px; padding:0px; overflow:hidden; font-family:"input_mono_regular
#ronin #wrapper #commander textarea { background: none; width: 100%; height: calc(100vh - 105px); resize: none; font-size: 12px;line-height: 15px; padding-right: 15px} #ronin #wrapper #commander textarea { background: none; width: 100%; height: calc(100vh - 105px); resize: none; font-size: 12px;line-height: 15px; padding-right: 15px}
#ronin #wrapper #commander #status { position: absolute; bottom: 0px; text-transform: lowercase; line-height: 15px; height: 30px; overflow: hidden; width: calc(100% - 75px); padding-left:45px;} #ronin #wrapper #commander #status { position: absolute; bottom: 0px; text-transform: lowercase; line-height: 15px; height: 30px; overflow: hidden; width: calc(100% - 75px); padding-left:45px;}
#ronin #wrapper #commander #status #run { display: block; width: 26px; height: 26px; position: absolute; top: 0px; border-radius: 15px; left:0px; cursor: pointer; border:2px solid #fff; transition: background-color 250ms, border-color 250ms} #ronin #wrapper #commander #status #run { display: block; width: 26px; height: 26px; position: absolute; top: 0px; border-radius: 15px; left:0px; cursor: pointer; border:2px solid #fff; transition: background-color 250ms, border-color 250ms}
#ronin #wrapper #commander #status #run:hover { background: none; transition: none }
#ronin.expand #wrapper #commander { width:100%; } #ronin.expand #wrapper #commander { width:100%; }
#ronin #surface, #ronin #guide { position: absolute; top:0px; -webkit-user-select: none;-webkit-app-region: no-drag; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><circle cx='10' cy='10' r='1' fill='%23555'></circle></svg>"); background-size: 10px 10px; background-position: -4px -4px; width:100%; height:100%; transition: left 250ms, opacity 250ms; opacity: 1; } #ronin #surface, #ronin #guide { position: absolute; top:0px; -webkit-user-select: none;-webkit-app-region: no-drag; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><circle cx='10' cy='10' r='1' fill='%23555'></circle></svg>"); background-size: 10px 10px; background-position: -4px -4px; width:100%; height:100%; transition: left 250ms, opacity 250ms; opacity: 1; }

View File

@ -1,5 +1,4 @@
function Commander (ronin) { function Commander (ronin) {
this.docs = new Docs(ronin)
this.el = document.createElement('div') this.el = document.createElement('div')
this.el.id = 'commander' this.el.id = 'commander'
this._input = document.createElement('textarea') this._input = document.createElement('textarea')
@ -18,15 +17,17 @@ function Commander (ronin) {
this.el.appendChild(this._status) this.el.appendChild(this._status)
host.appendChild(this.el) host.appendChild(this.el)
this._run.setAttribute('title', 'Run(c-R)') this._run.setAttribute('title', 'Run(c-R)')
this._input.setAttribute('autocomplete', 'off')
this._input.setAttribute('autocorrect', 'off')
this._input.setAttribute('autocapitalize', 'off')
this._input.setAttribute('spellcheck', 'false')
this._input.addEventListener('input', this.onInput) this._input.addEventListener('input', this.onInput)
this._input.addEventListener('click', this.onClick) this._input.addEventListener('click', this.onClick)
this._run.addEventListener('click', () => { this.run() }) this._run.addEventListener('click', () => { this.run() })
this._input.onkeydown = (e) => { this._input.onkeydown = (e) => {
if (e.keyCode == 9 || e.which == 9) { e.preventDefault(); this.inject(' ') } if (e.keyCode === 9 || e.which === 9) { e.preventDefault(); this.inject(' ') }
} }
this.docs.install()
} }
this.start = function () { this.start = function () {
@ -41,7 +42,7 @@ function Commander (ronin) {
if (this._input.value.trim() === '') { if (this._input.value.trim() === '') {
ronin.surface.maximize() ronin.surface.maximize()
} }
ronin.interpreter.run(txt) ronin.lisp.run(txt)
this.feedback() this.feedback()
} }
@ -89,12 +90,12 @@ function Commander (ronin) {
if (c === '(') { depth++ } else if (c === ')') { depth-- } if (c === '(') { depth++ } else if (c === ')') { depth-- }
if (c === ';') { if (c === ';') {
const indent = '\n' + (' '.repeat(depth)) const indent = '\n' + (' '.repeat(depth))
val = val.insert(indent, i) val = insert(val, indent, i)
i += indent.length i += indent.length
} }
if (c === '(') { if (c === '(') {
const indent = '\n' + (' '.repeat(depth - 1)) const indent = '\n' + (' '.repeat(depth - 1))
val = val.insert(indent, i) val = insert(val, indent, i)
i += indent.length i += indent.length
} }
} }
@ -103,7 +104,7 @@ function Commander (ronin) {
this.clean = function (input) { this.clean = function (input) {
const keywords = ['$pos+', '$pos', '$rect', '$line', '$x', '$y', '$xy'] const keywords = ['$pos+', '$pos', '$rect', '$line', '$x', '$y', '$xy']
for (word of keywords) { for (const word of keywords) {
input = input.replace(word, '').trim() input = input.replace(word, '').trim()
} }
return input return input
@ -115,12 +116,13 @@ function Commander (ronin) {
this._log.textContent = `${msg}` this._log.textContent = `${msg}`
} }
// Docs // Docs
const lstFn = this.getLastfn() // const lstFn = this.getLastfn()
const rect = ronin.surface.getFrame() // const rect = ronin.surface.getFrame()
const _docs = this.docs.hasDocs(lstFn) === true ? this.docs.print(lstFn) : `${ronin.source}:${this.length()} ${rect.w}x${rect.h}` // TODO
if (_docs !== this._docs.textContent) { // const _docs = this.docs.hasDocs(lstFn) === true ? this.docs.print(lstFn) : `${ronin.source}:${this.length()} ${rect.w}x${rect.h}`
this._docs.textContent = `${_docs}` // if (_docs !== this._docs.textContent) {
} // this._docs.textContent = `${_docs}`
// }
} }
// Injection // Injection
@ -152,15 +154,15 @@ function Commander (ronin) {
const append = words[0].indexOf('+') > -1 const append = words[0].indexOf('+') > -1
if (word === 'drag') { if (word === 'drag') {
this.cache = this.cache.replace('$drag', `(drag $rect $line)`) this.cache = this.cache.replace('$drag', '(drag $rect $line)')
} else if (word === 'view') { } else if (word === 'view') {
this.cache = this.cache.replace('$view', `(view $rect $rect)`) this.cache = this.cache.replace('$view', '(view $rect $rect)')
} else if (word === 'poly') { } else if (word === 'poly') {
this.cache = this.cache.replace('$poly', `(poly $pos+)`) this.cache = this.cache.replace('$poly', '(poly $pos+)')
} else if (word === 'move') { } else if (word === 'move') {
this.cache = this.cache.replace('$move', `(transform:move $wh)`) this.cache = this.cache.replace('$move', '(transform:move $wh)')
} else if (word === 'rotate') { } else if (word === 'rotate') {
this.cache = this.cache.replace('$rotate', `(transform:rotate $a)`) this.cache = this.cache.replace('$rotate', '(transform:rotate $a)')
} }
if (shape[word]) { if (shape[word]) {
@ -225,7 +227,7 @@ function Commander (ronin) {
// Splash // Splash
this.splash = `; welcome to ronin this.splash = `; welcome to ronin
; v2.30 ; v2.40
(clear) (clear)
(def logo-path "M60,60 L195,60 A45,45 0 0,1 240,105 A45,45 0 0,1 195,150 L60,150 M195,150 A45,45 0 0,1 240,195 L240,240 ") (def logo-path "M60,60 L195,60 A45,45 0 0,1 240,105 A45,45 0 0,1 195,150 L60,150 M195,150 A45,45 0 0,1 240,195 L240,240 ")
(def pos-x (def pos-x
@ -235,5 +237,7 @@ function Commander (ronin) {
(stroke (stroke
(svg pos-x pos-y logo-path) theme:b_high 5)` (svg pos-x pos-y logo-path) theme:b_high 5)`
String.prototype.insert = function (s, i) { return [this.slice(0, i), `${s}`, this.slice(i)].join('') } function insert (str, add, i) {
return [str.slice(0, i), `${add}`, str.slice(i)].join('')
}
} }

View File

@ -1,43 +0,0 @@
function Docs (ronin) {
this.dict = {}
this.load = () => {
const fs = require('fs')
const path = require('path')
const p = path.join(__dirname, 'scripts/', 'library.js')
if (!fs.existsSync(p)) { console.warn('Docs', 'File does not exist: ' + p); return }
const lines = fs.readFileSync(p, 'utf8').split('\n').filter((line) => { return line.substr(0, 7) === ' this.' })
return lines.map((line) => { return line.trim().substr(5).trim() })
}
this.install = (payload = this.load()) => {
for (const id in payload) {
const name = payload[id].substr(0, payload[id].indexOf(' = '))
const parent = payload[id].substr(payload[id].indexOf(' = ')).match(/\(([^)]+)\)/)
const params = parent ? parent[1].split(',').map((word) => { return word.indexOf(' = ') > -1 ? '~' + (word.split(' = ')[0]).trim() : word.trim() }) : []
const note = payload[id].indexOf('// ') > -1 ? payload[id].split('//')[1].trim() : ''
this.dict[name] = { note, params }
if (params.length < 1) { console.warn('Docs', 'Missing params for ' + name) }
if (note === '') { console.warn('Docs', 'Missing note for ' + name) }
}
console.log('Docs', `Loaded ${Object.keys(this.dict).length} functions.`)
console.log(this.toMarkdown())
}
this.toMarkdown = () => {
return Object.keys(this.dict).reduce((acc, item, key) => {
const example = `${item} ${this.dict[item].params.reduce((acc, item) => {
return `${acc}${item} `
}, '').trim()}`
return `${acc}- \`(${example.trim()})\` ${this.dict[item].note}\n`
}, '')
}
this.hasDocs = (name) => {
return !!this.dict[name]
}
this.print = (name) => {
return `(${name} ${this.dict[name].params.reduce((acc, item) => { return `${acc}${item} ` }, '').trim()})`
}
}

View File

@ -0,0 +1,79 @@
'use strict'
function Acels () {
this.all = {}
this.install = (host = window) => {
host.addEventListener('keydown', this.onKeyDown, false)
host.addEventListener('keyup', this.onKeyUp, false)
}
this.set = (type, name, key, downfn, upfn) => {
if (this.all[key]) { console.warn('Acels', `Trying to overwrite ${this.all[key].name}, with ${name}.`) }
this.all[key] = { type, name, downfn, upfn, key }
}
this.get = (key) => {
return this.all[key]
}
this.sort = () => {
const h = {}
for (const item of Object.values(this.all)) {
if (!h[item.type]) { h[item.type] = [] }
h[item.type].push(item)
}
return h
}
this.convert = (event) => {
const key = event.key.substr(0, 1).toUpperCase() + event.key.substr(1)
if ((event.ctrlKey || event.metaKey) && event.shiftKey) {
return `CmdOrCtrl+Shift+${key}`
}
if (event.shiftKey) {
return `Shift+${key}`
}
if (event.ctrlKey || event.metaKey) {
return `CmdOrCtrl+${key}`
}
return key
}
this.onKeyDown = (e) => {
const target = this.get(this.convert(e))
if (!target || !target.downfn) { return }
target.downfn()
e.preventDefault()
}
this.onKeyUp = (e) => {
const target = this.get(this.convert(e))
if (!target || !target.upfn) { return }
target.upfn()
e.preventDefault()
}
this.toMarkdown = () => {
const types = this.sort()
let text = ''
for (const type in types) {
text += `\n### ${type}\n\n`
for (const item of types[type]) {
text += `- \`${item.key}\`: ${item.info}\n`
}
}
return text.trim()
}
this.toString = () => {
const types = this.sort()
let text = ''
for (const type in types) {
for (const item of types[type]) {
text += `${type}: ${item.name} | ${item.key}\n`
}
}
return text.trim()
}
}

View File

@ -1,90 +0,0 @@
'use strict'
function Controller () {
const fs = require('fs')
const { dialog, app } = require('electron').remote
this.menu = { default: {} }
this.mode = 'default'
this.app = require('electron').remote.app
this.start = function () {
}
this.add = function (mode, cat, label, fn, accelerator) {
if (!this.menu[mode]) { this.menu[mode] = {} }
if (!this.menu[mode][cat]) { this.menu[mode][cat] = {} }
this.menu[mode][cat][label] = { fn: function (_menuItem, browserWindow) {
if (browserWindow) {
browserWindow.webContents.focus()
}
fn.apply(this, arguments)
},
accelerator: accelerator }
}
this.addRole = function (mode, cat, label) {
if (!this.menu[mode]) { this.menu[mode] = {} }
if (!this.menu[mode][cat]) { this.menu[mode][cat] = {} }
this.menu[mode][cat][label] = { role: label }
}
this.addSpacer = function (mode, cat, label, type = 'separator') {
if (!this.menu[mode]) { this.menu[mode] = {} }
if (!this.menu[mode][cat]) { this.menu[mode][cat] = {} }
this.menu[mode][cat][label] = { type: type }
}
this.clearCat = function (mode, cat) {
if (this.menu[mode]) { this.menu[mode][cat] = {} }
}
this.set = function (mode = 'default') {
this.mode = mode
this.commit()
}
this.format = function () {
const f = []
const m = this.menu[this.mode]
for (const cat in m) {
const submenu = []
for (const name in m[cat]) {
const option = m[cat][name]
if (option.role) {
submenu.push({ role: option.role })
} else if (option.type) {
submenu.push({ type: option.type })
} else {
submenu.push({ label: name, accelerator: option.accelerator, click: option.fn })
}
}
f.push({ label: cat, submenu: submenu })
}
return f
}
this.commit = function () {
console.log('Controller', 'Changing..')
this.app.injectMenu(this.format())
}
this.accelerator = function (key, menu) {
const acc = { basic: null, ctrl: null }
for (cat in menu) {
const options = menu[cat]
for (const id in options.submenu) {
const option = options.submenu[id]; if (option.role) { continue }
acc.basic = (option.accelerator.toLowerCase() === key.toLowerCase()) ? option.label.toUpperCase().replace('TOGGLE ', '').substr(0, 8).trim() : acc.basic
acc.ctrl = (option.accelerator.toLowerCase() === ('CmdOrCtrl+' + key).toLowerCase()) ? option.label.toUpperCase().replace('TOGGLE ', '').substr(0, 8).trim() : acc.ctrl
}
}
return acc
}
this.docs = function () {
// TODO
console.log(this.menu.default)
}
}

View File

@ -1,9 +1,6 @@
'use strict' 'use strict'
function Lisp (lib = {}) { function Lisp (lib = {}) {
const path = require('path')
const fs = require('fs')
const TYPES = { identifier: 0, number: 1, string: 2, bool: 3, symbol: 4 } const TYPES = { identifier: 0, number: 1, string: 2, bool: 3, symbol: 4 }
const Context = function (scope, parent) { const Context = function (scope, parent) {
@ -19,11 +16,6 @@ function Lisp (lib = {}) {
} }
const special = { const special = {
include: (input, context) => {
if (!input[1].value || !fs.existsSync(input[1].value)) { console.warn('Lisp', 'No file: ' + input[1].value); return [] }
const file = fs.readFileSync(input[1].value, { encoding: 'utf-8' })
return interpret(this.parse(`(${file})`), context)
},
let: function (input, context) { let: function (input, context) {
const letContext = input[1].reduce(function (acc, x) { const letContext = input[1].reduce(function (acc, x) {
acc.scope[x[0].value] = interpret(x[1], context) acc.scope[x[0].value] = interpret(x[1], context)
@ -164,7 +156,7 @@ function Lisp (lib = {}) {
} }
const tokenize = function (input) { const tokenize = function (input) {
const i = input.replace(/^\;.*\n?/gm, '').replace(/λ /g, 'lambda ').split('"') const i = input.replace(/^;.*\n?/gm, '').replace(/λ /g, 'lambda ').split('"')
return i.map(function (x, i) { return i.map(function (x, i) {
return i % 2 === 0 return i % 2 === 0
? x.replace(/\(/g, ' ( ') ? x.replace(/\(/g, ' ( ')

View File

@ -0,0 +1,62 @@
'use strict'
/* global FileReader */
/* global MouseEvent */
function Source () {
this.cache = {}
this.install = () => {
}
this.start = () => {
this.new()
}
this.new = () => {
console.log('Source', 'New file..')
this.cache = {}
}
this.open = (callback) => {
console.log('Source', 'Open file..')
const input = document.createElement('input')
input.type = 'file'
input.onchange = (e) => {
this.cache = e.target.files[0]
this.load(this.cache, callback)
}
input.click()
}
this.save = (name, content, type = 'text/plain', callback) => {
this.saveAs(name, content, type, callback)
}
this.saveAs = (name, content, type = 'text/plain', callback) => {
console.log('Source', 'Save new file..')
this.download(name, content, type, callback)
}
this.revert = () => {
}
// I/O
this.load = (file, callback) => {
const reader = new FileReader()
reader.onload = (event) => {
const res = event.target.result
callback(res)
}
reader.readAsText(file, 'UTF-8')
}
this.download = (name, content, type) => {
const pom = document.createElement('a')
pom.setAttribute('download', name)
pom.setAttribute('href', 'data:' + type + ';charset=utf-8,' + encodeURIComponent(content))
pom.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }))
}
}

View File

@ -1,5 +1,9 @@
'use strict' 'use strict'
/* global localStorage */
/* global FileReader */
/* global DOMParser */
function Theme (_default) { function Theme (_default) {
const themer = this const themer = this
@ -8,12 +12,12 @@ function Theme (_default) {
this.el = document.createElement('style') this.el = document.createElement('style')
this.el.type = 'text/css' this.el.type = 'text/css'
this.install = function (host = document.body, callback) { this.install = (host = document.body, callback) => {
host.appendChild(this.el) host.appendChild(this.el)
this.callback = callback this.callback = callback
} }
this.start = function () { this.start = () => {
console.log('Theme', 'Starting..') console.log('Theme', 'Starting..')
if (isJson(localStorage.theme)) { if (isJson(localStorage.theme)) {
const storage = JSON.parse(localStorage.theme) const storage = JSON.parse(localStorage.theme)
@ -26,10 +30,10 @@ function Theme (_default) {
this.load(_default) this.load(_default)
} }
this.load = function (data) { this.load = (data) => {
const theme = parse(data) const theme = parse(data)
if (!validate(theme)) { return } if (!validate(theme)) { return }
console.log('Theme', `Loaded theme!`) 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}; }` 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)) localStorage.setItem('theme', JSON.stringify(theme))
this.active = theme this.active = theme
@ -38,11 +42,11 @@ function Theme (_default) {
} }
} }
this.reset = function () { this.reset = () => {
this.load(_default) this.load(_default)
} }
this.get = function (key) { this.get = (key) => {
return this.active[key] return this.active[key]
} }
@ -53,13 +57,13 @@ function Theme (_default) {
// Drag // Drag
this.drag = function (e) { this.drag = (e) => {
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
e.dataTransfer.dropEffect = 'copy' e.dataTransfer.dropEffect = 'copy'
} }
this.drop = function (e) { this.drop = (e) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const file = e.dataTransfer.files[0] const file = e.dataTransfer.files[0]
@ -72,10 +76,10 @@ function Theme (_default) {
reader.readAsText(file) reader.readAsText(file)
} }
this.open = function () { this.open = () => {
const fs = require('fs') const fs = require('fs')
const { dialog, app } = require('electron').remote const { dialog, app } = require('electron').remote
let paths = dialog.showOpenDialog(app.win, { properties: ['openFile'], filters: [{ name: 'Themes', extensions: ['svg'] }] }) const paths = dialog.showOpenDialog(app.win, { properties: ['openFile'], filters: [{ name: 'Themes', extensions: ['svg'] }] })
if (!paths) { console.log('Nothing to load'); return } if (!paths) { console.log('Nothing to load'); return }
fs.readFile(paths[0], 'utf8', function (err, data) { fs.readFile(paths[0], 'utf8', function (err, data) {
if (err) throw err if (err) throw err
@ -106,15 +110,15 @@ function Theme (_default) {
const svg = new DOMParser().parseFromString(text, 'text/xml') const svg = new DOMParser().parseFromString(text, 'text/xml')
try { try {
return { return {
'background': svg.getElementById('background').getAttribute('fill'), background: svg.getElementById('background').getAttribute('fill'),
'f_high': svg.getElementById('f_high').getAttribute('fill'), f_high: svg.getElementById('f_high').getAttribute('fill'),
'f_med': svg.getElementById('f_med').getAttribute('fill'), f_med: svg.getElementById('f_med').getAttribute('fill'),
'f_low': svg.getElementById('f_low').getAttribute('fill'), f_low: svg.getElementById('f_low').getAttribute('fill'),
'f_inv': svg.getElementById('f_inv').getAttribute('fill'), f_inv: svg.getElementById('f_inv').getAttribute('fill'),
'b_high': svg.getElementById('b_high').getAttribute('fill'), b_high: svg.getElementById('b_high').getAttribute('fill'),
'b_med': svg.getElementById('b_med').getAttribute('fill'), b_med: svg.getElementById('b_med').getAttribute('fill'),
'b_low': svg.getElementById('b_low').getAttribute('fill'), b_low: svg.getElementById('b_low').getAttribute('fill'),
'b_inv': svg.getElementById('b_inv').getAttribute('fill') b_inv: svg.getElementById('b_inv').getAttribute('fill')
} }
} catch (err) { } catch (err) {
console.warn('Theme', 'Incomplete SVG Theme', err) console.warn('Theme', 'Incomplete SVG Theme', err)

View File

@ -1,3 +1,7 @@
'use strict'
/* global Image */
function Library (ronin) { function Library (ronin) {
// Modularity: Write simple parts connected by clean interfaces. // Modularity: Write simple parts connected by clean interfaces.
// Composition: Design programs to be connected to other programs. // Composition: Design programs to be connected to other programs.
@ -12,11 +16,11 @@ function Library (ronin) {
} }
this.export = (path, quality = 1.0) => { // Exports a graphic file with format. this.export = (path, quality = 1.0) => { // Exports a graphic file with format.
if (!path) { console.warn('Missing export path'); return path } // if (!path) { console.warn('Missing export path'); return path }
const dataUrl = ronin.surface.el.toDataURL(path.indexOf('.jpg') > -1 ? 'image/jpeg' : path.indexOf('.png') > -1 ? 'image/png' : format, quality) // const dataUrl = ronin.surface.el.toDataURL(path.indexOf('.jpg') > -1 ? 'image/jpeg' : path.indexOf('.png') > -1 ? 'image/png' : format, quality)
const data = dataUrl.replace(/^data:image\/png;base64,/, '').replace(/^data:image\/jpeg;base64,/, '') // const data = dataUrl.replace(/^data:image\/png;base64,/, '').replace(/^data:image\/jpeg;base64,/, '')
fs.writeFileSync(path, data, 'base64') // fs.writeFileSync(path, data, 'base64')
return path // return path
} }
this.open = async (path, ratio = 1) => { // Imports a graphic file and resizes the frame. this.open = async (path, ratio = 1) => { // Imports a graphic file and resizes the frame.
@ -403,7 +407,7 @@ function Library (ronin) {
} }
this.or = (a, b, ...rest) => { // Returns true if at least one condition is true. this.or = (a, b, ...rest) => { // Returns true if at least one condition is true.
let args = [a, b].concat(rest) const args = [a, b].concat(rest)
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
if (args[i]) { if (args[i]) {
return args[i] return args[i]
@ -437,7 +441,7 @@ function Library (ronin) {
this.reduce = async (arr, fn, acc) => { this.reduce = async (arr, fn, acc) => {
const length = arr.length const length = arr.length
let result = acc === undefined ? subject[0] : acc let result = acc === undefined ? arr[0] : acc
for (let i = acc === undefined ? 1 : 0; i < length; i++) { for (let i = acc === undefined ? 1 : 0; i < length; i++) {
result = await fn(result, arr[i], i, arr) result = await fn(result, arr[i], i, arr)
} }
@ -461,7 +465,7 @@ function Library (ronin) {
} }
this.range = (start, end, step = 1) => { this.range = (start, end, step = 1) => {
let arr = [] const arr = []
if (step > 0) { if (step > 0) {
for (let i = start; i <= end; i += step) { for (let i = start; i <= end; i += step) {
arr.push(i) arr.push(i)
@ -534,9 +538,9 @@ function Library (ronin) {
} }
this.sharpen = () => { // Returns the sharpen kernel. this.sharpen = () => { // Returns the sharpen kernel.
return [[ 0, -1, 0], return [[0, -1, 0],
[-1, 5, -1], [-1, 5, -1],
[ 0, -1, 0]] [0, -1, 0]]
} }
this.edge = () => { // Returns the edge kernel. this.edge = () => { // Returns the edge kernel.
@ -548,27 +552,27 @@ function Library (ronin) {
// File System // File System
this.dir = (path = this.dirpath()) => { // Returns the content of a directory. this.dir = (path = this.dirpath()) => { // Returns the content of a directory.
return fs.existsSync(path) ? fs.readdirSync(path) : [] // return fs.existsSync(path) ? fs.readdirSync(path) : []
} }
this.file = (path = this.filepath()) => { // Returns the content of a file. this.file = (path = this.filepath()) => { // Returns the content of a file.
return fs.existsSync(path) ? fs.readFileSync(path, 'utf8') : '' // return fs.existsSync(path) ? fs.readFileSync(path, 'utf8') : ''
} }
this.dirpath = (path = this.filepath()) => { // Returns the path of a directory. this.dirpath = (path = this.filepath()) => { // Returns the path of a directory.
return require('path').dirname(path) // return require('path').dirname(path)
} }
this.filepath = (path = ronin.source.path) => { // Returns the path of a file. this.filepath = (path = ronin.source.path) => { // Returns the path of a file.
return path // return path
} }
this.dirname = (path = this.filepath()) => { // Returns the name of a folder. this.dirname = (path = this.filepath()) => { // Returns the name of a folder.
return require('path').basename(require('path').dirname(path)) // return require('path').basename(require('path').dirname(path))
} }
this.filename = (path = this.filepath()) => { // Returns the name of a file. this.filename = (path = this.filepath()) => { // Returns the name of a file.
return require('path').parse(path).name // return require('path').parse(path).name
} }
this.offset = (a, b) => { // Offsets pos a with pos b, returns a. this.offset = (a, b) => { // Offsets pos a with pos b, returns a.
@ -578,7 +582,7 @@ function Library (ronin) {
} }
this.distance = (a, b) => { // Get distance between positions. this.distance = (a, b) => { // Get distance between positions.
return Math.sqrt(((ax - bx) * (ax - bx)) + ((ay - by) * (ay - by))) return Math.sqrt(((a.x - b.x) * (a.x - b.x)) + ((a.y - b.y) * (a.y - b.y)))
} }
this.echo = (...args) => { // Print arguments to interface. this.echo = (...args) => { // Print arguments to interface.

View File

@ -1,18 +0,0 @@
'use strict'
function Osc (ronin) {
const osc = require('node-osc')
this.port = 49162
this.start = function () {
const server = new osc.Server(49162, '0.0.0.0')
server.on('message', this.onMsg)
}
this.onMsg = (msg) => {
const address = msg.shift()
if (ronin.bindings[address]) {
ronin.bindings[address](msg)
}
}
}

View File

@ -1,26 +1,25 @@
function Ronin () { 'use strict'
const defaultTheme = {
background: '#111',
f_high: '#fff',
f_med: '#999',
f_low: '#444',
f_inv: '#000',
b_high: '#ffffff',
b_med: '#72dec2',
b_low: '#aaaaaa',
b_inv: '#ffb545'
}
/* global Acels */
/* global Theme */
/* global Source */
/* global Commander */
/* global Surface */
/* global Library */
/* global Lisp */
/* global requestAnimationFrame */
function Ronin () {
this.el = document.createElement('div') this.el = document.createElement('div')
this.el.id = 'ronin' this.el.id = 'ronin'
this.theme = new Theme(defaultTheme) this.acels = new Acels()
this.source = new Source(this) this.theme = new Theme()
this.source = new Source()
this.commander = new Commander(this) this.commander = new Commander(this)
this.surface = new Surface(this) this.surface = new Surface(this)
this.library = new Library(this) this.library = new Library(this)
this.interpreter = new Lisp(this.library) this.lisp = new Lisp(this.library)
this.osc = new Osc(this)
this.bindings = {} this.bindings = {}
@ -34,22 +33,52 @@ function Ronin () {
host.appendChild(this.el) host.appendChild(this.el)
this.theme.install() this.theme.install()
window.addEventListener('dragover', this.drag) window.addEventListener('dragover', this.onDrag)
window.addEventListener('drop', this.drop) window.addEventListener('drop', this.onDrop)
this.acels.set('File', 'New', 'CmdOrCtrl+N', () => { this.source.new(); this.surface.clear(); this.commander.clear() })
this.acels.set('File', 'Save', 'CmdOrCtrl+S', () => { this.source.save('export.lisp', this.commander._input.value, 'text/plain') })
this.acels.set('File', 'Save As', 'CmdOrCtrl+Shift+S', () => { this.source.saveAs() })
this.acels.set('File', 'Open', 'CmdOrCtrl+O', () => { this.source.open(this.whenOpen) })
this.acels.set('File', 'Revert', 'CmdOrCtrl+W', () => { this.source.revert() })
this.acels.set('View', 'Toggle Guides', 'CmdOrCtrl+Shift+H', () => { this.surface.toggleGuides() })
this.acels.set('View', 'Toggle Commander', 'CmdOrCtrl+K', () => { this.commander.toggle() })
this.acels.set('View', 'Expand Commander', 'CmdOrCtrl+Shift+K', () => { this.commander.toggle(true) })
this.acels.set('Project', 'Run', 'CmdOrCtrl+R', () => { this.commander.run() })
this.acels.set('Project', 'Reload Run', 'CmdOrCtrl+Shift+R', () => { this.source.revert(); this.commander.run() })
this.acels.set('Project', 'Re-Indent', 'CmdOrCtrl+Shift+I', () => { this.commander.reindent() })
this.acels.set('Project', 'Clean', 'Escape', () => { this.commander.cleanup() })
this.acels.install(window)
} }
this.start = function () { this.start = function () {
this.theme.start() console.log('Ronin', 'Starting..')
console.info(`${this.acels}`)
this.theme.start({
background: '#111',
f_high: '#fff',
f_med: '#999',
f_low: '#444',
f_inv: '#000',
b_high: '#ffffff',
b_med: '#72dec2',
b_low: '#aaaaaa',
b_inv: '#ffb545'
})
this.source.start() this.source.start()
this.commander.start() this.commander.start()
this.surface.start() this.surface.start()
this.osc.start()
this.loop() this.loop()
} }
this.whenOpen = (res) => {
this.commander.load(res)
this.commander.show()
}
this.loop = () => { this.loop = () => {
if (this.bindings['animate'] && typeof this.bindings['animate'] === 'function') { if (this.bindings.animate && typeof this.bindings.animate === 'function') {
this.bindings['animate']() this.bindings.animate()
} }
requestAnimationFrame(() => this.loop()) requestAnimationFrame(() => this.loop())
} }
@ -60,7 +89,7 @@ function Ronin () {
this.log = function (...msg) { this.log = function (...msg) {
this.commander.setStatus(msg.reduce((acc, val) => { this.commander.setStatus(msg.reduce((acc, val) => {
return acc + JSON.stringify(val).replace(/\"/g, '').trim() + ' ' return acc + JSON.stringify(val).replace(/"/g, '').trim() + ' '
}, '')) }, ''))
} }
@ -77,7 +106,7 @@ function Ronin () {
this.mouseOrigin = null this.mouseOrigin = null
this.onMouseDown = (e, id = 'mouse-down') => { this.onMouseDown = (e, id = 'mouse-down') => {
const pos = { x: e.offsetX * ronin.surface.ratio, y: e.offsetY * ronin.surface.ratio } const pos = { x: e.offsetX * this.surface.ratio, y: e.offsetY * this.surface.ratio }
this.mouseOrigin = pos this.mouseOrigin = pos
const shape = this.mouseShape(pos, id) const shape = this.mouseShape(pos, id)
if (this.bindings[id]) { if (this.bindings[id]) {
@ -94,20 +123,20 @@ function Ronin () {
} }
} }
this.onKeyUp = (e, id = 'key-up') => {
if (this.bindings[id]) {
this.bindings[id](e)
}
}
this.onKeyDown = (e, id = 'key-down') => { this.onKeyDown = (e, id = 'key-down') => {
if (this.bindings[id]) { if (this.bindings[id]) {
this.bindings[id](e) this.bindings[id](e)
} }
} }
this.onKeyUp = (e, id = 'key-up') => {
if (this.bindings[id]) {
this.bindings[id](e)
}
}
this.onMouseMove = (e, id = 'mouse-move') => { this.onMouseMove = (e, id = 'mouse-move') => {
const pos = { x: e.offsetX * ronin.surface.ratio, y: e.offsetY * ronin.surface.ratio } const pos = { x: e.offsetX * this.surface.ratio, y: e.offsetY * this.surface.ratio }
const shape = this.mouseShape(pos, id) const shape = this.mouseShape(pos, id)
if (this.bindings[id]) { if (this.bindings[id]) {
this.bindings[id](shape) this.bindings[id](shape)
@ -120,7 +149,7 @@ function Ronin () {
} }
this.onMouseUp = (e, id = 'mouse-up') => { this.onMouseUp = (e, id = 'mouse-up') => {
const pos = { x: e.offsetX * ronin.surface.ratio, y: e.offsetY * ronin.surface.ratio } const pos = { x: e.offsetX * this.surface.ratio, y: e.offsetY * this.surface.ratio }
const shape = this.mouseShape(pos, id) const shape = this.mouseShape(pos, id)
if (this.bindings[id]) { if (this.bindings[id]) {
this.bindings[id](shape) this.bindings[id](shape)
@ -140,6 +169,18 @@ function Ronin () {
this.mouseOrigin = null this.mouseOrigin = null
} }
this.onDrag = (e) => {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
}
this.onDrop = (e) => {
e.preventDefault()
e.stopPropagation()
this.source.load(e.dataTransfer.files[0], this.whenOpen)
}
this.mouseShape = (position, type) => { this.mouseShape = (position, type) => {
if (!this.mouseOrigin) { return } if (!this.mouseOrigin) { return }
const x = position.x const x = position.x
@ -174,48 +215,4 @@ function Ronin () {
} }
return { x, y, xy, wh, d, a, line, rect, pos, size, circle, arc, type, 'is-down': type !== 'mouse-up' ? true : null } return { x, y, xy, wh, d, a, line, rect, pos, size, circle, arc, type, 'is-down': type !== 'mouse-up' ? true : null }
} }
// Zoom
this.modZoom = function (mod = 0, set = false) {
try {
const { webFrame } = require('electron')
const currentZoomFactor = webFrame.getZoomFactor()
webFrame.setZoomFactor(set ? mod : currentZoomFactor + mod)
console.log(window.devicePixelRatio)
} catch (err) {
console.log('Cannot zoom')
}
}
this.setZoom = function (scale) {
try {
webFrame.setZoomFactor(scale)
} catch (err) {
console.log('Cannot zoom')
}
}
// Events
this.drag = (e) => {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
}
this.drop = (e) => {
e.preventDefault()
e.stopPropagation()
const file = e.dataTransfer.files[0]
if (!file || !file.name) { console.warn('File', 'Not a valid file.'); return }
const path = file.path ? file.path : file.name
if (this.commander._input.value.indexOf('$path') > -1) {
this.commander.injectPath(file.path)
this.commander.show()
} else if (path.indexOf('.lisp') > -1) {
this.source.read(path)
this.commander.show()
}
}
} }

View File

@ -1,151 +0,0 @@
'use strict'
function Source (ronin) {
const fs = require('fs')
const path = require('path')
const { dialog, app } = require('electron').remote
this.path = null
this.start = function () {
this.new()
}
this.new = function () {
console.log('Source', 'Make a new file..')
this.path = null
ronin.surface.clear()
ronin.commander.clear()
ronin.log(`New file.`)
}
this.open = function () {
console.log('Source', 'Open a file..')
let paths = dialog.showOpenDialog(app.win, { properties: ['openFile'], filters: [{ name: 'Ronin Lisp', extensions: ['lisp'] }] })
if (!paths) { console.log('Nothing to load'); return }
this.read(paths[0])
}
this.save = function (quitAfter = false) {
console.log('Source', 'Save..', this.path)
if (this.path) {
this.write(this.path, this.generate(), quitAfter)
} else {
this.saveAs(quitAfter)
}
}
this.saveAs = function (quitAfter = false) {
console.log('Source', 'Save a file as..')
dialog.showSaveDialog((loc) => {
if (loc === undefined) { return }
if (loc.indexOf('.lisp') < 0) { loc += '.lisp' }
this.write(loc, this.generate(), quitAfter)
this.path = loc
})
}
this.revert = function () {
if (!this.path) { return }
console.log('Source', 'Revert a file..')
this.read(this.path)
}
// I/O
this.write = function (loc, data = this.generate(), quitAfter = false) {
console.log('Source', 'Writing ' + loc)
fs.writeFileSync(loc, data)
if (quitAfter === true) {
app.exit()
}
ronin.log(`Writing file.`)
}
this.read = function (loc = this.path) {
if (!loc) { return }
if (!fs.existsSync(loc)) { console.warn('Source', 'File does not exist: ' + loc); return }
console.log('Source', 'Reading ' + loc)
this.path = loc
ronin.commander.load(fs.readFileSync(this.path, 'utf8'))
ronin.log(`Reading file.`)
}
this.run = function () {
ronin.commander.run()
}
this.quit = function (force = false) {
if (this.hasChanges() === true && force === false) {
this.verify()
} else {
app.exit()
}
}
this.verify = function () {
let response = dialog.showMessageBox(app.win, {
type: 'question',
buttons: ['Cancel', 'Discard', 'Save'],
title: 'Confirm',
message: 'Unsaved data will be lost. Would you like to save your changes before leaving?',
icon: path.join(__dirname, '../icon.png')
})
if (response === 2) {
this.save(true)
} else if (response === 1) {
app.exit()
}
}
this.hasChanges = function () {
console.log('Source', 'Looking for changes..')
if (!this.path) {
console.log('Source', 'File is unsaved..')
if (ronin.commander._input.value.length > 2) {
console.log('Source', `File is not empty.`)
return true
}
} else {
if (fs.existsSync(this.path)) {
console.log('Source', 'Comparing with last saved copy..')
const diff = isDifferent(fs.readFileSync(this.path, 'utf8'), this.generate())
if (diff === true) {
console.log('Source', 'File has been changed.')
return true
}
} else {
console.log('Source', 'File does not exist.')
return true
}
}
}
// Converters
this.generate = function (str = ronin.commander._input.value) {
return `${str}`
}
// Etc
this.name = function () {
return this.path ? path.basename(this.path, '.lisp') : null
}
this.folder = function (p = this.path) {
return p ? path.dirname(p) : null
}
this.toString = function () {
return this.path ? this.name() + '.lisp' : 'new'
}
function isDifferent (a, b) {
return a.trim() !== b.trim()
}
function clean (s) {
return s
}
}

View File

@ -1,3 +1,8 @@
'use strict'
/* global Path2D */
/* global Image */
function Surface (ronin) { function Surface (ronin) {
this.el = document.createElement('canvas') this.el = document.createElement('canvas')
this.el.id = 'surface' this.el.id = 'surface'
@ -5,6 +10,7 @@ function Surface (ronin) {
this._guide.id = 'guide' this._guide.id = 'guide'
this._guide.setAttribute('tabindex', '1') // focus is necessary to capture keyboard events this._guide.setAttribute('tabindex', '1') // focus is necessary to capture keyboard events
this.ratio = window.devicePixelRatio this.ratio = window.devicePixelRatio
// Contexts // Contexts
this.context = this.el.getContext('2d') this.context = this.el.getContext('2d')
this.guide = this._guide.getContext('2d') this.guide = this._guide.getContext('2d')
@ -132,7 +138,7 @@ function Surface (ronin) {
const positions = Object.values(poly) const positions = Object.values(poly)
const origin = positions.shift() const origin = positions.shift()
context.moveTo(origin.x, origin.y) context.moveTo(origin.x, origin.y)
for (pos of positions) { for (const pos of positions) {
context.lineTo(pos.x, pos.y) context.lineTo(pos.x, pos.y)
} }
} }
@ -173,7 +179,7 @@ function Surface (ronin) {
img.src = path img.src = path
img.onload = () => { img.onload = () => {
const rect = { x: 0, y: 0, w: img.width * ratio, h: img.height * ratio } const rect = { x: 0, y: 0, w: img.width * ratio, h: img.height * ratio }
this.resize(rect,true) this.resize(rect, true)
this.context.drawImage(img, rect.x, rect.y, rect.w, rect.h) this.context.drawImage(img, rect.x, rect.y, rect.w, rect.h)
resolve() resolve()
} }

29
index.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<head>
<meta charset='UTF-8'>
<script type="text/javascript" src="desktop/sources/scripts/lib/theme.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/lib/acels.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/lib/lisp.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/lib/source.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/ronin.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/commander.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/surface.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/library.js"></script>
<link rel="stylesheet" type="text/css" href="desktop/sources/links/reset.css"/>
<link rel="stylesheet" type="text/css" href="desktop/sources/links/fonts.css"/>
<link rel="stylesheet" type="text/css" href="desktop/sources/links/main.css"/>
<link rel="stylesheet" type="text/css" href="desktop/sources/links/theme.css"/>
<title>Ronin</title>
</head>
<body>
<script type="text/javascript">
const ronin = new Ronin()
ronin.install(document.body)
window.addEventListener('load', () => {
ronin.start()
})
</script>
</body>
</html>