1
0
mirror of synced 2024-11-17 04:23:50 +00:00

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:
Mark van Renswoude 2020-12-29 19:56:20 +01:00
parent 3ce66c0f21
commit f65a7f37e3
23 changed files with 567 additions and 86 deletions

View File

@ -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

View File

@ -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}

View 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}

View File

@ -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

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 one or more lines are too long

File diff suppressed because one or more lines are too long

29
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -1,48 +1,91 @@
<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>
<h1>Layers</h1> <a :class="{ active: tab === 'layers' }" @click="tab = 'layers'"><font-awesome-icon icon="layer-group" size="2x" fixed-width /> Layers</a>
<Layers class="block" /> <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>
<h1>Wood types</h1> <a :class="{ active: tab === 'about' }" @click="tab = 'about'"><font-awesome-icon icon="info-circle" size="2x" fixed-width /> About</a>
<Wood class="block" />
<h1>Save / load</h1>
<div class="loadSave block">
<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.
</p>
<p>
<input type="text" v-model="saveFilename" />
<button @click="save()">Save</button>
</p>
<p>
<input type="file" ref="loadFile" accept=".json" />
<button @click="load()">Load</button>
</p>
</div> </div>
<h1>About / feedback</h1> <div class="tabs">
<div class="about block"> <div class="tab" v-show="tab === 'settings'">
<p> <Settings />
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. </div>
</p>
<p> <div class="tab" v-show="tab === 'layers'">
Heavily inspired by <a href="http://www.lastalias.com/cbdesigner/">CBdesigner</a>. <Layers />
</p> </div>
<div class="tab" v-show="tab === 'wood'">
<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>
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>
<input type="text" v-model="saveFilename" />
<button @click="save">Save</button>
</p>
<p>
<input type="file" ref="loadFile" accept=".json" />
<button @click="load">Load</button>
</p>
</div>
<div class="tab" v-show="tab === '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>
</div> </div>
<div class="app-preview"> <div class="content">
<h1>Edge grain</h1> <div :class="{ hideOnPrint: !printEdgeGrain }">
<EdgeGrainPreview :scale="1" /> <h1>Edge grain</h1>
<EdgeGrainPreview :scale="1" />
</div>
<h1>End grain</h1> <div :class="{ hideOnPrint: !printEndGrain }">
<EndGrainPreview :scale="1" /> <h1>End grain</h1>
<EndGrainPreview :scale="1" />
</div>
<div :class="{ hideOnPrint: !printCuttingList }">
<h1>Cutting list</h1>
<CuttingList />
</div>
</div> </div>
</template> </template>
@ -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: white; {
color: black; background-color: #444444;
color: white;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: none;
@media print
{
background-color: white;
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;
} }
} }

View 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>

View File

@ -101,4 +101,25 @@ 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>

View File

@ -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()
@ -131,4 +136,25 @@ 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>

View File

@ -10,7 +10,7 @@
<span class="header">&nbsp;</span> <span class="header">&nbsp;</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">&nbsp;</span> <span class="header">&nbsp;</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;
} }
} }

View File

@ -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;
} }
} }

View File

@ -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">&nbsp;</span>
<span class="header">Name</span> <span class="header">Name</span>
<span class="header">Colour</span> <span class="header">Colour</span>
<span class="header">&nbsp;</span> <span class="header">&nbsp;</span>
<template v-for="(item, index) in wood"> <template v-for="(item, index) in wood">
<span>&nbsp;</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;
} }

View File

@ -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
View 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}.';
}
}
}