Working proof of concept
This commit is contained in:
parent
0f97197438
commit
64c066ffed
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
|
@ -0,0 +1,18 @@
|
|||
# CuttingBoard
|
||||
|
||||
A web-based cutting board designer.
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
ToDo
|
||||
====
|
||||
|
||||
Must have
|
||||
----
|
||||
- Implement switching units
|
||||
- Re-ordering of the layers (preferably drag/drop)
|
||||
- Save / load designs (clipboard, or preferably file download/upload - maybe Cloud storage integration later?)
|
||||
|
||||
Should have
|
||||
----
|
||||
- Render width and height of the boards in the previews
|
||||
- Material usage overview
|
||||
- Generate cutting list
|
||||
- Support for fractional inches (see, not all europeans look down on freedom units!)
|
||||
|
||||
Nice to have
|
||||
----
|
||||
- More advanced options, like custom direction per strip and mixing multiple edge grain boards with different layers for the end grain board (the code is half prepared for this by having the boards array encapsulating the layers, though it's all hardcoded to board[0] now)
|
||||
- 3D effect for previews emulating thickness / crosscut width
|
||||
- Make it a tiny bit prettier overall
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
.settings[data-v-660f17f9]{display:inline-grid;grid-template-columns:auto auto;grid-column-gap:1em;grid-row-gap:.25em}.settings h2[data-v-660f17f9]{font-size:80%;margin-top:1em;margin-bottom:0;grid-column:1/3}.layers[data-v-535527d4]{display:inline-grid;grid-template-columns:3em 20em 5em 3em;grid-column-gap:1em}.layers .add[data-v-535527d4]{grid-column:2/5;padding-bottom:1em}.layers .header[data-v-535527d4]{font-weight:700;margin-bottom:.25em}.wood[data-v-55181d8c]{display:inline-grid;grid-template-columns:3em 20em 5em 3em;grid-column-gap:1em}.wood .add[data-v-55181d8c]{grid-column:2/5;padding-bottom:1em}.wood .header[data-v-55181d8c]{font-weight:700;margin-bottom:.25em}#app{background-color:#fff;color:#000;font-family:Verdana,Arial,sans-serif;font-size:10pt;display:flex;flex-direction:horizontal}h1{background-color:#f0f0f0;border-bottom:1px solid silver;font-size:100%;margin-top:0;margin-bottom:.5em;padding:.25em}.app-settings{margin-right:1em}.app-settings .block{margin-bottom:2em}.app-preview .preview{margin-bottom:1em}.about,.todo{width:30em}
|
|
@ -0,0 +1 @@
|
|||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>CuttingBoard</title><link href="/css/app.037fc7fb.css" rel="preload" as="style"><link href="/js/app.f2508f9a.js" rel="preload" as="script"><link href="/js/chunk-vendors.d9b83edc.js" rel="preload" as="script"><link href="/css/app.037fc7fb.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but CuttingBoard doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.d9b83edc.js"></script><script src="/js/app.f2508f9a.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "cuttingboard",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^3.0.0",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<div class="app-settings">
|
||||
<h1>Settings</h1>
|
||||
<Settings class="block" />
|
||||
|
||||
<h1>Layers</h1>
|
||||
<Layers class="block" />
|
||||
|
||||
<h1>Wood types</h1>
|
||||
<Wood class="block" />
|
||||
|
||||
<h1>About / feedback</h1>
|
||||
<div class="about">
|
||||
<p>
|
||||
Created by Mark van Renswoude. Open-source and available under the Unlicense to the public domain on <a href="https://github.com/MvRens/CuttingBoard" target="_blank">Github</a>, where feedback is welcome under Issues.
|
||||
</p>
|
||||
<p>
|
||||
Heavily inspired by <a href="http://www.lastalias.com/cbdesigner/">CBdesigner</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app-preview">
|
||||
<h1>Edge grain</h1>
|
||||
<EndGrainPreview :scale="1" />
|
||||
|
||||
<h1>End grain</h1>
|
||||
<EdgeGrainPreview :scale="1" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Settings from './components/Settings.vue'
|
||||
import Layers from './components/Layers.vue'
|
||||
import Wood from './components/Wood.vue'
|
||||
import EndGrainPreview from './components/EndGrainPreview.vue'
|
||||
import EdgeGrainPreview from './components/EdgeGrainPreview.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
EndGrainPreview,
|
||||
EdgeGrainPreview,
|
||||
Settings,
|
||||
Layers,
|
||||
Wood
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#app {
|
||||
background-color: white;
|
||||
color: black;
|
||||
font-family: 'Verdana', 'Arial', sans-serif;
|
||||
font-size: 10pt;
|
||||
|
||||
display: flex;
|
||||
flex-direction: horizontal;
|
||||
}
|
||||
|
||||
|
||||
h1
|
||||
{
|
||||
background-color: #f0f0f0;
|
||||
border-bottom: solid 1px #c0c0c0;
|
||||
font-size: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: .5em;
|
||||
padding: .25em;
|
||||
}
|
||||
|
||||
.app-settings
|
||||
{
|
||||
margin-right: 1em;
|
||||
|
||||
.block
|
||||
{
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.app-preview
|
||||
{
|
||||
.preview
|
||||
{
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.about, .todo
|
||||
{
|
||||
width: 30em;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<div class="preview">
|
||||
<svg
|
||||
:width="viewportWidth"
|
||||
:height="viewportHeight"
|
||||
:viewBox="viewBox">
|
||||
<defs>
|
||||
<g id="strip">
|
||||
<rect
|
||||
v-for="(layer, index) in layers"
|
||||
:width="toPixels(settings.boardThickness)"
|
||||
:height="toPixels(layer.width)"
|
||||
x="0"
|
||||
:y="getLayerOffset(index)"
|
||||
:style="getLayerStyle(index)" />
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<use
|
||||
v-for="(strip, index) in stripsPerBoard"
|
||||
xlink:href="#strip"
|
||||
:x="index * settings.boardThickness"
|
||||
y="0"
|
||||
:transform="getLayerTransform(index)" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { units } from '../lib/units';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
scale: Number
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
settings() { return this.$store.state.settings; },
|
||||
wood() { return this.$store.state.wood; },
|
||||
layers() { return this.$store.state.boards[0].layers; },
|
||||
|
||||
stripsPerBoard()
|
||||
{
|
||||
const stripAndKerf = this.settings.crosscutWidth + this.settings.bladeKerf;
|
||||
if (stripAndKerf === 0)
|
||||
return 0;
|
||||
|
||||
return Math.floor((this.settings.boardLength + this.settings.bladeKerf) / stripAndKerf);
|
||||
},
|
||||
|
||||
boardWidth()
|
||||
{
|
||||
const boardWidth = this.stripsPerBoard * this.settings.boardThickness;
|
||||
return this.toPixels(boardWidth);
|
||||
},
|
||||
|
||||
boardHeight()
|
||||
{
|
||||
return this.layers
|
||||
.map(currentValue => currentValue.width)
|
||||
.reduce((accumulator, currentValue) => accumulator + currentValue);
|
||||
},
|
||||
|
||||
viewportWidth() { return Math.floor(this.boardWidth * this.scale); },
|
||||
viewportHeight() { return Math.floor(this.boardHeight * this.scale); },
|
||||
viewBox() { return '0 0 ' + this.boardWidth + ' ' + this.boardHeight; }
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
toPixels(value)
|
||||
{
|
||||
return units.toPixels(value, this.settings.units);
|
||||
},
|
||||
|
||||
getLayerOffset(index)
|
||||
{
|
||||
if (index < 0 || index >= this.layers.length)
|
||||
return 0;
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (let i = 0; i < index; i++)
|
||||
offset += this.layers[i].width;
|
||||
|
||||
return offset;
|
||||
},
|
||||
|
||||
getLayerStyle(index)
|
||||
{
|
||||
if (index < 0 || index >= this.layers.length)
|
||||
return 'fill: fuchsia';
|
||||
|
||||
const woodIndex = this.layers[index].wood;
|
||||
if (woodIndex === null)
|
||||
return '';
|
||||
|
||||
const borderStyle = this.settings.borders
|
||||
? '; stroke-width: 1; stroke: black'
|
||||
: '';
|
||||
|
||||
return 'fill: ' + this.wood[woodIndex].color + borderStyle;
|
||||
},
|
||||
|
||||
getLayerTransform(index)
|
||||
{
|
||||
if (!this.settings.alternateDirection || (index % 2) == 0)
|
||||
return '';
|
||||
|
||||
return 'scale(1, -1) translate(0, -' + this.boardHeight + ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div class="preview">
|
||||
<svg
|
||||
:width="viewportWidth"
|
||||
:height="viewportHeight"
|
||||
:viewBox="viewBox">
|
||||
|
||||
<rect
|
||||
v-for="(layer, index) in layers"
|
||||
:width="toPixels(settings.boardLength)"
|
||||
:height="toPixels(layer.width)"
|
||||
x="0"
|
||||
:y="getLayerOffset(index)"
|
||||
:style="getLayerStyle(index)" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { units } from '../lib/units';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
scale: Number
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
settings() { return this.$store.state.settings; },
|
||||
wood() { return this.$store.state.wood; },
|
||||
layers() { return this.$store.state.boards[0].layers; },
|
||||
|
||||
boardWidth() { return this.toPixels(this.settings.boardLength); },
|
||||
|
||||
boardHeight()
|
||||
{
|
||||
return this.layers
|
||||
.map(currentValue => currentValue.width)
|
||||
.reduce((accumulator, currentValue) => accumulator + currentValue);
|
||||
},
|
||||
|
||||
viewportWidth() { return Math.floor(this.boardWidth * this.scale); },
|
||||
viewportHeight() { return Math.floor(this.boardHeight * this.scale); },
|
||||
viewBox() { return '0 0 ' + this.boardWidth + ' ' + this.boardHeight; }
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
toPixels(value)
|
||||
{
|
||||
return units.toPixels(value, this.settings.units);
|
||||
},
|
||||
|
||||
getLayerOffset(index)
|
||||
{
|
||||
if (index < 0 || index >= this.layers.length)
|
||||
return 0;
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (let i = 0; i < index; i++)
|
||||
offset += this.layers[i].width;
|
||||
|
||||
return offset;
|
||||
},
|
||||
|
||||
getLayerStyle(index)
|
||||
{
|
||||
if (index < 0 || index >= this.layers.length)
|
||||
return 'fill: fuchsia';
|
||||
|
||||
const woodIndex = this.layers[index].wood;
|
||||
if (woodIndex === null)
|
||||
return '';
|
||||
|
||||
const borderStyle = this.settings.borders
|
||||
? '; stroke-width: 1; stroke: black'
|
||||
: '';
|
||||
|
||||
return 'fill: ' + this.wood[woodIndex].color + borderStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<div class="layers">
|
||||
<div class="add">
|
||||
<button @click="addLayer()">Add layer</button>
|
||||
</div>
|
||||
|
||||
<span class="header"> </span>
|
||||
<span class="header">Wood type</span>
|
||||
<span class="header">Width</span>
|
||||
<span class="header"> </span>
|
||||
|
||||
<template v-for="(layer, index) in layers">
|
||||
<div class="index">{{ index + 1 }}</div>
|
||||
<select v-model="layer.wood" class="wood">
|
||||
<option v-for="(item, index) in wood" :value="index">{{ item.name }}</option>
|
||||
</select>
|
||||
<input type="number" class="width" :value="layer.width" @input="layer.width = parseFloatDef($event.target.value)" />
|
||||
|
||||
<div class="remove">
|
||||
<button @click="removeLayer(index)">X</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { units } from '../lib/units';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
settings() { return this.$store.state.settings; },
|
||||
wood() { return this.$store.state.wood; },
|
||||
layers() { return this.$store.state.boards[0].layers; },
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
parseFloatDef(value)
|
||||
{
|
||||
const parsedValue = parseFloat(value);
|
||||
return Object.is(parsedValue, NaN) ? 0 : parsedValue;
|
||||
},
|
||||
|
||||
|
||||
addLayer()
|
||||
{
|
||||
this.$store.commit('addLayer', 0);
|
||||
},
|
||||
|
||||
|
||||
removeLayer(index)
|
||||
{
|
||||
this.$store.commit('removeLayer', { board: 0, layer: index });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layers
|
||||
{
|
||||
display: inline-grid;
|
||||
grid-template-columns: 3em 20em 5em 3em;
|
||||
grid-column-gap: 1em;
|
||||
|
||||
.add
|
||||
{
|
||||
grid-column: 2 / 5;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.header
|
||||
{
|
||||
font-weight: bold;
|
||||
margin-bottom: .25em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<div class="settings">
|
||||
<h2>Designer</h2>
|
||||
|
||||
<label for="units">Units</label>
|
||||
<select id="units" disabled>
|
||||
<option value="mm">Millimeters</option>
|
||||
<option value="cm">Centimeters</option>
|
||||
<option value="inch">Inches (fractional)</option>
|
||||
</select>
|
||||
|
||||
<label for="borders">Show borders</label>
|
||||
<input id="borders" type="checkbox" :checked="settings.borders" @change="$store.commit('updateSettings', { borders: $event.target.checked })" />
|
||||
|
||||
<h2>Material</h2>
|
||||
<label for="boardThickness">Board thickness</label>
|
||||
<input id="boardThickness" type="number" :value="settings.boardThickness" @change="$store.commit('updateSettings', { boardThickness: parseFloatDef($event.target.value) })" />
|
||||
|
||||
<label for="boardLength">Board length</label>
|
||||
<input id="boardLength" type="number" :value="settings.boardLength" @change="$store.commit('updateSettings', { boardLength: parseFloatDef($event.target.value) })" />
|
||||
|
||||
<label for="bladeKerf">Blade kerf</label>
|
||||
<input id="bladeKerf" type="number" :value="settings.bladeKerf" @change="$store.commit('updateSettings', { bladeKerf: parseFloatDef($event.target.value) })" />
|
||||
|
||||
<h2>End grain</h2>
|
||||
<label for="crosscutWidth">Crosscut width</label>
|
||||
<input id="crosscutWidth" type="number" :value="settings.crosscutWidth" @change="$store.commit('updateSettings', { crosscutWidth: parseFloatDef($event.target.value) })" />
|
||||
|
||||
<label for="alternateDirection">Alternate direction</label>
|
||||
<input id="alternateDirection" type="checkbox" :checked="settings.alternateDirection" @change="$store.commit('updateSettings', { alternateDirection: $event.target.checked })" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { units } from '../lib/units';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
settings() { return this.$store.state.settings; }
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
parseFloatDef(value)
|
||||
{
|
||||
const parsedValue = parseFloat(value);
|
||||
return Object.is(parsedValue, NaN) ? 0 : parsedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.settings
|
||||
{
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-column-gap: 1em;
|
||||
grid-row-gap: .25em;
|
||||
|
||||
h2
|
||||
{
|
||||
font-size: 80%;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0;
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div class="wood">
|
||||
<div class="add">
|
||||
<button @click="addWood()">Add wood type</button>
|
||||
</div>
|
||||
|
||||
<span class="header"> </span>
|
||||
<span class="header">Name</span>
|
||||
<span class="header">Colour</span>
|
||||
<span class="header"> </span>
|
||||
|
||||
<template v-for="(item, index) in wood">
|
||||
<span> </span>
|
||||
<input type="text" class="name" v-model="item.name" />
|
||||
<input type="color" class="color" v-model="item.color" />
|
||||
|
||||
<div class="remove">
|
||||
<button @click="removeWood(index)">X</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
settings() { return this.$store.state.settings; },
|
||||
wood() { return this.$store.state.wood; }
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
addWood()
|
||||
{
|
||||
this.$store.commit('addWood');
|
||||
},
|
||||
|
||||
|
||||
removeWood(index)
|
||||
{
|
||||
this.$store.commit('removeWood', index);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wood
|
||||
{
|
||||
display: inline-grid;
|
||||
grid-template-columns: 3em 20em 5em 3em;
|
||||
grid-column-gap: 1em;
|
||||
|
||||
.add
|
||||
{
|
||||
grid-column: 2 / 5;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.header
|
||||
{
|
||||
font-weight: bold;
|
||||
margin-bottom: .25em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,43 @@
|
|||
const millimetersPerInch = 25.4;
|
||||
const millimetersPerCentimeter = 10;
|
||||
const pixelsPerMillimeter = 1;
|
||||
|
||||
|
||||
|
||||
const units = {
|
||||
toPixels(value, units)
|
||||
{
|
||||
return this.toMillimeters(value, units) * pixelsPerMillimeter;
|
||||
},
|
||||
|
||||
|
||||
toMillimeters(value, units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case 'mm': return value;
|
||||
case 'cm': return value * millimetersPerCentimeter;
|
||||
case 'inch': return value * millimetersPerInch;
|
||||
}
|
||||
|
||||
console.error('Invalid units type: ' + units);
|
||||
return 0;
|
||||
},
|
||||
|
||||
|
||||
fromMillimeters(value, units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case 'mm': return value;
|
||||
case 'cm': return value / millimetersPerCentimeter;
|
||||
case 'inch': return value / millimetersPerInch;
|
||||
}
|
||||
|
||||
console.error('Invalid units type: ' + units);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export { units }
|
|
@ -0,0 +1,5 @@
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import store from './store'
|
||||
|
||||
createApp(App).use(store).mount('#app')
|
|
@ -0,0 +1,118 @@
|
|||
import { createStore } from 'vuex';
|
||||
import { units } from './lib/units';
|
||||
|
||||
export default createStore({
|
||||
state: {
|
||||
settings: {
|
||||
units: 'mm',
|
||||
borders: false,
|
||||
boardThickness: 20,
|
||||
boardLength: 700,
|
||||
bladeKerf: 3.5,
|
||||
crosscutWidth: 30,
|
||||
|
||||
alternateDirection: true
|
||||
},
|
||||
|
||||
wood: [
|
||||
{ name: 'Walnut', color: '#58443f' },
|
||||
{ name: 'Maple', color: '#f2e0aa' },
|
||||
{ name: 'Cherry', color: '#bb8359' },
|
||||
{ name: 'Mahogany', color: '#98473f' },
|
||||
{ name: 'Yellowheart', color: '#ffff84' },
|
||||
{ name: 'White oak', color: '#fdf4b9' },
|
||||
{ name: 'Bubinga', color: '#7e3c34' },
|
||||
{ name: 'Jatoba', color: '#9b281c' },
|
||||
{ name: 'Padouk', color: '#933350' }
|
||||
],
|
||||
|
||||
boards: [
|
||||
{
|
||||
layers: [
|
||||
{ wood: 0, width: 20 },
|
||||
{ wood: 1, width: 20 },
|
||||
{ wood: 0, width: 20 },
|
||||
{ wood: 1, width: 20 },
|
||||
{ wood: 0, width: 20 },
|
||||
{ wood: 1, width: 20 },
|
||||
{ wood: 0, width: 20 },
|
||||
{ wood: 1, width: 20 },
|
||||
{ wood: 0, width: 20 },
|
||||
{ wood: 1, width: 20 },
|
||||
{ wood: 0, width: 20 },
|
||||
{ wood: 1, width: 20 },
|
||||
{ wood: 0, width: 20 },
|
||||
{ wood: 1, width: 20 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
mutations: {
|
||||
addLayer(state, board)
|
||||
{
|
||||
if (board < 0 || board >= state.boards.length)
|
||||
return;
|
||||
|
||||
state.boards[board].layers.push({
|
||||
wood: 0,
|
||||
width: units.fromMillimeters(20, state.settings.units)
|
||||
});
|
||||
},
|
||||
|
||||
removeLayer(state, payload)
|
||||
{
|
||||
if (payload.board < 0 || payload.board >= state.boards.length)
|
||||
return;
|
||||
|
||||
if (payload.layer < 0 || payload.layer >= state.boards[payload.board].length)
|
||||
return;
|
||||
|
||||
state.boards[payload.board].layers.splice(payload.layer, 1);
|
||||
},
|
||||
|
||||
|
||||
addWood(state)
|
||||
{
|
||||
state.wood.push({
|
||||
name: 'Wood #' + (state.wood.length + 1),
|
||||
color: '#f2e0aa'
|
||||
});
|
||||
},
|
||||
|
||||
removeWood(state, index)
|
||||
{
|
||||
if (index < 0 || index >= state.wood.length)
|
||||
return;
|
||||
|
||||
// Update all layers
|
||||
state.boards.forEach(board =>
|
||||
{
|
||||
board.layers.forEach(layer =>
|
||||
{
|
||||
if (layer.wood === index)
|
||||
layer.wood = null
|
||||
else if (layer.wood > index)
|
||||
layer.wood--;
|
||||
});
|
||||
});
|
||||
|
||||
state.wood.splice(index, 1);
|
||||
},
|
||||
|
||||
|
||||
updateSettings(state, payload)
|
||||
{
|
||||
for (const property in payload)
|
||||
{
|
||||
if (!payload.hasOwnProperty(property) || !state.settings.hasOwnProperty(property))
|
||||
continue;
|
||||
|
||||
state.settings[property] = payload[property];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
}
|
||||
})
|
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
outputDir: 'docs',
|
||||
chainWebpack: config => {
|
||||
config
|
||||
.plugin('html')
|
||||
.tap(args => {
|
||||
args[0].title = 'CuttingBoard';
|
||||
return args;
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue