aboutsummaryrefslogtreecommitdiff
path: root/src/components/Tile.vue
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/Tile.vue')
-rw-r--r--src/components/Tile.vue158
1 files changed, 102 insertions, 56 deletions
diff --git a/src/components/Tile.vue b/src/components/Tile.vue
index 986ca77..1e88ef9 100644
--- a/src/components/Tile.vue
+++ b/src/components/Tile.vue
@@ -1,20 +1,22 @@
<script lang="ts">
import {defineComponent, PropType} from 'vue';
+import InfoIcon from './icon/InfoIcon.vue';
import {LayoutNode} from '../layout';
-import TileImg from './TileImg.vue';
// Component holds a tree-node structure representing a tile or tile-group to be rendered
export default defineComponent({
props: {
layoutNode: {type: Object as PropType<LayoutNode>, required: true},
options: {type: Object, required: true},
+ nonAbsPos: {type: Boolean, default: false}, // Don't use absolute positioning (only applies for leaf nodes)
// Layout settings from parent
headerSz: {type: Number, required: true},
tileSpacing: {type: Number, required: true},
},
data(){
return {
- nonLeafHighlight: false,
+ highlight: false,
+ infoMouseOver: false,
clickHoldTimer: 0, // Used to recognise a click-and-hold event
animating: false, // Used to prevent content overlap and overflow during transitions
}
@@ -37,7 +39,7 @@ export default defineComponent({
tileStyles(): Record<string,string> {
return {
// Places div using layoutNode, with centering if root
- position: 'absolute',
+ position: this.nonAbsPos && this.isLeaf ? 'static' : 'absolute',
left: (this.layoutNode.hidden ? 0 : this.layoutNode.pos[0]) + 'px',
top: (this.layoutNode.hidden ? 0 : this.layoutNode.pos[1]) + 'px',
width: (this.layoutNode.hidden ? 0 : this.layoutNode.dims[0]) + 'px',
@@ -55,13 +57,55 @@ export default defineComponent({
'--tileSpacing': this.tileSpacing + 'px',
};
},
+ leafStyles(): Record<string,string> {
+ return {
+ width: '100%',
+ height: '100%',
+ // Image
+ backgroundImage:
+ 'linear-gradient(to bottom, rgba(0,0,0,0.4), rgba(0,0,0,0) 40%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.4) 100%),' +
+ 'url(\'/img/' + this.layoutNode.tolNode.name.replaceAll('\'', '\\\'') + '.png\')',
+ backgroundSize: 'cover',
+ // Child layout
+ display: 'flex',
+ flexDirection: 'column',
+ // Other
+ borderRadius: this.options.borderRadius + 'px',
+ boxShadow: this.highlight ? this.options.shadowHighlight :
+ (this.layoutNode.hasFocus ? this.options.shadowFocused : this.options.shadowNormal),
+ };
+ },
+ leafHeaderStyles(): Record<string,string> {
+ return {
+ height: (this.options.imgTileFontSz + this.options.imgTilePadding * 2) + 'px',
+ lineHeight: this.options.imgTileFontSz + 'px',
+ fontSize: this.options.imgTileFontSz + 'px',
+ padding: this.options.imgTilePadding + 'px',
+ color: this.isExpandable ? this.options.expandableImgTileColor : this.options.imgTileColor,
+ // For ellipsis
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ };
+ },
+ infoIconStyles(): Record<string,string> {
+ return {
+ width: this.options.infoIconSz + 'px',
+ height: this.options.infoIconSz + 'px',
+ marginTop: 'auto',
+ marginBottom: this.options.infoIconPadding + 'px',
+ marginRight: this.options.infoIconPadding + 'px',
+ alignSelf: 'flex-end',
+ color: this.infoMouseOver ? this.options.infoIconHoverColor : this.options.infoIconColor,
+ };
+ },
nonLeafStyles(): Record<string,string> {
let temp = {
width: '100%',
height: '100%',
backgroundColor: this.nonLeafBgColor,
borderRadius: this.options.borderRadius + 'px',
- boxShadow: this.animating ? 'none' : (this.nonLeafHighlight ? this.options.shadowHighlight :
+ boxShadow: this.animating ? 'none' : (this.highlight ? this.options.shadowHighlight :
(this.layoutNode.hasFocus ? this.options.shadowFocused : this.options.shadowNormal)),
};
if (this.layoutNode.sepSweptArea != null){
@@ -92,7 +136,7 @@ export default defineComponent({
let commonStyles = {
position: 'absolute',
backgroundColor: this.nonLeafBgColor,
- boxShadow: this.animating ? 'none' : (this.nonLeafHighlight ? this.options.shadowHighlight :
+ boxShadow: this.animating ? 'none' : (this.highlight ? this.options.shadowHighlight :
(this.layoutNode.hasFocus ? this.options.shadowFocused : this.options.shadowNormal)),
transitionDuration: this.options.transitionDuration + 'ms',
transitionProperty: 'left, top, width, height',
@@ -136,71 +180,69 @@ export default defineComponent({
},
},
methods: {
- // Leaf click handling
- onLeafMouseDown(){
+ // Click handling
+ onMouseDown(){
+ this.highlight = false;
clearTimeout(this.clickHoldTimer);
this.clickHoldTimer = setTimeout(() => {
this.clickHoldTimer = 0;
- this.onLeafClickHold();
+ this.onClickHold();
}, this.options.clickHoldDuration);
},
- onLeafMouseUp(){
- if (this.clickHoldTimer > 0){
- clearTimeout(this.clickHoldTimer);
- this.clickHoldTimer = 0;
- this.onLeafClick();
- }
- },
- onLeafClick(){
- if (!this.isExpandable){
- console.log('Ignored click on non-expandable node');
- return;
- }
- this.prepForTransition();
- this.$emit('leaf-clicked', this.layoutNode);
- },
- onLeafClickHold(){
- if (!this.isExpandable){
+ onClickHold(){
+ if (this.isLeaf && !this.isExpandable){
console.log('Ignored click-hold on non-expandable node');
return;
}
this.prepForTransition();
- this.$emit('leaf-click-held', this.layoutNode);
- },
- // Non-leaf click handling
- onHeaderMouseDown(){
- this.nonLeafHighlight = false;
- clearTimeout(this.clickHoldTimer);
- this.clickHoldTimer = setTimeout(() => {
- this.clickHoldTimer = 0;
- this.onHeaderClickHold();
- }, this.options.clickHoldDuration);
+ if (this.isLeaf){
+ this.$emit('leaf-click-held', this.layoutNode);
+ } else {
+ this.$emit('header-click-held', this.layoutNode);
+ }
},
- onHeaderMouseUp(){
+ onMouseUp(){
if (this.clickHoldTimer > 0){
clearTimeout(this.clickHoldTimer);
this.clickHoldTimer = 0;
- this.onHeaderClick();
+ this.onClick();
}
},
- onHeaderClick(){
- this.prepForTransition();
- this.$emit('header-clicked', this.layoutNode);
- },
- onHeaderClickHold(){
+ onClick(){
+ if (this.isLeaf && !this.isExpandable){
+ console.log('Ignored click on non-expandable node');
+ return;
+ }
this.prepForTransition();
- this.$emit('header-click-held', this.layoutNode);
+ if (this.isLeaf){
+ this.$emit('leaf-clicked', this.layoutNode);
+ } else {
+ this.$emit('header-clicked', this.layoutNode);
+ }
},
prepForTransition(){
this.animating = true;
setTimeout(() => {this.animating = false}, this.options.transitionDuration);
},
+ onInfoClick(evt: Event){
+ this.$emit('info-icon-clicked', this.layoutNode);
+ },
// For coloured-outlines on hovered-over leaf-tiles or non-leaf-headers
- onNonLeafMouseEnter(evt: Event){
- this.nonLeafHighlight = true;
+ onMouseEnter(evt: Event){
+ if (!this.isLeaf || this.isExpandable){
+ this.highlight = true;
+ }
+ },
+ onMouseLeave(evt: Event){
+ if (!this.isLeaf || this.isExpandable){
+ this.highlight = false;
+ }
},
- onNonLeafMouseLeave(evt: Event){
- this.nonLeafHighlight = false;
+ onInfoMouseEnter(evt: Event){
+ this.infoMouseOver = true;
+ },
+ onInfoMouseLeave(evt: Event){
+ this.infoMouseOver = false;
},
// Child event propagation
onInnerLeafClicked(data: LayoutNode){
@@ -226,29 +268,33 @@ export default defineComponent({
},
},
name: 'tile', // Need this to use self in template
- components: {
- TileImg,
- },
+ components: {InfoIcon, },
emits: ['leaf-clicked', 'header-clicked', 'leaf-click-held', 'header-click-held', 'info-icon-clicked'],
});
</script>
<template>
<div :style="tileStyles">
- <tile-img v-if="isLeaf" :layoutNode="layoutNode" :tileSz="layoutNode.dims[0]" :options="options"
- @mousedown="onLeafMouseDown" @mouseup="onLeafMouseUp" @info-icon-clicked="onInnerInfoIconClicked"/>
+ <div v-if="isLeaf" :style="leafStyles" :class="isExpandable ? ['hover:cursor-pointer'] : []"
+ @mouseenter="onMouseEnter" @mouseleave="onMouseLeave"
+ @mousedown="onMouseDown" @mouseup="onMouseUp">
+ <h1 :style="leafHeaderStyles">{{layoutNode.tolNode.name}}</h1>
+ <info-icon :style="infoIconStyles" class="hover:cursor-pointer"
+ @mouseenter="onInfoMouseEnter" @mouseleave="onInfoMouseLeave"
+ @click.stop="onInfoClick" @mousedown.stop @mouseup.stop/>
+ </div>
<div v-else :style="nonLeafStyles" ref="nonLeaf">
<h1 v-if="showHeader" :style="nonLeafHeaderStyles" class="hover:cursor-pointer"
- @mouseenter="onNonLeafMouseEnter" @mouseleave="onNonLeafMouseLeave"
- @mousedown="onHeaderMouseDown" @mouseup="onHeaderMouseUp">
+ @mouseenter="onMouseEnter" @mouseleave="onMouseLeave"
+ @mousedown="onMouseDown" @mouseup="onMouseUp">
{{layoutNode.tolNode.name}}
</h1>
<div :style="sepSweptAreaStyles" ref="sepSweptArea"
:class="layoutNode?.sepSweptArea?.sweptLeft ? 'hide-right-edge' : 'hide-top-edge'">
<h1 v-if="layoutNode?.sepSweptArea?.sweptLeft === false"
:style="nonLeafHeaderStyles" class="hover:cursor-pointer"
- @mouseenter="onNonLeafMouseEnter" @mouseleave="onNonLeafMouseLeave"
- @mousedown="onHeaderMouseDown" @mouseup="onHeaderMouseUp">
+ @mouseenter="onMouseEnter" @mouseleave="onMouseLeave"
+ @mousedown="onMouseDown" @mouseup="onMouseUp">
{{layoutNode.tolNode.name}}
</h1>
</div>