electron simulator (#531)
|
@ -12,6 +12,9 @@ jobs:
|
|||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '11.x'
|
||||
- name: download dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -70,6 +73,9 @@ jobs:
|
|||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '11.x'
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '2.7.x'
|
||||
|
@ -112,6 +118,9 @@ jobs:
|
|||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '11.x'
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '2.7.x'
|
||||
|
|
|
@ -8,9 +8,10 @@ dist/
|
|||
build/
|
||||
locales/
|
||||
/inx/
|
||||
messages.po
|
||||
*.po
|
||||
/DEBUG
|
||||
.pydevproject
|
||||
.project
|
||||
/debug.log
|
||||
/debug.svg
|
||||
/.idea
|
||||
|
|
13
Makefile
|
@ -17,8 +17,17 @@ messages.po:
|
|||
rm -f messages.po
|
||||
bin/pyembroidery-gettext > pyembroidery-format-descriptions.py
|
||||
bin/inkstitch-fonts-gettext > inkstitch-fonts-metadata.py
|
||||
pybabel extract -o messages.po -F babel.conf --add-location=full --add-comments=l10n,L10n,L10N --sort-by-file --strip-comments -k N_ .
|
||||
rm pyembroidery-format-descriptions.py
|
||||
pybabel extract -o messages-babel.po -F babel.conf --add-location=full --add-comments=l10n,L10n,L10N --sort-by-file --strip-comments -k N_ -k '$$gettext' .
|
||||
rm pyembroidery-format-descriptions.py inkstitch-fonts-metadata.py
|
||||
find electron/src -name '*.html' -o -name '*.js' -o -name '*.vue' | xargs electron/node_modules/.bin/gettext-extract --quiet --attribute v-translate --output messages-vue.po
|
||||
msgcat -o messages.po messages-babel.po messages-vue.po
|
||||
|
||||
electron/src/renderer/assets/translations.json: $(addsuffix /LC_MESSAGES/inkstitch.po,$(wildcard locales/*))
|
||||
find locales -name '*.po' -a ! -empty | \
|
||||
xargs electron/node_modules/.bin/gettext-compile --output electron/src/renderer/assets/translations.json
|
||||
|
||||
%.po: %.mo
|
||||
msgunfmt -o $@ $<
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
|
|
@ -4,3 +4,5 @@
|
|||
|
||||
[jinja2: print/templates/*.html]
|
||||
[jinja2: templates/*]
|
||||
|
||||
[babelvueextractor.extract.extract_vue: **.vue]
|
||||
|
|
|
@ -8,9 +8,9 @@ cp -a images/examples palettes symbols fonts dist/inkstitch
|
|||
cp -a icons locales print dist/inkstitch/bin
|
||||
|
||||
if [ "$BUILD" = "osx" ]; then
|
||||
cp -a electron/dist/mac dist/inkstitch/electron
|
||||
cp -a electron/build/mac dist/inkstitch/electron
|
||||
else
|
||||
cp -a electron/dist/*-unpacked dist/inkstitch/electron
|
||||
cp -a electron/build/*-unpacked dist/inkstitch/electron
|
||||
fi
|
||||
|
||||
mkdir artifacts
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
|
||||
|
||||
if [ "$BUILD" = "windows" ]; then
|
||||
cd electron
|
||||
yarn --link-duplicates --pure-lockfile && yarn run dist -w --ia32
|
||||
args="-w --ia32"
|
||||
elif [ "$BUILD" = "linux" ]; then
|
||||
args="-l --x64"
|
||||
|
||||
docker run --rm \
|
||||
-e ELECTRON_CACHE=$HOME/.cache/electron \
|
||||
-v ${PWD}/electron:/project \
|
||||
-v ~/.cache/electron:/root/.cache/electron \
|
||||
electronuserland/builder:wine \
|
||||
/bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn run dist ${args}"
|
||||
else
|
||||
cd electron
|
||||
npm install
|
||||
npm run dist
|
||||
elif [ "$BUILD" = "osx" ]; then
|
||||
args="-m"
|
||||
fi
|
||||
|
||||
cd electron
|
||||
npm install -g yarn
|
||||
yarn --link-duplicates --pure-lockfile
|
||||
yarn run dist ${args}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"comments": false,
|
||||
"env": {
|
||||
"main": {
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": { "node": 7 }
|
||||
}],
|
||||
"stage-0"
|
||||
]
|
||||
},
|
||||
"renderer": {
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false
|
||||
}],
|
||||
"stage-0"
|
||||
]
|
||||
},
|
||||
"web": {
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false
|
||||
}],
|
||||
"stage-0"
|
||||
]
|
||||
}
|
||||
},
|
||||
"plugins": ["transform-runtime"]
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
'use strict'
|
||||
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
const { say } = require('cfonts')
|
||||
const chalk = require('chalk')
|
||||
const del = require('del')
|
||||
const { spawn } = require('child_process')
|
||||
const webpack = require('webpack')
|
||||
const Multispinner = require('multispinner')
|
||||
|
||||
|
||||
const mainConfig = require('./webpack.main.config')
|
||||
const rendererConfig = require('./webpack.renderer.config')
|
||||
const webConfig = require('./webpack.web.config')
|
||||
|
||||
const doneLog = chalk.bgGreen.white(' DONE ') + ' '
|
||||
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
|
||||
const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
|
||||
const isCI = process.env.CI || false
|
||||
|
||||
if (process.env.BUILD_TARGET === 'clean') clean()
|
||||
else if (process.env.BUILD_TARGET === 'web') web()
|
||||
else build()
|
||||
|
||||
function clean () {
|
||||
del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
|
||||
console.log(`\n${doneLog}\n`)
|
||||
process.exit()
|
||||
}
|
||||
|
||||
function build () {
|
||||
greeting()
|
||||
|
||||
del.sync(['dist/electron/*', '!.gitkeep'])
|
||||
|
||||
const tasks = ['main', 'renderer']
|
||||
const m = new Multispinner(tasks, {
|
||||
preText: 'building',
|
||||
postText: 'process'
|
||||
})
|
||||
|
||||
let results = ''
|
||||
|
||||
m.on('success', () => {
|
||||
process.stdout.write('\x1B[2J\x1B[0f')
|
||||
console.log(`\n\n${results}`)
|
||||
console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
|
||||
process.exit()
|
||||
})
|
||||
|
||||
pack(mainConfig).then(result => {
|
||||
results += result + '\n\n'
|
||||
m.success('main')
|
||||
}).catch(err => {
|
||||
m.error('main')
|
||||
console.log(`\n ${errorLog}failed to build main process`)
|
||||
console.error(`\n${err}\n`)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
pack(rendererConfig).then(result => {
|
||||
results += result + '\n\n'
|
||||
m.success('renderer')
|
||||
}).catch(err => {
|
||||
m.error('renderer')
|
||||
console.log(`\n ${errorLog}failed to build renderer process`)
|
||||
console.error(`\n${err}\n`)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
function pack (config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
config.mode = 'production'
|
||||
webpack(config, (err, stats) => {
|
||||
if (err) reject(err.stack || err)
|
||||
else if (stats.hasErrors()) {
|
||||
let err = ''
|
||||
|
||||
stats.toString({
|
||||
chunks: false,
|
||||
colors: true
|
||||
})
|
||||
.split(/\r?\n/)
|
||||
.forEach(line => {
|
||||
err += ` ${line}\n`
|
||||
})
|
||||
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(stats.toString({
|
||||
chunks: false,
|
||||
colors: true
|
||||
}))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function web () {
|
||||
del.sync(['dist/web/*', '!.gitkeep'])
|
||||
webConfig.mode = 'production'
|
||||
webpack(webConfig, (err, stats) => {
|
||||
if (err || stats.hasErrors()) console.log(err)
|
||||
|
||||
console.log(stats.toString({
|
||||
chunks: false,
|
||||
colors: true
|
||||
}))
|
||||
|
||||
process.exit()
|
||||
})
|
||||
}
|
||||
|
||||
function greeting () {
|
||||
const cols = process.stdout.columns
|
||||
let text = ''
|
||||
|
||||
if (cols > 85) text = 'lets-build'
|
||||
else if (cols > 60) text = 'lets-|build'
|
||||
else text = false
|
||||
|
||||
if (text && !isCI) {
|
||||
say(text, {
|
||||
colors: ['yellow'],
|
||||
font: 'simple3d',
|
||||
space: false
|
||||
})
|
||||
} else console.log(chalk.yellow.bold('\n lets-build'))
|
||||
console.log()
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
|
||||
|
||||
hotClient.subscribe(event => {
|
||||
/**
|
||||
* Reload browser when HTMLWebpackPlugin emits a new index.html
|
||||
*
|
||||
* Currently disabled until jantimon/html-webpack-plugin#680 is resolved.
|
||||
* https://github.com/SimulatedGREG/electron-vue/issues/437
|
||||
* https://github.com/jantimon/html-webpack-plugin/issues/680
|
||||
*/
|
||||
// if (event.action === 'reload') {
|
||||
// window.location.reload()
|
||||
// }
|
||||
|
||||
/**
|
||||
* Notify `mainWindow` when `main` process is compiling,
|
||||
* giving notice for an expected reload of the `electron` process
|
||||
*/
|
||||
if (event.action === 'compiling') {
|
||||
document.body.innerHTML += `
|
||||
<style>
|
||||
#dev-client {
|
||||
background: #4fc08d;
|
||||
border-radius: 4px;
|
||||
bottom: 20px;
|
||||
box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
|
||||
color: #fff;
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
left: 20px;
|
||||
padding: 8px 12px;
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="dev-client">
|
||||
Compiling Main Process...
|
||||
</div>
|
||||
`
|
||||
}
|
||||
})
|
|
@ -0,0 +1,192 @@
|
|||
'use strict'
|
||||
|
||||
const chalk = require('chalk')
|
||||
const electron = require('electron')
|
||||
const path = require('path')
|
||||
const { say } = require('cfonts')
|
||||
const { spawn } = require('child_process')
|
||||
const webpack = require('webpack')
|
||||
const WebpackDevServer = require('webpack-dev-server')
|
||||
const webpackHotMiddleware = require('webpack-hot-middleware')
|
||||
|
||||
const mainConfig = require('./webpack.main.config')
|
||||
const rendererConfig = require('./webpack.renderer.config')
|
||||
|
||||
let electronProcess = null
|
||||
let manualRestart = false
|
||||
let hotMiddleware
|
||||
|
||||
function logStats (proc, data) {
|
||||
let log = ''
|
||||
|
||||
log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)
|
||||
log += '\n\n'
|
||||
|
||||
if (typeof data === 'object') {
|
||||
data.toString({
|
||||
colors: true,
|
||||
chunks: false
|
||||
}).split(/\r?\n/).forEach(line => {
|
||||
log += ' ' + line + '\n'
|
||||
})
|
||||
} else {
|
||||
log += ` ${data}\n`
|
||||
}
|
||||
|
||||
log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n'
|
||||
|
||||
console.log(log)
|
||||
}
|
||||
|
||||
function startRenderer () {
|
||||
return new Promise((resolve, reject) => {
|
||||
rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
|
||||
rendererConfig.mode = 'development'
|
||||
const compiler = webpack(rendererConfig)
|
||||
hotMiddleware = webpackHotMiddleware(compiler, {
|
||||
log: false,
|
||||
heartbeat: 2500
|
||||
})
|
||||
|
||||
compiler.hooks.compilation.tap('compilation', compilation => {
|
||||
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
hotMiddleware.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
compiler.hooks.done.tap('done', stats => {
|
||||
logStats('Renderer', stats)
|
||||
})
|
||||
|
||||
const server = new WebpackDevServer(
|
||||
compiler,
|
||||
{
|
||||
contentBase: path.join(__dirname, '../'),
|
||||
quiet: true,
|
||||
before (app, ctx) {
|
||||
app.use(hotMiddleware)
|
||||
ctx.middleware.waitUntilValid(() => {
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
server.listen(9080)
|
||||
})
|
||||
}
|
||||
|
||||
function startMain () {
|
||||
return new Promise((resolve, reject) => {
|
||||
mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
|
||||
mainConfig.mode = 'development'
|
||||
const compiler = webpack(mainConfig)
|
||||
|
||||
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
|
||||
logStats('Main', chalk.white.bold('compiling...'))
|
||||
hotMiddleware.publish({ action: 'compiling' })
|
||||
done()
|
||||
})
|
||||
|
||||
compiler.watch({}, (err, stats) => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
logStats('Main', stats)
|
||||
|
||||
if (electronProcess && electronProcess.kill) {
|
||||
manualRestart = true
|
||||
process.kill(electronProcess.pid)
|
||||
electronProcess = null
|
||||
startElectron()
|
||||
|
||||
setTimeout(() => {
|
||||
manualRestart = false
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function startElectron () {
|
||||
process.argv.shift();
|
||||
process.argv.shift();
|
||||
var args = [
|
||||
'--inspect=5858',
|
||||
path.join(__dirname, '../dist/electron/main.js')
|
||||
].concat(process.argv)
|
||||
|
||||
// detect yarn or npm and process commandline args accordingly
|
||||
if (process.env.npm_execpath.endsWith('yarn.js')) {
|
||||
args = args.concat(process.argv.slice(3))
|
||||
} else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
|
||||
args = args.concat(process.argv.slice(2))
|
||||
}
|
||||
|
||||
electronProcess = spawn(electron, args)
|
||||
|
||||
electronProcess.stdout.on('data', data => {
|
||||
electronLog(data, 'blue')
|
||||
})
|
||||
electronProcess.stderr.on('data', data => {
|
||||
electronLog(data, 'red')
|
||||
})
|
||||
|
||||
electronProcess.on('close', () => {
|
||||
if (!manualRestart) process.exit()
|
||||
})
|
||||
}
|
||||
|
||||
function electronLog (data, color) {
|
||||
let log = ''
|
||||
data = data.toString().split(/\r?\n/)
|
||||
data.forEach(line => {
|
||||
log += ` ${line}\n`
|
||||
})
|
||||
if (/[0-9A-z]+/.test(log)) {
|
||||
console.log(
|
||||
chalk[color].bold('┏ Electron -------------------') +
|
||||
'\n\n' +
|
||||
log +
|
||||
chalk[color].bold('┗ ----------------------------') +
|
||||
'\n'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function greeting () {
|
||||
const cols = process.stdout.columns
|
||||
let text = ''
|
||||
|
||||
if (cols > 104) text = 'electron-vue'
|
||||
else if (cols > 76) text = 'electron-|vue'
|
||||
else text = false
|
||||
|
||||
if (text) {
|
||||
say(text, {
|
||||
colors: ['yellow'],
|
||||
font: 'simple3d',
|
||||
space: false
|
||||
})
|
||||
} else console.log(chalk.yellow.bold('\n electron-vue'))
|
||||
console.log(chalk.blue(' getting ready...') + '\n')
|
||||
}
|
||||
|
||||
function init () {
|
||||
greeting()
|
||||
|
||||
Promise.all([startRenderer(), startMain()])
|
||||
.then(() => {
|
||||
startElectron()
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
init()
|
|
@ -0,0 +1,72 @@
|
|||
'use strict'
|
||||
|
||||
process.env.BABEL_ENV = 'main'
|
||||
|
||||
const path = require('path')
|
||||
const { dependencies } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const BabiliWebpackPlugin = require('babili-webpack-plugin')
|
||||
|
||||
let mainConfig = {
|
||||
entry: {
|
||||
main: path.join(__dirname, '../src/main/index.js')
|
||||
},
|
||||
externals: [
|
||||
...Object.keys(dependencies || {})
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
node: {
|
||||
__dirname: process.env.NODE_ENV !== 'production',
|
||||
__filename: process.env.NODE_ENV !== 'production'
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../dist/electron')
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.js', '.json', '.node']
|
||||
},
|
||||
target: 'electron-main'
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust mainConfig for development settings
|
||||
*/
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
mainConfig.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust mainConfig for production settings
|
||||
*/
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
mainConfig.plugins.push(
|
||||
new BabiliWebpackPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = mainConfig
|
|
@ -0,0 +1,173 @@
|
|||
'use strict'
|
||||
|
||||
process.env.BABEL_ENV = 'renderer'
|
||||
|
||||
const path = require('path')
|
||||
const { dependencies } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const BabiliWebpackPlugin = require('babili-webpack-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
|
||||
/**
|
||||
* List of node_modules to include in webpack bundle
|
||||
*
|
||||
* Required for specific packages like Vue UI libraries
|
||||
* that provide pure *.vue files that need compiling
|
||||
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
|
||||
*/
|
||||
// 'vue-slider-component' needed here due to this issue:
|
||||
// https://github.com/SimulatedGREG/electron-vue/issues/725
|
||||
let whiteListedModules = ['vue', 'vue-slider-component']
|
||||
|
||||
let rendererConfig = {
|
||||
devtool: '#cheap-module-eval-source-map',
|
||||
entry: {
|
||||
renderer: path.join(__dirname, '../src/renderer/main.js')
|
||||
},
|
||||
externals: [
|
||||
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: ['vue-style-loader', 'css-loader', 'less-loader']
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['vue-style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: 'vue-html-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: 'node-loader'
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
use: {
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
extractCSS: process.env.NODE_ENV === 'production',
|
||||
loaders: {
|
||||
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
|
||||
scss: 'vue-style-loader!css-loader!sass-loader',
|
||||
less: 'vue-style-loader!css-loader!less-loader'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'media/[name]--[folder].[ext]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name]--[folder].[ext]'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
node: {
|
||||
__dirname: process.env.NODE_ENV !== 'production',
|
||||
__filename: process.env.NODE_ENV !== 'production'
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new MiniCssExtractPlugin({filename: 'styles.css'}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: path.resolve(__dirname, '../src/index.ejs'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true
|
||||
},
|
||||
nodeModules: process.env.NODE_ENV !== 'production'
|
||||
? path.resolve(__dirname, '../node_modules')
|
||||
: false
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
],
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../dist/electron')
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.join(__dirname, '../src/renderer'),
|
||||
'vue$': 'vue/dist/vue.esm.js'
|
||||
},
|
||||
extensions: ['.js', '.vue', '.json', '.css', '.node']
|
||||
},
|
||||
target: 'electron-renderer'
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust rendererConfig for development settings
|
||||
*/
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
rendererConfig.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust rendererConfig for production settings
|
||||
*/
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
rendererConfig.devtool = ''
|
||||
|
||||
rendererConfig.plugins.push(
|
||||
new BabiliWebpackPlugin(),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.join(__dirname, '../static'),
|
||||
to: path.join(__dirname, '../dist/electron/static'),
|
||||
ignore: ['.*']
|
||||
}
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = rendererConfig
|
|
@ -0,0 +1,132 @@
|
|||
'use strict'
|
||||
|
||||
process.env.BABEL_ENV = 'web'
|
||||
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const BabiliWebpackPlugin = require('babili-webpack-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
|
||||
let webConfig = {
|
||||
devtool: '#cheap-module-eval-source-map',
|
||||
entry: {
|
||||
web: path.join(__dirname, '../src/renderer/main.js')
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: ['vue-style-loader', 'css-loader', 'less-loader']
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['vue-style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: 'vue-html-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
include: [ path.resolve(__dirname, '../src/renderer') ],
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
use: {
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
extractCSS: true,
|
||||
loaders: {
|
||||
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
|
||||
scss: 'vue-style-loader!css-loader!sass-loader',
|
||||
less: 'vue-style-loader!css-loader!less-loader'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name].[ext]'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new MiniCssExtractPlugin({filename: 'styles.css'}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: path.resolve(__dirname, '../src/index.ejs'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true
|
||||
},
|
||||
nodeModules: false
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.IS_WEB': 'true'
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
],
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.join(__dirname, '../dist/web')
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.join(__dirname, '../src/renderer'),
|
||||
'vue$': 'vue/dist/vue.esm.js'
|
||||
},
|
||||
extensions: ['.js', '.vue', '.json', '.css']
|
||||
},
|
||||
target: 'web'
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust webConfig for production settings
|
||||
*/
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
webConfig.devtool = ''
|
||||
|
||||
webConfig.plugins.push(
|
||||
new BabiliWebpackPlugin(),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.join(__dirname, '../static'),
|
||||
to: path.join(__dirname, '../dist/web/static'),
|
||||
ignore: ['.*']
|
||||
}
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = webConfig
|
|
@ -0,0 +1,26 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true
|
||||
},
|
||||
extends: 'standard',
|
||||
globals: {
|
||||
__static: true
|
||||
},
|
||||
plugins: [
|
||||
'html'
|
||||
],
|
||||
'rules': {
|
||||
// allow paren-less arrow functions
|
||||
'arrow-parens': 0,
|
||||
// allow async-await
|
||||
'generator-star-spacing': 0,
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
|
||||
}
|
||||
}
|
|
@ -1,2 +1,10 @@
|
|||
node_modules
|
||||
out
|
||||
.DS_Store
|
||||
dist/electron/*
|
||||
dist/web/*
|
||||
build/*
|
||||
!build/icons
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
npm-debug.log.*
|
||||
thumbs.db
|
||||
!.gitkeep
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
Electron UI
|
||||
===========
|
||||
|
||||
This is the home of the future Electron-based UI. Currently only the Print extension uses Electron.
|
||||
|
||||
For local development, run `yarn` in this directory to install dependencies and build Electron.
|
Przed Szerokość: | Wysokość: | Rozmiar: 39 KiB Po Szerokość: | Wysokość: | Rozmiar: 39 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 8.0 KiB Po Szerokość: | Wysokość: | Rozmiar: 8.0 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 938 B Po Szerokość: | Wysokość: | Rozmiar: 938 B |
Przed Szerokość: | Wysokość: | Rozmiar: 1.5 KiB Po Szerokość: | Wysokość: | Rozmiar: 1.5 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 19 KiB Po Szerokość: | Wysokość: | Rozmiar: 19 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 2.0 KiB Po Szerokość: | Wysokość: | Rozmiar: 2.0 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 3.0 KiB Po Szerokość: | Wysokość: | Rozmiar: 3.0 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 38 KiB Po Szerokość: | Wysokość: | Rozmiar: 38 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 4.0 KiB Po Szerokość: | Wysokość: | Rozmiar: 4.0 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 6.0 KiB Po Szerokość: | Wysokość: | Rozmiar: 6.0 KiB |
Przed Szerokość: | Wysokość: | Rozmiar: 39 KiB Po Szerokość: | Wysokość: | Rozmiar: 39 KiB |
|
@ -3,23 +3,31 @@
|
|||
"productName": "inkstitch-gui",
|
||||
"version": "1.0.0",
|
||||
"description": "Ink/Stitch GUI",
|
||||
"main": "src/main.js",
|
||||
"main": "./dist/electron/main.js",
|
||||
"scripts": {
|
||||
"pack": "electron-builder --dir",
|
||||
"dist": "electron-builder",
|
||||
"dev": "electron ."
|
||||
"dist": "node .electron-vue/build.js && electron-builder",
|
||||
"dev": "node .electron-vue/dev-runner.js"
|
||||
},
|
||||
"build": {
|
||||
"productName": "inkstitch-gui",
|
||||
"appId": "org.inkstitch.gui",
|
||||
"directories": {
|
||||
"output": "build"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"linux": {
|
||||
"icon": "src/assets/icons/png/512x512.png",
|
||||
"icon": "build/icons",
|
||||
"target": "dir"
|
||||
},
|
||||
"win": {
|
||||
"icon": "src/assets/icons/win/inkstitch.ico",
|
||||
"icon": "build/icons/win/inkstitch.ico",
|
||||
"target": "dir"
|
||||
},
|
||||
"mac": {
|
||||
"icon": "src/assets/icons/mac/inkstitch.icns",
|
||||
"icon": "build/icons/mac/inkstitch.icns",
|
||||
"target": "dir"
|
||||
}
|
||||
},
|
||||
|
@ -27,13 +35,73 @@
|
|||
"author": "lex",
|
||||
"license": "GPL-3.0+",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.22",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.10.2",
|
||||
"@fortawesome/vue-fontawesome": "^0.1.6",
|
||||
"axios": "^0.19.0",
|
||||
"electron-compile": "^6.4.4",
|
||||
"tmp": "0.1.0"
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"mousetrap": "^1.6.3",
|
||||
"query-string": "^6.8.2",
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
"@svgdotjs/svg.panzoom.js": "https://github.com/inkstitch/svg.panzoom.js",
|
||||
"@svgdotjs/svg.filter.js": "^3.0.7",
|
||||
"svgpath": "^2.2.3",
|
||||
"tmp": "0.1.0",
|
||||
"vue": "^2.5.16",
|
||||
"vue-gettext": "^2.1.5",
|
||||
"vue-loading-overlay": "^3.2.0",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-slider-component": "^3.0.38",
|
||||
"vue2-transitions": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ajv": "^6.5.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-register": "^6.26.0",
|
||||
"babili-webpack-plugin": "^0.1.2",
|
||||
"cfonts": "^2.1.2",
|
||||
"chalk": "^2.4.1",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"cross-env": "^5.1.6",
|
||||
"css-loader": "^0.28.11",
|
||||
"del": "^3.0.0",
|
||||
"devtron": "^1.4.0",
|
||||
"easygettext": "^2.7.0",
|
||||
"electron": "4.1.3",
|
||||
"electron-builder": "^20.39.0",
|
||||
"electron-packager": "13.1.1",
|
||||
"electron-prebuilt-compile": "4.0.0"
|
||||
"electron-debug": "^1.5.0",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-prebuilt-compile": "4.0.0",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-standard": "^11.0.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-html": "^4.0.3",
|
||||
"eslint-plugin-import": "^2.12.0",
|
||||
"eslint-plugin-node": "^6.0.1",
|
||||
"eslint-plugin-promise": "^3.8.0",
|
||||
"eslint-plugin-standard": "^3.1.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "0.4.0",
|
||||
"multispinner": "^0.2.1",
|
||||
"node-loader": "^0.6.0",
|
||||
"style-loader": "^0.21.0",
|
||||
"url-loader": "^1.0.1",
|
||||
"vue-html-loader": "^1.2.4",
|
||||
"vue-loader": "^15.2.4",
|
||||
"vue-style-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"webpack": "^4.15.1",
|
||||
"webpack-cli": "^3.0.8",
|
||||
"webpack-dev-server": "^3.1.4",
|
||||
"webpack-hot-middleware": "^2.22.2",
|
||||
"webpack-merge": "^4.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>inkstitch-gui</title>
|
||||
<% if (htmlWebpackPlugin.options.nodeModules) { %>
|
||||
<!-- Add `node_modules/` to global paths so `require` works properly in development -->
|
||||
<script>
|
||||
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
|
||||
</script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- Set `__static` path to static files in production -->
|
||||
<% if (!process.browser) { %>
|
||||
<script>
|
||||
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
</script>
|
||||
<% } %>
|
||||
|
||||
<!-- webpack builds are automatically injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
const axios = require('axios')
|
||||
const queryString = require('query-string')
|
||||
|
||||
var port = queryString.parse(global.location.search).port
|
||||
|
||||
module.exports = axios.create({
|
||||
baseURL: `http://127.0.0.1:${port}/`
|
||||
})
|
|
@ -0,0 +1,9 @@
|
|||
module.exports.selectLanguage = function () {
|
||||
['LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'].forEach(language => {
|
||||
if (process.env[language]) {
|
||||
return process.env[language].split(":")[0]
|
||||
}
|
||||
})
|
||||
|
||||
return "en_US"
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron';
|
||||
var fs = require('fs');
|
||||
var path = require('path')
|
||||
var tmp = require('tmp')
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow;
|
||||
|
||||
const createWindow = () => {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({icon: path.join(__dirname, 'assets/icons/png/512x512.png')});
|
||||
|
||||
mainWindow.maximize();
|
||||
|
||||
// and load the index.html of the app.
|
||||
if (process.argv[1] == ".") {
|
||||
// run in development mode with `electron . <url>`
|
||||
var url = process.argv[2];
|
||||
} else {
|
||||
var url = process.argv[1];
|
||||
}
|
||||
mainWindow.loadURL(url);
|
||||
|
||||
//mainWindow.webContents.openDevTools();
|
||||
|
||||
// Emitted when the window is closed.
|
||||
mainWindow.on('closed', () => {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
mainWindow = null;
|
||||
});
|
||||
};
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', createWindow);
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
ipcMain.on('save-pdf', function (event, pageSize) {
|
||||
mainWindow.webContents.printToPDF({"pageSize": pageSize}, function(error, data) {
|
||||
dialog.showSaveDialog(mainWindow, {"defaultPath": "inkstitch.pdf"}, function(filename, bookmark) {
|
||||
if (typeof filename !== 'undefined')
|
||||
fs.writeFileSync(filename, data, 'utf-8');
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.on('open-pdf', function (event, pageSize) {
|
||||
mainWindow.webContents.printToPDF({"pageSize": pageSize}, function(error, data) {
|
||||
tmp.file({keep: true, discardDescriptor: true}, function(err, path, fd, cleanupCallback) {
|
||||
fs.writeFileSync(path, data, 'utf-8');
|
||||
shell.openItem(path);
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* This file is used specifically and only for development. It installs
|
||||
* `electron-debug` & `vue-devtools`. There shouldn't be any need to
|
||||
* modify this file, but it can be used to extend your development
|
||||
* environment.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
// Install `electron-debug` with `devtron`
|
||||
require('electron-debug')({ showDevTools: true })
|
||||
|
||||
// Install `vue-devtools`
|
||||
require('electron').app.on('ready', () => {
|
||||
let installExtension = require('electron-devtools-installer')
|
||||
installExtension.default(installExtension.VUEJS_DEVTOOLS)
|
||||
.then(() => {})
|
||||
.catch(err => {
|
||||
console.log('Unable to install `vue-devtools`: \n', err)
|
||||
})
|
||||
})
|
||||
|
||||
// Require `main` process to boot app
|
||||
require('./index')
|
|
@ -0,0 +1,90 @@
|
|||
'use strict'
|
||||
|
||||
import {app, BrowserWindow} from 'electron'
|
||||
|
||||
const url = require('url')
|
||||
|
||||
/**
|
||||
* Set `__static` path to static files in production
|
||||
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
|
||||
*/
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// we were run as electron --inspect=5858 path/to/main.js <args>
|
||||
// so get rid of the first two args
|
||||
console.log("args " + process.argv)
|
||||
process.argv.shift()
|
||||
process.argv.shift()
|
||||
} else {
|
||||
global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
}
|
||||
|
||||
let mainWindow
|
||||
|
||||
var target = process.argv[1] || "";
|
||||
var targetURL = url.parse(target)
|
||||
var winURL = null;
|
||||
|
||||
// Print PDF will give us a full URL to a flask server, bypassing Vue entirely.
|
||||
// Eventually this will be migrated to Vue.
|
||||
if (targetURL.protocol) {
|
||||
winURL = target
|
||||
} else {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winURL = `http://localhost:9080/?${targetURL.query || ""}#${targetURL.pathname || ""}`
|
||||
} else {
|
||||
winURL = `file://${__dirname}/index.html?${targetURL.query || ""}#${targetURL.pathname || ""}`;
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
mainWindow = new BrowserWindow({
|
||||
height: 563,
|
||||
useContentSize: true,
|
||||
width: 1000,
|
||||
webPreferences: {nodeIntegration: true}
|
||||
})
|
||||
|
||||
mainWindow.loadURL(winURL)
|
||||
mainWindow.maximize()
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
app.on('ready', createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (mainWindow === null) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Auto Updater
|
||||
*
|
||||
* Uncomment the following code below and install `electron-updater` to
|
||||
* support auto updating. Code Signing with a valid certificate is required.
|
||||
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
|
||||
*/
|
||||
|
||||
/*
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
autoUpdater.quitAndInstall()
|
||||
})
|
||||
|
||||
app.on('ready', () => {
|
||||
if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates()
|
||||
})
|
||||
*/
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'my-project'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* CSS */
|
||||
</style>
|
|
@ -0,0 +1,583 @@
|
|||
const inkStitch = require("../../../lib/api")
|
||||
const Mousetrap = require("mousetrap")
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
require('@svgdotjs/svg.panzoom.js/src/svg.panzoom.js')
|
||||
require('@svgdotjs/svg.filter.js')
|
||||
const svgpath = require('svgpath')
|
||||
import Loading from 'vue-loading-overlay';
|
||||
import 'vue-loading-overlay/dist/vue-loading.css';
|
||||
import VueSlider from 'vue-slider-component'
|
||||
import 'vue-slider-component/theme/default.css'
|
||||
|
||||
const throttle = require('lodash.throttle')
|
||||
|
||||
function SliderMark(command, icon) {
|
||||
this.label = ""
|
||||
this.command = command
|
||||
this.icon = icon
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'simulator',
|
||||
components: {
|
||||
Loading,
|
||||
VueSlider
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
loading: false,
|
||||
controlsExpanded: true,
|
||||
infoExpanded: false,
|
||||
infoMaxHeight: 0,
|
||||
speed: 16,
|
||||
currentStitch: 1,
|
||||
currentStitchDisplay: 1,
|
||||
direction: 1,
|
||||
numStitches: 1,
|
||||
animating: false,
|
||||
sliderProcess: dotPos => this.sliderColorSections,
|
||||
showTrims: false,
|
||||
showJumps: false,
|
||||
showColorChanges: false,
|
||||
showStops: false,
|
||||
showNeedlePenetrationPoints: false,
|
||||
showRealisticPreview: false,
|
||||
showCursor: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentStitch: throttle(function () {
|
||||
this.currentStitchDisplay = Math.floor(this.currentStitch)
|
||||
}, 100, {leading: true, trailing: true}),
|
||||
showNeedlePenetrationPoints: function () {
|
||||
if (this.needlePenetrationPoints === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.needlePenetrationPoints.forEach(npp => {
|
||||
if (this.showNeedlePenetrationPoints) {
|
||||
npp.show()
|
||||
} else {
|
||||
npp.hide()
|
||||
}
|
||||
})
|
||||
},
|
||||
showRealisticPreview() {
|
||||
let animating = this.animating
|
||||
this.stop()
|
||||
|
||||
if (this.showRealisticPreview) {
|
||||
if (this.realisticPreview === null) {
|
||||
// This workflow should be improved and might be a bit unconventional.
|
||||
// We don't want to make the user wait for it too long.
|
||||
// It would be best, if the realistic preview could load before it is actually requested.
|
||||
this.$nextTick(() => {this.loading=true})
|
||||
setImmediate(()=> {this.generateRealisticPaths()})
|
||||
setImmediate(()=> {this.loading = false})
|
||||
}
|
||||
|
||||
setImmediate(()=> {
|
||||
for (let i = 1; i < this.stitches.length; i++) {
|
||||
if (i < this.currentStitch) {
|
||||
this.realisticPaths[i].show()
|
||||
} else {
|
||||
this.realisticPaths[i].hide()
|
||||
}
|
||||
}
|
||||
|
||||
this.simulation.hide()
|
||||
this.realisticPreview.show()
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
for (let i = 1; i < this.stitches.length; i++) {
|
||||
if (i < this.currentStitch) {
|
||||
this.stitchPaths[i].show()
|
||||
} else {
|
||||
this.stitchPaths[i].hide()
|
||||
}
|
||||
}
|
||||
|
||||
this.simulation.show()
|
||||
this.realisticPreview.hide()
|
||||
|
||||
}
|
||||
if (animating) {
|
||||
this.start()
|
||||
}
|
||||
},
|
||||
showCursor: function () {
|
||||
if (this.showCursor) {
|
||||
this.cursor.show()
|
||||
} else {
|
||||
this.cursor.hide()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
speedDisplay() {
|
||||
return this.speed * this.direction
|
||||
},
|
||||
currentCommand() {
|
||||
let stitch = this.stitches[Math.floor(this.currentStitch)]
|
||||
|
||||
if (stitch === undefined || stitch === null) {
|
||||
return ""
|
||||
}
|
||||
|
||||
let label = "STITCH"
|
||||
switch (true) {
|
||||
case stitch.jump:
|
||||
label = this.$gettext("JUMP")
|
||||
break
|
||||
case stitch.trim:
|
||||
label = this.$gettext("TRIM")
|
||||
break
|
||||
case stitch.stop:
|
||||
label = this.$gettext("STOP")
|
||||
break
|
||||
case stitch.color_change:
|
||||
label = this.$gettext("COLOR CHANGE")
|
||||
break
|
||||
}
|
||||
|
||||
return label
|
||||
},
|
||||
paused() {
|
||||
return !this.animating
|
||||
},
|
||||
forward() {
|
||||
return this.direction > 0
|
||||
},
|
||||
reverse() {
|
||||
return this.direction < 0
|
||||
},
|
||||
sliderMarks() {
|
||||
var marks = {}
|
||||
|
||||
if (this.showTrims)
|
||||
Object.assign(marks, this.trimMarks);
|
||||
|
||||
if (this.showJumps)
|
||||
Object.assign(marks, this.jumpMarks);
|
||||
|
||||
if (this.showColorChanges)
|
||||
Object.assign(marks, this.colorChangeMarks);
|
||||
|
||||
if (this.showStops)
|
||||
Object.assign(marks, this.stopMarks);
|
||||
|
||||
return marks
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleInfo() {
|
||||
this.infoExpanded = !this.infoExpanded;
|
||||
this.infoMaxHeight = this.$refs.controlInfoButton.getBoundingClientRect().top;
|
||||
},
|
||||
toggleControls() {
|
||||
this.controlsExpanded = !this.controlsExpanded;
|
||||
},
|
||||
animationSpeedUp() {
|
||||
this.speed *= 2.0
|
||||
},
|
||||
animationSlowDown() {
|
||||
this.speed = Math.max(this.speed / 2.0, 1)
|
||||
},
|
||||
animationReverse() {
|
||||
this.direction = -1
|
||||
this.start()
|
||||
},
|
||||
animationForward() {
|
||||
this.direction = 1
|
||||
this.start()
|
||||
},
|
||||
toggleAnimation(e) {
|
||||
if (this.animating) {
|
||||
this.stop()
|
||||
} else {
|
||||
this.start()
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
},
|
||||
animationForwardOneStitch() {
|
||||
this.setCurrentStitch(this.currentStitch + 1)
|
||||
},
|
||||
animationBackwardOneStitch() {
|
||||
this.setCurrentStitch(this.currentStitch - 1)
|
||||
},
|
||||
animationNextCommand() {
|
||||
let nextCommandIndex = this.getNextCommandIndex()
|
||||
if (nextCommandIndex === -1) {
|
||||
this.setCurrentStitch(this.stitches.length)
|
||||
} else {
|
||||
this.setCurrentStitch(this.commandList[nextCommandIndex])
|
||||
}
|
||||
},
|
||||
animationPreviousCommand() {
|
||||
let nextCommandIndex = this.getNextCommandIndex()
|
||||
let prevCommandIndex = 0
|
||||
if (nextCommandIndex === -1) {
|
||||
prevCommandIndex = this.commandList.length - 2
|
||||
} else {
|
||||
prevCommandIndex = nextCommandIndex - 2
|
||||
}
|
||||
let previousCommand = this.commandList[prevCommandIndex]
|
||||
if (previousCommand === undefined) {
|
||||
previousCommand = 1
|
||||
}
|
||||
this.setCurrentStitch(previousCommand)
|
||||
},
|
||||
getNextCommandIndex() {
|
||||
let currentStitch = this.currentStitchDisplay
|
||||
let nextCommand = this.commandList.findIndex(function (command) {
|
||||
return command > currentStitch
|
||||
})
|
||||
return nextCommand
|
||||
},
|
||||
onCurrentStitchEntered() {
|
||||
let newCurrentStitch = parseInt(this.$refs.currentStitchInput.value)
|
||||
|
||||
if (isNaN(newCurrentStitch)) {
|
||||
this.$refs.currentStitchInput.value = Math.floor(this.currentStitch)
|
||||
} else {
|
||||
this.setCurrentStitch(parseInt(newCurrentStitch))
|
||||
}
|
||||
},
|
||||
setCurrentStitch(newCurrentStitch) {
|
||||
this.stop()
|
||||
this.currentStitch = newCurrentStitch
|
||||
this.clampCurrentStitch()
|
||||
this.renderFrame()
|
||||
},
|
||||
clampCurrentStitch() {
|
||||
this.currentStitch = Math.max(Math.min(this.currentStitch, this.numStitches), 0)
|
||||
},
|
||||
animate() {
|
||||
let frameStart = performance.now()
|
||||
let frameTime = null
|
||||
|
||||
if (this.lastFrameStart !== null) {
|
||||
frameTime = frameStart - this.lastFrameStart
|
||||
} else {
|
||||
frameTime = this.targetFramePeriod
|
||||
}
|
||||
|
||||
this.lastFrameStart = frameStart
|
||||
|
||||
let numStitches = this.speed * Math.max(frameTime, this.targetFramePeriod) / 1000.0;
|
||||
this.currentStitch = this.currentStitch + numStitches * this.direction
|
||||
this.clampCurrentStitch()
|
||||
|
||||
this.renderFrame()
|
||||
|
||||
if (this.animating && this.shouldAnimate()) {
|
||||
this.timer = setTimeout(this.animate, Math.max(0, this.targetFramePeriod - frameTime))
|
||||
} else {
|
||||
this.timer = null;
|
||||
this.stop()
|
||||
}
|
||||
},
|
||||
renderFrame() {
|
||||
while (this.renderedStitch < this.currentStitch) {
|
||||
this.renderedStitch += 1
|
||||
if (this.showRealisticPreview) {
|
||||
this.realisticPaths[this.renderedStitch].show()
|
||||
} else {
|
||||
this.stitchPaths[this.renderedStitch].show();
|
||||
}
|
||||
}
|
||||
|
||||
while (this.renderedStitch > this.currentStitch) {
|
||||
if (this.showRealisticPreview) {
|
||||
this.realisticPaths[this.renderedStitch].hide()
|
||||
} else {
|
||||
this.stitchPaths[this.renderedStitch].hide();
|
||||
}
|
||||
this.renderedStitch -= 1
|
||||
}
|
||||
|
||||
this.moveCursor()
|
||||
},
|
||||
shouldAnimate() {
|
||||
if (this.direction == 1 && this.currentStitch < this.numStitches) {
|
||||
return true;
|
||||
} else if (this.direction == -1 && this.currentStitch > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
start() {
|
||||
if (!this.animating && this.shouldAnimate()) {
|
||||
this.animating = true
|
||||
this.timer = setTimeout(this.animate, 0);
|
||||
}
|
||||
},
|
||||
stop() {
|
||||
if (this.animating) {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.animating = false
|
||||
this.lastFrameStart = null
|
||||
}
|
||||
},
|
||||
resizeCursor() {
|
||||
// This makes the cursor stay the same size when zooming in or out.
|
||||
// I'm not exactly sure how it works, but it does.
|
||||
this.cursor.size(25 / this.svg.zoom())
|
||||
this.cursor.stroke({width: 2 / this.svg.zoom()})
|
||||
|
||||
// SVG.js seems to move the cursor when we resize it, so we need to put
|
||||
// it back where it goes.
|
||||
this.moveCursor()
|
||||
|
||||
this.adjustScale()
|
||||
},
|
||||
moveCursor() {
|
||||
let stitch = this.stitches[Math.floor(this.currentStitch)]
|
||||
if (stitch === null || stitch === undefined) {
|
||||
this.cursor.hide()
|
||||
} else if (this.showCursor) {
|
||||
this.cursor.show()
|
||||
this.cursor.center(stitch.x, stitch.y)
|
||||
}
|
||||
},
|
||||
adjustScale: throttle(function () {
|
||||
let one_mm = 96 / 25.4 * this.svg.zoom();
|
||||
let scaleWidth = one_mm
|
||||
let simulatorWidth = this.$refs.simulator.getBoundingClientRect().width
|
||||
let maxWidth = Math.min(simulatorWidth / 2, 300)
|
||||
|
||||
while (scaleWidth > maxWidth) {
|
||||
scaleWidth /= 2.0
|
||||
}
|
||||
|
||||
while (scaleWidth < 100) {
|
||||
scaleWidth += one_mm
|
||||
}
|
||||
|
||||
let scaleMM = scaleWidth / one_mm
|
||||
|
||||
this.scale.plot(`M0,0 v10 h${scaleWidth / 2} v-5 v5 h${scaleWidth / 2} v-10`)
|
||||
|
||||
// round and strip trailing zeros, source: https://stackoverflow.com/a/53397618
|
||||
let mm = scaleMM.toFixed(8).replace(/([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1')
|
||||
this.scaleLabel.text(`${mm} mm`)
|
||||
}, 100, {leading: true, trailing: true}
|
||||
),
|
||||
generateMarks() {
|
||||
this.commandList = Array()
|
||||
for (let i = 1; i < this.stitches.length; i++) {
|
||||
if (this.stitches[i].trim) {
|
||||
this.trimMarks[i] = new SliderMark("trim", "cut")
|
||||
this.commandList.push(i)
|
||||
} else if (this.stitches[i].stop) {
|
||||
this.stopMarks[i] = new SliderMark("stop", "pause")
|
||||
this.commandList.push(i)
|
||||
} else if (this.stitches[i].jump) {
|
||||
this.jumpMarks[i] = new SliderMark("jump", "frog")
|
||||
this.commandList.push(i)
|
||||
} else if (this.stitches[i].color_change) {
|
||||
this.colorChangeMarks[i] = new SliderMark("color-change", "exchange-alt")
|
||||
this.commandList.push(i)
|
||||
}
|
||||
}
|
||||
},
|
||||
generateColorSections() {
|
||||
var currentStitch = 0
|
||||
this.stitchPlan.color_blocks.forEach(color_block => {
|
||||
this.sliderColorSections.push([
|
||||
(currentStitch + 1) / this.numStitches * 100,
|
||||
(currentStitch + color_block.stitches.length) / this.numStitches * 100,
|
||||
{backgroundColor: color_block.color.visible_on_white.hex}
|
||||
])
|
||||
currentStitch += color_block.stitches.length
|
||||
})
|
||||
},
|
||||
generateMarker(color) {
|
||||
return this.svg.marker(3, 3, add => {
|
||||
let needlePenetrationPoint = add.circle(3).fill(color).hide()
|
||||
this.needlePenetrationPoints.push(needlePenetrationPoint)
|
||||
})
|
||||
},
|
||||
generateScale() {
|
||||
let svg = SVG().addTo(this.$refs.simulator)
|
||||
svg.node.classList.add("simulation-scale")
|
||||
this.scale = svg.path("M0,0").stroke({color: "black", width: "1px"}).fill("none")
|
||||
this.scaleLabel = svg.text("0 mm").move(0, 12)
|
||||
this.scaleLabel.node.classList.add("simulation-scale-label")
|
||||
},
|
||||
generateCursor() {
|
||||
this.cursor =
|
||||
this.svg.path("M0,0 v2.8 h1.2 v-2.8 h2.8 v-1.2 h-2.8 v-2.8 h-1.2 v2.8 h-2.8 v1.2 h2.8")
|
||||
.stroke({
|
||||
width: 0.1,
|
||||
color: '#FFFFFF',
|
||||
})
|
||||
.fill('#000000')
|
||||
this.cursor.node.classList.add("cursor")
|
||||
},
|
||||
generateRealisticPaths() {
|
||||
|
||||
// Create Realistic Filter
|
||||
this.filter = this.svg.defs().filter()
|
||||
|
||||
this.filter.attr({id: "realistic-stitch-filter", x: "-0.1", y: "-0.1", height: "1.2", width: "1.2", style: "color-interpolation-filters:sRGB"})
|
||||
this.filter.gaussianBlur({id: "gaussianBlur1", stdDeviation: "1.5", in: "SourceAlpha"})
|
||||
this.filter.componentTransfer(function (add) {
|
||||
add.funcR({ type: "identity" }),
|
||||
add.funcG({ type: "identity" }),
|
||||
add.funcB({ type: "identity", slope: "4.53" }),
|
||||
add.funcA({ type: "gamma", slope: "0.149", intercept: "0", amplitude: "3.13", offset: "-0.33" })
|
||||
}).attr({id: "componentTransfer1", in: "gaussianBlur1"})
|
||||
this.filter.composite({id: "composite1", in: "componentTransfer1", in2: "SourceAlpha", operator: "in"})
|
||||
this.filter.gaussianBlur({id: "gaussianBlur2", in: "composite1", stdDeviation: 0.09})
|
||||
this.filter.morphology({id: "morphology1", in: "gaussianBlur2", operator: "dilate", radius: 0.1})
|
||||
this.filter.specularLighting({id: "specularLighting1", in: "morphology1", specularConstant: 0.709, surfaceScale: 30}).pointLight({z: 10})
|
||||
this.filter.gaussianBlur({id: "gaussianBlur3", in: "specularLighting1", stdDeviation: 0.04})
|
||||
this.filter.composite({id: "composite2", in: "gaussianBlur3", in2: "SourceGraphic", operator: "arithmetic", k2: 1, k3: 1, k1: 0, k4: 0})
|
||||
this.filter.composite({in: "composite2", in2: "SourceAlpha", operator: "in"})
|
||||
|
||||
// Create realistic paths in it's own group and move it behind the cursor
|
||||
this.realisticPreview = this.svg.group({id: 'realistic'}).backward()
|
||||
|
||||
this.stitchPlan.color_blocks.forEach(color_block => {
|
||||
let color = `${color_block.color.visible_on_white.hex}`
|
||||
let realistic_path_attrs = {fill: color, stroke: "none", filter: this.filter}
|
||||
|
||||
let stitching = false
|
||||
let prevStitch = null
|
||||
color_block.stitches.forEach(stitch => {
|
||||
|
||||
let realisticPath = null
|
||||
if (stitching && prevStitch) {
|
||||
|
||||
// Position
|
||||
let stitch_center = []
|
||||
stitch_center.x = (prevStitch.x + stitch.x) / 2.0
|
||||
stitch_center.y = (prevStitch.y + stitch.y) / 2.0
|
||||
|
||||
// Angle
|
||||
var stitch_angle = Math.atan2(stitch.y - prevStitch.y, stitch.x - prevStitch.x) * (180 / Math.PI)
|
||||
|
||||
// Length
|
||||
let path_length = Math.hypot(stitch.x - prevStitch.x, stitch.y - prevStitch.y)
|
||||
|
||||
var path = `M0,0 c 0.4,0,0.4,0.3,0.4,0.6 c 0,0.3,-0.1,0.6,-0.4,0.6 v 0.2,-0.2 h -${path_length} c -0.4,0,-0.4,-0.3,-0.4,-0.6 c 0,-0.3,0.1,-0.6,0.4,-0.6 v -0.2,0.2 z`
|
||||
path = svgpath(path).rotate(stitch_angle).toString()
|
||||
|
||||
realisticPath = this.realisticPreview.path(path).attr(realistic_path_attrs).center(stitch_center.x, stitch_center.y).hide()
|
||||
|
||||
} else {
|
||||
realisticPath = this.realisticPreview.rect(0, 1).attr(realistic_path_attrs).center(stitch.x, stitch.y).hide()
|
||||
}
|
||||
|
||||
this.realisticPaths.push(realisticPath)
|
||||
|
||||
if (stitch.trim || stitch.color_change) {
|
||||
stitching = false
|
||||
} else if (!stitch.jump) {
|
||||
stitching = true
|
||||
}
|
||||
prevStitch = stitch
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
// non-reactive properties
|
||||
this.targetFPS = 30
|
||||
this.targetFramePeriod = 1000.0 / this.targetFPS
|
||||
this.renderedStitch = 0
|
||||
this.lastFrameStart = null
|
||||
this.stitchPaths = [null] // 1-indexed to match up with stitch number display
|
||||
this.realisticPaths = [null]
|
||||
this.stitches = [null]
|
||||
this.svg = null
|
||||
this.simulation = null
|
||||
this.realisticPreview = null
|
||||
this.timer = null
|
||||
this.sliderColorSections = []
|
||||
this.trimMarks = {}
|
||||
this.stopMarks = {}
|
||||
this.colorChangeMarks = {}
|
||||
this.jumpMarks = {}
|
||||
this.needlePenetrationPoints = []
|
||||
this.cursor = null
|
||||
},
|
||||
mounted: function () {
|
||||
this.svg = SVG().addTo(this.$refs.simulator).size('100%', '100%').panZoom({zoomMin: 0.1})
|
||||
this.svg.node.classList.add('simulation')
|
||||
this.simulation = this.svg.group({id: 'line'})
|
||||
|
||||
this.loading = true
|
||||
|
||||
inkStitch.get('stitch_plan').then(response => {
|
||||
this.stitchPlan = response.data
|
||||
let [minx, miny, maxx, maxy] = this.stitchPlan.bounding_box
|
||||
let width = maxx - minx
|
||||
let height = maxy - miny
|
||||
this.svg.viewbox(0, 0, width, height);
|
||||
|
||||
this.stitchPlan.color_blocks.forEach(color_block => {
|
||||
let color = `${color_block.color.visible_on_white.hex}`
|
||||
let path_attrs = {fill: "none", stroke: color, "stroke-width": 0.3}
|
||||
let marker = this.generateMarker(color)
|
||||
|
||||
let stitching = false
|
||||
let prevStitch = null
|
||||
color_block.stitches.forEach(stitch => {
|
||||
stitch.x -= minx
|
||||
stitch.y -= miny
|
||||
|
||||
let path = null
|
||||
if (stitching && prevStitch) {
|
||||
path = this.simulation.path(`M${prevStitch.x},${prevStitch.y} ${stitch.x},${stitch.y}`).attr(path_attrs).hide()
|
||||
} else {
|
||||
path = this.simulation.path(`M${stitch.x},${stitch.y} ${stitch.x},${stitch.y}`).attr(path_attrs).hide()
|
||||
}
|
||||
path.marker('end', marker)
|
||||
this.stitchPaths.push(path)
|
||||
this.stitches.push(stitch)
|
||||
|
||||
if (stitch.trim || stitch.color_change) {
|
||||
stitching = false
|
||||
} else if (!stitch.jump) {
|
||||
stitching = true
|
||||
}
|
||||
|
||||
prevStitch = stitch
|
||||
})
|
||||
})
|
||||
|
||||
this.numStitches = this.stitches.length - 1
|
||||
this.generateMarks()
|
||||
this.generateColorSections()
|
||||
this.generateScale()
|
||||
this.generateCursor()
|
||||
|
||||
this.loading = false
|
||||
|
||||
// v-on:keydown doesn't seem to work, maybe an Electron issue?
|
||||
Mousetrap.bind("up", this.animationSpeedUp)
|
||||
Mousetrap.bind("down", this.animationSlowDown)
|
||||
Mousetrap.bind("left", this.animationReverse)
|
||||
Mousetrap.bind("right", this.animationForward)
|
||||
Mousetrap.bind("pagedown", this.animationPreviousCommand)
|
||||
Mousetrap.bind("pageup", this.animationNextCommand)
|
||||
Mousetrap.bind("space", this.toggleAnimation)
|
||||
Mousetrap.bind("+", this.animationForwardOneStitch)
|
||||
Mousetrap.bind("-", this.animationBackwardOneStitch)
|
||||
|
||||
this.svg.on('zoom', this.resizeCursor)
|
||||
this.resizeCursor()
|
||||
|
||||
this.start()
|
||||
})
|
||||
}
|
||||
}
|
Po Szerokość: | Wysokość: | Rozmiar: 60 KiB |
|
@ -0,0 +1,280 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
color: rgb(0, 51, 153);
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.loading {
|
||||
border-radius: 1rem;
|
||||
border: 3px solid rgb(0, 51, 153);
|
||||
background-color: rgba(0, 51, 153, 0.1);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
color: rgb(0, 51, 153)
|
||||
}
|
||||
|
||||
.fa-spin-fast {
|
||||
animation: fa-spin 0.4s infinite linear;
|
||||
}
|
||||
|
||||
.fa-button {
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.fa-fast {
|
||||
transform: skew(-15deg, -15deg) rotate(15deg) scale(1.25, 0.90);
|
||||
}
|
||||
|
||||
.fa-motion-lines {
|
||||
transform: scale(1.0, 1.6) translate(0, -18%) skew(-15deg, -15deg) rotate(15deg);
|
||||
}
|
||||
|
||||
.fa-thin-line {
|
||||
transform: scale(1.4, 0.4);
|
||||
}
|
||||
|
||||
.panel {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-flow: wrap;
|
||||
}
|
||||
|
||||
.panel > * {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.panel fieldset {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 2px solid rgb(0, 51, 153);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
position: absolute;
|
||||
top: -0.2rem;
|
||||
right: -2px;
|
||||
font-size: 0.8rem;
|
||||
color: white;
|
||||
background: rgb(0, 51, 153);
|
||||
transform: scale(1, 0.8) translate(0, -100%);
|
||||
}
|
||||
|
||||
.window-controls > div {
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.control-info {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow-y: auto;
|
||||
transform: translate(-100%, -100%);
|
||||
padding: 0 1rem 1rem;
|
||||
background: white;
|
||||
color: black;
|
||||
border: 1px solid rgb(0, 51, 153);
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.control-info h1 {
|
||||
background: rgb(0, 51, 153);
|
||||
color: white;
|
||||
margin: 0 -1rem 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.control-info div {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.control-info > div {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.control-info div:first-child p {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.control-info p {
|
||||
display: table-cell;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
fieldset button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
fieldset.command span.current-command {
|
||||
display: block;
|
||||
width: 18rem;
|
||||
margin: 0 auto;
|
||||
font-family: sans-serif;
|
||||
font-size: 2rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
fieldset.show-commands {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
fieldset.show-commands span {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
fieldset.show-commands span.npp {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
fieldset.show-commands span:first-of-type {
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
button.pressed {
|
||||
border-style: inset;
|
||||
}
|
||||
|
||||
.vue-slider {
|
||||
height: 25px !important;
|
||||
}
|
||||
|
||||
.slider-container {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.slider-container > * {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.slider-box {
|
||||
width: calc(100% - 11rem);
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.slider-container::v-deep .vue-slider-mark-step {
|
||||
width: 2px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
background: #545454;
|
||||
transform: translate(1px, -25%);
|
||||
box-shadow: inset 1px 0 1px #ffffffbd, inset -1px 0 1px black;
|
||||
}
|
||||
|
||||
.slider-container::v-deep .vue-slider-mark:first-child .vue-slider-mark-step,
|
||||
.slider-container::v-deep .vue-slider-mark:last-child .vue-slider-mark-step {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slider-container::v-deep .vue-slider-rail {
|
||||
border: 1px solid #dedede;
|
||||
}
|
||||
|
||||
.slider-container::v-deep .vue-slider-process {
|
||||
border-radius: 0;
|
||||
box-shadow: inset 0px 2px 2px #ffffff80, inset 0px -2px 2px #0000002e;
|
||||
}
|
||||
|
||||
.slider-container::v-deep .vue-slider-process:first-child {
|
||||
border-top-left-radius: 15px;
|
||||
border-bottom-left-radius: 15px;
|
||||
}
|
||||
|
||||
.slider-container::v-deep .vue-slider-process:nth-last-child(3) {
|
||||
border-top-right-radius: 15px;
|
||||
border-bottom-right-radius: 15px;
|
||||
}
|
||||
|
||||
.vue-slider-mark-label {
|
||||
font-size: 1.4rem;
|
||||
color: #545454;
|
||||
}
|
||||
|
||||
.vue-slider-mark-label svg path {
|
||||
stroke: white;
|
||||
stroke-width: 10px;
|
||||
}
|
||||
|
||||
.slider-label-trim {
|
||||
transform: scale(1, 0.75) translate(-50%, -9px);
|
||||
}
|
||||
|
||||
.slider-label-stop {
|
||||
font-size: 1.2rem;
|
||||
transform: translate(-50%, 12px);
|
||||
}
|
||||
|
||||
.slider-label-jump {
|
||||
transform: translate(-50%, -50px);
|
||||
}
|
||||
|
||||
.slider-label-color-change {
|
||||
transform: translate(-50%, 10px);
|
||||
}
|
||||
|
||||
.current-stitch-input {
|
||||
width: 4rem;
|
||||
float: right;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.simulator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 95vh;
|
||||
}
|
||||
|
||||
.current-command {
|
||||
color: rgb(0, 51, 153);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* we need ::v-deep here because the svg tag is added by svg.js and so Vue
|
||||
can't add the data-v-* attribute
|
||||
*/
|
||||
div.simulator::v-deep svg.simulation {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
order: -2;
|
||||
}
|
||||
|
||||
div.simulator::v-deep svg.simulation-scale {
|
||||
height: 50px;
|
||||
order: -1;
|
||||
}
|
||||
|
||||
div.simulator::v-deep .simulation-scale-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div.simulator::v-deep .cursor {
|
||||
/* not sure what to add here to make it more visible */
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
<template>
|
||||
<div ref="simulator" class="simulator vld-parent">
|
||||
<fieldset>
|
||||
<div class="window-controls">
|
||||
<div ref="controlInfoButton" class="control-info-button" v-on:click="toggleInfo">
|
||||
<font-awesome-icon icon="info"/>
|
||||
<collapse-transition>
|
||||
<div class="control-info" v-show="infoExpanded" v-bind:style="{'max-height': infoMaxHeight + 'px'}">
|
||||
<h1>
|
||||
<font-awesome-icon icon="info" class="info-icon"/>
|
||||
<translate>Simulator Shortcut Keys</translate>
|
||||
</h1>
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
<translate>Button</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Function</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Shortcut Key</translate>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="pause" class="fa-button"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Pause</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Space</translate>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="play" class="fa-button"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Play</translate>
|
||||
</p>
|
||||
<p>P</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="angle-double-left" class="fa-button"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Play backward</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-comment="name for left arrow keyboard key">← Arrow left</translate>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="angle-double-right" class="fa-button"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Play forward</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-comment="name for right arrow keyboard key">→ Arrow right</translate>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="shoe-prints" class="fa-button fa-flip-horizontal"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-comment="description of keyboard shortcut that moves one stitch backward in simulator">
|
||||
One step backward
|
||||
</translate>
|
||||
</p>
|
||||
<p>-
|
||||
<translate translate-comment="name for this keyboard key: -">Minus</translate>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="shoe-prints" class="fa-button"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-comment="description of keyboard shortcut that moves one stitch forward in simulator">
|
||||
One step forward
|
||||
</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-comment="name for this keyboard key: +">+ Plus</translate>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="step-backward" class="fa-button"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Jump to previous command</translate>
|
||||
</p>
|
||||
<p><translate translate-comment="name for page down keyboard key">Page down (PgDn)</translate></p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="step-forward" class="fa-button"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Jump to next command</translate>
|
||||
</p>
|
||||
<p><translate translate-comment="name for page up keyboard key">Page up (PgUp)</translate></p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="hippo" class="fa-button"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Slow down</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-comment="name for down arrow keyboard key">↓ Arrow down</translate>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<font-awesome-icon icon="horse" class="fa-button"/>
|
||||
</p>
|
||||
<p>
|
||||
<translate>Speed up</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-comment="name for up arrow keyboard key">↑ Arrow up</translate>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</collapse-transition>
|
||||
</div>
|
||||
<div class="toggle-controls" v-on:click="toggleControls">
|
||||
<font-awesome-icon v-if="controlsExpanded" icon="minus"/>
|
||||
<font-awesome-icon v-else icon="plus"/>
|
||||
</div>
|
||||
</div>
|
||||
<collapse-transition>
|
||||
<div class="panel" v-show="controlsExpanded">
|
||||
<fieldset class="controls">
|
||||
<legend>
|
||||
<translate>Controls</translate>
|
||||
</legend>
|
||||
<button v-on:click="stop" :class="{pressed: paused}" :title="$gettext('Pause (space)')">
|
||||
<font-awesome-icon icon="pause" size="2x" class="fa-button"/>
|
||||
</button>
|
||||
<button v-on:click="start" :class="{pressed: animating}" :title="$gettext('Play (arrow left | arrow right)')">
|
||||
<font-awesome-icon icon="play" size="2x" class="fa-button"/>
|
||||
</button>
|
||||
<button v-on:click="animationReverse" :class="{pressed: reverse}" :title="$gettext('Play backward (arrow left)')">
|
||||
<font-awesome-icon icon="angle-double-left" size="2x" class="fa-button" :mask="['fas', 'stop']"/>
|
||||
</button>
|
||||
<button v-on:click="animationForward" :class="{pressed: forward}" :title="$gettext('Play forward (arrow right)')">
|
||||
<font-awesome-icon icon="angle-double-right" size="2x" class="fa-button" :mask="['fas', 'stop']"/>
|
||||
</button>
|
||||
<button v-on:click="animationBackwardOneStitch" :title="$gettext('One step backward (-)')">
|
||||
<font-awesome-icon icon="shoe-prints" size="2x" class="fa-button fa-flip-horizontal"/>
|
||||
</button>
|
||||
<button v-on:click="animationForwardOneStitch" :title="$gettext('One step forward (+)')">
|
||||
<font-awesome-icon icon="shoe-prints" size="2x" class="fa-button"/>
|
||||
</button>
|
||||
<button v-on:click="animationPreviousCommand" :title="$gettext('Jump to previous command (Page down)')">
|
||||
<font-awesome-icon icon="step-backward" size="2x" class="fa-button"/>
|
||||
</button>
|
||||
<button v-on:click="animationNextCommand" :title="$gettext('Jump to next command (Page up)')">
|
||||
<font-awesome-icon icon="step-forward" size="2x" class="fa-button"/>
|
||||
</button>
|
||||
</fieldset>
|
||||
<fieldset class="speed">
|
||||
<legend>
|
||||
<translate :translate-n="speed" translate-plural="Speed: %{speed} stitches/sec">Speed: %{speed} stitch/sec</translate>
|
||||
</legend>
|
||||
<button v-on:click="animationSlowDown" :title="$gettext('Slow down (arrow down)')">
|
||||
<font-awesome-icon icon="hippo" size="2x" class="fa-button"/>
|
||||
</button>
|
||||
<button v-on:click="animationSpeedUp" :title="$gettext('Speed up (arrow up)')">
|
||||
<font-awesome-icon icon="align-right" class="fa-motion-lines"/>
|
||||
<font-awesome-icon icon="horse" size="2x" class="fa-button fa-fast"/>
|
||||
</button>
|
||||
</fieldset>
|
||||
<fieldset class="command">
|
||||
<legend>
|
||||
<translate>Command</translate>
|
||||
</legend>
|
||||
<span class="current-command">{{currentCommand}}</span>
|
||||
</fieldset>
|
||||
<fieldset class="show-commands">
|
||||
<legend>Show</legend>
|
||||
<span>
|
||||
<input id="trim-checkbox" type="checkbox" v-model="showTrims"/>
|
||||
<label for="trim-checkbox"><font-awesome-icon icon="cut"/> <translate>trims</translate></label>
|
||||
<br/>
|
||||
<input id="jump-checkbox" type="checkbox" v-model="showJumps"/>
|
||||
<label for="jump-checkbox"><font-awesome-icon icon="frog"/> <translate>jumps</translate></label>
|
||||
</span>
|
||||
<span>
|
||||
<input id="color-change-checkbox" type="checkbox" v-model="showColorChanges"/>
|
||||
<label for="color-change-checkbox"><font-awesome-icon icon="exchange-alt"/> <translate>color changes</translate></label>
|
||||
<br/>
|
||||
<input id="stop-checkbox" type="checkbox" v-model="showStops"/>
|
||||
<label for="stop-checkbox"><font-awesome-icon icon="pause"/> <translate>stops</translate></label>
|
||||
</span>
|
||||
<span class="npp">
|
||||
<input id="npp-checkbox" type="checkbox" v-model="showNeedlePenetrationPoints"/>
|
||||
<label for="npp-checkbox">
|
||||
<font-awesome-layers>
|
||||
<font-awesome-icon icon="circle" transform="shrink-9"/>
|
||||
<font-awesome-icon icon="minus" class="fa-thin-line"/>
|
||||
</font-awesome-layers>
|
||||
<span v-translate>needle<br/>points</span>
|
||||
</label>
|
||||
</span>
|
||||
<span>
|
||||
<input id="realistic-checkbox" type="checkbox" v-model="showRealisticPreview"/>
|
||||
<label for="realistic-checkbox"><font-awesome-icon icon="eye"/> <span v-translate>realistic</span></label>
|
||||
<br/>
|
||||
<input id="cursor-checkbox" type="checkbox" v-model="showCursor"/>
|
||||
<label for="cursor-checkbox"><font-awesome-icon icon="plus"/> <span v-translate>cursor</span></label>
|
||||
</span>
|
||||
</fieldset>
|
||||
</div>
|
||||
</collapse-transition>
|
||||
<div class="slider-container">
|
||||
<span>1</span>
|
||||
<span class="slider-box">
|
||||
<vue-slider
|
||||
:value="currentStitchDisplay"
|
||||
@change="setCurrentStitch"
|
||||
:min="0"
|
||||
:max="numStitches"
|
||||
:duration="0"
|
||||
:marks="sliderMarks"
|
||||
:process="sliderProcess">
|
||||
<template v-slot:label="mark">
|
||||
<div :class="['vue-slider-mark-label', `slider-label-${mark.command}`, { active: mark.active }]">
|
||||
<font-awesome-icon :icon="mark.icon"/>
|
||||
</div>
|
||||
</template>
|
||||
</vue-slider>
|
||||
</span>
|
||||
<span>{{numStitches}}</span>
|
||||
<input ref="currentStitchInput"
|
||||
class="current-stitch-input"
|
||||
:value="currentStitchDisplay"
|
||||
@change="onCurrentStitchEntered"
|
||||
@focus="stop"/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<loading :active.sync="loading" :is-full-page="false">
|
||||
<div class="loading">
|
||||
<div class="loading-icon">
|
||||
<font-awesome-icon icon="spinner" size="4x" pulse/>
|
||||
</div>
|
||||
<div class="loading-text">
|
||||
<translate>Rendering stitch-plan...</translate>
|
||||
</div>
|
||||
</div>
|
||||
</loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="../assets/js/simulator.js"></script>
|
||||
|
||||
<style src="../assets/style/simulator.css" scoped></style>
|
|
@ -0,0 +1,83 @@
|
|||
// ES6
|
||||
import Vue from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
import App from './App'
|
||||
import router from './router'
|
||||
|
||||
import {library} from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faAlignRight,
|
||||
faAngleDoubleLeft,
|
||||
faAngleDoubleRight,
|
||||
faAngleRight,
|
||||
faCircle,
|
||||
faCut,
|
||||
faExchangeAlt,
|
||||
faEye,
|
||||
faFrog,
|
||||
faHippo,
|
||||
faHorse,
|
||||
faInfo,
|
||||
faMinus,
|
||||
faPause,
|
||||
faPlay,
|
||||
faPlus,
|
||||
faShoePrints,
|
||||
faSpinner,
|
||||
faStepBackward,
|
||||
faStepForward,
|
||||
faStop
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import {FontAwesomeIcon, FontAwesomeLayers} from '@fortawesome/vue-fontawesome'
|
||||
import Transitions from 'vue2-transitions'
|
||||
import GetTextPlugin from 'vue-gettext'
|
||||
import translations from './assets/translations.json'
|
||||
import {selectLanguage} from '../lib/i18n'
|
||||
|
||||
// We have to add to the library every icon we use anywhere in the UI.
|
||||
// This avoids the need to bundle the entire font-awesome icon set with
|
||||
// Ink/Stitch.
|
||||
library.add(
|
||||
faAlignRight,
|
||||
faAngleDoubleLeft,
|
||||
faAngleDoubleRight,
|
||||
faAngleRight,
|
||||
faCircle,
|
||||
faCut,
|
||||
faExchangeAlt,
|
||||
faEye,
|
||||
faFrog,
|
||||
faHippo,
|
||||
faHorse,
|
||||
faInfo,
|
||||
faMinus,
|
||||
faPause,
|
||||
faPlay,
|
||||
faPlus,
|
||||
faShoePrints,
|
||||
faSpinner,
|
||||
faStepBackward,
|
||||
faStepForward,
|
||||
faStop
|
||||
)
|
||||
|
||||
Vue.component('font-awesome-icon', FontAwesomeIcon)
|
||||
Vue.component('font-awesome-layers', FontAwesomeLayers)
|
||||
|
||||
Vue.use(Transitions)
|
||||
Vue.use(GetTextPlugin, {
|
||||
translations: translations,
|
||||
defaultLanguage: selectLanguage(),
|
||||
silent: true
|
||||
})
|
||||
|
||||
Vue.http = Vue.prototype.$http = axios
|
||||
Vue.config.productionTip = false
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
components: {App},
|
||||
router,
|
||||
template: '<App/>'
|
||||
}).$mount('#app')
|
|
@ -0,0 +1,18 @@
|
|||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
routes: [
|
||||
{
|
||||
path: '/simulator',
|
||||
name: 'simulator',
|
||||
component: require('@/components/Simulator').default
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
redirect: '/'
|
||||
}
|
||||
]
|
||||
})
|
6935
electron/yarn.lock
|
@ -0,0 +1 @@
|
|||
from .server import APIServer
|
|
@ -0,0 +1,110 @@
|
|||
import errno
|
||||
import logging
|
||||
import socket
|
||||
from threading import Thread
|
||||
import time
|
||||
|
||||
from flask import Flask, request, g
|
||||
import requests
|
||||
|
||||
from ..utils.json import InkStitchJSONEncoder
|
||||
from .simulator import simulator
|
||||
from .stitch_plan import stitch_plan
|
||||
|
||||
|
||||
class APIServer(Thread):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.extension = args[0]
|
||||
Thread.__init__(self, *args[1:], **kwargs)
|
||||
self.daemon = True
|
||||
self.shutting_down = False
|
||||
self.app = None
|
||||
self.host = None
|
||||
self.port = None
|
||||
self.ready = False
|
||||
|
||||
self.__setup_app()
|
||||
|
||||
def __setup_app(self): # noqa: C901
|
||||
self.app = Flask(__name__)
|
||||
self.app.json_encoder = InkStitchJSONEncoder
|
||||
|
||||
self.app.register_blueprint(simulator, url_prefix="/simulator")
|
||||
self.app.register_blueprint(stitch_plan, url_prefix="/stitch_plan")
|
||||
|
||||
@self.app.before_request
|
||||
def store_extension():
|
||||
# make the InkstitchExtension object available to the view handling
|
||||
# this request
|
||||
g.extension = self.extension
|
||||
|
||||
@self.app.route('/shutdown', methods=['POST'])
|
||||
def shutdown():
|
||||
self.shutting_down = True
|
||||
request.environ.get('werkzeug.server.shutdown')()
|
||||
return "shutting down"
|
||||
|
||||
@self.app.route('/ping')
|
||||
def ping():
|
||||
return "pong"
|
||||
|
||||
def stop(self):
|
||||
# for whatever reason, shutting down only seems possible in
|
||||
# the context of a flask request, so we'll just make one
|
||||
requests.post("http://%s:%s/shutdown" % (self.host, self.port))
|
||||
|
||||
def disable_logging(self):
|
||||
logging.getLogger('werkzeug').setLevel(logging.ERROR)
|
||||
|
||||
def run(self):
|
||||
self.disable_logging()
|
||||
|
||||
self.host = "127.0.0.1"
|
||||
self.port = 5000
|
||||
|
||||
while True:
|
||||
try:
|
||||
self.app.run(self.host, self.port, threaded=True)
|
||||
except socket.error as e:
|
||||
if e.errno == errno.EADDRINUSE:
|
||||
self.port += 1
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
break
|
||||
|
||||
def ready_checker(self):
|
||||
"""Wait until the server is started.
|
||||
|
||||
Annoyingly, there's no way to get a callback to be run when the Flask
|
||||
server starts. Instead, we'll have to poll.
|
||||
"""
|
||||
|
||||
while True:
|
||||
if self.port:
|
||||
try:
|
||||
response = requests.get("http://%s:%s/ping" % (self.host, self.port))
|
||||
if response.status_code == 200:
|
||||
break
|
||||
except socket.error, e:
|
||||
if e.errno == errno.ECONNREFUSED:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
def start_server(self):
|
||||
"""Start the API server.
|
||||
|
||||
returns: port (int) -- the port that the server is listening on
|
||||
(on localhost)
|
||||
"""
|
||||
|
||||
checker = Thread(target=self.ready_checker)
|
||||
checker.start()
|
||||
self.start()
|
||||
checker.join()
|
||||
|
||||
return self.port
|
|
@ -0,0 +1,3 @@
|
|||
from flask import Blueprint
|
||||
|
||||
simulator = Blueprint('simulator', __name__)
|
|
@ -0,0 +1,17 @@
|
|||
from flask import Blueprint, g, jsonify
|
||||
|
||||
from ..stitch_plan import patches_to_stitch_plan
|
||||
|
||||
|
||||
stitch_plan = Blueprint('stitch_plan', __name__)
|
||||
|
||||
|
||||
@stitch_plan.route('')
|
||||
def get_stitch_plan():
|
||||
if not g.extension.get_elements():
|
||||
return dict(colors=[], stitch_blocks=[], commands=[])
|
||||
|
||||
patches = g.extension.elements_to_patches(g.extension.elements)
|
||||
stitch_plan = patches_to_stitch_plan(patches)
|
||||
|
||||
return jsonify(stitch_plan)
|
|
@ -16,7 +16,7 @@ from output import Output
|
|||
from params import Params
|
||||
from print_pdf import Print
|
||||
from remove_embroidery_settings import RemoveEmbroiderySettings
|
||||
from simulate import Simulate
|
||||
from simulator import Simulator
|
||||
from stitch_plan_preview import StitchPlanPreview
|
||||
from zip import Zip
|
||||
|
||||
|
@ -25,7 +25,6 @@ __all__ = extensions = [Embroider,
|
|||
Install,
|
||||
Params,
|
||||
Print,
|
||||
Simulate,
|
||||
Input,
|
||||
Output,
|
||||
Zip,
|
||||
|
@ -40,4 +39,5 @@ __all__ = extensions = [Embroider,
|
|||
Troubleshoot,
|
||||
RemoveEmbroiderySettings,
|
||||
BreakApart,
|
||||
ImportThreadlist]
|
||||
ImportThreadlist,
|
||||
Simulator]
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
from ..api import APIServer
|
||||
from ..gui import open_url
|
||||
|
||||
from .base import InkstitchExtension
|
||||
|
||||
|
||||
class Simulator(InkstitchExtension):
|
||||
def __init__(self):
|
||||
InkstitchExtension.__init__(self)
|
||||
|
||||
def effect(self):
|
||||
api_server = APIServer(self)
|
||||
port = api_server.start_server()
|
||||
electron = open_url("/simulator?port=%d" % port)
|
||||
electron.wait()
|
||||
api_server.stop()
|
||||
api_server.join()
|
|
@ -31,3 +31,6 @@ class Stitch(Point):
|
|||
|
||||
def copy(self):
|
||||
return Stitch(self.x, self.y, self.color, self.jump, self.stop, self.trim, self.color_change, self.no_ties)
|
||||
|
||||
def __json__(self):
|
||||
return vars(self)
|
||||
|
|
|
@ -98,6 +98,14 @@ class StitchPlan(object):
|
|||
def __repr__(self):
|
||||
return "StitchPlan(%s)" % ", ".join(repr(cb) for cb in self.color_blocks)
|
||||
|
||||
def __json__(self):
|
||||
return dict(color_blocks=self.color_blocks,
|
||||
num_stops=self.num_stops,
|
||||
num_trims=self.num_trims,
|
||||
num_stitches=self.num_stitches,
|
||||
bounding_box=self.bounding_box
|
||||
)
|
||||
|
||||
@property
|
||||
def num_colors(self):
|
||||
"""Number of unique colors in the stitch plan."""
|
||||
|
@ -175,6 +183,9 @@ class ColorBlock(object):
|
|||
def __delitem__(self, item):
|
||||
del self.stitches[item]
|
||||
|
||||
def __json__(self):
|
||||
return dict(color=self.color, stitches=self.stitches)
|
||||
|
||||
def has_color(self):
|
||||
return self._color is not None
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import simplestyle
|
||||
import re
|
||||
import colorsys
|
||||
from pyembroidery.EmbThread import EmbThread
|
||||
import re
|
||||
|
||||
import simplestyle
|
||||
|
||||
|
||||
class ThreadColor(object):
|
||||
|
@ -27,6 +28,20 @@ class ThreadColor(object):
|
|||
self.number = number
|
||||
self.manufacturer = manufacturer
|
||||
|
||||
def __json__(self):
|
||||
jsonified = self._as_dict()
|
||||
jsonified["visible_on_white"] = self.visible_on_white._as_dict()
|
||||
|
||||
return jsonified
|
||||
|
||||
def _as_dict(self):
|
||||
return dict(name=self.name,
|
||||
number=self.number,
|
||||
manufacturer=self.manufacturer,
|
||||
rgb=self.rgb,
|
||||
hex=self.to_hex_str(),
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, ThreadColor):
|
||||
return self.rgb == other.rgb
|
||||
|
|
|
@ -63,6 +63,9 @@ class Point:
|
|||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __json__(self):
|
||||
return vars(self)
|
||||
|
||||
def __add__(self, other):
|
||||
return Point(self.x + other.x, self.y + other.y)
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from flask.json import JSONEncoder
|
||||
|
||||
|
||||
class InkStitchJSONEncoder(JSONEncoder):
|
||||
"""JSON encoder class that runs __json__ on an object if available.
|
||||
|
||||
The __json__ method should return a JSON-compatible representation of the
|
||||
object.
|
||||
"""
|
||||
|
||||
def default(self, obj):
|
||||
try:
|
||||
return obj.__json__()
|
||||
except AttributeError:
|
||||
return JSONEncoder.default(self, obj)
|
|
@ -1,8 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>{% trans %}Simulate{% endtrans %}</name>
|
||||
<id>org.inkstitch.simulate.{{ locale }}</id>
|
||||
<param name="extension" type="string" gui-hidden="true">simulate</param>
|
||||
<name>{% trans %}Simulator{% endtrans %}</name>
|
||||
<id>org.inkstitch.simulator.{{ locale }}</id>
|
||||
<dependency type="executable" location="extensions">inkstitch.py</dependency>
|
||||
<dependency type="executable" location="extensions">inkex.py</dependency>
|
||||
<param name="extension" type="string" gui-hidden="true">simulator</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
|
@ -10,6 +12,6 @@
|
|||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
{{ command_tag | safe }}
|
||||
<command reldir="extensions" interpreter="python">inkstitch.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|