aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTerry Truong <terry06890@gmail.com>2022-05-23 07:02:16 +1000
committerTerry Truong <terry06890@gmail.com>2022-05-23 07:05:21 +1000
commit19984e11e3bfd793188fd84c621a304330f4a303 (patch)
treec6ce61fa0eb782d3025d94733cd0c3fd1e10e6a2 /src
parent6edf3fe12a7c895eb55292281eac497377128fd2 (diff)
Display 'compound images'
Adjust genLinkedImgs.py to associate multiple images to compound-nodes. Adjust server to send multiple image filenames for such nodes. Adjust Tile and TileInfoModal to display those compound-images.
Diffstat (limited to 'src')
-rw-r--r--src/App.vue3
-rw-r--r--src/components/AncestryBar.vue2
-rw-r--r--src/components/Tile.vue83
-rw-r--r--src/components/TileInfoModal.vue80
-rw-r--r--src/tol.ts2
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>
diff --git a/src/tol.ts b/src/tol.ts
index 41ace2c..15f1a94 100644
--- a/src/tol.ts
+++ b/src/tol.ts
@@ -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;