aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/data/README.md2
-rwxr-xr-xbackend/data/genLinkedImgs.py55
-rwxr-xr-xbackend/server.py60
-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
8 files changed, 213 insertions, 74 deletions
diff --git a/backend/data/README.md b/backend/data/README.md
index 438188c..a1bc287 100644
--- a/backend/data/README.md
+++ b/backend/data/README.md
@@ -39,7 +39,7 @@ data.db Tables
- names: name TEXT, alt\_name TEXT, pref\_alt INT, PRIMARY KEY(name, alt\_name)
- eol\_ids: id INT PRIMARY KEY, name TEXT
- images: eol\_id INT PRIMARY KEY, source\_url TEXT, license TEXT, copyright\_owner TEXT
-- linked\_imgs: name TEXT PRIMARY KEY, eol\_id INT
+- linked\_imgs: name TEXT PRIMARY KEY, eol\_id INT, eol\_id2 INT
- descs: name TEXT PRIMARY KEY, desc TEXT, redirected INT, wiki\_id INT, from\_dbp INT
- r\_nodes: name TEXT PRIMARY KEY, tips INT
- r\_edges: node TEXT, child TEXT, p\_support INT, PRIMARY KEY (node, child)
diff --git a/backend/data/genLinkedImgs.py b/backend/data/genLinkedImgs.py
index 5f49ffc..d32196e 100755
--- a/backend/data/genLinkedImgs.py
+++ b/backend/data/genLinkedImgs.py
@@ -1,6 +1,6 @@
#!/usr/bin/python3
-import sys
+import sys, re
import sqlite3
usageInfo = f"usage: {sys.argv[0]}\n"
@@ -11,11 +11,14 @@ if len(sys.argv) > 1:
sys.exit(1)
dbFile = "data.db"
+compoundNameRegex = re.compile(r"\[(.+) \+ (.+)]")
+upPropagateCompoundImgs = False
# Open db
dbCon = sqlite3.connect(dbFile)
dbCur = dbCon.cursor()
-dbCur.execute("CREATE TABLE linked_imgs (name TEXT PRIMARY KEY, eol_id INT)")
+dbCur.execute("CREATE TABLE linked_imgs (name TEXT PRIMARY KEY, eol_id INT, eol_id2 INT)")
+ # The eol_id2 column is used to provide the second part of a compound-image
# Get nodes with images
print("Getting nodes with images")
resolvedNodes = {} # Will map node names to eol IDs with a usable image
@@ -28,7 +31,8 @@ print("Got {} nodes".format(len(resolvedNodes)))
# Iterate through resolved nodes, resolving ancestors where able
print("Resolving ancestor nodes")
nodesToResolve = {}
-processedNodes = set()
+processedNodes = {}
+parentToChosenTips = {}
iterNum = 0
while len(resolvedNodes) > 0:
iterNum += 1
@@ -36,7 +40,7 @@ while len(resolvedNodes) > 0:
print("At iteration {}".format(iterNum))
# Get next node
(nodeName, eolId) = resolvedNodes.popitem()
- processedNodes.add(nodeName)
+ processedNodes[nodeName] = eolId
# Traverse upwards, resolving ancestors if able
while True:
# Get parent
@@ -56,9 +60,10 @@ while len(resolvedNodes) > 0:
# Check if highest-tips child
if (childObjs[0]["name"] == nodeName):
# Resolve parent, and continue from it
- dbCur.execute("INSERT INTO linked_imgs VALUES (?, ?)", (parent, eolId))
+ dbCur.execute("INSERT INTO linked_imgs VALUES (?, ?, ?)", (parent, eolId, None))
del nodesToResolve[parent]
- processedNodes.add(parent)
+ processedNodes[parent] = eolId
+ parentToChosenTips[parent] = childObjs[0]["tips"]
nodeName = parent
continue
else:
@@ -71,8 +76,44 @@ while len(resolvedNodes) > 0:
for (name, childObjs) in nodesToResolve.items():
childObj = next(c for c in childObjs if c["eolId"] != None)
resolvedNodes[name] = childObj["eolId"]
- dbCur.execute("INSERT INTO linked_imgs VALUES (?, ?)", (name, childObj["eolId"]))
+ parentToChosenTips[name] = childObj["tips"]
+ dbCur.execute("INSERT INTO linked_imgs VALUES (?, ?, ?)", (name, childObj["eolId"], None))
nodesToResolve.clear()
+# Iterate through processed nodes with compound names
+print("Replacing images for compound-name nodes")
+iterNum = 0
+for nodeName in processedNodes.keys():
+ iterNum += 1
+ if iterNum % 1e3 == 0:
+ print("At iteration {}".format(iterNum))
+ #
+ match = compoundNameRegex.fullmatch(nodeName)
+ if match != None:
+ # Replace associated image with subname images
+ (subName1, subName2) = match.group(1,2)
+ eolIdPair = [0, 0]
+ if subName1 in processedNodes:
+ eolIdPair[0] = processedNodes[subName1]
+ if subName2 in processedNodes:
+ eolIdPair[1] = processedNodes[subName2]
+ dbCur.execute("UPDATE linked_imgs SET eol_id = ?, eol_id2 = ? WHERE name = ?",
+ (eolIdPair[0], eolIdPair[1], nodeName,))
+ if upPropagateCompoundImgs:
+ # Repeat operation for parents, where needed
+ while True:
+ # Get parent
+ row = dbCur.execute("SELECT node FROM edges WHERE child = ?", (nodeName,)).fetchone()
+ if row != None:
+ parent = row[0]
+ # Check num tips
+ (numTips,) = dbCur.execute("SELECT tips from nodes WHERE name = ?", (nodeName,)).fetchone()
+ if parent in parentToChosenTips and parentToChosenTips[parent] <= numTips:
+ # Replace associated image
+ dbCur.execute("UPDATE linked_imgs SET eol_id = ?, eol_id2 = ? WHERE name = ?",
+ (eolIdPair[0], eolIdPair[1], parent))
+ nodeName = parent
+ continue
+ break
# Close db
dbCon.commit()
dbCon.close()
diff --git a/backend/server.py b/backend/server.py
index f15c95f..5152503 100755
--- a/backend/server.py
+++ b/backend/server.py
@@ -16,9 +16,11 @@ usageInfo += "Starts a server that listens for GET requests to http://" + hostna
usageInfo += "Responds to path+query /data/type1?name=name1 with JSON data.\n"
usageInfo += "An additional query parameter tree=reduced is usable to get reduced-tree data\n"
usageInfo += "\n"
-usageInfo += "If type1 is 'node': Responds with map from names to objects representing node name1 and it's children.\n"
+usageInfo += "If type1 is 'node': Responds with map from names to TolNode objects for node name1 and it's children.\n"
usageInfo += "If type1 is 'chain': Like 'node', but gets nodes from name1 up to the root, and their direct children.\n"
-usageInfo += "If type1 is 'search': Responds with a tolnode name that has alt-name name1, or null.\n"
+usageInfo += "If type1 is 'search': Responds with a SearchSuggResponse object.\n"
+usageInfo += "If type1 is 'info': Responds with a TileInfoResponse object.\n"
+usageInfo += "(Object type information can be found in src/)\n"
if len(sys.argv) > 1:
print(usageInfo, file=sys.stderr)
sys.exit(1)
@@ -59,9 +61,16 @@ def lookupNodes(names, useReducedTree):
nodeObjs[name]["imgName"] = str(eolId) + ".jpg"
# Get 'linked' images for unresolved names
unresolvedNames = [n for n in nodeObjs if nodeObjs[n]["imgName"] == None]
- query = "SELECT name, eol_id from linked_imgs WHERE name IN ({})".format(",".join(["?"] * len(unresolvedNames)))
- for (name, eolId) in cur.execute(query, unresolvedNames):
- nodeObjs[name]["imgName"] = str(eolId) + ".jpg"
+ query = "SELECT name, eol_id, eol_id2 from linked_imgs WHERE name IN ({})"
+ query = query.format(",".join(["?"] * len(unresolvedNames)))
+ for (name, eolId, eolId2) in cur.execute(query, unresolvedNames):
+ if eolId2 == None:
+ nodeObjs[name]["imgName"] = str(eolId) + ".jpg"
+ else:
+ nodeObjs[name]["imgName"] = [
+ str(eolId) + ".jpg" if eolId != 0 else None,
+ str(eolId2) + ".jpg" if eolId2 != 0 else None,
+ ]
# Get preferred-name info
query = "SELECT name, alt_name FROM names WHERE pref_alt = 1 AND name IN ({})".format(queryParamStr)
for (name, altName) in cur.execute(query, names):
@@ -106,30 +115,41 @@ def lookupNodeInfo(name, useReducedTree):
nodeObj = temp[name] if name in temp else None
# Get node desc
descData = None
- query = "SELECT desc, redirected, wiki_id, from_dbp from descs WHERE descs.name = ?"
match = re.fullmatch(r"\[(.+) \+ (.+)]", name)
if match == None:
+ query = "SELECT desc, redirected, wiki_id, from_dbp from descs WHERE descs.name = ?"
row = cur.execute(query, (name,)).fetchone()
if row != None:
descData = {"text": row[0], "fromRedirect": row[1] == 1, "wikiId": row[2], "fromDbp": row[3] == 1}
else:
- # Get descs for compound-node elements
+ # Get descs for compound-node element
descData = [None, None]
- row = cur.execute(query, (match.group(1),)).fetchone()
- if row != None:
- descData[0] = {"text": row[0], "fromRedirect": row[1] == 1, "wikiId": row[2], "fromDbp": row[3] == 1}
- row = cur.execute(query, (match.group(2),)).fetchone()
- if row != None:
- descData[1] = {"text": row[0], "fromRedirect": row[1] == 1, "wikiId": row[2], "fromDbp": row[3] == 1}
+ query = "SELECT name, desc, redirected, wiki_id, from_dbp from descs WHERE descs.name IN (?, ?)"
+ for row in cur.execute(query, match.group(1,2)):
+ if row[0] == match.group(1):
+ descData[0] = {"text": row[1], "fromRedirect": row[2] == 1, "wikiId": row[3], "fromDbp": row[4] == 1}
+ else:
+ descData[1] = {"text": row[1], "fromRedirect": row[2] == 1, "wikiId": row[3], "fromDbp": row[4] == 1}
# Get img info
- imgInfo = None
- if nodeObj != None and nodeObj["imgName"] != None:
- eolId = int(nodeObj["imgName"][:-4]) # Convert filename excluding .jpg suffix
- imgInfoQuery = "SELECT eol_id, source_url, license, copyright_owner FROM images WHERE eol_id = ?"
- row = cur.execute(imgInfoQuery, (eolId,)).fetchone()
- imgInfo = {"eolId": row[0], "sourceUrl": row[1], "license": row[2], "copyrightOwner": row[3]}
+ imgData = None
+ if nodeObj != None:
+ if isinstance(nodeObj["imgName"], str):
+ eolId = int(nodeObj["imgName"][:-4]) # Convert filename excluding .jpg suffix
+ query = "SELECT eol_id, source_url, license, copyright_owner FROM images WHERE eol_id = ?"
+ row = cur.execute(query, (eolId,)).fetchone()
+ imgData = {"eolId": row[0], "sourceUrl": row[1], "license": row[2], "copyrightOwner": row[3]}
+ elif isinstance(nodeObj["imgName"], list):
+ # Get info for compound-image parts
+ imgData = [None, None]
+ idsToLookup = [int(n[:-4]) for n in nodeObj["imgName"] if n != None]
+ query = "SELECT eol_id, source_url, license, copyright_owner FROM images WHERE eol_id IN (?, ?)"
+ for row in cur.execute(query, idsToLookup):
+ if str(row[0]) == nodeObj["imgName"][0][:-4]:
+ imgData[0] = {"eolId": row[0], "sourceUrl": row[1], "license": row[2], "copyrightOwner": row[3]}
+ else:
+ imgData[1] = {"eolId": row[0], "sourceUrl": row[1], "license": row[2], "copyrightOwner": row[3]}
#
- return {"descData": descData, "imgInfo": imgInfo, "nodeObj": nodeObj}
+ return {"descData": descData, "imgData": imgData, "nodeObj": nodeObj}
class DbServer(BaseHTTPRequestHandler):
def do_GET(self):
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;