diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.vue | 3 | ||||
| -rw-r--r-- | src/components/AncestryBar.vue | 2 | ||||
| -rw-r--r-- | src/components/Tile.vue | 83 | ||||
| -rw-r--r-- | src/components/TileInfoModal.vue | 80 | ||||
| -rw-r--r-- | src/tol.ts | 2 |
5 files changed, 124 insertions, 46 deletions
diff --git a/src/App.vue b/src/App.vue index e1da1f2..e26de01 100644 --- a/src/App.vue +++ b/src/App.vue @@ -638,7 +638,8 @@ export default defineComponent({ @search-close="searchOpen = false" @search-node="onSearchNode" @info-icon-click="onInfoIconClick"/> </transition> <transition name="fade"> - <tile-info-modal v-if="infoModalNodeName != null" :nodeName="infoModalNodeName" :tolMap="tolMap" :uiOpts="uiOpts" + <tile-info-modal v-if="infoModalNodeName != null" + :nodeName="infoModalNodeName" :tolMap="tolMap" :lytOpts="lytOpts" :uiOpts="uiOpts" @info-modal-close="infoModalNodeName = null"/> </transition> <transition name="fade"> diff --git a/src/components/AncestryBar.vue b/src/components/AncestryBar.vue index ca865e9..5419f78 100644 --- a/src/components/AncestryBar.vue +++ b/src/components/AncestryBar.vue @@ -71,7 +71,7 @@ export default defineComponent({ onTileClick(node: LayoutNode){ this.$emit('detached-ancestor-click', node); }, - onInfoIconClick(data: LayoutNode){ + onInfoIconClick(data: string){ this.$emit('info-icon-click', data); } }, diff --git a/src/components/Tile.vue b/src/components/Tile.vue index 0a404e6..7f15f3c 100644 --- a/src/components/Tile.vue +++ b/src/components/Tile.vue @@ -86,6 +86,9 @@ export default defineComponent({ } return capitalizeWords(this.tolNode.commonName || this.layoutNode.name); }, + hasCompoundImage(): boolean { + return Array.isArray(this.tolNode.imgName); + }, isOverflownRoot(): boolean { return this.overflownDim > 0 && !this.layoutNode.hidden && this.layoutNode.children.length > 0; }, @@ -147,16 +150,30 @@ export default defineComponent({ return layoutStyles; }, leafStyles(): Record<string,string> { - return { - // Image (and scrims) - backgroundImage: this.tolNode.imgName != null ? - 'linear-gradient(to bottom, rgba(0,0,0,0.4), #0000 40%, #0000 60%, rgba(0,0,0,0.4) 100%),' + - 'url(\'/img/' + this.tolNode.imgName.replaceAll('\'', '\\\'') + '\')' : - 'none', - backgroundColor: '#1c1917', - backgroundSize: 'cover', - borderRadius: 'inherit', - }; + if (!this.hasCompoundImage){ + return { + // Image (and scrims) + backgroundImage: this.tolNode.imgName != null ? + 'linear-gradient(to bottom, rgba(0,0,0,0.4), #0000 40%, #0000 60%, rgba(0,0,0,0.4) 100%),' + + 'url(\'/img/' + (this.tolNode.imgName as string).replaceAll('\'', '\\\'') + '\')' : + 'none', + backgroundColor: '#1c1917', + backgroundSize: 'cover', + // Child layout + display: 'flex', + flexDirection: 'column', + // Other + borderRadius: 'inherit', + }; + } else { + return { + // Child layout + display: 'grid', + gridTemplateColumns: '1fr', + // Other + borderRadius: 'inherit', + }; + } }, leafHeaderStyles(): Record<string,string> { let numChildren = this.tolNode.children.length; @@ -181,6 +198,12 @@ export default defineComponent({ whiteSpace: 'nowrap', }; }, + leafFirstImgStyles(): Record<string,string> { + return this.leafSubImgStyles(0); + }, + leafSecondImgStyles(): Record<string,string> { + return this.leafSubImgStyles(1); + }, nonleafStyles(): Record<string,string> { let styles = { position: 'static', @@ -237,6 +260,8 @@ export default defineComponent({ minWidth: size, minHeight: size, margin: this.uiOpts.infoIconMargin + 'px', + marginTop: 'auto', + marginLeft: 'auto', }; }, sepSweptAreaStyles(): Record<string,string> { @@ -312,7 +337,7 @@ export default defineComponent({ // For scrolling to a focused child if overflownRoot hasFocusedChild(newVal, oldVal){ if (newVal && this.isOverflownRoot){ - let focusedChild = this.layoutNode.children.find(n => n.hasFocus) + let focusedChild = this.layoutNode.children.find(n => n.hasFocus)! let bottomY = focusedChild.pos[1] + focusedChild.dims[1] + this.lytOpts.tileSpacing; let scrollTop = Math.max(0, bottomY - (this.overflownDim / 2)); // 'scrollTop' won't go over max this.$el.scrollTop = scrollTop; @@ -390,6 +415,21 @@ export default defineComponent({ } }, // Other + leafSubImgStyles(idx: number): Record<string,string> { + return { + width: '100%', + height: '100%', + // Image (and scrims) + backgroundImage: (this.tolNode.imgName![idx]! != null) ? + 'linear-gradient(to bottom, rgba(0,0,0,0.4), #0000 40%, #0000 60%, rgba(0,0,0,0.4) 100%),' + + 'url(\'/img/' + this.tolNode.imgName![idx]!.replaceAll('\'', '\\\'') + '\')' : + 'none', + backgroundColor: '#1c1917', + backgroundSize: 'cover', + borderRadius: 'inherit', + clipPath: idx == 0 ? 'polygon(0 0, 100% 0, 0 100%)' : 'polygon(100% 0, 0 100%, 100% 100%)', + }; + }, onTransitionEnd(evt: Event){ if (this.inTransition){ this.inTransition = false; @@ -411,13 +451,22 @@ export default defineComponent({ <template> <div :style="styles" @transitionend="onTransitionEnd" @scroll="onScroll"> <!-- Need enclosing div for transitions --> - <div v-if="isLeaf" :style="leafStyles" - class="w-full h-full flex flex-col overflow-hidden" :class="{'hover:cursor-pointer': isExpandableLeaf}" + <div v-if="isLeaf" :style="leafStyles" class="w-full h-full" :class="{'hover:cursor-pointer': isExpandableLeaf}" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @mousedown="onMouseDown" @mouseup="onMouseUp"> - <h1 :style="leafHeaderStyles">{{displayName}}</h1> - <info-icon :style="[infoIconStyles, {marginTop: 'auto'}]" - class="self-end text-white/10 hover:text-white hover:cursor-pointer" - @click.stop="onInfoIconClick" @mousedown.stop @mouseup.stop/> + <template v-if="!hasCompoundImage"> + <h1 :style="leafHeaderStyles">{{displayName}}</h1> + <info-icon :style="infoIconStyles" + class="text-white/10 hover:text-white hover:cursor-pointer" + @click.stop="onInfoIconClick" @mousedown.stop @mouseup.stop/> + </template> + <template v-else> + <div :style="leafFirstImgStyles" class="col-start-1 row-start-1"></div> + <div :style="leafSecondImgStyles" class="col-start-1 row-start-1"></div> + <h1 :style="leafHeaderStyles" class="col-start-1 row-start-1 z-10">{{displayName}}</h1> + <info-icon :style="infoIconStyles" + class="col-start-1 row-start-1 z-10 text-white/10 hover:text-white hover:cursor-pointer" + @click.stop="onInfoIconClick" @mousedown.stop @mouseup.stop/> + </template> </div> <div v-else :style="nonleafStyles" ref="nonleaf"> <div v-if="showNonleafHeader" :style="nonleafHeaderStyles" class="flex hover:cursor-pointer" diff --git a/src/components/TileInfoModal.vue b/src/components/TileInfoModal.vue index 4e39a4e..e10c330 100644 --- a/src/components/TileInfoModal.vue +++ b/src/components/TileInfoModal.vue @@ -1,26 +1,34 @@ <script lang="ts"> import {defineComponent, PropType} from 'vue'; import CloseIcon from './icon/CloseIcon.vue'; +import Tile from './Tile.vue' import {LayoutNode} from '../layout'; +import type {LayoutOptions} from '../layout'; import type {TolMap} from '../tol'; import {TolNode} from '../tol'; import {capitalizeWords} from '../util'; -type DescData = {text: string, fromRedirect: boolean, wikiId: number, fromDbp: boolean}; - // Represents a node description +type DescInfo = {text: string, fromRedirect: boolean, wikiId: number, fromDbp: boolean}; +type ImgInfo = {eolId: string, sourceUrl: string, license: string, copyrightOwner: string} +type TileInfoResponse = { + tolNode: null | TolNode, + descData: null | DescInfo | [DescInfo, DescInfo], + imgData: null | ImgInfo | [ImgInfo, ImgInfo], +}; // Displays information about a tree-of-life node export default defineComponent({ data(){ return { tolNode: null as null | TolNode, - descData: null as null | DescData | [DescData, DescData], - imgInfo: null as null | {eolId: string, sourceUrl: string, license: string, copyrightOwner: string}, + descData: null as null | DescInfo | [DescInfo, DescInfo], + imgData: null as null | ImgInfo | [ImgInfo, ImgInfo], }; }, props: { nodeName: {type: String, required: true}, tolMap: {type: Object as PropType<TolMap>, required: true}, + lytOpts: {type: Object as PropType<LayoutOptions>, required: true}, uiOpts: {type: Object, required: true}, }, computed: { @@ -31,18 +39,16 @@ export default defineComponent({ return `${capitalizeWords(this.tolNode.commonName)} (aka ${capitalizeWords(this.nodeName)})`; } }, - imgStyles(): Record<string,string> { - return { - backgroundImage: this.tolNode != null && this.tolNode.imgName != null ? - 'linear-gradient(to bottom, rgba(0,0,0,0.4), #0000 40%, #0000 60%, rgba(0,0,0,0.4) 100%),' + - 'url(\'/img/' + this.tolNode.imgName.replaceAll('\'', '\\\'') + '\')' : - 'none', - backgroundColor: '#1c1917', - width: this.uiOpts.infoModalImgSz + 'px', - height: this.uiOpts.infoModalImgSz + 'px', - backgroundSize: 'cover', - borderRadius: this.uiOpts.borderRadius + 'px', - }; + subName1(): string { + return this.displayName.substring(1, this.displayName.indexOf(' + ')); + }, + subName2(): string { + return this.displayName.substring(this.displayName.indexOf(' + ') + 3, this.displayName.length - 1); + }, + dummyNode(): LayoutNode { + let newNode = new LayoutNode(this.nodeName, []); + newNode.dims = [this.uiOpts.infoModalImgSz, this.uiOpts.infoModalImgSz]; + return newNode; }, }, methods: { @@ -63,18 +69,18 @@ export default defineComponent({ if (obj != null){ this.tolNode = obj.nodeObj; this.descData = obj.descData; - this.imgInfo = obj.imgInfo; + this.imgData = obj.imgData; } }); }, - components: {CloseIcon, }, + components: {CloseIcon, Tile, }, emits: ['info-modal-close', ], }); </script> <template> <div class="fixed left-0 top-0 w-full h-full bg-black/40" @click="onCloseClick"> - <div class="absolute left-1/2 -translate-x-1/2 w-4/5 top-1/2 -translate-y-1/2 p-4 + <div class="absolute left-1/2 -translate-x-1/2 w-4/5 h-4/5 overflow-scroll top-1/2 -translate-y-1/2 p-4 bg-stone-50 rounded-md shadow shadow-black"> <close-icon @click.stop="onCloseClick" ref="closeIcon" class="block absolute top-2 right-2 w-6 h-6 hover:cursor-pointer"/> @@ -87,14 +93,36 @@ export default defineComponent({ <hr class="mb-4 border-stone-400"/> <div class="flex"> <div> - <div :style="imgStyles" class="mr-4" alt="an image"></div> - <div v-if="imgInfo != null"> + <tile :layoutNode="dummyNode" :tolMap="tolMap" :nonAbsPos="true" :lytOpts="lytOpts" :uiOpts="uiOpts" + class="mr-4"/> + <div v-if="imgData == null"> + (No image found) + </div> + <div v-else-if="!Array.isArray(imgData)"> <ul> - <li>License: {{imgInfo.license}}</li> - <li><a :href="imgInfo.sourceUrl" class="underline">Source URL</a></li> - <li>Copyright Owner: {{imgInfo.copyrightOwner}}</li> + <li>License: {{imgData.license}}</li> + <li><a :href="imgData.sourceUrl" class="underline">Source URL</a></li> + <li>Copyright Owner: {{imgData.copyrightOwner}}</li> </ul> </div> + <div v-else> + <div v-if="imgData[0] != null"> + <h2 class="font-bold">Top-left Image</h2> + <ul> + <li>License: {{imgData[0].license}}</li> + <li><a :href="imgData[0].sourceUrl" class="underline">Source URL</a></li> + <li>Copyright Owner: {{imgData[0].copyrightOwner}}</li> + </ul> + </div> + <div v-if="imgData[1] != null"> + <h2 class="font-bold">Bottom-right Image</h2> + <ul> + <li>License: {{imgData[1].license}}</li> + <li><a :href="imgData[1].sourceUrl" class="underline">Source URL</a></li> + <li>Copyright Owner: {{imgData[1].copyrightOwner}}</li> + </ul> + </div> + </div> </div> <div v-if="descData == null"> (No description found) @@ -112,11 +140,11 @@ export default defineComponent({ </div> <div v-else> <div> - <h2 class="font-bold">{{displayName.substring(1, displayName.indexOf(' + '))}}</h2> + <h2 class="font-bold">{{subName1}}</h2> <div>{{descData[0].text}}</div> </div> <div> - <h2 class="font-bold">{{displayName.substring(displayName.indexOf(' + ') + 3, displayName.length - 1)}}</h2> + <h2 class="font-bold">{{subName2}}</h2> <div>{{descData[1].text}}</div> </div> </div> @@ -10,8 +10,8 @@ export class TolNode { parent: string | null; tips: number; pSupport: boolean; - imgName: null | string; commonName: null | string; + imgName: null | string | [string, string]; constructor(children: string[] = [], parent = null, tips = 0, pSupport = false){ this.children = children; this.parent = parent; |
