aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTerry Truong <terry06890@gmail.com>2022-06-26 18:49:36 +1000
committerTerry Truong <terry06890@gmail.com>2022-06-26 18:51:41 +1000
commitd2d6f0496ce816e9238e785ed3d0e7bd61b2483b (patch)
tree0a6dac78da3f236bc46599f6dac865f80d1959b9 /src
parent09b6244d94b9a176172de448d3bb7fa386cb8995 (diff)
Refactor TileInfoModal
Also change server info-response format to include sub-node common-names
Diffstat (limited to 'src')
-rw-r--r--src/App.vue10
-rw-r--r--src/README.md2
-rw-r--r--src/components/TileInfoModal.vue240
-rw-r--r--src/layout.ts2
-rw-r--r--src/lib.ts15
5 files changed, 129 insertions, 140 deletions
diff --git a/src/App.vue b/src/App.vue
index 7576278..a3d0559 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -59,7 +59,8 @@ function getDefaultUiOpts(lytOpts: LayoutOptions): UiOptions {
let screenSz = getBreakpoint();
// Reused option values
let textColor = '#fafaf9';
- let bgColor = '#292524', bgColorLight = '#44403c', bgColorDark = '#1c1917',
+ let bgColor = '#292524', bgColorAlt = '#fafaf9',
+ bgColorLight = '#44403c', bgColorDark = '#1c1917',
bgColorLight2 = '#57534e', bgColorDark2 = '#0e0c0b';
let altColor = '#a3e623', altColorDark = '#65a30d';
let accentColor = '#f59e0b';
@@ -69,6 +70,7 @@ function getDefaultUiOpts(lytOpts: LayoutOptions): UiOptions {
// Shared coloring/sizing
textColor,
bgColor,
+ bgColorAlt,
bgColorLight,
bgColorDark,
bgColorLight2,
@@ -256,7 +258,7 @@ export default defineComponent({
let response = await fetch(urlPath);
responseObj = await response.json();
} catch (error){
- console.log('ERROR loading tolnode data', error);
+ console.log('ERROR: Unable to retreive tol-node data', error);
return false;
}
Object.getOwnPropertyNames(responseObj).forEach(n => {this.tolMap.set(n, responseObj[n])});
@@ -354,7 +356,7 @@ export default defineComponent({
let response = await fetch(urlPath);
responseObj = await response.json();
} catch (error){
- console.log('ERROR loading tolnode data', error);
+ console.log('ERROR: Unable to retreive tol-node data', error);
return false;
}
Object.getOwnPropertyNames(responseObj).forEach(n => {this.tolMap.set(n, responseObj[n])});
@@ -813,7 +815,7 @@ export default defineComponent({
let response = await fetch(urlPath);
responseObj = await response.json();
} catch (error) {
- console.log('ERROR: Unable to get tree data', error);
+ console.log('ERROR: Unable to retrieve tree data', error);
return;
}
// Get root node name
diff --git a/src/README.md b/src/README.md
index dbd2ecb..6bf764c 100644
--- a/src/README.md
+++ b/src/README.md
@@ -2,7 +2,7 @@
- main.ts: Included by ../index.html. Creates the main Vue component.
- App.vue: The main Vue component.
- components:
- - Tile.vue: Displays a tree-of-life node, and can include child nodes.
+ - Tile.vue: Displays a tree-of-life node.
- TileInfoModal.vue: Modal displaying info about a Tile's node.
- SearchModal.vue: Modal with a search bar.
- SettingsModal: Modal displaying configurable settings.
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>
diff --git a/src/layout.ts b/src/layout.ts
index 15c156b..79d3b70 100644
--- a/src/layout.ts
+++ b/src/layout.ts
@@ -308,7 +308,7 @@ let sqrLayout: LayoutFn = function (node, pos, dims, showHeader, allowCollapse,
let numChildren = node.children.length;
let areaAR = newDims[0] / newDims[1]; // Aspect ratio
let lowestEmpSpc = Number.POSITIVE_INFINITY, usedNumCols = 0, usedNumRows = 0, usedTileSz = 0;
- const MAX_TRIES = 20; // If there are many possibilities, skip some
+ const MAX_TRIES = 50; // If there are many possibilities, skip some
let ptlNumCols = numChildren == 1 ? [1] :
linspace(1, numChildren, Math.min(numChildren, MAX_TRIES)).map(n => Math.floor(n));
for (let numCols of ptlNumCols){
diff --git a/src/lib.ts b/src/lib.ts
index 7841ac8..b3e6acc 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -48,11 +48,15 @@ export type ImgInfo = {
license: string,
artist: string,
credit: string,
-}
-export type TileInfoResponse = {
- tolNode: null | TolNode,
- descData: null | DescInfo | [DescInfo, DescInfo],
- imgData: null | ImgInfo | [ImgInfo, ImgInfo],
+};
+export type NodeInfo = {
+ tolNode: TolNode,
+ descInfo: null | DescInfo,
+ imgInfo: null | ImgInfo,
+};
+export type InfoResponse = {
+ nodeInfo: NodeInfo,
+ subNodesInfo: [] | [NodeInfo, NodeInfo],
};
// Used by auto-mode and tutorial
@@ -65,6 +69,7 @@ export type UiOptions = {
// Shared coloring/sizing
textColor: string, // CSS color
bgColor: string,
+ bgColorAlt: string,
bgColorLight: string,
bgColorDark: string,
bgColorLight2: string,