diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/AncestryBar.vue | 7 | ||||
| -rw-r--r-- | src/components/HelpModal.vue | 51 | ||||
| -rw-r--r-- | src/components/IconButton.vue | 2 | ||||
| -rw-r--r-- | src/components/SButton.vue | 2 | ||||
| -rw-r--r-- | src/components/SearchModal.vue | 27 | ||||
| -rw-r--r-- | src/components/SettingsModal.vue | 85 | ||||
| -rw-r--r-- | src/components/Tile.vue | 14 | ||||
| -rw-r--r-- | src/components/TileInfoModal.vue | 142 | ||||
| -rw-r--r-- | src/components/TutorialPane.vue | 24 | ||||
| -rw-r--r-- | src/components/icon/HelpIcon.vue | 9 |
10 files changed, 211 insertions, 152 deletions
diff --git a/src/components/AncestryBar.vue b/src/components/AncestryBar.vue index cf9513f..e7ba8a5 100644 --- a/src/components/AncestryBar.vue +++ b/src/components/AncestryBar.vue @@ -26,6 +26,7 @@ export default defineComponent({ computed: { imgSz(){ return this.breadth - this.lytOpts.tileSpacing - this.uiOpts.scrollGap; + // Intentionally omitting extra tileSpacing, to allow for scrollGap with less image shrinkage }, dummyNodes(){ // Childless versions of 'nodes' used to parameterise <tile>s return this.nodes.map(n => { @@ -53,10 +54,10 @@ export default defineComponent({ watch: { // Used to scroll to end of bar upon node/screen changes nodes(){ - this.$nextTick(() => this.scrollToEnd()); // Without timeout, seems to run before new tiles are added + this.$nextTick(() => this.scrollToEnd()); }, vert(){ - setTimeout(() => this.scrollToEnd(), 0); + this.$nextTick(() => this.scrollToEnd()); }, }, methods: { @@ -67,7 +68,7 @@ export default defineComponent({ onInfoIconClick(data: string){ this.$emit('info-click', data); }, - // For converting vertical scroll to horizontal + // For converting vertical scrolling to horizontal onWheelEvt(evt: WheelEvent){ if (!this.vert && Math.abs(evt.deltaX) < Math.abs(evt.deltaY)){ this.$el.scrollLeft -= (evt.deltaY > 0 ? -30 : 30); diff --git a/src/components/HelpModal.vue b/src/components/HelpModal.vue index 7a18c05..f2ae8a4 100644 --- a/src/components/HelpModal.vue +++ b/src/components/HelpModal.vue @@ -1,30 +1,34 @@ <template> <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 p-4" :style="styles"> + <div class="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 + w-4/5 max-w-[20cm] max-h-[80%] overflow-auto" :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">Help Info</h1> - <hr class="mb-4 border-stone-400"/> - <div class="mb-4"> - Lorem ipsum dolor sit amet, consectetur adipiscing - elit, sed do eiusmod tempor incididunt ut labore - et dolore magna aliqua. Ut enim ad minim veniam, - quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. + class="block absolute top-1 right-1 md:top-2 md:right-2 w-8 h-8 hover:cursor-pointer"/> + <h1 class="text-center text-xl font-bold py-2 border-b border-stone-400">Help</h1> + <div class="p-2 border-b border-stone-400"> + <div> + Lorem ipsum dolor sit amet, consectetur adipiscing + elit, sed do eiusmod tempor incididunt ut labore + et dolore magna aliqua. + </div> + <ul class="list-disc ml-3"> + <li>What is the Tree of Life? ('tips', )</li> + <li>Representing the Tree + (OneZoom, iTol) + (tile layout, overflown-root, ) + (leaf header colors, asterisk, compound nodes/names/images, ) + </li> + <li>Data sources (OTOL, EOL, Wikipedia) (imprecision: tree, names, images, descs, )</li> + <li>Using tilo + (tutorial) + (settings: layout methods, reduced trees, click-label-to-reset) + (keyboard shortcuts) + </li> + <li>Project page, error contact, other credits (feathericons, ionicons), </li> + </ul> </div> - <div> - Lorem ipsum dolor sit amet, consectetur adipiscing - elit, sed do eiusmod tempor incididunt ut labore - et dolore magna aliqua. Ut enim ad minim veniam, - quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. Duis aute irure - dolor in reprehenderit in voluptate velit esse - cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt - in culpa qui officia deserunt mollit anim id - est laborum. - </div> - <s-button :style="{color: uiOpts.textColor, backgroundColor: uiOpts.bgColor}" @click.stop="onStartTutorial"> + <s-button :disabled="tutOpen" class="mx-auto my-2" + :style="{color: uiOpts.textColor, backgroundColor: uiOpts.bgColor}" @click.stop="onStartTutorial"> Start Tutorial </s-button> </div> @@ -39,6 +43,7 @@ import {UiOptions} from '../lib'; export default defineComponent({ props: { + tutOpen: {type: Boolean, default: false}, uiOpts: {type: Object as PropType<UiOptions>, required: true}, }, computed: { diff --git a/src/components/IconButton.vue b/src/components/IconButton.vue index 0a32095..0294c5b 100644 --- a/src/components/IconButton.vue +++ b/src/components/IconButton.vue @@ -1,6 +1,6 @@ <template> <div class="p-2 rounded-full hover:cursor-pointer" - :class="{'hover:brightness-125': !disabled, 'brightness-75': disabled}" + :class="{'hover:brightness-125': !disabled, 'brightness-50': disabled}" :style="{width: size + 'px', height: size + 'px', padding: (size / 5) + 'px'}"> <slot class="w-full h-full">?</slot> </div> diff --git a/src/components/SButton.vue b/src/components/SButton.vue index a480c37..54c7843 100644 --- a/src/components/SButton.vue +++ b/src/components/SButton.vue @@ -1,7 +1,7 @@ <template> <button :disabled="disabled" class="block rounded px-4 py-2" - :class="{'hover:brightness-125': !disabled, 'brightness-75': disabled}"> + :class="{'hover:brightness-125': !disabled, 'brightness-50': disabled}"> <slot>?</slot> </button> </template> diff --git a/src/components/SearchModal.vue b/src/components/SearchModal.vue index 4d39772..55e50a1 100644 --- a/src/components/SearchModal.vue +++ b/src/components/SearchModal.vue @@ -1,17 +1,17 @@ <template> <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/4 w-3/4 max-w-[12cm] -translate-y-1/2 flex" :style="styles"> - <input type="text" class="block border p-1 rounded-l-[inherit] grow" ref="searchInput" + <div class="absolute left-1/2 -translate-x-1/2 top-1/4 -translate-y-1/2 min-w-3/4 md:min-w-[12cm] flex" + :style="styles"> + <input type="text" class="block border p-1 px-2 rounded-l-[inherit] grow" ref="searchInput" @keyup.enter="onSearch" @keyup.esc="onClose" @input="onInput" @keydown.down.prevent="onDownKey" @keydown.up.prevent="onUpKey"/> <div class="p-1 hover:cursor-pointer"> <search-icon @click.stop="onSearch" class="block w-8 h-8"/> </div> - <div class="absolute top-[100%] w-full" - :style="{backgroundColor: uiOpts.bgColorAlt, color: uiOpts.textColorAlt}"> + <div class="absolute top-[100%] w-full overflow-hidden" :style="suggContainerStyles"> <div v-for="(sugg, idx) of searchSuggs" :style="{backgroundColor: idx == focusedSuggIdx ? uiOpts.bgColorAltDark : uiOpts.bgColorAlt}" - class="border p-1 hover:underline hover:cursor-pointer flex" + class="border-b p-1 px-2 hover:underline hover:cursor-pointer flex" @click="resolveSearch(sugg.canonicalName || sugg.name)"> <div class="grow overflow-hidden whitespace-nowrap text-ellipsis"> <span>{{suggDisplayStrings[idx][0]}}</span> @@ -21,11 +21,10 @@ <info-icon class="hover:cursor-pointer my-auto w-5 h-5" @click.stop="onInfoIconClick(sugg.canonicalName || sugg.name)"/> </div> - <div v-if="searchHadMoreSuggs" class="text-center border">...</div> + <div v-if="searchHadMoreSuggs" class="text-center">• • •</div> </div> <label :style="animateLabelStyles" class="flex gap-1"> - <input type="checkbox" v-model="uiOpts.searchJumpMode" - @change="$emit('setting-chg', 'searchJumpMode')"/> + <input type="checkbox" v-model="uiOpts.searchJumpMode" @change="$emit('setting-chg', 'searchJumpMode')"/> <div class="text-sm">Jump to result</div> </label> </div> @@ -43,7 +42,7 @@ import {getServerResponse, SearchSugg, SearchSuggResponse, UiOptions} from '../l export default defineComponent({ props: { - tolMap: {type: Object as PropType<TolMap>, required: true}, // Added to from a search response + tolMap: {type: Object as PropType<TolMap>, required: true}, // Upon a search response, gets new nodes added lytOpts: {type: Object as PropType<LayoutOptions>, required: true}, uiOpts: {type: Object as PropType<UiOptions>, required: true}, }, @@ -71,6 +70,14 @@ export default defineComponent({ boxShadow: this.uiOpts.shadowNormal, }; }, + suggContainerStyles(): Record<string,string> { + let br = this.uiOpts.borderRadius; + return { + backgroundColor: this.uiOpts.bgColorAlt, + color: this.uiOpts.textColorAlt, + borderRadius: `0 0 ${br}px ${br}px`, + }; + }, animateLabelStyles(): Record<string,string> { return { position: 'absolute', @@ -167,7 +174,7 @@ export default defineComponent({ onUpKey(){ if (this.focusedSuggIdx != null){ this.focusedSuggIdx = (this.focusedSuggIdx - 1 + this.searchSuggs.length) % this.searchSuggs.length; - // The addition after -1 is to avoid becoming negative + // The addition after '-1' is to avoid becoming negative } }, // Search events diff --git a/src/components/SettingsModal.vue b/src/components/SettingsModal.vue index 315ddb4..73b204d 100644 --- a/src/components/SettingsModal.vue +++ b/src/components/SettingsModal.vue @@ -1,15 +1,34 @@ <template> <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 - min-w-[8cm] max-h-[80%] overflow-auto p-3" :style="styles"> + min-w-[8cm] max-w-[80%] max-h-[80%] overflow-auto" :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-xl font-bold mb-2">Settings</h1> - <div class="border rounded p-1"> - <h2 class="text-center">Layout</h2> - <div class="flex gap-2"> - <div class="grow"> - <div>Sweep leaves to side</div> + class="block absolute top-1 right-1 md:top-2 md:right-2 w-8 h-8 hover:cursor-pointer" /> + <h1 class="text-xl md:text-2xl font-bold text-center py-2 border-b border-stone-400">Settings</h1> + <div class="border-b border-stone-400 pb-2"> + <h2 class="font-bold md:text-xl text-center pt-1 md:pt-2 md:pb-1">Timing</h2> + <div class="grid grid-cols-[130px_minmax(0,1fr)_65px] gap-1 px-2 md:px-3"> + <!-- Row 1 --> + <label for="animTimeInput" @click="onReset('UI', 'transitionDuration')" :class="rLabelClasses"> + Animation Time + </label> + <input type="range" min="0" max="1000" v-model.number="uiOpts.transitionDuration" + @change="onSettingChg('UI', 'transitionDuration')" class="my-auto" name="animTimeInput"/> + <div class="my-auto text-right">{{uiOpts.transitionDuration}} ms</div> + <!-- Row 2 --> + <label for="autoDelayInput" @click="onReset('UI', 'autoActionDelay')" :class="rLabelClasses"> + Auto-mode Delay + </label> + <input type="range" min="0" max="1000" v-model.number="uiOpts.autoActionDelay" + @change="onSettingChg('UI', 'autoActionDelay')" class="my-auto" name="autoDelayInput"/> + <div class="my-auto text-right">{{uiOpts.autoActionDelay}} ms</div> + </div> + </div> + <div class="border-b border-stone-400 pb-2"> + <h2 class="font-bold md:text-xl text-center pt-1 md:pt-2 md:pb-1">Layout</h2> + <div class="flex gap-2 justify-around px-2 pb-1"> + <div> + <div>Sweep leaves left</div> <ul> <li> <label> <input type="radio" v-model="sweepLeaves" :value="true" @change="onSettingChg('LYT', 'layoutType')"/> Yes </label> </li> @@ -17,7 +36,7 @@ @change="onSettingChg('LYT', 'layoutType')"/> No </label> </li> </ul> </div> - <div class="grow"> + <div> <div>Sweep into parent</div> <ul> <li> <label> <input type="radio" :disabled="!sweepLeaves" v-model="lytOpts.sweepToParent" @@ -29,12 +48,13 @@ </ul> </div> </div> - <div class="grid grid-cols-[minmax(0,3fr)_minmax(0,4fr)_minmax(0,2fr)] gap-1"> + <div class="grid grid-cols-[100px_minmax(0,1fr)_65px] gap-1 w-fit mx-auto px-2 md:px-3"> <!-- Row 1 --> <label for="minTileSizeInput" @click="onReset('LYT', 'minTileSz')" :class="rLabelClasses"> Min Tile Size </label> - <input type="range" min="0" max="400" v-model.number="lytOpts.minTileSz" + <input type="range" + min="15" :max="uiOpts.breakpoint == 'sm' ? 150 : 200" v-model.number="lytOpts.minTileSz" @input="onSettingChgThrottled('LYT', 'minTileSz')" @change="onSettingChg('LYT', 'minTileSz')" name="minTileSizeInput" ref="minTileSzInput"/> <div class="my-auto text-right">{{pxToDisplayStr(lytOpts.minTileSz)}}</div> @@ -42,7 +62,7 @@ <label for="maxTileSizeInput" @click="onReset('LYT', 'maxTileSz')" :class="rLabelClasses"> Max Tile Size </label> - <input type="range" min="0" max="400" v-model.number="lytOpts.maxTileSz" + <input type="range" min="15" max="400" v-model.number="lytOpts.maxTileSz" @input="onSettingChgThrottled('LYT', 'maxTileSz')" @change="onSettingChg('LYT', 'maxTileSz')" name="maxTileSizeInput" ref="maxTileSzInput"/> <div class="my-auto text-right">{{pxToDisplayStr(lytOpts.maxTileSz)}}</div> @@ -56,32 +76,13 @@ <div class="my-auto text-right">{{pxToDisplayStr(lytOpts.tileSpacing)}}</div> </div> </div> - <div class="border rounded p-1"> - <h2 class="text-center">Timing</h2> - <div class="grid grid-cols-[minmax(0,3fr)_minmax(0,4fr)_minmax(0,2fr)] gap-1"> - <!-- Row 1 --> - <label for="animTimeInput" @click="onReset('UI', 'transitionDuration')" :class="rLabelClasses"> - Animation Time - </label> - <input type="range" min="0" max="3000" v-model.number="uiOpts.transitionDuration" - @change="onSettingChg('UI', 'transitionDuration')" class="my-auto" name="animTimeInput"/> - <div class="my-auto text-right">{{uiOpts.transitionDuration}} ms</div> - <!-- Row 2 --> - <label for="autoDelayInput" @click="onReset('UI', 'autoActionDelay')" :class="rLabelClasses"> - Auto-mode Delay - </label> - <input type="range" min="0" max="3000" v-model.number="uiOpts.autoActionDelay" - @change="onSettingChg('UI', 'autoActionDelay')" class="my-auto" name="autoDelayInput"/> - <div class="my-auto text-right">{{uiOpts.autoActionDelay}} ms</div> - </div> - </div> - <div class="border rounded p-1"> - <h2 class="text-center">Other</h2> + <div class="border-b border-stone-400 pb-2 px-2 md:px-3"> + <h2 class="font-bold md:text-xl text-center pt-1 md:pt-2 -mb-2 ">Other</h2> <div> - <div>Tree to use</div> - <ul> + Tree to use + <ul class="flex justify-evenly"> <li> <label> <input type="radio" v-model="uiOpts.tree" value="trimmed" - @change="onSettingChg('UI', 'tree')"/> Comprehensive </label> </li> + @change="onSettingChg('UI', 'tree')"/> Complex </label> </li> <li> <label> <input type="radio" v-model="uiOpts.tree" value="images" @change="onSettingChg('UI', 'tree')"/> Visual </label> </li> <li> <label> <input type="radio" v-model="uiOpts.tree" value="picked" @@ -97,7 +98,7 @@ @change="onSettingChg('UI', 'disableShortcuts')"/> Disable keyboard shortcuts </label> </div> </div> - <s-button class="mx-auto mt-2" :style="{color: uiOpts.textColor, backgroundColor: uiOpts.bgColor}" + <s-button class="mx-auto my-2" :style="{color: uiOpts.textColor, backgroundColor: uiOpts.bgColor}" @click="onResetAll"> Reset </s-button> @@ -160,13 +161,13 @@ export default defineComponent({ let maxInput = this.$refs.maxTileSzInput as HTMLInputElement; if (option == 'minTileSz' && Number(minInput.value) > Number(maxInput.value)){ this.lytOpts.maxTileSz = this.lytOpts.minTileSz; - this.$emit('setting-chg', 'LYT', 'maxTileSz', {save: false}); + this.$emit('setting-chg', 'LYT', 'maxTileSz'); } else if (option == 'maxTileSz' && Number(maxInput.value) < Number(minInput.value)){ this.lytOpts.minTileSz = this.lytOpts.maxTileSz; - this.$emit('setting-chg', 'LYT', 'minTileSz', {save: false}); + this.$emit('setting-chg', 'LYT', 'minTileSz'); } } - // Notify App + // Notify parent component this.$emit('setting-chg', optionType, option, {relayout: optionType == 'LYT', reinit: optionType == 'UI' && option == 'tree'}); // Possibly make saved-indicator appear/animate @@ -188,6 +189,7 @@ export default defineComponent({ } }, onReset(optionType: OptionType, option: string){ + // Restore the setting's default let defaultLytOpts = getDefaultLytOpts(); let defaultUiOpts = getDefaultUiOpts(defaultLytOpts); if (optionType == 'LYT'){ @@ -206,6 +208,7 @@ export default defineComponent({ } (this.uiOpts[uiOpt] as any) = defaultUiOpts[uiOpt]; } + // Notify parent component this.onSettingChg(optionType, option); }, onResetAll(){ @@ -215,7 +218,7 @@ export default defineComponent({ let needReinit = this.uiOpts.tree != defaultUiOpts.tree; Object.assign(this.lytOpts, defaultLytOpts); Object.assign(this.uiOpts, defaultUiOpts); - // Notify App + // Notify parent component this.$emit('reset', needReinit); // Clear saved-indicator this.saved = false; diff --git a/src/components/Tile.vue b/src/components/Tile.vue index 9b43467..ab1538f 100644 --- a/src/components/Tile.vue +++ b/src/components/Tile.vue @@ -17,14 +17,14 @@ @click.stop="onInfoIconClick" @mousedown.stop @mouseup.stop/> </template> </div> - <div v-else :style="nonleafStyles" ref="nonleaf"> + <div v-else :style="nonleafStyles"> <div v-if="showNonleafHeader" :style="nonleafHeaderStyles" class="flex hover:cursor-pointer" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @mousedown="onMouseDown" @mouseup="onMouseUp"> <h1 :style="nonleafHeaderTextStyles" class="grow">{{displayName}}</h1> <info-icon v-if="infoIconDisabled" :style="infoIconStyles" :class="infoIconClasses" @click.stop="onInfoIconClick" @mousedown.stop @mouseup.stop/> </div> - <div :style="sepSweptAreaStyles" :class="sepSweptAreaHideEdgeClass" ref="sepSweptArea"> + <div :style="sepSweptAreaStyles" :class="sepSweptAreaHideEdgeClass"> <div v-if="layoutNode.sepSweptArea?.sweptLeft === false" :style="nonleafHeaderStyles" class="flex hover:cursor-pointer" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @mousedown="onMouseDown" @mouseup="onMouseUp"> @@ -162,7 +162,7 @@ export default defineComponent({ } }, fontSz(): number { - return 0.75 * this.lytOpts.headerSz; + return 0.8 * this.lytOpts.headerSz; }, styles(): Record<string,string> { let layoutStyles = { @@ -234,10 +234,11 @@ export default defineComponent({ break; } } + let screenSz = this.uiOpts.breakpoint; return { height: this.lytOpts.headerSz + 'px', - padding: ((this.lytOpts.headerSz - this.fontSz) / 2) + 'px', - lineHeight: this.fontSz + 'px', + padding: `0 ${(this.lytOpts.headerSz - this.fontSz)}px`, + lineHeight: this.lytOpts.headerSz + 'px', fontSize: this.fontSz + 'px', color: textColor, // For ellipsis @@ -288,8 +289,7 @@ export default defineComponent({ }, nonleafHeaderTextStyles(): Record<string,string> { return { - margin: 'auto 0', - lineHeight: this.fontSz + 'px', + lineHeight: this.lytOpts.headerSz + 'px', fontSize: this.fontSz + 'px', textAlign: 'center', color: this.uiOpts.textColor, diff --git a/src/components/TileInfoModal.vue b/src/components/TileInfoModal.vue index f507f8d..6b7bd26 100644 --- a/src/components/TileInfoModal.vue +++ b/src/components/TileInfoModal.vue @@ -1,64 +1,96 @@ <template> <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"> - {{getDisplayName(nodeName, tolNode)}} - <div v-if="tolNode != null"> - ({{tolNode.children.length}} children, {{tolNode.tips}} tips, + <div class="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 + max-w-[80%] min-w-[8cm] md:min-w-[15cm] max-h-[80%]" + :style="styles"> + <div class="pb-1 md:pb-2"> + <div class="flex"> + <h1 class="text-center text-xl font-bold grow pt-2 pb-1 md:text-2xl md:pt-3 md:pb-1"> + {{getDisplayName(nodeName, tolNode)}} + </h1> + <close-icon @click.stop="onClose" ref="closeIcon" + class="block m-1 md:m-2 w-8 h-8 hover:cursor-pointer"/> + </div> + <div class="flex justify-evenly text-sm md:text-base"> + <div> Children: {{tolNode.children.length}} </div> + <div> Tips: {{tolNode.tips}} </div> + <div> <a :href="'https://tree.opentreeoflife.org/opentree/argus/opentree13.4@' + tolNode.otolId" - target="_blank">{{tolNode.otolId}}</a>) + target="_blank">OTOL <external-link-icon class="inline-block w-3 h-3"/></a> + </div> + </div> + <div v-if="nodes.length > 1" class="text-center text-sm px-2"> + <div> (This is a compound node. The details below describe two descendants) </div> </div> - </h1> - <hr class="mb-4 border-stone-400"/> - <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"> + </div> + <div v-for="idx in (nodes.length == 1 ? [0] : [0, 1])" class="border-t border-stone-400 p-2 md:p-3 clear-both"> + <h1 v-if="nodes.length > 1" class="text-center font-bold mb-1"> {{getDisplayName(subNames![idx], nodes[idx])}} </h1> <div v-if="nodes[idx] == null" class="text-center"> - (This node has been trimmed away) + (This node was trimmed away duing tree simplification) </div> - <div v-else class="flex gap-1"> - <div class="w-1/2"> - <div v-if="imgInfos[idx] == null" :style="getImgStyles(nodes[idx])"/> - <a v-else :href="imgInfos[idx]!.url" target="_blank"> + <div v-else> + <div v-if="imgInfos[idx] != null" class="mt-1 mr-2 md:mb-2 md:mr-4 md:float-left"> + <a :href="imgInfos[idx]!.url" target="_blank" class="block w-fit mx-auto"> <div :style="getImgStyles(nodes[idx])"/> </a> - <ul v-if="imgInfos[idx]! != null"> - <li>Obtained via: {{imgInfos[idx]!.src}}</li> - <li>License: - <a :href="licenseToUrl(imgInfos[idx]!.license)" target="_blank"> - <span class="underline">{{imgInfos[idx]!.license}}</span> - <external-link-icon class="inline-block w-3 h-3 ml-1"/> - </a> - </li> - <li v-if="imgInfos[idx]!.url != ''"> - <a :href="imgInfos[idx]!.url" target="_blank"> - <span class="underline">Source URL</span> - <external-link-icon class="inline-block w-3 h-3 ml-1"/> - </a> - </li> - <li v-if="imgInfos[idx]!.artist != ''">Artist: {{imgInfos[idx]!.artist}}</li> - <li v-if="imgInfos[idx]!.credit != ''" class="overflow-auto"> - Credit: {{imgInfos[idx]!.credit}} - </li> - </ul> + <div class="relative"> + <details class="w-fit mx-auto text-sm hover:cursor-pointer" + @click="onDetailsClick($event, idx)" ref="srcInfoDetails"> + <summary>Source information</summary> + <transition name="fade" @after-leave="onSrcInfoHideTransition(idx)"> + <div v-if="srcInfoShow[idx]" class="absolute left-1/2 -translate-x-1/2 w-max max-w-full + md:left-0 md:translate-x-0 md:max-w-[14cm] pb-2"> <!-- Provides bottom-padding --> + <ul class="rounded shadow p-1 overflow-auto" + :style="{backgroundColor: uiOpts.bgColor, color: uiOpts.textColor}"> + <li v-if="imgInfos[idx]!.url != ''"> + <span :style="{color: uiOpts.altColor}">Source: </span> + <a :href="imgInfos[idx]!.url" target="_blank">Link</a> + <external-link-icon class="inline-block w-3 h-3 ml-1"/> + </li> + <li v-if="imgInfos[idx]!.artist != ''"> + <span :style="{color: uiOpts.altColor}">Artist: </span> + {{imgInfos[idx]!.artist}} + </li> + <li v-if="imgInfos[idx]!.credit != ''"> + <span :style="{color: uiOpts.altColor}">Credits: </span> + {{imgInfos[idx]!.credit}} + </li> + <li> + <span :style="{color: uiOpts.altColor}">License: </span> + <a :href="licenseToUrl(imgInfos[idx]!.license)" target="_blank"> + {{imgInfos[idx]!.license}} + </a> + <external-link-icon class="inline-block w-3 h-3 ml-1"/> + </li> + <li v-if="imgInfos[idx]!.src != 'picked'"> + <span :style="{color: uiOpts.altColor}">Obtained via: </span> + <a v-if="imgInfos[idx]!.src == 'eol'" href="https://eol.org/">EoL</a> + <a v-else href="https://www.wikipedia.org/">Wikipedia</a> + <external-link-icon class="inline-block w-3 h-3 ml-1"/> + </li> + <li> + <span :style="{color: uiOpts.altColor}">Changes: </span> + Cropped and resized + </li> + </ul> + </div> + </transition> + </details> + </div> </div> <div v-if="descInfos[idx]! != null"> - <div> - Redirected: {{descInfos[idx]!.fromRedirect}} <br/> - Short-description from {{descInfos[idx]!.fromDbp ? 'DBpedia' : 'Wikipedia'}} <br/> + <div>{{descInfos[idx]!.text}}</div> + <div class="text-sm text-right"> + From <a :href="'https://en.wikipedia.org/?curid=' + descInfos[idx]!.wikiId" target="_blank"> - <span class="underline">Wikipedia Link</span> - <external-link-icon class="inline-block w-3 h-3 ml-1"/> + Wikipedia </a> + <external-link-icon class="inline-block w-3 h-3"/> </div> - <hr/> - <div>{{descInfos[idx]!.text}}</div> </div> - <div v-else> + <div v-else class="text-center"> (No description found) </div> </div> @@ -85,6 +117,11 @@ export default defineComponent({ lytOpts: {type: Object as PropType<LayoutOptions>, required: true}, uiOpts: {type: Object as PropType<UiOptions>, required: true}, }, + data(){ + return { + srcInfoShow: [false, false], // For transitioning source-info panes when enclosing <details> are clicked + }; + }, computed: { tolNode(): TolNode { return this.infoResponse.nodeInfo.tolNode; @@ -138,8 +175,8 @@ export default defineComponent({ imgName = tolNode.imgName; } return { - width: this.lytOpts.maxTileSz + 'px', - height: this.lytOpts.maxTileSz + 'px', + width: '200px', + height: '200px', backgroundImage: imgName != null ? `url('${getImagePath(imgName as string)}')` : 'none', @@ -184,6 +221,17 @@ export default defineComponent({ this.$emit('close'); } }, + onDetailsClick(evt: Event, idx: number){ + this.srcInfoShow[idx] = !this.srcInfoShow[idx]; + if (this.srcInfoShow[idx] == false){ + evt.preventDefault(); // Delay <details> closure, to allow for transition + } + }, + onSrcInfoHideTransition(idx: number){ + // Close <details>, now that it's content has transitioned away + let details = this.$refs.srcInfoDetails[idx]; + details.removeAttribute('open'); + }, }, components: {CloseIcon, ExternalLinkIcon, }, emits: ['close', ], diff --git a/src/components/TutorialPane.vue b/src/components/TutorialPane.vue index 289f386..430db8a 100644 --- a/src/components/TutorialPane.vue +++ b/src/components/TutorialPane.vue @@ -1,12 +1,10 @@ <template> -<div :style="styles" class="p-2 flex flex-col justify-between"> - <div class="flex"> - <h2 class="text-center mb-2 mx-auto"> - {{stage == 0 ? 'Welcome' : `Tutorial (Step ${stage} of ${lastStage})`}} - </h2> - <close-icon @click.stop="onClose" - class="block w-6 h-6 hover:cursor-pointer"/> - </div> +<div :style="styles" class="relative flex flex-col justify-between"> + <close-icon @click.stop="onClose" + class="block absolute top-2 right-2 w-8 h-8 hover:cursor-pointer"/> + <h1 class="text-center text-lg font-bold pt-3 pb-2"> + {{stage == 0 ? 'Welcome' : `Tutorial (Step ${stage} of ${lastStage})`}} + </h1> <transition name="fade" mode="out-in"> <div v-if="stage == 0" :style="contentStyles"> This is a visualiser for the biological Tree of Life. For more information, @@ -31,7 +29,7 @@ {{touchDevice ? 'Tap' : 'Click'}} the icon on a tile's bottom-right to bring up more information <span class="block text-sm brightness-50"> - For an expanded tile, it's at the header's right + For an expanded tile, it's on the header's right </span> </div> <div v-else-if="stage == 6" :style="contentStyles"> @@ -47,11 +45,11 @@ {{touchDevice ? 'Tap' : 'Click'}} the settings icon </div> <div v-else-if="stage == 9" :style="contentStyles"> - And finally, tap the help icon for more information + And finally, {{touchDevice ? 'tap' : 'click'}} the help icon for more information </div> </transition> <!-- Buttons --> - <div class="w-full flex justify-evenly mt-2"> + <div class="w-full my-2 flex justify-evenly"> <template v-if="stage == 0"> <s-button :style="buttonStyles" @click="onStartTutorial">Start Tutorial</s-button> <s-button :style="buttonStyles" @click="onSkipTutorial">Skip</s-button> @@ -88,12 +86,12 @@ export default defineComponent({ stage: 0, // Indicates the current step of the tutorial (stage 0 is the welcome message) lastStage: 9, disabledOnce: false, // Set to true after disabling features at stage 1 - hidNextPrevOnce: false, // Used to hide prev/next buttons when initially at stage 1 stageActions: [ // Specifies, for stages 1+, what action to enable (can repeat an action to enable nothing new) 'expand', 'collapse', 'expandToView', 'unhideAncestor', 'tileInfo', 'search', 'autoMode', 'settings', 'help', ] as Action[], + hidNextPrevOnce: false, // Used to hide prev/next buttons when initially at stage 1 }; }, computed: { @@ -106,8 +104,6 @@ export default defineComponent({ contentStyles(): Record<string,string> { return { padding: '0 0.5cm', - maxWidth: '15cm', - margin: '0 auto', overflow: 'auto', textAlign: 'center', }; diff --git a/src/components/icon/HelpIcon.vue b/src/components/icon/HelpIcon.vue index c8e4ca2..8486686 100644 --- a/src/components/icon/HelpIcon.vue +++ b/src/components/icon/HelpIcon.vue @@ -1,9 +1,8 @@ <template> -<svg viewBox="0 0 24 24" fill="none" - stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> - <circle cx="12" cy="12" r="10"/> - <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/> - <line x1="12" y1="17" x2="12.01" y2="17"/> +<svg viewBox="0 0 512 512"> + <path d="M160 164s1.44-33 33.54-59.46C212.6 88.83 235.49 84.28 256 84c18.73-.23 35.47 2.94 45.48 7.82C318.59 100.2 352 120.6 352 164c0 45.67-29.18 66.37-62.35 89.18S248 298.36 248 324" + fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="45"/> + <circle cx="248" cy="430" r="32" fill="currentColor"/> </svg> </template> |
