mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-04 13:56:07 +02:00
add right-click context menu to current user item
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
import { ContextMenuRoot, useForwardPropsEmits } from "reka-ui";
|
||||
|
||||
const props = defineProps({
|
||||
pressOpenDelay: { type: Number, required: false },
|
||||
dir: { type: String, required: false },
|
||||
modal: { type: Boolean, required: false },
|
||||
});
|
||||
const emits = defineEmits(["update:open"]);
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRoot data-slot="context-menu" v-bind="forwarded">
|
||||
<slot />
|
||||
</ContextMenuRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { Check } from "lucide-vue-next";
|
||||
import {
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: [Boolean, String], required: false },
|
||||
disabled: { type: Boolean, required: false },
|
||||
textValue: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const emits = defineEmits(["select", "update:modelValue"]);
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuCheckboxItem
|
||||
data-slot="context-menu-checkbox-item"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"
|
||||
>
|
||||
<ContextMenuItemIndicator>
|
||||
<slot name="indicator-icon">
|
||||
<Check class="size-4" />
|
||||
</slot>
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</ContextMenuCheckboxItem>
|
||||
</template>
|
||||
@@ -0,0 +1,62 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import {
|
||||
ContextMenuContent,
|
||||
ContextMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
forceMount: { type: Boolean, required: false },
|
||||
loop: { type: Boolean, required: false },
|
||||
sideFlip: { type: Boolean, required: false },
|
||||
alignOffset: { type: Number, required: false },
|
||||
alignFlip: { type: Boolean, required: false },
|
||||
avoidCollisions: { type: Boolean, required: false },
|
||||
collisionBoundary: { type: null, required: false },
|
||||
collisionPadding: { type: [Number, Object], required: false },
|
||||
hideShiftedArrow: { type: Boolean, required: false },
|
||||
sticky: { type: String, required: false },
|
||||
hideWhenDetached: { type: Boolean, required: false },
|
||||
positionStrategy: { type: String, required: false },
|
||||
disableUpdateOnLayoutShift: { type: Boolean, required: false },
|
||||
prioritizePosition: { type: Boolean, required: false },
|
||||
reference: { type: null, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const emits = defineEmits([
|
||||
"escapeKeyDown",
|
||||
"pointerDownOutside",
|
||||
"focusOutside",
|
||||
"interactOutside",
|
||||
"closeAutoFocus",
|
||||
]);
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuPortal>
|
||||
<ContextMenuContent
|
||||
data-slot="context-menu-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--reka-context-menu-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</ContextMenuContent>
|
||||
</ContextMenuPortal>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
import { ContextMenuGroup } from "reka-ui";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuGroup data-slot="context-menu-group" v-bind="props">
|
||||
<slot />
|
||||
</ContextMenuGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { ContextMenuItem, useForwardPropsEmits } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
disabled: { type: Boolean, required: false },
|
||||
textValue: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
inset: { type: Boolean, required: false },
|
||||
variant: { type: String, required: false, default: "default" },
|
||||
});
|
||||
const emits = defineEmits(["select"]);
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuItem
|
||||
data-slot="context-menu-item"
|
||||
:data-inset="inset ? '' : undefined"
|
||||
:data-variant="variant"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</ContextMenuItem>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { ContextMenuLabel } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
inset: { type: Boolean, required: false },
|
||||
});
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuLabel
|
||||
data-slot="context-menu-label"
|
||||
:data-inset="inset ? '' : undefined"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</ContextMenuLabel>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
import { ContextMenuPortal } from "reka-ui";
|
||||
|
||||
const props = defineProps({
|
||||
to: { type: null, required: false },
|
||||
disabled: { type: Boolean, required: false },
|
||||
defer: { type: Boolean, required: false },
|
||||
forceMount: { type: Boolean, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuPortal data-slot="context-menu-portal" v-bind="props">
|
||||
<slot />
|
||||
</ContextMenuPortal>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { ContextMenuRadioGroup, useForwardPropsEmits } from "reka-ui";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: null, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
});
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRadioGroup
|
||||
data-slot="context-menu-radio-group"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot />
|
||||
</ContextMenuRadioGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { Circle } from "lucide-vue-next";
|
||||
import {
|
||||
ContextMenuItemIndicator,
|
||||
ContextMenuRadioItem,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
value: { type: null, required: true },
|
||||
disabled: { type: Boolean, required: false },
|
||||
textValue: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const emits = defineEmits(["select"]);
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuRadioItem
|
||||
data-slot="context-menu-radio-item"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"
|
||||
>
|
||||
<ContextMenuItemIndicator>
|
||||
<slot name="indicator-icon">
|
||||
<Circle class="size-2 fill-current" />
|
||||
</slot>
|
||||
</ContextMenuItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</ContextMenuRadioItem>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { ContextMenuSeparator } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuSeparator
|
||||
data-slot="context-menu-separator"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('bg-border -mx-1 my-1 h-px', props.class)"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
data-slot="context-menu-shortcut"
|
||||
:class="
|
||||
cn('text-muted-foreground ml-auto text-xs tracking-widest', props.class)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
import { ContextMenuSub, useForwardPropsEmits } from "reka-ui";
|
||||
|
||||
const props = defineProps({
|
||||
defaultOpen: { type: Boolean, required: false },
|
||||
open: { type: Boolean, required: false },
|
||||
});
|
||||
const emits = defineEmits(["update:open"]);
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuSub data-slot="context-menu-sub" v-bind="forwarded">
|
||||
<slot />
|
||||
</ContextMenuSub>
|
||||
</template>
|
||||
@@ -0,0 +1,57 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { ContextMenuSubContent, useForwardPropsEmits } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
forceMount: { type: Boolean, required: false },
|
||||
loop: { type: Boolean, required: false },
|
||||
sideOffset: { type: Number, required: false },
|
||||
sideFlip: { type: Boolean, required: false },
|
||||
alignOffset: { type: Number, required: false },
|
||||
alignFlip: { type: Boolean, required: false },
|
||||
avoidCollisions: { type: Boolean, required: false },
|
||||
collisionBoundary: { type: null, required: false },
|
||||
collisionPadding: { type: [Number, Object], required: false },
|
||||
arrowPadding: { type: Number, required: false },
|
||||
hideShiftedArrow: { type: Boolean, required: false },
|
||||
sticky: { type: String, required: false },
|
||||
hideWhenDetached: { type: Boolean, required: false },
|
||||
positionStrategy: { type: String, required: false },
|
||||
updatePositionStrategy: { type: String, required: false },
|
||||
disableUpdateOnLayoutShift: { type: Boolean, required: false },
|
||||
prioritizePosition: { type: Boolean, required: false },
|
||||
reference: { type: null, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const emits = defineEmits([
|
||||
"escapeKeyDown",
|
||||
"pointerDownOutside",
|
||||
"focusOutside",
|
||||
"interactOutside",
|
||||
"entryFocus",
|
||||
"openAutoFocus",
|
||||
"closeAutoFocus",
|
||||
]);
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuSubContent
|
||||
data-slot="context-menu-sub-content"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--reka-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</ContextMenuSubContent>
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { ChevronRight } from "lucide-vue-next";
|
||||
import { ContextMenuSubTrigger, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
disabled: { type: Boolean, required: false },
|
||||
textValue: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
inset: { type: Boolean, required: false },
|
||||
});
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuSubTrigger
|
||||
data-slot="context-menu-sub-trigger"
|
||||
:data-inset="inset ? '' : undefined"
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto" />
|
||||
</ContextMenuSubTrigger>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
import { ContextMenuTrigger, useForwardProps } from "reka-ui";
|
||||
|
||||
const props = defineProps({
|
||||
disabled: { type: Boolean, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContextMenuTrigger data-slot="context-menu-trigger" v-bind="forwardedProps">
|
||||
<slot />
|
||||
</ContextMenuTrigger>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
export { default as ContextMenu } from "./ContextMenu.vue";
|
||||
export { default as ContextMenuCheckboxItem } from "./ContextMenuCheckboxItem.vue";
|
||||
export { default as ContextMenuContent } from "./ContextMenuContent.vue";
|
||||
export { default as ContextMenuGroup } from "./ContextMenuGroup.vue";
|
||||
export { default as ContextMenuItem } from "./ContextMenuItem.vue";
|
||||
export { default as ContextMenuLabel } from "./ContextMenuLabel.vue";
|
||||
export { default as ContextMenuRadioGroup } from "./ContextMenuRadioGroup.vue";
|
||||
export { default as ContextMenuRadioItem } from "./ContextMenuRadioItem.vue";
|
||||
export { default as ContextMenuSeparator } from "./ContextMenuSeparator.vue";
|
||||
export { default as ContextMenuShortcut } from "./ContextMenuShortcut.vue";
|
||||
export { default as ContextMenuSub } from "./ContextMenuSub.vue";
|
||||
export { default as ContextMenuSubContent } from "./ContextMenuSubContent.vue";
|
||||
export { default as ContextMenuSubTrigger } from "./ContextMenuSubTrigger.vue";
|
||||
export { default as ContextMenuTrigger } from "./ContextMenuTrigger.vue";
|
||||
@@ -28,35 +28,79 @@
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.row.type === 'me-item'">
|
||||
<div class="x-friend-item hover:bg-muted/50" @click="showUserDialog(currentUser.id)">
|
||||
<div class="avatar" :class="userStatusClass(currentUser)">
|
||||
<img :src="userImage(currentUser)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail h-9 flex flex-col justify-between">
|
||||
<span class="name" :style="{ color: currentUser.$userColour }">{{
|
||||
currentUser.displayName
|
||||
}}</span>
|
||||
<Location
|
||||
v-if="isGameRunning && !gameLogDisabled"
|
||||
class="extra block truncate text-xs"
|
||||
:location="lastLocation.location"
|
||||
:traveling="lastLocationDestination"
|
||||
:link="false" />
|
||||
<Location
|
||||
v-else-if="
|
||||
isRealInstance(currentUser.$locationTag) ||
|
||||
isRealInstance(currentUser.$travelingToLocation)
|
||||
"
|
||||
class="extra block truncate text-xs"
|
||||
:location="currentUser.$locationTag"
|
||||
:traveling="currentUser.$travelingToLocation"
|
||||
:link="false" />
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger as-child>
|
||||
<div
|
||||
class="x-friend-item hover:bg-muted/50"
|
||||
@click="showUserDialog(currentUser.id)">
|
||||
<div class="avatar" :class="userStatusClass(currentUser)">
|
||||
<img :src="userImage(currentUser)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail h-9 flex flex-col justify-between">
|
||||
<span class="name" :style="{ color: currentUser.$userColour }">{{
|
||||
currentUser.displayName
|
||||
}}</span>
|
||||
<Location
|
||||
v-if="isGameRunning && !gameLogDisabled"
|
||||
class="extra block truncate text-xs"
|
||||
:location="lastLocation.location"
|
||||
:traveling="lastLocationDestination"
|
||||
:link="false" />
|
||||
<Location
|
||||
v-else-if="
|
||||
isRealInstance(currentUser.$locationTag) ||
|
||||
isRealInstance(currentUser.$travelingToLocation)
|
||||
"
|
||||
class="extra block truncate text-xs"
|
||||
:location="currentUser.$locationTag"
|
||||
:traveling="currentUser.$travelingToLocation"
|
||||
:link="false" />
|
||||
|
||||
<span v-else class="extra block truncate text-xs">{{
|
||||
currentUser.statusDescription
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="extra block truncate text-xs">{{
|
||||
currentUser.statusDescription
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger>
|
||||
{{ t('dialog.user.actions.edit_status') }}
|
||||
</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent>
|
||||
<ContextMenuCheckboxItem
|
||||
v-for="option in statusOptions"
|
||||
:key="option.value"
|
||||
:model-value="currentUser.status === option.value"
|
||||
class="gap-2"
|
||||
@click="changeStatus(option.value)">
|
||||
<i class="x-user-status" :class="option.statusClass"></i>
|
||||
{{ option.label }}
|
||||
</ContextMenuCheckboxItem>
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger>
|
||||
{{ t('dialog.social_status.history') }}
|
||||
</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent>
|
||||
<ContextMenuCheckboxItem
|
||||
:model-value="!currentUser.statusDescription"
|
||||
@click="setStatusFromHistory('')">
|
||||
{{ t('dialog.gallery_select.none') }}
|
||||
</ContextMenuCheckboxItem>
|
||||
<ContextMenuSeparator v-if="recentStatuses.length" />
|
||||
<ContextMenuCheckboxItem
|
||||
v-for="(item, idx) in recentStatuses"
|
||||
:key="idx"
|
||||
:model-value="currentUser.statusDescription === item"
|
||||
@click="setStatusFromHistory(item)">
|
||||
{{ item }}
|
||||
</ContextMenuCheckboxItem>
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.row.type === 'instance-header'">
|
||||
@@ -79,15 +123,29 @@
|
||||
</div>
|
||||
<BackToTop :virtualizer="virtualizer" :target="scrollViewportRef" :tooltip="false" />
|
||||
</div>
|
||||
<SocialStatusDialog
|
||||
:social-status-dialog="socialStatusDialog"
|
||||
:social-status-history-table="socialStatusHistoryTable" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { ChevronDown } from 'lucide-vue-next';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVirtualizer } from '@tanstack/vue-virtual';
|
||||
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuContent,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger
|
||||
} from '../../../components/ui/context-menu';
|
||||
import {
|
||||
useAdvancedSettingsStore,
|
||||
useAppearanceSettingsStore,
|
||||
@@ -99,10 +157,12 @@
|
||||
} from '../../../stores';
|
||||
import { isRealInstance, userImage, userStatusClass } from '../../../shared/utils';
|
||||
import { getFriendsLocations } from '../../../shared/utils/location.js';
|
||||
import { userRequest } from '../../../api';
|
||||
|
||||
import BackToTop from '../../../components/BackToTop.vue';
|
||||
import FriendItem from './FriendItem.vue';
|
||||
import Location from '../../../components/Location.vue';
|
||||
import SocialStatusDialog from '../../../components/dialogs/UserDialog/SocialStatusDialog.vue';
|
||||
import configRepository from '../../../service/config';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -547,4 +607,56 @@
|
||||
virtualizer.value?.measure?.();
|
||||
});
|
||||
});
|
||||
|
||||
const statusOptions = computed(() => [
|
||||
{
|
||||
value: 'join me',
|
||||
statusClass: 'joinme',
|
||||
label: t('dialog.user.status.join_me')
|
||||
},
|
||||
{
|
||||
value: 'active',
|
||||
statusClass: 'online',
|
||||
label: t('dialog.user.status.online')
|
||||
},
|
||||
{
|
||||
value: 'ask me',
|
||||
statusClass: 'askme',
|
||||
label: t('dialog.user.status.ask_me')
|
||||
},
|
||||
{
|
||||
value: 'busy',
|
||||
statusClass: 'busy',
|
||||
label: t('dialog.user.status.busy')
|
||||
}
|
||||
]);
|
||||
|
||||
const recentStatuses = computed(() => {
|
||||
const history = currentUser.value?.statusHistory;
|
||||
if (!history || !history.length) return [];
|
||||
return history.slice(0, 10);
|
||||
});
|
||||
|
||||
const socialStatusDialog = ref({
|
||||
visible: false,
|
||||
loading: false,
|
||||
status: '',
|
||||
statusDescription: ''
|
||||
});
|
||||
const socialStatusHistoryTable = ref({
|
||||
data: [],
|
||||
layout: 'table'
|
||||
});
|
||||
|
||||
function changeStatus(value) {
|
||||
userRequest.saveCurrentUser({ status: value }).then(() => {
|
||||
toast.success('Status updated');
|
||||
});
|
||||
}
|
||||
|
||||
function setStatusFromHistory(status) {
|
||||
userRequest.saveCurrentUser({ statusDescription: status }).then(() => {
|
||||
toast.success('Status updated');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user