FollowTarget improvements

- Show current world/location when follow target is visible
- Add ellipsis overflow
- Add copy location on click
This commit is contained in:
James Lyne 2022-01-10 22:16:05 +00:00
parent 6d49021220
commit 6717cab096
3 changed files with 55 additions and 18 deletions

View File

@ -47,8 +47,7 @@ import {LeafletMouseEvent} from "leaflet";
import {useStore} from "@/store"; import {useStore} from "@/store";
import WorldListItem from "@/components/sidebar/WorldListItem.vue"; import WorldListItem from "@/components/sidebar/WorldListItem.vue";
import {CSSProperties, ref} from "vue"; import {CSSProperties, ref} from "vue";
import {getUrlForLocation} from "@/util"; import {clipboardError, clipboardSuccess, getUrlForLocation} from "@/util";
import {notify} from "@kyvg/vue3-notification";
import {nextTick} from 'vue'; import {nextTick} from 'vue';
import {handleKeyboardEvent} from "@/util/events"; import {handleKeyboardEvent} from "@/util/events";
@ -69,8 +68,6 @@ export default defineComponent({
messageCopyLink = computed(() => store.state.messages.contextMenuCopyLink), messageCopyLink = computed(() => store.state.messages.contextMenuCopyLink),
messageCenterHere = computed(() => store.state.messages.contextMenuCenterHere), messageCenterHere = computed(() => store.state.messages.contextMenuCenterHere),
messageCopySuccess = computed(() => store.state.messages.copyToClipboardSuccess),
messageCopyError = computed(() => store.state.messages.copyToClipboardError),
menuElement = ref<HTMLInputElement | null>(null), menuElement = ref<HTMLInputElement | null>(null),
menuVisible = computed(() => !!event.value), menuVisible = computed(() => !!event.value),
@ -162,12 +159,6 @@ export default defineComponent({
} }
} }
const copySuccess = () => notify(messageCopySuccess.value);
const copyError = (e: Error) => {
notify({ type: 'error', text: messageCopyError.value });
console.error('Error copying to clipboard', e);
};
watch(event, value => { watch(event, value => {
if(value) { if(value) {
props.leaflet.closePopup(); props.leaflet.closePopup();
@ -221,8 +212,8 @@ export default defineComponent({
messageCopyLink, messageCopyLink,
messageCenterHere, messageCenterHere,
copySuccess, copySuccess: clipboardSuccess(),
copyError, copyError: clipboardError(),
menuVisible, menuVisible,
menuElement, menuElement,

View File

@ -21,7 +21,10 @@
<div :class="{'following__target': true, 'following__target--hidden': target.hidden}"> <div :class="{'following__target': true, 'following__target--hidden': target.hidden}">
<img v-if="imagesEnabled" width="32" height="32" class="target__icon" :src="image" alt="" /> <img v-if="imagesEnabled" width="32" height="32" class="target__icon" :src="image" alt="" />
<span class="target__name" v-html="target.displayName"></span> <span class="target__name" v-html="target.displayName"></span>
<span class="target__status" v-show="target.hidden">{{ messageHidden }}</span> <span class="target__status">{{ status }}</span>
<span class="target__location" v-show="!target.hidden" v-clipboard:copy="location"
v-clipboard:success="copySuccess"
v-clipboard:error="copyError">{{ location }}</span>
<button class="target__unfollow" type="button" :title="messageUnfollowTitle" <button class="target__unfollow" type="button" :title="messageUnfollowTitle"
@click.prevent="unfollow" :aria-label="messageUnfollow"> @click.prevent="unfollow" :aria-label="messageUnfollow">
<SvgIcon name="cross"></SvgIcon> <SvgIcon name="cross"></SvgIcon>
@ -34,7 +37,7 @@
import {useStore} from "@/store"; import {useStore} from "@/store";
import {MutationTypes} from "@/store/mutation-types"; import {MutationTypes} from "@/store/mutation-types";
import {computed, defineComponent, onMounted, ref, watch} from "@vue/runtime-core"; import {computed, defineComponent, onMounted, ref, watch} from "@vue/runtime-core";
import {getMinecraftHead} from '@/util'; import {clipboardError, clipboardSuccess, getMinecraftHead} from '@/util';
import defaultImage from '@/assets/images/player_face.png'; import defaultImage from '@/assets/images/player_face.png';
import {LiveAtlasPlayer} from "@/index"; import {LiveAtlasPlayer} from "@/index";
import SvgIcon from "@/components/SvgIcon.vue"; import SvgIcon from "@/components/SvgIcon.vue";
@ -60,17 +63,35 @@ export default defineComponent({
messageUnfollowTitle = computed(() => store.state.messages.followingTitleUnfollow), messageUnfollowTitle = computed(() => store.state.messages.followingTitleUnfollow),
messageHidden = computed(() => store.state.messages.followingHidden), messageHidden = computed(() => store.state.messages.followingHidden),
status = computed(() => {
if (props.target.hidden) {
return messageHidden.value;
}
const world = store.state.worlds.get(props.target.location.world || '');
return world ? world.displayName : messageHidden.value;
}),
location = computed(() => {
if (props.target.hidden) {
return messageHidden.value;
}
return `${Math.floor(props.target.location.x)}, ${props.target.location.y}, ${Math.floor(props.target.location.z)}`;
}),
unfollow = () => { unfollow = () => {
store.commit(MutationTypes.CLEAR_FOLLOW_TARGET, undefined); store.commit(MutationTypes.CLEAR_FOLLOW_TARGET, undefined);
}, },
updatePlayerImage = async () => { updatePlayerImage = async () => {
image.value = defaultImage; image.value = defaultImage;
if(imagesEnabled.value) { if (imagesEnabled.value) {
try { try {
const result = await getMinecraftHead(props.target, 'small'); const result = await getMinecraftHead(props.target, 'small');
image.value = result.src; image.value = result.src;
} catch (e) {} } catch (e) {
}
} }
}; };
@ -85,6 +106,10 @@ export default defineComponent({
messageUnfollow, messageUnfollow,
messageUnfollowTitle, messageUnfollowTitle,
messageHidden, messageHidden,
status,
location,
copySuccess: clipboardSuccess(),
copyError: clipboardError(),
} }
}, },
}); });
@ -99,8 +124,8 @@ export default defineComponent({
.following__target { .following__target {
display: grid; display: grid;
grid-template-columns: min-content 1fr; grid-template-columns: min-content 1fr;
grid-template-rows: 1fr min-content min-content 1fr; grid-template-rows: 1fr min-content min-content min-content 1fr;
grid-template-areas: "icon ." "icon name" "icon status" "icon ."; grid-template-areas: "icon ." "icon name" "icon status" "icon location" "icon .";
grid-auto-flow: column; grid-auto-flow: column;
align-items: center; align-items: center;
@ -136,12 +161,24 @@ export default defineComponent({
font-size: 1.3rem; font-size: 1.3rem;
} }
.target__location {
grid-area: location;
font-family: monospace;
cursor: pointer;
}
&.following__target--hidden { &.following__target--hidden {
.target__icon { .target__icon {
filter: grayscale(1); filter: grayscale(1);
opacity: 0.5; opacity: 0.5;
} }
} }
> * {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
} }
@media (max-width: 480px), (max-height: 480px) { @media (max-width: 480px), (max-height: 480px) {

View File

@ -17,6 +17,7 @@
import {useStore} from "@/store"; import {useStore} from "@/store";
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition"; import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
import {HeadQueueEntry, LiveAtlasPlayer, LiveAtlasPlayerImageSize} from "@/index"; import {HeadQueueEntry, LiveAtlasPlayer, LiveAtlasPlayerImageSize} from "@/index";
import {notify} from "@kyvg/vue3-notification";
const headCache = new Map<string, HTMLImageElement>(), const headCache = new Map<string, HTMLImageElement>(),
headUnresolvedCache = new Map<string, Promise<HTMLImageElement>>(), headUnresolvedCache = new Map<string, Promise<HTMLImageElement>>(),
@ -203,3 +204,11 @@ export const focus = (selector: string) => {
(element as HTMLElement).focus(); (element as HTMLElement).focus();
} }
} }
export const clipboardSuccess = () => () => notify(useStore().state.messages.copyToClipboardSuccess);
export const clipboardError = () => (e: Error) => {
notify({ type: 'error', text: useStore().state.messages.copyToClipboardError });
console.error('Error copying to clipboard', e);
};