From d2d6f0496ce816e9238e785ed3d0e7bd61b2483b Mon Sep 17 00:00:00 2001
From: Terry Truong <terry06890@gmail.com>
Date: Sun, 26 Jun 2022 18:49:36 +1000
Subject: Refactor TileInfoModal

Also change server info-response format to include sub-node common-names
---
 src/components/TileInfoModal.vue | 240 ++++++++++++++++++---------------------
 1 file changed, 111 insertions(+), 129 deletions(-)

(limited to 'src/components/TileInfoModal.vue')

diff --git a/src/components/TileInfoModal.vue b/src/components/TileInfoModal.vue
index 90ad9a7..c8f8047 100644
--- a/src/components/TileInfoModal.vue
+++ b/src/components/TileInfoModal.vue
@@ -1,76 +1,66 @@
 <script lang="ts">
+
 import {defineComponent, PropType} from 'vue';
 import CloseIcon from './icon/CloseIcon.vue';
-import Tile from './Tile.vue'
 import {LayoutNode, LayoutOptions} from '../layout';
-import {TolNode, TolMap, UiOptions, DescInfo, ImgInfo, TileInfoResponse} from '../lib';
+import {TolNode, TolMap, UiOptions, DescInfo, ImgInfo, NodeInfo, InfoResponse} from '../lib';
 import {capitalizeWords} from '../util';
 
-// Displays information about a tree-of-life node
 export default defineComponent({
-	data(){
-		return {
-			tolNode: null as null | TolNode,
-			descInfo: null as null | DescInfo,
-			descInfo1: null as null | DescInfo,
-			descInfo2: null as null | DescInfo,
-			imgInfo: null as null | ImgInfo,
-			imgInfo1: null as null | ImgInfo,
-			imgInfo2: null as null | ImgInfo,
-		};
-	},
 	props: {
 		nodeName: {type: String, required: true},
 		tolMap: {type: Object as PropType<TolMap>, required: true},
+		// Options
 		lytOpts: {type: Object as PropType<LayoutOptions>, required: true},
 		uiOpts: {type: Object as PropType<UiOptions>, required: true},
 	},
+	data(){
+		return {
+			// These are set using a server response
+			tolNode: null as null | TolNode,
+			nodes: [] as TolNode[], // The nodes to display info for
+			imgInfos: [] as (ImgInfo | null)[],
+			descInfos: [] as (DescInfo | null)[],
+		};
+	},
 	computed: {
-		displayName(): string {
-			if (this.tolNode == null || this.tolNode.commonName == null){
-				return capitalizeWords(this.nodeName);
-			} else {
-				return `${capitalizeWords(this.tolNode.commonName)} (aka ${capitalizeWords(this.nodeName)})`;
-			}
-		},
-		subName1(): string {
-			return this.displayName.substring(1, this.displayName.indexOf(' + '));
-		},
-		subName2(): string {
-			return this.displayName.substring(this.displayName.indexOf(' + ') + 3, this.displayName.length - 1);
-		},
-		imgStyles(): Record<string,string> {
-			return this.getImgStyles(this.tolNode == null ? null : this.tolNode.imgName as string);
-		},
-		firstImgStyles(): Record<string,string> {
-			return this.getImgStyles(this.tolNode!.imgName![0]);
-		},
-		secondImgStyles(): Record<string,string> {
-			return this.getImgStyles(this.tolNode!.imgName![1]);
+		subNames(): [string, string] | null {
+			const regex = /\[(.+) \+ (.+)\]/;
+			let results = regex.exec(this.nodeName);
+			return results == null ? null : [results[1], results[2]];
 		},
-		dummyNode(): LayoutNode {
-			let newNode = new LayoutNode(this.nodeName, []);
-			newNode.dims = [this.lytOpts.maxTileSz, this.lytOpts.maxTileSz];
-			return newNode;
+		styles(): Record<string,string> {
+			return {
+				backgroundColor: this.uiOpts.bgColorAlt,
+				borderRadius: this.uiOpts.borderRadius + 'px',
+				boxShadow: this.uiOpts.shadowNormal,
+				overflow: 'visible auto',
+			};
 		},
 	},
 	methods: {
-		onCloseClick(evt: Event){
-			if (evt.target == this.$el || (this.$refs.closeIcon as typeof CloseIcon).$el.contains(evt.target)){
-				this.$emit('close');
+		getDisplayName(name: string, tolNode: TolNode | null): string {
+			if (tolNode == null || tolNode.commonName == null){
+				return capitalizeWords(name);
+			} else {
+				return `${capitalizeWords(tolNode.commonName)} (aka ${capitalizeWords(name)})`;
 			}
 		},
-		getImgStyles(imgName: string | null){
+		getImgStyles(tolNode: TolNode): Record<string,string> {
+			let imgName = null;
+			if (typeof(tolNode.imgName) === 'string'){ // Exclude string-array case
+				imgName = tolNode.imgName;
+			}
 			return {
-				boxShadow: this.uiOpts.shadowNormal,
-				borderRadius: this.uiOpts.borderRadius + 'px',
+				width: this.lytOpts.maxTileSz + 'px',
+				height: this.lytOpts.maxTileSz + 'px',
 				backgroundImage: imgName != null ?
 					'url(\'/img/' + imgName.replaceAll('\'', '\\\'') + '\')' :
 					'none',
-				backgroundColor: '#1c1917',
+				backgroundColor: this.uiOpts.bgColorDark,
 				backgroundSize: 'cover',
-				width: this.lytOpts.maxTileSz + 'px',
-				height: this.lytOpts.maxTileSz + 'px',
+				borderRadius: this.uiOpts.borderRadius + 'px',
+				boxShadow: this.uiOpts.shadowNormal,
 			};
 		},
 		licenseToUrl(license: string){
@@ -103,41 +93,54 @@ export default defineComponent({
 				return "[INVALID LICENSE]";
 			}
 		},
+		onClose(evt: Event){
+			if (evt.target == this.$el || (this.$refs.closeIcon as typeof CloseIcon).$el.contains(evt.target)){
+				this.$emit('close');
+			}
+		},
 	},
-	created(){
+	async created(){
+		// Query server for tol-node info
 		let url = new URL(window.location.href);
 		url.pathname = '/data/info';
 		url.search = '?name=' + encodeURIComponent(this.nodeName);
-		url.search += this.uiOpts.useReducedTree ? '&tree=reduced' : '';
-		fetch(url.toString())
-			.then(response => response.json())
-			.then(obj => {
-				this.tolNode = obj.tolNode;
-				if (!Array.isArray(obj.descData)){
-					this.descInfo = obj.descData;
-				} else {
-					[this.descInfo1, this.descInfo2] = obj.descData;
-				}
-				if (!Array.isArray(obj.imgData)){
-					this.imgInfo = obj.imgData;
-				} else {
-					[this.imgInfo1, this.imgInfo2] = obj.imgData;
-				}
-			});
+		if (this.uiOpts.useReducedTree){
+			url.search += '&tree=reduced';
+		}
+		let responseObj: InfoResponse;
+		try {
+			let response = await fetch(url.toString());
+			responseObj = await response.json();
+		} catch (error){
+			console.log("ERROR: Unable to retrieve data from server")
+			return;
+		}
+		// Set fields from response
+		this.tolNode = responseObj.nodeInfo.tolNode;
+		if (responseObj.subNodesInfo.length == 0){
+			this.nodes = [this.tolNode]
+			this.imgInfos = [responseObj.nodeInfo.imgInfo];
+			this.descInfos = [responseObj.nodeInfo.descInfo];
+		} else {
+			for (let nodeInfo of responseObj.subNodesInfo){
+				this.nodes.push(nodeInfo.tolNode);
+				this.imgInfos.push(nodeInfo.imgInfo);
+				this.descInfos.push(nodeInfo.descInfo);
+			}
+		}
 	},
-	components: {CloseIcon, Tile, },
+	components: {CloseIcon, },
 	emits: ['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 max-h-[80%] overflow-y-auto top-1/2 -translate-y-1/2 p-4
-		bg-stone-50 rounded-md shadow shadow-black">
-		<close-icon @click.stop="onCloseClick" ref="closeIcon"
+<div class="fixed left-0 top-0 w-full h-full bg-black/40" @click="onClose">
+	<div class="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 w-4/5 max-h-[80%] p-4" :style="styles">
+		<close-icon @click.stop="onClose" ref="closeIcon"
 			class="block absolute top-2 right-2 w-6 h-6 hover:cursor-pointer"/>
 		<h1 class="text-center text-xl font-bold mb-2">
-			{{displayName}}
+			{{getDisplayName(nodeName, tolNode)}}
 			<div v-if="tolNode != null">
 				({{tolNode.children.length}} children, {{tolNode.tips}} tips,
 					<a :href="'https://tree.opentreeoflife.org/opentree/argus/opentree13.4@' + tolNode.otolId">
@@ -145,66 +148,45 @@ export default defineComponent({
 			</div>
 		</h1>
 		<hr class="mb-4 border-stone-400"/>
-		<div class="flex">
-			<div class="mr-4">
-				<div v-if="tolNode == null"/>
-				<div v-else-if="!Array.isArray(tolNode.imgName)">
-					<div :style="imgStyles"/>
-					<ul v-if="imgInfo != null">
-						<li>Obtained via: {{imgInfo.src}}</li>
-						<li>License: <a :href="licenseToUrl(imgInfo.license)">{{imgInfo.license}}</a></li>
-						<li><a :href="imgInfo.url" class="underline">Source URL</a></li>
-						<li>Artist: {{imgInfo.artist}}</li>
-						<li v-if="imgInfo.credit != ''">Credit: {{imgInfo.credit}}</li>
-					</ul>
-				</div>
-				<div v-else>
-					<div v-if="tolNode.imgName[0] != null" :style="firstImgStyles"/>
-					<ul v-if="imgInfo1 != null">
-						<li>Obtained via: {{imgInfo1.src}}</li>
-						<li>License: <a :href="licenseToUrl(imgInfo1.license)">{{imgInfo1.license}}</a></li>
-						<li><a :href="imgInfo1.url" class="underline">Source URL</a></li>
-						<li>Artist: {{imgInfo1.artist}}</li>
-						<li v-if="imgInfo1.credit != ''">Credit: {{imgInfo1.credit}}</li>
-					</ul>
-					<div v-if="tolNode.imgName[1] != null" :style="secondImgStyles"/>
-					<ul v-if="imgInfo2 != null">
-						<li>Obtained via: {{imgInfo2.src}}</li>
-						<li>License: <a :href="licenseToUrl(imgInfo2.license)">{{imgInfo2.license}}</a></li>
-						<li><a :href="imgInfo2.url" class="underline">Source URL</a></li>
-						<li>Artist: {{imgInfo2.artist}}</li>
-						<li v-if="imgInfo2.credit != ''">Credit: {{imgInfo2.credit}}</li>
-					</ul>
-				</div>
-			</div>
-			<div v-if="descInfo == null && descInfo1 == null && descInfo2 == null">
-				(No description found)
-			</div>
-			<div v-else-if="descInfo != null">
-				<div>
-					Redirected: {{descInfo.fromRedirect}} <br/>
-					Short-description from {{descInfo.fromDbp ? 'DBpedia' : 'Wikipedia'}} <br/>
-					<a :href="'https://en.wikipedia.org/?curid=' + descInfo.wikiId" class="underline">
-						Wikipedia Link
-					</a>
-				</div>
-				<hr/>
-				<div>{{descInfo.text}}</div>
-			</div>
-			<div v-else>
-				<div>
-					<h2 class="font-bold">{{subName1}}</h2>
-					<div v-if="descInfo1 != null">{{descInfo1.text}}</div>
-					<div v-else>(No description found)</div>
-				</div>
-				<div>
-					<h2 class="font-bold">{{subName2}}</h2>
-					<div v-if="descInfo2 != null">{{descInfo2.text}}</div>
-					<div v-else>(No description found)</div>
+		<div v-if="tolNode == null">Querying server</div>
+		<template v-else>
+			<div v-if="nodes.length > 1">This is a compound node</div>
+			<div v-for="idx in (nodes.length == 1 ? [0] : [0, 1])">
+				<h1 v-if="nodes.length > 1" class="text-center font-bold">
+					{{getDisplayName(subNames![idx], nodes[idx])}}
+				</h1>
+				<div class="flex gap-1">
+					<div class="w-1/2">
+						<div :style="getImgStyles(nodes[idx])"/>
+						<ul v-if="imgInfos[idx]! != null">
+							<li>Obtained via: {{imgInfos[idx]!.src}}</li>
+							<li>License:
+								<a :href="licenseToUrl(imgInfos[idx]!.license)">{{imgInfos[idx]!.license}}</a>
+							</li>
+							<li><a :href="imgInfos[idx]!.url" class="underline">Source URL</a></li>
+							<li>Artist: {{imgInfos[idx]!.artist}}</li>
+							<li v-if="imgInfos[idx]!.credit != ''" class="overflow-auto">
+								Credit: {{imgInfos[idx]!.credit}}
+							</li>
+						</ul>
+					</div>
+					<div v-if="descInfos[idx]! != null">
+						<div>
+							Redirected: {{descInfos[idx]!.fromRedirect}} <br/>
+							Short-description from {{descInfos[idx]!.fromDbp ? 'DBpedia' : 'Wikipedia'}} <br/>
+							<a :href="'https://en.wikipedia.org/?curid=' + descInfos[idx]!.wikiId" class="underline">
+								Wikipedia Link
+							</a>
+						</div>
+						<hr/>
+						<div>{{descInfos[idx]!.text}}</div>
+					</div>
+					<div v-else>
+						(No description found)
+					</div>
 				</div>
 			</div>
-		</div>
-		
+		</template>
 	</div>
 </div>
 </template>
-- 
cgit v1.2.3

