diff options
| author | Terry Truong <terry06890@gmail.com> | 2022-03-28 18:54:10 +1100 |
|---|---|---|
| committer | Terry Truong <terry06890@gmail.com> | 2022-03-28 18:54:10 +1100 |
| commit | c9a116654004c014257a2aac8d6cf82bc7d7d580 (patch) | |
| tree | 2b9f5cf11093a1969a8292bdd7c2eeb71e65b5c0 /src | |
| parent | bcf60b4e1aa2283821010715b33009d8f4a48207 (diff) | |
Merge TileImg back into Tile, adding nonAbsPos prop for AncestryBar usage
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/AncestryBar.vue | 17 | ||||
| -rw-r--r-- | src/components/Tile.vue | 158 | ||||
| -rw-r--r-- | src/components/TileImg.vue | 105 |
3 files changed, 114 insertions, 166 deletions
diff --git a/src/components/AncestryBar.vue b/src/components/AncestryBar.vue index 46f1edf..0cdadf8 100644 --- a/src/components/AncestryBar.vue +++ b/src/components/AncestryBar.vue @@ -1,7 +1,7 @@ <script lang="ts"> import {defineComponent, PropType} from 'vue'; import {LayoutNode} from '../layout'; -import TileImg from './TileImg.vue' +import Tile from './Tile.vue' export default defineComponent({ props: { @@ -23,6 +23,13 @@ export default defineComponent({ tileSz(){ return (this.wideArea ? this.dims[1] : this.dims[0]) - (this.tileMargin * 2) - this.scrollBarOffset; }, + usedNodes(){ + return this.nodes.map(n => { + let newNode = new LayoutNode(n.tolNode, []); + newNode.dims = [this.tileSz, this.tileSz]; + return newNode; + }); + }, hasOverflow(){ let len = this.tileMargin + (this.tileSz + this.tileMargin) * this.nodes.length; return len > (this.wideArea ? this.dims[0] : this.dims[1]); @@ -59,7 +66,7 @@ export default defineComponent({ } }, components: { - TileImg, + Tile, }, emits: ['detached-ancestor-clicked', 'info-icon-clicked'], }); @@ -67,8 +74,8 @@ export default defineComponent({ <template> <div :style="styles"> - <tile-img v-for="node in nodes" :key="node.tolNode.name" - :layoutNode="node" :tileSz="tileSz" :options="options" - @click="onClick(node)" @info-icon-clicked="onInnerInfoIconClicked"/> + <tile v-for="(node, idx) in usedNodes" :key="node.tolNode.name" :layoutNode="node" + :nonAbsPos="true" :headerSz="0" :tileSpacing="0" :options="options" + @leaf-clicked="onClick(nodes[idx])" @info-icon-clicked="onInnerInfoIconClicked"/> </div> </template> 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> diff --git a/src/components/TileImg.vue b/src/components/TileImg.vue deleted file mode 100644 index 3de0fa8..0000000 --- a/src/components/TileImg.vue +++ /dev/null @@ -1,105 +0,0 @@ -<script lang="ts"> -import {defineComponent, PropType} from 'vue'; -import InfoIcon from './icon/InfoIcon.vue'; -import {LayoutNode} from '../layout'; - -export default defineComponent({ - props: { - layoutNode: {type: Object as PropType<LayoutNode>, required: true}, - tileSz: {type: Number, required: true}, //px (edge length) - options: {type: Object, required: true}, - }, - data(){ - return { - highlight: false, - infoMouseOver: false, - }; - }, - computed: { - isExpandable(){ - return this.layoutNode.tolNode.children.length > 0; - }, - styles(): Record<string,string> { - return { - // Sizing - width: this.tileSz + 'px', - height: this.tileSz + 'px', - minWidth: this.tileSz + 'px', - minHeight: this.tileSz + 'px', - // 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), - }; - }, - headerStyles(): 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, - }; - }, - }, - methods: { - onMouseEnter(evt: Event){ - if (this.isExpandable){ - this.highlight = true; - } - }, - onMouseLeave(evt: Event){ - if (this.isExpandable){ - this.highlight = false; - } - }, - onMouseDown(evt: Event){ - this.highlight = false; - }, - onInfoMouseEnter(evt: Event){ - this.infoMouseOver = true; - }, - onInfoMouseLeave(evt: Event){ - this.infoMouseOver = false; - }, - onInfoClick(evt: Event){ - this.$emit('info-icon-clicked', this.layoutNode); - }, - }, - components: {InfoIcon, }, - emits: ['info-icon-clicked'], -}); -</script> - -<template> -<div :style="styles" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @mousedown="onMouseDown" - :class="isExpandable ? ['hover:cursor-pointer'] : []"> - <h1 :style="headerStyles">{{layoutNode.tolNode.name}}</h1> - <info-icon :style="infoIconStyles" class="hover:cursor-pointer" - @mouseenter="onInfoMouseEnter" @mouseleave="onInfoMouseLeave" - @click.stop="onInfoClick" @mousedown.stop @mouseup.stop/> -</div> -</template> |
