Added cutting list and bill of materials
Redesigned the interface Fixed a small rounding error in the end grain preview
This commit is contained in:
parent
3ce66c0f21
commit
f65a7f37e3
5
TODO.md
5
TODO.md
@ -3,13 +3,12 @@ ToDo
|
|||||||
|
|
||||||
Should have
|
Should have
|
||||||
----
|
----
|
||||||
- Material usage overview
|
|
||||||
- Generate cutting list
|
|
||||||
- Support for fractional inches (see, not all europeans look down on freedom units!)
|
- Support for fractional inches (see, not all europeans look down on freedom units!)
|
||||||
|
|
||||||
Nice to have
|
Nice to have
|
||||||
----
|
----
|
||||||
|
- Show remaining material
|
||||||
|
- Theme selection for the preview background
|
||||||
- Render width and height of the boards in the previews (simplified version implemented, moved to Nice to have)
|
- Render width and height of the boards in the previews (simplified version implemented, moved to 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)
|
- 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
|
- 3D effect for previews emulating thickness / crosscut width
|
||||||
- Make it a tiny bit prettier overall
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
.settings[data-v-7f9fc5e0]{display:inline-grid;grid-template-columns:auto auto;grid-column-gap:1em;grid-row-gap:.25em}.settings h2[data-v-7f9fc5e0]{font-size:80%;margin-top:1em;margin-bottom:0;grid-column:1/3}.layers[data-v-631d833e]{display:inline-grid;grid-template-columns:3em 20em 5em 3em;grid-column-gap:1em}.layers .hint[data-v-631d833e]{color:grey;text-align:center;grid-column:1/5;margin-bottom:1em}.layers .index[data-v-631d833e]{cursor:pointer}.layers .index.dropTargetAbove[data-v-631d833e]{border-top:1px solid #000}.layers .index.dropTargetBelow[data-v-631d833e]{border-bottom:1px solid #000}.layers .add[data-v-631d833e]{grid-column:2/5;padding-bottom:1em}.layers .header[data-v-631d833e]{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,.loadSave{width:30em}
|
|
1
docs/css/app.fe0619ef.css
Normal file
1
docs/css/app.fe0619ef.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
.settings[data-v-7b473d48]{display:inline-grid;grid-template-columns:auto auto;grid-column-gap:1em;grid-row-gap:.25em}.settings h2[data-v-7b473d48]{color:grey;font-size:80%;margin-top:1em;margin-bottom:.25em;grid-column:1/3}.layers[data-v-6c03155a]{display:inline-grid;grid-template-columns:3em 20em 5em 3em;grid-column-gap:1em}.layers .hint[data-v-6c03155a]{color:grey;text-align:center;grid-column:1/5;margin-bottom:1em}.layers .index[data-v-6c03155a]{cursor:pointer}.layers .index.dropTargetAbove[data-v-6c03155a]{border-top:1px solid #fff}.layers .index.dropTargetBelow[data-v-6c03155a]{border-bottom:1px solid #fff}.layers .add[data-v-6c03155a]{grid-column:2/5;padding-bottom:1em}.layers .header[data-v-6c03155a]{font-weight:700;margin-bottom:.25em}.wood[data-v-423726cc]{display:inline-grid;grid-template-columns:23em 5em 3em;grid-column-gap:1em}.wood .add[data-v-423726cc]{grid-column:1/4;padding-bottom:1em}.wood .header[data-v-423726cc]{font-weight:700;margin-bottom:.25em}.dimensions[data-v-5e438b66]{margin-bottom:.5em}@media screen{svg[data-v-5e438b66]{box-shadow:0 0 3em #000}}@media print{svg[data-v-5e438b66]{max-width:100%}}.dimensions[data-v-6637d5af]{margin-bottom:.5em}@media screen{svg[data-v-6637d5af]{box-shadow:0 0 3em #000}}@media print{svg[data-v-6637d5af]{max-width:100%}}h2[data-v-a57a7268]{font-size:110%}.list[data-v-a57a7268]{border-collapse:collapse;margin-top:1em;margin-bottom:3em}.list td[data-v-a57a7268],.list th[data-v-a57a7268]{padding:.25em;padding-left:1em;padding-right:1em}.list .dimension[data-v-a57a7268]{text-align:right}.list tr:nth-child(2n) td[data-v-a57a7268]{background-color:#555}@media print{.list tr:nth-child(2n) td[data-v-a57a7268]{background-color:#f0f0f0}}body,html{background-color:#444;color:#fff;margin:0;padding:0;width:100%;height:100%;overflow:none}@media print{body,html{background-color:#fff;color:#000;overflow:visible}}#app{font-family:Verdana,Arial,sans-serif;font-size:10pt;display:flex;flex-direction:horizontal;width:100%;height:100%}a{color:#9cf}input,select{background-color:#303030;color:#fff;border:1px solid #606060;padding-top:.3em;padding-bottom:.3em}input:focus,select:focus{outline:1px solid grey}input[type=number]{text-align:right}button{background-color:#404040;color:#fff;border:1px solid #606060;padding-top:.3em;padding-bottom:.3em}button:hover{background-color:grey}@media print{.hideOnPrint{display:none}}.sidebar[data-v-42450ddb]{background-color:#383838;color:#fff;width:35em;flex-shrink:0;box-shadow:0 0 3em #101010}.sidebar .toolbar[data-v-42450ddb]{background-color:#333;box-shadow:-.2em 0 .5em #000;margin-bottom:.5em}.sidebar .toolbar a[data-v-42450ddb]{color:#fff;display:inline-block;padding:.5em;padding-left:1em;padding-right:1em;cursor:pointer}.sidebar .toolbar a.active[data-v-42450ddb]{background-color:#06c}.sidebar .toolbar a[data-v-42450ddb]:hover:not(.active){background-color:#004d99}.sidebar .toolbar a>svg[data-v-42450ddb]{display:block;margin-left:auto;margin-right:auto}.sidebar .tab[data-v-42450ddb]{padding:1em}.settings[data-v-42450ddb]{margin-right:1em}.settings .block[data-v-42450ddb]{margin-bottom:2em}.content[data-v-42450ddb]{flex-grow:1;padding:2em;padding-left:3em;overflow:auto}@media print{.content[data-v-42450ddb]{background-color:#fff;color:#000;overflow:visible}}.content h1[data-v-42450ddb]{margin-top:0;margin-bottom:0;font-size:150%}.content .preview[data-v-42450ddb]{margin-bottom:2em}.about[data-v-42450ddb],.loadSave[data-v-42450ddb]{width:30em}
|
@ -1 +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.896567a2.css" rel="preload" as="style"><link href="js/app.9efdd4b6.js" rel="preload" as="script"><link href="js/chunk-vendors.135c102b.js" rel="preload" as="script"><link href="css/app.896567a2.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.135c102b.js"></script><script src="js/app.9efdd4b6.js"></script></body></html>
|
<!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.fe0619ef.css" rel="preload" as="style"><link href="js/app.9fbbf467.js" rel="preload" as="script"><link href="js/chunk-vendors.72b70512.js" rel="preload" as="script"><link href="css/app.fe0619ef.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.72b70512.js"></script><script src="js/app.9fbbf467.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
2
docs/js/app.9fbbf467.js
Normal file
2
docs/js/app.9fbbf467.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/js/app.9fbbf467.js.map
Normal file
1
docs/js/app.9fbbf467.js.map
Normal file
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
17
docs/js/chunk-vendors.72b70512.js
Normal file
17
docs/js/chunk-vendors.72b70512.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/js/chunk-vendors.72b70512.js.map
Normal file
1
docs/js/chunk-vendors.72b70512.js.map
Normal file
File diff suppressed because one or more lines are too long
29
package-lock.json
generated
29
package-lock.json
generated
@ -1034,6 +1034,35 @@
|
|||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@fortawesome/fontawesome-common-types": {
|
||||||
|
"version": "0.2.32",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.32.tgz",
|
||||||
|
"integrity": "sha512-ux2EDjKMpcdHBVLi/eWZynnPxs0BtFVXJkgHIxXRl+9ZFaHPvYamAfCzeeQFqHRjuJtX90wVnMRaMQAAlctz3w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@fortawesome/fontawesome-svg-core": {
|
||||||
|
"version": "1.2.32",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.32.tgz",
|
||||||
|
"integrity": "sha512-XjqyeLCsR/c/usUpdWcOdVtWFVjPbDFBTQkn2fQRrWhhUoxriQohO2RWDxLyUM8XpD+Zzg5xwJ8gqTYGDLeGaQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "^0.2.32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/free-solid-svg-icons": {
|
||||||
|
"version": "5.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.1.tgz",
|
||||||
|
"integrity": "sha512-EFMuKtzRMNbvjab/SvJBaOOpaqJfdSap/Nl6hst7CgrJxwfORR1drdTV6q1Ib/JVzq4xObdTDcT6sqTaXMqfdg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "^0.2.32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/vue-fontawesome": {
|
||||||
|
"version": "3.0.0-3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.0-3.tgz",
|
||||||
|
"integrity": "sha512-fCM7+R9M7Y/ipKC5n9hukGpJHhe53JOENGqtku/KWtpXsnbGik3AS5zfJYEupV2uXOw/5S0RSSfttQ2hNIrmFA=="
|
||||||
|
},
|
||||||
"@hapi/address": {
|
"@hapi/address": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
||||||
|
@ -7,11 +7,14 @@
|
|||||||
"build": "vue-cli-service build"
|
"build": "vue-cli-service build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/vue-fontawesome": "^3.0.0-3",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vuex": "^4.0.0-0"
|
"vuex": "^4.0.0-0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||||
"@vue/cli-service": "~4.5.0",
|
"@vue/cli-service": "~4.5.0",
|
||||||
|
236
src/App.vue
236
src/App.vue
@ -1,33 +1,66 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-settings">
|
<div class="sidebar hideOnPrint">
|
||||||
<h1>Settings</h1>
|
<div class="toolbar">
|
||||||
<Settings class="block" />
|
<a :class="{ active: tab === 'settings' }" @click="tab = 'settings'"><font-awesome-icon icon="sliders-h" size="2x" fixed-width /> Settings</a>
|
||||||
|
<a :class="{ active: tab === 'wood' }" @click="tab = 'wood'"><font-awesome-icon icon="tree" size="2x" fixed-width /> Wood</a>
|
||||||
|
<a :class="{ active: tab === 'layers' }" @click="tab = 'layers'"><font-awesome-icon icon="layer-group" size="2x" fixed-width /> Layers</a>
|
||||||
|
<a :class="{ active: tab === 'saveLoad' }" @click="tab = 'saveLoad'"><font-awesome-icon icon="save" size="2x" fixed-width /> Save / load</a>
|
||||||
|
<a :class="{ active: tab === 'print' }" @click="tab = 'print'"><font-awesome-icon icon="print" size="2x" fixed-width /> Print</a>
|
||||||
|
<a :class="{ active: tab === 'about' }" @click="tab = 'about'"><font-awesome-icon icon="info-circle" size="2x" fixed-width /> About</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1>Layers</h1>
|
<div class="tabs">
|
||||||
<Layers class="block" />
|
<div class="tab" v-show="tab === 'settings'">
|
||||||
|
<Settings />
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1>Wood types</h1>
|
<div class="tab" v-show="tab === 'layers'">
|
||||||
<Wood class="block" />
|
<Layers />
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1>Save / load</h1>
|
<div class="tab" v-show="tab === 'wood'">
|
||||||
<div class="loadSave block">
|
<Wood />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab" v-show="tab === 'print'">
|
||||||
|
<p>
|
||||||
|
Use your browser's built-in print functionality (for example, Ctrl+P on Windows) or click the button below to get a printable version of your board and cutting list.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" v-model="printEdgeGrain" id="printEdgeGrain" />
|
||||||
|
<label for="printEdgeGrain"> Edge grain preview</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" v-model="printEndGrain" id="printEndGrain" />
|
||||||
|
<label for="printEndGrain"> End grain preview</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" v-model="printCuttingList" id="printCuttingList" />
|
||||||
|
<label for="printCuttingList"> Cutting list and bill of materials</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<button @click="print">Print</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab" v-show="tab === 'saveLoad'">
|
||||||
<p>
|
<p>
|
||||||
Below you can download the current settings or load them again from a file. You can also bookmark the current page or copy the URL from the address bar instead, as it is automatically updated whenever you change anything.
|
Below you can download the current settings or load them again from a file. You can also bookmark the current page or copy the URL from the address bar instead, as it is automatically updated whenever you change anything.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<input type="text" v-model="saveFilename" />
|
<input type="text" v-model="saveFilename" />
|
||||||
<button @click="save()">Save</button>
|
<button @click="save">Save</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<input type="file" ref="loadFile" accept=".json" />
|
<input type="file" ref="loadFile" accept=".json" />
|
||||||
<button @click="load()">Load</button>
|
<button @click="load">Load</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1>About / feedback</h1>
|
<div class="tab" v-show="tab === 'about'">
|
||||||
<div class="about block">
|
|
||||||
<p>
|
<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.
|
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>
|
||||||
@ -36,14 +69,24 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="app-preview">
|
<div class="content">
|
||||||
|
<div :class="{ hideOnPrint: !printEdgeGrain }">
|
||||||
<h1>Edge grain</h1>
|
<h1>Edge grain</h1>
|
||||||
<EdgeGrainPreview :scale="1" />
|
<EdgeGrainPreview :scale="1" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="{ hideOnPrint: !printEndGrain }">
|
||||||
<h1>End grain</h1>
|
<h1>End grain</h1>
|
||||||
<EndGrainPreview :scale="1" />
|
<EndGrainPreview :scale="1" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div :class="{ hideOnPrint: !printCuttingList }">
|
||||||
|
<h1>Cutting list</h1>
|
||||||
|
<CuttingList />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -52,6 +95,7 @@ import Layers from './components/Layers.vue'
|
|||||||
import Wood from './components/Wood.vue'
|
import Wood from './components/Wood.vue'
|
||||||
import EndGrainPreview from './components/EndGrainPreview.vue'
|
import EndGrainPreview from './components/EndGrainPreview.vue'
|
||||||
import EdgeGrainPreview from './components/EdgeGrainPreview.vue'
|
import EdgeGrainPreview from './components/EdgeGrainPreview.vue'
|
||||||
|
import CuttingList from './components/CuttingList.vue'
|
||||||
|
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
import { bytesToBase64, base64ToBytes } from './lib/base64';
|
import { bytesToBase64, base64ToBytes } from './lib/base64';
|
||||||
@ -63,13 +107,19 @@ export default {
|
|||||||
EdgeGrainPreview,
|
EdgeGrainPreview,
|
||||||
Settings,
|
Settings,
|
||||||
Layers,
|
Layers,
|
||||||
Wood
|
Wood,
|
||||||
|
CuttingList
|
||||||
},
|
},
|
||||||
|
|
||||||
data()
|
data()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
saveFilename: 'My cutting board'
|
tab: 'settings',
|
||||||
|
saveFilename: 'My cutting board',
|
||||||
|
|
||||||
|
printEdgeGrain: true,
|
||||||
|
printEndGrain: true,
|
||||||
|
printCuttingList: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -135,6 +185,12 @@ export default {
|
|||||||
this.$store.commit('load', event.target.result);
|
this.$store.commit('load', event.target.result);
|
||||||
});
|
});
|
||||||
reader.readAsBinaryString(loadFile);
|
reader.readAsBinaryString(loadFile);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
print()
|
||||||
|
{
|
||||||
|
window.print();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -148,28 +204,133 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#app {
|
html, body
|
||||||
|
{
|
||||||
|
background-color: #444444;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: none;
|
||||||
|
|
||||||
|
@media print
|
||||||
|
{
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: black;
|
color: black;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#app
|
||||||
|
{
|
||||||
font-family: 'Verdana', 'Arial', sans-serif;
|
font-family: 'Verdana', 'Arial', sans-serif;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: horizontal;
|
flex-direction: horizontal;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a
|
||||||
h1
|
|
||||||
{
|
{
|
||||||
background-color: #f0f0f0;
|
color: #99ccff;
|
||||||
border-bottom: solid 1px #c0c0c0;
|
|
||||||
font-size: 100%;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: .5em;
|
|
||||||
padding: .25em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-settings
|
input, select
|
||||||
|
{
|
||||||
|
background-color: #303030;
|
||||||
|
color: white;
|
||||||
|
border: solid 1px #606060;
|
||||||
|
padding-top: .3em;
|
||||||
|
padding-bottom: .3em;
|
||||||
|
|
||||||
|
&:focus
|
||||||
|
{
|
||||||
|
outline: solid 1px #808080;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='number']
|
||||||
|
{
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
button
|
||||||
|
{
|
||||||
|
background-color: #404040;
|
||||||
|
color: white;
|
||||||
|
border: solid 1px #606060;
|
||||||
|
padding-top: .3em;
|
||||||
|
padding-bottom: .3em;
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
{
|
||||||
|
background-color: #808080;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideOnPrint
|
||||||
|
{
|
||||||
|
@media print
|
||||||
|
{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.sidebar
|
||||||
|
{
|
||||||
|
background-color: #383838;
|
||||||
|
color: white;
|
||||||
|
width: 35em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 0 3em #101010;
|
||||||
|
|
||||||
|
.toolbar
|
||||||
|
{
|
||||||
|
background-color: #333333;
|
||||||
|
box-shadow: -.2em 0 .5em black;
|
||||||
|
margin-bottom: .5em;
|
||||||
|
|
||||||
|
a
|
||||||
|
{
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
padding: .5em;
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active
|
||||||
|
{
|
||||||
|
background-color: #0066cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.active)
|
||||||
|
{
|
||||||
|
background-color: #004d99;
|
||||||
|
}
|
||||||
|
|
||||||
|
> svg
|
||||||
|
{
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab
|
||||||
|
{
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings
|
||||||
{
|
{
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
|
|
||||||
@ -179,11 +340,30 @@ h1
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-preview
|
.content
|
||||||
{
|
{
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 2em;
|
||||||
|
padding-left: 3em;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
@media print
|
||||||
|
{
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1
|
||||||
|
{
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
.preview
|
.preview
|
||||||
{
|
{
|
||||||
margin-bottom: 1em;
|
margin-bottom: 2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
136
src/components/CuttingList.vue
Normal file
136
src/components/CuttingList.vue
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<table class="list">
|
||||||
|
<tr>
|
||||||
|
<th>Layer</th>
|
||||||
|
<th>Wood species</th>
|
||||||
|
<th class="dimension">Width</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="(layer, index) in layers">
|
||||||
|
<td>{{ index + 1 }}</td>
|
||||||
|
<td>{{ getLayerWood(index) }}</td>
|
||||||
|
<td class="dimension">{{ getLayerWidth(index) }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Bill of materials</h2>
|
||||||
|
<table class="list">
|
||||||
|
<tr>
|
||||||
|
<th>Wood species</th>
|
||||||
|
<th class="dimension">Thickness</th>
|
||||||
|
<th class="dimension">Length</th>
|
||||||
|
<th class="dimension">Width</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="stock in bom">
|
||||||
|
<td>{{ stock.woodName }}</td>
|
||||||
|
<td class="dimension">{{ display(settings.boardThickness) }}</td>
|
||||||
|
<td class="dimension">{{ display(settings.boardLength) }}</td>
|
||||||
|
<td class="dimension">{{ display(stock.width) }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { units } from '../lib/units';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
settings() { return this.$store.state.settings; },
|
||||||
|
layers() { return this.$store.state.boards[0].layers; },
|
||||||
|
wood() { return this.$store.state.wood; },
|
||||||
|
|
||||||
|
bom()
|
||||||
|
{
|
||||||
|
const woodTally = {};
|
||||||
|
|
||||||
|
this.layers.forEach(layer =>
|
||||||
|
{
|
||||||
|
if (woodTally.hasOwnProperty(layer.wood))
|
||||||
|
woodTally[layer.wood] += layer.width + this.settings.bladeKerf;
|
||||||
|
else
|
||||||
|
woodTally[layer.wood] = layer.width;
|
||||||
|
});
|
||||||
|
|
||||||
|
const bom = [];
|
||||||
|
|
||||||
|
for (let wood in woodTally)
|
||||||
|
{
|
||||||
|
if (!woodTally.hasOwnProperty(wood))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bom.push({
|
||||||
|
woodName: wood !== null && wood >= 0 && wood < this.wood.length ? this.wood[wood].name : '',
|
||||||
|
width: woodTally[wood]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return bom;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
getLayerWood(index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= this.layers.length)
|
||||||
|
return '';
|
||||||
|
|
||||||
|
const woodIndex = this.layers[index].wood;
|
||||||
|
if (woodIndex === null || woodIndex < 0 || woodIndex >= this.wood.length)
|
||||||
|
return '';
|
||||||
|
|
||||||
|
return this.wood[woodIndex].name;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
getLayerWidth(index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= this.layers.length)
|
||||||
|
return '';
|
||||||
|
|
||||||
|
return this.display(this.layers[index].width);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
display(value)
|
||||||
|
{
|
||||||
|
return units.display(value, this.settings.units);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
h2
|
||||||
|
{
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list
|
||||||
|
{
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 3em;
|
||||||
|
|
||||||
|
th, td
|
||||||
|
{
|
||||||
|
padding: .25em;
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dimension
|
||||||
|
{
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) td
|
||||||
|
{
|
||||||
|
background-color: #555555;
|
||||||
|
|
||||||
|
@media print
|
||||||
|
{
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -102,3 +102,24 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dimensions
|
||||||
|
{
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
svg
|
||||||
|
{
|
||||||
|
@media screen
|
||||||
|
{
|
||||||
|
box-shadow: 0 0 3em black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print
|
||||||
|
{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -48,7 +48,12 @@ export default {
|
|||||||
if (stripAndKerf === 0)
|
if (stripAndKerf === 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return Math.floor((this.settings.boardLength + this.settings.bladeKerf) / stripAndKerf);
|
let stripsPerBoard = (this.settings.boardLength + this.settings.bladeKerf) / stripAndKerf;
|
||||||
|
|
||||||
|
// Try to account for rounding errors
|
||||||
|
stripsPerBoard = units.limitDecimals(stripsPerBoard, 3);
|
||||||
|
|
||||||
|
return Math.floor(stripsPerBoard);
|
||||||
},
|
},
|
||||||
|
|
||||||
boardWidth()
|
boardWidth()
|
||||||
@ -132,3 +137,24 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dimensions
|
||||||
|
{
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
svg
|
||||||
|
{
|
||||||
|
@media screen
|
||||||
|
{
|
||||||
|
box-shadow: 0 0 3em black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print
|
||||||
|
{
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<span class="header"> </span>
|
<span class="header"> </span>
|
||||||
<span class="header">Wood type</span>
|
<span class="header">Wood species</span>
|
||||||
<span class="header">Width</span>
|
<span class="header">Width</span>
|
||||||
<span class="header"> </span>
|
<span class="header"> </span>
|
||||||
|
|
||||||
@ -177,12 +177,12 @@ export default {
|
|||||||
|
|
||||||
&.dropTargetAbove
|
&.dropTargetAbove
|
||||||
{
|
{
|
||||||
border-top: solid 1px black;
|
border-top: solid 1px white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dropTargetBelow
|
&.dropTargetBelow
|
||||||
{
|
{
|
||||||
border-bottom: solid 1px black;
|
border-bottom: solid 1px white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,9 +61,10 @@ export default {
|
|||||||
|
|
||||||
h2
|
h2
|
||||||
{
|
{
|
||||||
|
color: #808080;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-bottom: 0;
|
margin-bottom: .25em;
|
||||||
grid-column: 1 / 3;
|
grid-column: 1 / 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wood">
|
<div class="wood">
|
||||||
<div class="add">
|
<div class="add">
|
||||||
<button @click="addWood()">Add wood type</button>
|
<button @click="addWood()">Add wood species</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="header"> </span>
|
|
||||||
<span class="header">Name</span>
|
<span class="header">Name</span>
|
||||||
<span class="header">Colour</span>
|
<span class="header">Colour</span>
|
||||||
<span class="header"> </span>
|
<span class="header"> </span>
|
||||||
|
|
||||||
<template v-for="(item, index) in wood">
|
<template v-for="(item, index) in wood">
|
||||||
<span> </span>
|
|
||||||
<input type="text" class="name" v-model="item.name" />
|
<input type="text" class="name" v-model="item.name" />
|
||||||
<input type="color" class="color" v-model="item.color" />
|
<input type="color" class="color" v-model="item.color" />
|
||||||
|
|
||||||
@ -48,12 +46,12 @@ export default {
|
|||||||
.wood
|
.wood
|
||||||
{
|
{
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
grid-template-columns: 3em 20em 5em 3em;
|
grid-template-columns: 23em 5em 3em;
|
||||||
grid-column-gap: 1em;
|
grid-column-gap: 1em;
|
||||||
|
|
||||||
.add
|
.add
|
||||||
{
|
{
|
||||||
grid-column: 2 / 5;
|
grid-column: 1 / 4;
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
src/main.js
17
src/main.js
@ -1,5 +1,14 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue';
|
||||||
import App from './App.vue'
|
import App from './App.vue';
|
||||||
import store from './store'
|
import store from './store';
|
||||||
|
|
||||||
createApp(App).use(store).mount('#app')
|
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||||
|
import { faSlidersH, faLayerGroup, faSave, faPrint, faInfoCircle, faTree } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||||
|
|
||||||
|
library.add(faSlidersH, faLayerGroup, faSave, faPrint, faInfoCircle, faTree);
|
||||||
|
|
||||||
|
createApp(App)
|
||||||
|
.use(store)
|
||||||
|
.component('font-awesome-icon', FontAwesomeIcon)
|
||||||
|
.mount('#app');
|
||||||
|
69
src/mixins.scss
Normal file
69
src/mixins.scss
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
$breakpoints: (
|
||||||
|
xs: 576px,
|
||||||
|
sm: 768px,
|
||||||
|
md: 992px,
|
||||||
|
lg: 1200px
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@mixin respond-above($breakpoint)
|
||||||
|
{
|
||||||
|
@if map-has-key($breakpoints, $breakpoint)
|
||||||
|
{
|
||||||
|
$breakpoint-value: map-get($breakpoints, $breakpoint);
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-value)
|
||||||
|
{
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@else
|
||||||
|
{
|
||||||
|
@warn 'Invalid breakpoint: #{$breakpoint}.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@mixin respond-below($breakpoint)
|
||||||
|
{
|
||||||
|
@if map-has-key($breakpoints, $breakpoint)
|
||||||
|
{
|
||||||
|
$breakpoint-value: map-get($breakpoints, $breakpoint);
|
||||||
|
|
||||||
|
@media (max-width: ($breakpoint-value - 1))
|
||||||
|
{
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@else
|
||||||
|
{
|
||||||
|
@warn 'Invalid breakpoint: #{$breakpoint}.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@mixin respond-between($lower, $upper)
|
||||||
|
{
|
||||||
|
@if map-has-key($breakpoints, $lower) and map-has-key($breakpoints, $upper)
|
||||||
|
{
|
||||||
|
$lower-breakpoint: map-get($breakpoints, $lower);
|
||||||
|
$upper-breakpoint: map-get($breakpoints, $upper);
|
||||||
|
|
||||||
|
@media (min-width: $lower-breakpoint) and (max-width: ($upper-breakpoint - 1))
|
||||||
|
{
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@else
|
||||||
|
{
|
||||||
|
@if (map-has-key($breakpoints, $lower) == false)
|
||||||
|
{
|
||||||
|
@warn 'Your lower breakpoint was invalid: #{$lower}.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (map-has-key($breakpoints, $upper) == false)
|
||||||
|
{
|
||||||
|
@warn 'Your upper breakpoint was invalid: #{$upper}.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user