aboutsummaryrefslogtreecommitdiff
path: root/src/components/TileInfoModal.vue
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/TileInfoModal.vue')
-rw-r--r--src/components/TileInfoModal.vue324
1 files changed, 161 insertions, 163 deletions
diff --git a/src/components/TileInfoModal.vue b/src/components/TileInfoModal.vue
index 39e2b5b..fc09d86 100644
--- a/src/components/TileInfoModal.vue
+++ b/src/components/TileInfoModal.vue
@@ -1,9 +1,9 @@
<template>
-<div class="fixed left-0 top-0 w-full h-full bg-black/40" @click="onClose">
+<div class="fixed left-0 top-0 w-full h-full bg-black/40" @click="onClose" ref="rootRef">
<div class="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2
max-w-[80%] w-2/3 min-w-[8cm] md:w-[14cm] lg:w-[16cm] max-h-[80%]" :style="styles">
<div class="pb-1 md:pb-2">
- <close-icon @click.stop="onClose" ref="closeIcon"
+ <close-icon @click.stop="onClose" ref="closeRef"
class="absolute top-1 right-1 md:top-2 md:right-2 w-8 h-8 hover:cursor-pointer"/>
<div class="absolute top-1 left-1 md:top-2 md:left-2 flex items-center">
<a :href="'/?node=' + encodeURIComponent(nodeName)" class="block w-8 h-8 p-[2px] hover:cursor-pointer"
@@ -23,7 +23,7 @@
<div v-if="tolNode.iucn != null">
<a href="https://en.wikipedia.org/wiki/Endangered_species_(IUCN_status)"
target="_blank" title="IUCN Conservation Status">IUCN</a>:
- <span :style="iucnStyles">{{getDisplayIucn(tolNode.iucn)}}</span>
+ <span :style="iucnStyles(tolNode.iucn)">{{getDisplayIucn(tolNode.iucn)}}</span>
</div>
<div>
<a :href="'https://tree.opentreeoflife.org/opentree/argus/opentree13.4@' + tolNode.otolId"
@@ -111,176 +111,174 @@
</div>
</template>
-<script lang="ts">
-import {defineComponent, PropType} from 'vue';
+<script setup lang="ts">
+import {ref, computed, PropType} from 'vue';
import SCollapsible from './SCollapsible.vue';
import CloseIcon from './icon/CloseIcon.vue';
import ExternalLinkIcon from './icon/ExternalLinkIcon.vue';
import DownIcon from './icon/DownIcon.vue';
import LinkIcon from './icon/LinkIcon.vue';
-import {TolNode, TolMap} from '../tol';
-import {LayoutNode, LayoutOptions} from '../layout';
-import {getImagePath, DescInfo, ImgInfo, NodeInfo, InfoResponse, UiOptions} from '../lib';
+import {TolNode} from '../tol';
+import {LayoutOptions} from '../layout';
+import {getImagePath, DescInfo, ImgInfo, InfoResponse, UiOptions} from '../lib';
import {capitalizeWords} from '../util';
-export default defineComponent({
- props: {
- // Node data to display
- nodeName: {type: String, required: true},
- infoResponse: {type: Object as PropType<InfoResponse>, required: true},
- // Options
- lytOpts: {type: Object as PropType<LayoutOptions>, required: true},
- uiOpts: {type: Object as PropType<UiOptions>, required: true},
- },
- data(){
- return {
- linkCopied: false, // Used to temporarily show a 'link copied' label
- };
- },
- computed: {
- tolNode(): TolNode {
- return this.infoResponse.nodeInfo.tolNode;
- },
- nodes(): (TolNode | null)[] {
- if (this.infoResponse.subNodesInfo.length == 0){
- return [this.tolNode];
- } else {
- return this.infoResponse.subNodesInfo.map(nodeInfo => nodeInfo != null ? nodeInfo.tolNode : null);
- }
- },
- imgInfos(): (ImgInfo | null)[] {
- if (this.infoResponse.subNodesInfo.length == 0){
- return [this.infoResponse.nodeInfo.imgInfo];
- } else {
- return this.infoResponse.subNodesInfo.map(nodeInfo => nodeInfo != null ? nodeInfo.imgInfo : null);
- }
- },
- descInfos(): (DescInfo | null)[] {
- if (this.infoResponse.subNodesInfo.length == 0){
- return [this.infoResponse.nodeInfo.descInfo];
- } else {
- return this.infoResponse.subNodesInfo.map(nodeInfo => nodeInfo != null ? nodeInfo.descInfo : null);
- }
- },
- subNames(): [string, string] | null {
- const regex = /\[(.+) \+ (.+)\]/;
- let results = regex.exec(this.nodeName);
- return results == null ? null : [results[1], results[2]];
- },
- styles(): Record<string,string> {
- return {
- backgroundColor: this.uiOpts.bgColorAlt,
- borderRadius: this.uiOpts.borderRadius + 'px',
- boxShadow: this.uiOpts.shadowNormal,
- overflow: 'visible auto',
- };
- },
- iucnStyles(): Record<string,string> {
- let col = 'currentcolor';
- switch (this.tolNode.iucn){
- case 'least concern': col = 'green'; break;
- case 'near threatened': col = 'limegreen'; break;
- case 'vulnerable': col = 'goldenrod'; break;
- case 'endangered': col = 'darkorange'; break;
- case 'critically endangered': col = 'red'; break;
- case 'extinct in the wild':
- case 'extinct species': col = 'gray'; break;
- }
- return {
- color: col,
- };
- },
- linkCopyLabelStyles(): Record<string,string> {
- return {
- color: this.uiOpts.textColor,
- backgroundColor: this.uiOpts.bgColor,
- borderRadius: this.uiOpts.borderRadius + 'px',
- };
- },
- },
- methods: {
- getDisplayName(name: string, tolNode: TolNode | null): string {
- if (tolNode == null || tolNode.commonName == null){
- return capitalizeWords(name);
- } else {
- return `${capitalizeWords(tolNode.commonName)} (aka ${capitalizeWords(name)})`;
- }
- },
- getDisplayIucn(iucn: string){
- switch (this.tolNode.iucn){
- case 'least concern': return 'LC';
- case 'near threatened': return 'NT';
- case 'vulnerable': return 'VN';
- case 'endangered': return 'EN';
- case 'critically endangered': return 'CR';
- case 'extinct in the wild': return 'EX';
- case 'extinct species': return 'ES';
- case 'data deficient': return 'DD';
+// Refs
+const rootRef = ref(null as HTMLDivElement | null);
+const closeRef = ref(null as typeof CloseIcon | null);
+
+// Props + events
+const props = defineProps({
+ // Node data to display
+ nodeName: {type: String, required: true},
+ infoResponse: {type: Object as PropType<InfoResponse>, required: true},
+ // Options
+ lytOpts: {type: Object as PropType<LayoutOptions>, required: true},
+ uiOpts: {type: Object as PropType<UiOptions>, required: true},
+});
+const emit = defineEmits(['close']);
+
+// InfoResponse computed data
+const tolNode = computed(() => props.infoResponse.nodeInfo.tolNode);
+const nodes = computed((): (TolNode | null)[] => {
+ if (props.infoResponse.subNodesInfo.length == 0){
+ return [tolNode.value];
+ } else {
+ return props.infoResponse.subNodesInfo.map(nodeInfo => nodeInfo != null ? nodeInfo.tolNode : null);
+ }
+});
+const imgInfos = computed((): (ImgInfo | null)[] => {
+ if (props.infoResponse.subNodesInfo.length == 0){
+ return [props.infoResponse.nodeInfo.imgInfo];
+ } else {
+ return props.infoResponse.subNodesInfo.map(nodeInfo => nodeInfo != null ? nodeInfo.imgInfo : null);
+ }
+});
+const descInfos = computed((): (DescInfo | null)[] => {
+ if (props.infoResponse.subNodesInfo.length == 0){
+ return [props.infoResponse.nodeInfo.descInfo];
+ } else {
+ return props.infoResponse.subNodesInfo.map(nodeInfo => nodeInfo != null ? nodeInfo.descInfo : null);
+ }
+});
+const subNames = computed((): [string, string] | null => {
+ const regex = /\[(.+) \+ (.+)\]/;
+ let results = regex.exec(props.nodeName);
+ return results == null ? null : [results[1], results[2]];
+});
+
+// InfoResponse data converters
+function getDisplayName(name: string, tolNode: TolNode | null): string {
+ if (tolNode == null || tolNode.commonName == null){
+ return capitalizeWords(name);
+ } else {
+ return `${capitalizeWords(tolNode.commonName)} (aka ${capitalizeWords(name)})`;
+ }
+}
+function getDisplayIucn(iucn: string){
+ switch (iucn){
+ case 'least concern': return 'LC';
+ case 'near threatened': return 'NT';
+ case 'vulnerable': return 'VN';
+ case 'endangered': return 'EN';
+ case 'critically endangered': return 'CR';
+ case 'extinct in the wild': return 'EX';
+ case 'extinct species': return 'ES';
+ case 'data deficient': return 'DD';
+ }
+}
+function licenseToUrl(license: string){
+ license = license.toLowerCase().replaceAll('-', ' ');
+ if (license == 'cc0'){
+ return 'https://creativecommons.org/publicdomain/zero/1.0/';
+ } else if (license == 'cc publicdomain'){
+ return 'https://creativecommons.org/licenses/publicdomain/';
+ } else {
+ const regex = /cc by( nc)?( sa)?( ([0-9.]+)( [a-z]+)?)?/;
+ let results = regex.exec(license);
+ if (results != null){
+ let url = 'https://creativecommons.org/licenses/by';
+ if (results[1] != null){
+ url += '-nc';
}
- },
- getImgStyles(tolNode: TolNode | null): Record<string,string> {
- let imgName = null;
- if (tolNode != null && typeof(tolNode.imgName) === 'string'){ // Exclude string-array case
- imgName = tolNode.imgName;
+ if (results[2] != null){
+ url += '-sa';
}
- return {
- width: '200px',
- height: '200px',
- backgroundImage: imgName != null ?
- `url('${getImagePath(imgName as string)}')` :
- 'none',
- backgroundColor: this.uiOpts.bgColorDark,
- backgroundSize: 'cover',
- borderRadius: this.uiOpts.borderRadius + 'px',
- boxShadow: this.uiOpts.shadowNormal,
- };
- },
- licenseToUrl(license: string){
- license = license.toLowerCase().replaceAll('-', ' ');
- if (license == 'cc0'){
- return 'https://creativecommons.org/publicdomain/zero/1.0/';
- } else if (license == 'cc publicdomain'){
- return 'https://creativecommons.org/licenses/publicdomain/';
+ if (results[4] != null){
+ url += '/' + results[4];
} else {
- const regex = /cc by( nc)?( sa)?( ([0-9.]+)( [a-z]+)?)?/;
- let results = regex.exec(license);
- if (results != null){
- let url = 'https://creativecommons.org/licenses/by';
- if (results[1] != null){
- url += '-nc';
- }
- if (results[2] != null){
- url += '-sa';
- }
- if (results[4] != null){
- url += '/' + results[4];
- } else {
- url += '/4.0';
- }
- if (results[5] != null){
- url += '/' + results[5].substring(1);
- }
- return url;
- }
- return "[INVALID LICENSE]";
+ url += '/4.0';
}
- },
- onClose(evt: Event){
- if (evt.target == this.$el || (this.$refs.closeIcon as typeof CloseIcon).$el.contains(evt.target)){
- this.$emit('close');
+ if (results[5] != null){
+ url += '/' + results[5].substring(1);
}
- },
- onLinkIconClick(evt: Event){
- // Copy link to clipboard
- let url = new URL(window.location.href);
- url.search = (new URLSearchParams({node: this.nodeName})).toString();
- navigator.clipboard.writeText(url.toString());
- // Show visual indicator
- this.linkCopied = true;
- setTimeout(() => {this.linkCopied = false}, 1500);
- },
- },
- components: {SCollapsible, CloseIcon, ExternalLinkIcon, DownIcon, LinkIcon, },
- emits: ['close', ],
-});
+ return url;
+ }
+ return "[INVALID LICENSE]";
+ }
+}
+
+// Close handling
+function onClose(evt: Event){
+ if (evt.target == rootRef.value || closeRef.value!.$el.contains(evt.target)){
+ emit('close');
+ }
+}
+
+// Copy-link handling
+const linkCopied = ref(false); // Used to temporarily show a 'link copied' label
+function onLinkIconClick(){
+ // Copy link to clipboard
+ let url = new URL(window.location.href);
+ url.search = (new URLSearchParams({node: props.nodeName})).toString();
+ navigator.clipboard.writeText(url.toString());
+ // Show visual indicator
+ linkCopied.value = true;
+ setTimeout(() => {linkCopied.value = false}, 1500);
+}
+
+// Styles
+const styles = computed(() => ({
+ backgroundColor: props.uiOpts.bgColorAlt,
+ borderRadius: props.uiOpts.borderRadius + 'px',
+ boxShadow: props.uiOpts.shadowNormal,
+ overflow: 'visible auto',
+}));
+function getImgStyles(tolNode: TolNode | null): Record<string,string> {
+ let imgName = null;
+ if (tolNode != null && typeof(tolNode.imgName) === 'string'){ // Exclude string-array case
+ imgName = tolNode.imgName;
+ }
+ return {
+ width: '200px',
+ height: '200px',
+ backgroundImage: imgName != null ?
+ `url('${getImagePath(imgName as string)}')` :
+ 'none',
+ backgroundColor: props.uiOpts.bgColorDark,
+ backgroundSize: 'cover',
+ borderRadius: props.uiOpts.borderRadius + 'px',
+ boxShadow: props.uiOpts.shadowNormal,
+ };
+}
+function iucnStyles(iucn: string): Record<string,string>{
+ let col = 'currentcolor';
+ switch (iucn){
+ case 'least concern': col = 'green'; break;
+ case 'near threatened': col = 'limegreen'; break;
+ case 'vulnerable': col = 'goldenrod'; break;
+ case 'endangered': col = 'darkorange'; break;
+ case 'critically endangered': col = 'red'; break;
+ case 'extinct in the wild':
+ case 'extinct species': col = 'gray'; break;
+ }
+ return {
+ color: col,
+ };
+}
+const linkCopyLabelStyles = computed(() => ({
+ color: props.uiOpts.textColor,
+ backgroundColor: props.uiOpts.bgColor,
+ borderRadius: props.uiOpts.borderRadius + 'px',
+}));
</script>