mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-06 22:46:06 +02:00
add right-click context menu to current user item
This commit is contained in:
Generated
+367
-614
File diff suppressed because it is too large
Load Diff
+13
-13
@@ -33,7 +33,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dnd-kit/vue": "^0.3.2",
|
"@dnd-kit/vue": "^0.3.2",
|
||||||
"@electron/rebuild": "^4.0.3",
|
"@electron/rebuild": "^4.0.3",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.3",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@fontsource-variable/noto-sans-jp": "^5.2.10",
|
"@fontsource-variable/noto-sans-jp": "^5.2.10",
|
||||||
"@fontsource-variable/noto-sans-kr": "^5.2.10",
|
"@fontsource-variable/noto-sans-kr": "^5.2.10",
|
||||||
@@ -43,13 +43,13 @@
|
|||||||
"@kamiya4047/eslint-plugin-pretty-import": "^0.1.6",
|
"@kamiya4047/eslint-plugin-pretty-import": "^0.1.6",
|
||||||
"@pinia/testing": "^1.0.3",
|
"@pinia/testing": "^1.0.3",
|
||||||
"@sentry/vite-plugin": "^4.9.1",
|
"@sentry/vite-plugin": "^4.9.1",
|
||||||
"@sentry/vue": "^10.38.0",
|
"@sentry/vue": "^10.39.0",
|
||||||
"@sigma/edge-curve": "^3.1.0",
|
"@sigma/edge-curve": "^3.1.0",
|
||||||
"@sigma/node-border": "^3.0.0",
|
"@sigma/node-border": "^3.0.0",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@tanstack/vue-table": "^8.21.3",
|
"@tanstack/vue-table": "^8.21.3",
|
||||||
"@tanstack/vue-virtual": "^3.13.18",
|
"@tanstack/vue-virtual": "^3.13.19",
|
||||||
"@types/node": "^25.2.3",
|
"@types/node": "^25.3.0",
|
||||||
"@vee-validate/zod": "^4.15.1",
|
"@vee-validate/zod": "^4.15.1",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.4",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.4",
|
"@vitejs/plugin-vue-jsx": "^5.1.4",
|
||||||
@@ -62,10 +62,10 @@
|
|||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"electron": "^39.5.2",
|
"electron": "^39.6.1",
|
||||||
"electron-builder": "^26.7.0",
|
"electron-builder": "^26.8.1",
|
||||||
"embla-carousel-vue": "^8.6.0",
|
"embla-carousel-vue": "^8.6.0",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.3",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.5",
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
"eslint-plugin-vue": "^9.33.0",
|
"eslint-plugin-vue": "^9.33.0",
|
||||||
@@ -74,23 +74,23 @@
|
|||||||
"graphology-communities-louvain": "^2.0.2",
|
"graphology-communities-louvain": "^2.0.2",
|
||||||
"graphology-layout-forceatlas2": "^0.10.1",
|
"graphology-layout-forceatlas2": "^0.10.1",
|
||||||
"graphology-layout-noverlap": "^0.4.2",
|
"graphology-layout-noverlap": "^0.4.2",
|
||||||
"jsdom": "^28.0.0",
|
"jsdom": "^28.1.0",
|
||||||
"lightningcss": "^1.31.1",
|
"lightningcss": "^1.31.1",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
"noty": "^3.2.0-beta-deprecated",
|
"noty": "^3.2.0-beta-deprecated",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"reka-ui": "^2.8.0",
|
"reka-ui": "^2.8.2",
|
||||||
"remixicon": "^4.9.1",
|
"remixicon": "^4.9.1",
|
||||||
"sass-embedded": "^1.97.3",
|
"sass-embedded": "^1.97.3",
|
||||||
"sigma": "^3.0.2",
|
"sigma": "^3.0.2",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.2.1",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"vee-validate": "^4.15.1",
|
"vee-validate": "^4.15.1",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vitest": "^3.2.4",
|
"vitest": "^3.2.4",
|
||||||
"vue": "^3.5.28",
|
"vue": "^3.5.29",
|
||||||
"vue-i18n": "^11.2.8",
|
"vue-i18n": "^11.2.8",
|
||||||
"vue-input-otp": "^0.3.2",
|
"vue-input-otp": "^0.3.2",
|
||||||
"vue-json-pretty": "^2.6.0",
|
"vue-json-pretty": "^2.6.0",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
<template v-else-if="item.row.type === 'me-item'">
|
<template v-else-if="item.row.type === 'me-item'">
|
||||||
<div class="x-friend-item hover:bg-muted/50" @click="showUserDialog(currentUser.id)">
|
<ContextMenu>
|
||||||
<div class="avatar" :class="userStatusClass(currentUser)">
|
<ContextMenuTrigger as-child>
|
||||||
<img :src="userImage(currentUser)" loading="lazy" />
|
<div
|
||||||
</div>
|
class="x-friend-item hover:bg-muted/50"
|
||||||
<div class="detail h-9 flex flex-col justify-between">
|
@click="showUserDialog(currentUser.id)">
|
||||||
<span class="name" :style="{ color: currentUser.$userColour }">{{
|
<div class="avatar" :class="userStatusClass(currentUser)">
|
||||||
currentUser.displayName
|
<img :src="userImage(currentUser)" loading="lazy" />
|
||||||
}}</span>
|
</div>
|
||||||
<Location
|
<div class="detail h-9 flex flex-col justify-between">
|
||||||
v-if="isGameRunning && !gameLogDisabled"
|
<span class="name" :style="{ color: currentUser.$userColour }">{{
|
||||||
class="extra block truncate text-xs"
|
currentUser.displayName
|
||||||
:location="lastLocation.location"
|
}}</span>
|
||||||
:traveling="lastLocationDestination"
|
<Location
|
||||||
:link="false" />
|
v-if="isGameRunning && !gameLogDisabled"
|
||||||
<Location
|
class="extra block truncate text-xs"
|
||||||
v-else-if="
|
:location="lastLocation.location"
|
||||||
isRealInstance(currentUser.$locationTag) ||
|
:traveling="lastLocationDestination"
|
||||||
isRealInstance(currentUser.$travelingToLocation)
|
:link="false" />
|
||||||
"
|
<Location
|
||||||
class="extra block truncate text-xs"
|
v-else-if="
|
||||||
:location="currentUser.$locationTag"
|
isRealInstance(currentUser.$locationTag) ||
|
||||||
:traveling="currentUser.$travelingToLocation"
|
isRealInstance(currentUser.$travelingToLocation)
|
||||||
:link="false" />
|
"
|
||||||
|
class="extra block truncate text-xs"
|
||||||
|
:location="currentUser.$locationTag"
|
||||||
|
:traveling="currentUser.$travelingToLocation"
|
||||||
|
:link="false" />
|
||||||
|
|
||||||
<span v-else class="extra block truncate text-xs">{{
|
<span v-else class="extra block truncate text-xs">{{
|
||||||
currentUser.statusDescription
|
currentUser.statusDescription
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
<template v-else-if="item.row.type === 'instance-header'">
|
<template v-else-if="item.row.type === 'instance-header'">
|
||||||
@@ -79,15 +123,29 @@
|
|||||||
</div>
|
</div>
|
||||||
<BackToTop :virtualizer="virtualizer" :target="scrollViewportRef" :tooltip="false" />
|
<BackToTop :virtualizer="virtualizer" :target="scrollViewportRef" :tooltip="false" />
|
||||||
</div>
|
</div>
|
||||||
|
<SocialStatusDialog
|
||||||
|
:social-status-dialog="socialStatusDialog"
|
||||||
|
:social-status-history-table="socialStatusHistoryTable" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
|
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { ChevronDown } from 'lucide-vue-next';
|
import { ChevronDown } from 'lucide-vue-next';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { toast } from 'vue-sonner';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useVirtualizer } from '@tanstack/vue-virtual';
|
import { useVirtualizer } from '@tanstack/vue-virtual';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuCheckboxItem,
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuSeparator,
|
||||||
|
ContextMenuSub,
|
||||||
|
ContextMenuSubContent,
|
||||||
|
ContextMenuSubTrigger,
|
||||||
|
ContextMenuTrigger
|
||||||
|
} from '../../../components/ui/context-menu';
|
||||||
import {
|
import {
|
||||||
useAdvancedSettingsStore,
|
useAdvancedSettingsStore,
|
||||||
useAppearanceSettingsStore,
|
useAppearanceSettingsStore,
|
||||||
@@ -99,10 +157,12 @@
|
|||||||
} from '../../../stores';
|
} from '../../../stores';
|
||||||
import { isRealInstance, userImage, userStatusClass } from '../../../shared/utils';
|
import { isRealInstance, userImage, userStatusClass } from '../../../shared/utils';
|
||||||
import { getFriendsLocations } from '../../../shared/utils/location.js';
|
import { getFriendsLocations } from '../../../shared/utils/location.js';
|
||||||
|
import { userRequest } from '../../../api';
|
||||||
|
|
||||||
import BackToTop from '../../../components/BackToTop.vue';
|
import BackToTop from '../../../components/BackToTop.vue';
|
||||||
import FriendItem from './FriendItem.vue';
|
import FriendItem from './FriendItem.vue';
|
||||||
import Location from '../../../components/Location.vue';
|
import Location from '../../../components/Location.vue';
|
||||||
|
import SocialStatusDialog from '../../../components/dialogs/UserDialog/SocialStatusDialog.vue';
|
||||||
import configRepository from '../../../service/config';
|
import configRepository from '../../../service/config';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -547,4 +607,56 @@
|
|||||||
virtualizer.value?.measure?.();
|
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>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user