diff options
| -rw-r--r-- | src/App.vue | 18 | ||||
| -rw-r--r-- | src/components/TileInfoModal.vue | 35 | ||||
| -rw-r--r-- | src/components/icon/LinkIcon.vue | 12 | ||||
| -rw-r--r-- | src/index.css | 13 |
4 files changed, 73 insertions, 5 deletions
diff --git a/src/App.vue b/src/App.vue index df2b9b7..cd38621 100644 --- a/src/App.vue +++ b/src/App.vue @@ -889,8 +889,13 @@ export default defineComponent({ }, // For initialisation async initTreeFromServer(firstInit = true){ + // Get possible target node from URL + let nodeName = (new URL(window.location.href)).searchParams.get('node'); // Query server let urlParams = 'type=node'; + if (nodeName != null){ + urlParams += '&name=' + encodeURIComponent(nodeName) + '&toroot=true'; + } urlParams += '&tree=' + this.uiOpts.tree; let responseObj: {[x: string]: TolNode} = await this.loadFromServer(urlParams); if (responseObj == null){ @@ -915,14 +920,21 @@ export default defineComponent({ this.layoutTree = initLayoutTree(this.tolMap, rootName, 0); this.activeRoot = this.layoutTree; this.layoutMap = initLayoutMap(this.layoutTree); - // Relayout - await this.updateAreaDims(); - this.relayoutWithCollapse(false); // Skip initial transition if (firstInit){ this.justInitialised = true; setTimeout(() => {this.justInitialised = false}, this.uiOpts.transitionDuration); } + // Relayout + await this.updateAreaDims(); + this.relayoutWithCollapse(false); + // Possibly jump to a target node + if (nodeName != null){ + let oldSetting = this.uiOpts.searchJumpMode; + this.uiOpts.searchJumpMode = true; + await this.onSearch(nodeName); + this.uiOpts.searchJumpMode = oldSetting; + } }, async reInit(){ if (this.activeRoot != this.layoutTree){ diff --git a/src/components/TileInfoModal.vue b/src/components/TileInfoModal.vue index 92d19de..9e0f2c3 100644 --- a/src/components/TileInfoModal.vue +++ b/src/components/TileInfoModal.vue @@ -5,7 +5,16 @@ :style="styles"> <div class="pb-1 md:pb-2"> <close-icon @click.stop="onClose" ref="closeIcon" - class="absolute top-1 right-1 md:m-2 w-8 h-8 hover:cursor-pointer"/> + 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" + @click.prevent="onLinkIconClick"> + <link-icon/> + </a> + <transition name="fadeslow"> + <div v-if="linkCopied" class="text-sm p-1 ml-2" :style="linkCopyLabelStyles">Link Copied</div> + </transition> + </div> <h1 class="text-center text-xl font-bold pt-2 pb-1 mx-10 md:text-2xl md:pt-3 md:pb-1"> {{getDisplayName(nodeName, tolNode)}} </h1> @@ -102,6 +111,7 @@ 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'; @@ -116,6 +126,11 @@ export default defineComponent({ 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; @@ -154,6 +169,13 @@ export default defineComponent({ overflow: 'visible auto', }; }, + 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 { @@ -215,8 +237,17 @@ export default defineComponent({ this.$emit('close'); } }, + onLinkIconClick(evt: Event){ + // Copy link to clipboard + let url = new URL(window.location.href); + url.search = 'node=' + encodeURIComponent(this.nodeName); + navigator.clipboard.writeText(url.toString()); + // Show visual indicator + this.linkCopied = true; + setTimeout(() => {this.linkCopied = false}, 1500); + }, }, - components: {SCollapsible, CloseIcon, ExternalLinkIcon, DownIcon, }, + components: {SCollapsible, CloseIcon, ExternalLinkIcon, DownIcon, LinkIcon, }, emits: ['close', ], }); </script> diff --git a/src/components/icon/LinkIcon.vue b/src/components/icon/LinkIcon.vue new file mode 100644 index 0000000..49996b7 --- /dev/null +++ b/src/components/icon/LinkIcon.vue @@ -0,0 +1,12 @@ +<template> +<svg viewBox="0 0 24 24" fill="none" + stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> + <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path> + <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path> +</svg> +</template> + +<script lang="ts"> +import {defineComponent, PropType} from 'vue'; +export default defineComponent({}); +</script> diff --git a/src/index.css b/src/index.css index 5987443..7c01490 100644 --- a/src/index.css +++ b/src/index.css @@ -23,6 +23,19 @@ a { transition-duration: 300ms; transition-timing-function: ease-out; } +.fadeslow-enter-from, .fadeslow-leave-to { + opacity: 0; +} +.fadeslow-enter-active { + transition-property: opacity; + transition-duration: 300ms; + transition-timing-function: ease-out; +} +.fadeslow-leave-active { + transition-property: opacity; + transition-duration: 1000ms; + transition-timing-function: linear; +} .fadein-leave-to { opacity: 0; } |
