346 lines
8.7 KiB
Vue
346 lines
8.7 KiB
Vue
<template>
|
|
<div class="preview">
|
|
<div class="dimensions">Dimensions: {{ display(boardWidth) }} x {{ display(boardHeight) }} x {{ display(settings.crosscutWidth) }}</div>
|
|
|
|
<div v-if="boards.length > 1" class="draghint hideOnPrint">Click and drag strips to reorder them. Click once to reverse the direction.</div>
|
|
|
|
<svg
|
|
:width="viewportWidth"
|
|
:height="viewportHeight"
|
|
:viewBox="viewBox"
|
|
:class="{ dragging: dropTarget !== null, highlightBoard: settings.highlightBoard && volatile.highlightedBoard !== null, highlightLayer: settings.highlightLayer && volatile.highlightedBoard !== null && volatile.highlightedLayer !== null }">
|
|
<defs>
|
|
<g v-for="(board, boardIndex) in boards" :id="'strip' + boardIndex" class="boardLayer" :class="{ highlightedBoard: boardIndex === volatile.highlightedBoard }">
|
|
<rect
|
|
v-for="(layer, index) in board.layers"
|
|
:width="toPixels(board.thickness)"
|
|
:height="toPixels(layer.width)"
|
|
x="0"
|
|
:y="getBoardLayerOffset(board, index)"
|
|
:style="getBoardLayerStyle(board, index)"
|
|
class="layer"
|
|
:class="{ highlightedLayer: boardIndex === volatile.highlightedBoard && index == volatile.highlightedLayer }" />
|
|
</g>
|
|
<g id="dropTarget">
|
|
<line x1="0" y1="0" x2="0" :y2="boardPixelHeight" style="stroke: white; stroke-width: 2" />
|
|
</g>
|
|
</defs>
|
|
|
|
<use
|
|
v-for="(layer, index) in endGrain"
|
|
:ref="'strip' + index"
|
|
:href="'#strip' + layer.board"
|
|
:x="getLayerOffset(index)"
|
|
y="0"
|
|
:transform="getLayerTransform(index)"
|
|
@mousedown.prevent="mouseDown(index, $event)" />
|
|
|
|
<use
|
|
v-if="dropTarget !== null"
|
|
href="#dropTarget"
|
|
:x="getLayerOffset(dropTarget)" />
|
|
</svg>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { units } from '../lib/units';
|
|
|
|
export default {
|
|
props: {
|
|
scale: Number
|
|
},
|
|
|
|
|
|
data()
|
|
{
|
|
return {
|
|
dragIndex: null,
|
|
dropTarget: null
|
|
};
|
|
},
|
|
|
|
|
|
computed: {
|
|
volatile() { return this.$store.state.volatile; },
|
|
settings() { return this.$store.state.settings; },
|
|
boards() { return this.$store.state.boards; },
|
|
wood() { return this.$store.state.wood; },
|
|
endGrain() { return this.$store.state.endGrain },
|
|
|
|
boardWidth()
|
|
{
|
|
const self = this;
|
|
|
|
return this.endGrain
|
|
.map(layer => layer.board >= 0 && layer.board < self.boards.length ? self.boards[layer.board].thickness : 0)
|
|
.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
|
|
},
|
|
|
|
boardHeight()
|
|
{
|
|
// Calculate the total width of each board (adding all the layers, inner map/reduce),
|
|
// then use the maximum value (outer map/reduce)
|
|
return this.boards
|
|
.map(board => board.layers
|
|
.map(layer => layer.width)
|
|
.reduce((accumulator, currentValue) => accumulator + currentValue, 0))
|
|
.reduce((accumulator, currentValue) => currentValue > accumulator ? currentValue : accumulator, 0);
|
|
},
|
|
|
|
boardPixelWidth()
|
|
{
|
|
return this.toPixels(this.boardWidth);
|
|
},
|
|
|
|
boardPixelHeight()
|
|
{
|
|
return this.toPixels(this.boardHeight);
|
|
},
|
|
|
|
viewportWidth() { return Math.floor(this.boardPixelWidth * this.scale); },
|
|
viewportHeight() { return Math.floor(this.boardPixelHeight * this.scale); },
|
|
viewBox() { return '0 0 ' + this.boardPixelWidth + ' ' + this.boardPixelHeight; }
|
|
},
|
|
|
|
|
|
methods: {
|
|
toPixels(value)
|
|
{
|
|
return units.toPixels(value, this.settings.units);
|
|
},
|
|
|
|
display(value)
|
|
{
|
|
return units.display(value, this.settings.units);
|
|
},
|
|
|
|
getBoardLayerOffset(board, index)
|
|
{
|
|
if (index < 0 || index >= board.layers.length)
|
|
return 0;
|
|
|
|
let offset = 0;
|
|
|
|
for (let i = 0; i < index; i++)
|
|
offset += board.layers[i].width;
|
|
|
|
return this.toPixels(offset);
|
|
},
|
|
|
|
getBoardLayerStyle(board, index)
|
|
{
|
|
if (index < 0 || index >= board.layers.length)
|
|
return 'fill: fuchsia';
|
|
|
|
const woodIndex = board.layers[index].wood;
|
|
if (woodIndex < 0 || woodIndex >= this.wood.length)
|
|
return '';
|
|
|
|
const borderStyle = this.settings.borders
|
|
? '; stroke-width: 1; stroke: black'
|
|
: '';
|
|
|
|
return 'fill: ' + this.wood[woodIndex].color + borderStyle;
|
|
},
|
|
|
|
getLayerOffset(index)
|
|
{
|
|
if (index < 0 || index > this.endGrain.length)
|
|
return 0;
|
|
|
|
let offset = 0;
|
|
|
|
for (let i = 0; i < index; i++)
|
|
{
|
|
const boardIndex = this.endGrain[i].board;
|
|
if (boardIndex >= 0 && boardIndex < this.boards.length)
|
|
offset += this.boards[boardIndex].thickness;
|
|
}
|
|
|
|
return this.toPixels(offset);
|
|
},
|
|
|
|
getLayerTransform(index)
|
|
{
|
|
let reversed = false;
|
|
|
|
switch (this.settings.direction)
|
|
{
|
|
case 'alternate':
|
|
reversed = (index % 2) == 0;
|
|
break;
|
|
|
|
case 'custom':
|
|
reversed = index >= 0 && index < this.endGrain.length && this.endGrain[index].reversed;
|
|
break;
|
|
}
|
|
|
|
return reversed ? 'scale(1, -1) translate(0, -' + this.boardPixelHeight + ')' : '';
|
|
},
|
|
|
|
reverseLayer(index)
|
|
{
|
|
if (this.settings.direction !== 'custom')
|
|
return;
|
|
|
|
if (index < 0 || index >= this.endGrain.length)
|
|
return;
|
|
|
|
this.endGrain[index].reversed = !this.endGrain[index].reversed;
|
|
},
|
|
|
|
|
|
mouseDown(index, event)
|
|
{
|
|
const self = this;
|
|
const startX = event.pageX;
|
|
let dragging = false;
|
|
|
|
const dragMouseMove = (moveEvent) =>
|
|
{
|
|
if (!dragging)
|
|
{
|
|
if (Math.abs(moveEvent.pageX - startX) >= 5)
|
|
{
|
|
self.dragIndex = index;
|
|
dragging = true;
|
|
}
|
|
}
|
|
|
|
if (dragging)
|
|
self.dropTarget = self.getTargetStrip(moveEvent.pageX);
|
|
};
|
|
|
|
let dragMouseUp;
|
|
dragMouseUp = () =>
|
|
{
|
|
document.removeEventListener('mousemove', dragMouseMove);
|
|
document.removeEventListener('mouseup', dragMouseUp);
|
|
|
|
if (dragging)
|
|
{
|
|
if (self.dragIndex !== self.dropTarget)
|
|
self.$store.commit('moveEndgrain', { from: self.dragIndex, to: self.dropTarget });
|
|
|
|
self.dropTarget = null;
|
|
self.dragIndex = null;
|
|
}
|
|
else
|
|
self.reverseLayer(index);
|
|
};
|
|
|
|
document.addEventListener('mousemove', dragMouseMove);
|
|
document.addEventListener('mouseup', dragMouseUp);
|
|
},
|
|
|
|
|
|
getTargetStrip(xPos)
|
|
{
|
|
if (this.endGrain.length == 0)
|
|
return null;
|
|
|
|
const firstStrip = this.getPageOffsetRect(this.$refs.strip0);
|
|
const lastStrip = this.getPageOffsetRect(this.$refs['strip' + (this.endGrain.length - 1)]);
|
|
|
|
// On or above the first item
|
|
if (xPos <= firstStrip.right)
|
|
return 0;
|
|
|
|
// Below the last item
|
|
if (xPos >= lastStrip.right)
|
|
return this.endGrain.length;
|
|
|
|
// On the last item
|
|
if (xPos >= lastStrip.left)
|
|
return this.endGrain.length - 1;
|
|
|
|
// Check the previous target first, as it is most likely unchanged due to how
|
|
// often mouseMove events occur
|
|
if (this.dropTarget !== null && this.dropTarget > 0 && this.dropTarget < this.endGrain.length - 1)
|
|
{
|
|
const currentTarget = this.getPageOffsetRect(this.$refs['strip' + this.dropTarget]);
|
|
if (xPos >= currentTarget.left && xPos < currentTarget.right)
|
|
return this.dropTarget;
|
|
}
|
|
|
|
// Just loop through all the strips, there shouldn't be enough to warrant anything more efficient
|
|
for (let i = 1; i < this.endGrain.length - 1; i++)
|
|
{
|
|
const testTarget = this.getPageOffsetRect(this.$refs['strip' + i]);
|
|
if (xPos >= testTarget.left && xPos < testTarget.right)
|
|
return i;
|
|
}
|
|
|
|
// This should never occur, so it probably will!
|
|
return null;
|
|
},
|
|
|
|
|
|
getPageOffsetRect(element)
|
|
{
|
|
const clientRect = element.getBoundingClientRect();
|
|
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
|
|
return {
|
|
top: clientRect.top + scrollTop,
|
|
left: clientRect.left + scrollLeft,
|
|
right: clientRect.right + scrollLeft,
|
|
bottom: clientRect.bottom + scrollTop
|
|
};
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.dimensions
|
|
{
|
|
margin-bottom: .5em;
|
|
}
|
|
|
|
|
|
.draghint
|
|
{
|
|
margin-bottom: 2em;
|
|
}
|
|
|
|
|
|
svg
|
|
{
|
|
user-select: none;
|
|
|
|
@media screen
|
|
{
|
|
box-shadow: 0 0 3em black;
|
|
}
|
|
|
|
@media print
|
|
{
|
|
max-width: 100%;
|
|
}
|
|
|
|
&.dragging
|
|
{
|
|
cursor: grabbing;
|
|
}
|
|
|
|
|
|
&.highlightBoard
|
|
{
|
|
.boardLayer:not(.highlightedBoard)
|
|
{
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
&.highlightLayer
|
|
{
|
|
.layer:not(.highlightedLayer)
|
|
{
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
}
|
|
</style> |