feat: explicitly define events in VueFlow component
# What's changed? * add a duplicate `Emits` type defining the emitter functions * replace `onScopeDispose` with `tryOnScopeDispose`
This commit is contained in:
+11
-8
@@ -1,13 +1,14 @@
|
|||||||
// Generated by 'unplugin-auto-import'
|
// Generated by 'unplugin-auto-import'
|
||||||
// We suggest you to commit this file into source control
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const $: typeof import('vue/macros')['$']
|
|
||||||
const $$: typeof import('vue/macros')['$$']
|
const $$: typeof import('vue/macros')['$$']
|
||||||
|
const $: typeof import('vue/macros')['$']
|
||||||
const $computed: typeof import('vue/macros')['$computed']
|
const $computed: typeof import('vue/macros')['$computed']
|
||||||
const $customRef: typeof import('vue/macros')['$customRef']
|
const $customRef: typeof import('vue/macros')['$customRef']
|
||||||
const $ref: typeof import('vue/macros')['$ref']
|
const $ref: typeof import('vue/macros')['$ref']
|
||||||
const $shallowRef: typeof import('vue/macros')['$shallowRef']
|
const $shallowRef: typeof import('vue/macros')['$shallowRef']
|
||||||
const $toRef: typeof import('vue/macros')['$toRef']
|
const $toRef: typeof import('vue/macros')['$toRef']
|
||||||
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||||
const computed: typeof import('vue')['computed']
|
const computed: typeof import('vue')['computed']
|
||||||
@@ -31,7 +32,6 @@ declare global {
|
|||||||
const defineComponent: typeof import('vue')['defineComponent']
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
|
||||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
@@ -39,6 +39,8 @@ declare global {
|
|||||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||||
const inject: typeof import('vue')['inject']
|
const inject: typeof import('vue')['inject']
|
||||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||||
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
const isReadonly: typeof import('vue')['isReadonly']
|
const isReadonly: typeof import('vue')['isReadonly']
|
||||||
const isRef: typeof import('vue')['isRef']
|
const isRef: typeof import('vue')['isRef']
|
||||||
const logicAnd: typeof import('@vueuse/core')['logicAnd']
|
const logicAnd: typeof import('@vueuse/core')['logicAnd']
|
||||||
@@ -124,8 +126,8 @@ declare global {
|
|||||||
const useDark: typeof import('@vueuse/core')['useDark']
|
const useDark: typeof import('@vueuse/core')['useDark']
|
||||||
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
||||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
|
||||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||||
|
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||||
@@ -204,8 +206,8 @@ declare global {
|
|||||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
|
||||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||||
|
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||||
@@ -216,10 +218,10 @@ declare global {
|
|||||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
|
||||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
|
||||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||||
|
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||||
|
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||||
@@ -235,8 +237,9 @@ declare global {
|
|||||||
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
||||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||||
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
||||||
|
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||||
|
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||||
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
||||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||||
const whenever: typeof import('@vueuse/core')['whenever']
|
const whenever: typeof import('@vueuse/core')['whenever']
|
||||||
}
|
}
|
||||||
export {}
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export default (options?: FlowProps): VueFlowStore => {
|
|||||||
provide(VueFlow, vueFlow)
|
provide(VueFlow, vueFlow)
|
||||||
|
|
||||||
// dispose of state values and storage entry
|
// dispose of state values and storage entry
|
||||||
onScopeDispose(() => {
|
tryOnScopeDispose(() => {
|
||||||
if (storage.get(vueFlow!.id)) {
|
if (storage.get(vueFlow!.id)) {
|
||||||
storage.remove(vueFlow!.id)
|
storage.remove(vueFlow!.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { D3ZoomEvent } from 'd3-zoom'
|
||||||
import Viewport from '../Viewport/Viewport.vue'
|
import Viewport from '../Viewport/Viewport.vue'
|
||||||
import { createHooks, useHooks } from '../../store'
|
import { useHooks } from '../../store'
|
||||||
import { useVueFlow } from '../../composables'
|
import { useVueFlow } from '../../composables'
|
||||||
import type { FlowProps } from '../../types/flow'
|
import type { FlowElements, FlowProps } from '../../types/flow'
|
||||||
import { Slots } from '../../context'
|
import { Slots } from '../../context'
|
||||||
|
import type {
|
||||||
|
Connection,
|
||||||
|
EdgeChange,
|
||||||
|
EdgeMouseEvent,
|
||||||
|
GraphEdge,
|
||||||
|
GraphNode,
|
||||||
|
NodeChange,
|
||||||
|
NodeDragEvent,
|
||||||
|
NodeMouseEvent,
|
||||||
|
OnConnectStartParams,
|
||||||
|
ViewpaneTransform,
|
||||||
|
VueFlowStore,
|
||||||
|
} from '../../types'
|
||||||
import useWatch from './watch'
|
import useWatch from './watch'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<FlowProps>(), {
|
const props = withDefaults(defineProps<FlowProps>(), {
|
||||||
@@ -26,7 +40,57 @@ const props = withDefaults(defineProps<FlowProps>(), {
|
|||||||
connectionLineStyle: undefined,
|
connectionLineStyle: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:nodes', 'update:edges'])
|
const emit = defineEmits<{
|
||||||
|
(event: 'nodesChange', changes: NodeChange[]): void
|
||||||
|
(event: 'edgesChange', changes: EdgeChange[]): void
|
||||||
|
(event: 'nodeDoubleClick', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeClick', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeMouseEnter', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeMouseMove', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeMouseLeave', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeContextMenu', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeDragStart', nodeDragEvent: NodeDragEvent): void
|
||||||
|
(event: 'nodeDrag', nodeDragEvent: NodeDragEvent): void
|
||||||
|
(event: 'nodeDragStop', nodeDragEvent: NodeDragEvent): void
|
||||||
|
(event: 'miniMapNodeClick', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'miniMapNodeDoubleClick', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'miniMapNodeMouseEnter', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'miniMapNodeMouseMove', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'miniMapNodeMouseLeave', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'connect', connectionEvent: Connection): void
|
||||||
|
(
|
||||||
|
event: 'connectStart',
|
||||||
|
connectionEvent: {
|
||||||
|
event: MouseEvent
|
||||||
|
} & OnConnectStartParams,
|
||||||
|
): void
|
||||||
|
(event: 'connectStop', connectionEvent: MouseEvent): void
|
||||||
|
(event: 'connectEnd', connectionEvent: MouseEvent): void
|
||||||
|
(event: 'moveStart', moveEvent: { event: D3ZoomEvent<HTMLDivElement, any>; flowTransform: ViewpaneTransform }): void
|
||||||
|
(event: 'move', moveEvent: { event: D3ZoomEvent<HTMLDivElement, any>; flowTransform: ViewpaneTransform }): void
|
||||||
|
(event: 'moveEnd', moveEvent: { event: D3ZoomEvent<HTMLDivElement, any>; flowTransform: ViewpaneTransform }): void
|
||||||
|
(event: 'selectionDragStart', selectionEvent: NodeDragEvent): void
|
||||||
|
(event: 'selectionDrag', selectionEvent: NodeDragEvent): void
|
||||||
|
(event: 'selectionDragStop', selectionEvent: NodeDragEvent): void
|
||||||
|
(event: 'selectionContextMenu', selectionEvent: { event: MouseEvent; nodes: GraphNode[] }): void
|
||||||
|
(event: 'paneReady', paneEvent: VueFlowStore): void
|
||||||
|
(event: 'paneScroll', paneEvent: WheelEvent | undefined): void
|
||||||
|
(event: 'paneClick', paneEvent: MouseEvent): void
|
||||||
|
(event: 'paneContextMenu', paneEvent: MouseEvent): void
|
||||||
|
(event: 'edgeContextMenu', edgeMouseEvent: EdgeMouseEvent): void
|
||||||
|
(event: 'edgeMouseEnter', edgeMouseEvent: EdgeMouseEvent): void
|
||||||
|
(event: 'edgeMouseMove', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeMouseLeave', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeDoubleClick', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeClick', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeUpdateStart', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeUpdate', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeUpdateEnd', edgeMouseEvent: MouseEvent): void
|
||||||
|
|
||||||
|
(event: 'update:modelValue', value: FlowElements): void
|
||||||
|
(event: 'update:nodes', value: GraphNode[]): void
|
||||||
|
(event: 'update:edges', value: GraphEdge[]): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const modelValue = useVModel(props, 'modelValue', emit)
|
const modelValue = useVModel(props, 'modelValue', emit)
|
||||||
const modelNodes = useVModel(props, 'nodes', emit)
|
const modelNodes = useVModel(props, 'nodes', emit)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { EmitFunc, FlowHooks } from '~/types'
|
import type { Emits, FlowHooks } from '~/types'
|
||||||
|
|
||||||
// flow event hooks
|
// flow event hooks
|
||||||
export const createHooks = (): FlowHooks => ({
|
export const createHooks = (): FlowHooks => ({
|
||||||
@@ -44,18 +44,18 @@ export const createHooks = (): FlowHooks => ({
|
|||||||
edgeUpdateEnd: createEventHook(),
|
edgeUpdateEnd: createEventHook(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const bind = (emit: EmitFunc, hooks: FlowHooks) => {
|
const bind = (emit: Emits, hooks: FlowHooks) => {
|
||||||
for (const [key, value] of Object.entries(hooks)) {
|
for (const [key, value] of Object.entries(hooks)) {
|
||||||
const listener = (data: any) => {
|
const listener = (data: any) => {
|
||||||
emit(key as keyof FlowHooks, data)
|
emit(key as any, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
value.on(listener)
|
value.on(listener)
|
||||||
|
|
||||||
onScopeDispose(() => {
|
tryOnScopeDispose(() => {
|
||||||
value.off(listener)
|
value.off(listener)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (emit: EmitFunc, hooks: FlowHooks) => bind(emit, hooks)
|
export default (emit: Emits, hooks: FlowHooks) => bind(emit, hooks)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { Connection, OnConnectStartParams } from './connection'
|
|||||||
import type { ViewpaneTransform } from './zoom'
|
import type { ViewpaneTransform } from './zoom'
|
||||||
import type { EdgeChange, NodeChange } from './changes'
|
import type { EdgeChange, NodeChange } from './changes'
|
||||||
import type { VueFlowStore } from './store'
|
import type { VueFlowStore } from './store'
|
||||||
|
import type { FlowElements } from './flow'
|
||||||
|
|
||||||
export type MouseTouchEvent = MouseEvent | TouchEvent | PointerEvent
|
export type MouseTouchEvent = MouseEvent | TouchEvent | PointerEvent
|
||||||
|
|
||||||
@@ -88,4 +89,54 @@ export type FlowHooksEmit = Readonly<{
|
|||||||
[key in keyof FlowEvents]: EventHookTrigger<FlowEvents[key]>
|
[key in keyof FlowEvents]: EventHookTrigger<FlowEvents[key]>
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export type EmitFunc = (name: keyof FlowHooks, ...args: FlowEvents[keyof FlowEvents][]) => void
|
export interface Emits {
|
||||||
|
(event: 'nodesChange', changes: NodeChange[]): void
|
||||||
|
(event: 'edgesChange', changes: EdgeChange[]): void
|
||||||
|
(event: 'nodeDoubleClick', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeClick', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeMouseEnter', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeMouseMove', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeMouseLeave', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeContextMenu', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'nodeDragStart', nodeDragEvent: NodeDragEvent): void
|
||||||
|
(event: 'nodeDrag', nodeDragEvent: NodeDragEvent): void
|
||||||
|
(event: 'nodeDragStop', nodeDragEvent: NodeDragEvent): void
|
||||||
|
(event: 'miniMapNodeClick', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'miniMapNodeDoubleClick', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'miniMapNodeMouseEnter', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'miniMapNodeMouseMove', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'miniMapNodeMouseLeave', nodeMouseEvent: NodeMouseEvent): void
|
||||||
|
(event: 'connect', connectionEvent: Connection): void
|
||||||
|
(
|
||||||
|
event: 'connectStart',
|
||||||
|
connectionEvent: {
|
||||||
|
event: MouseEvent
|
||||||
|
} & OnConnectStartParams,
|
||||||
|
): void
|
||||||
|
(event: 'connectStop', connectionEvent: MouseEvent): void
|
||||||
|
(event: 'connectEnd', connectionEvent: MouseEvent): void
|
||||||
|
(event: 'moveStart', moveEvent: { event: D3ZoomEvent<HTMLDivElement, any>; flowTransform: ViewpaneTransform }): void
|
||||||
|
(event: 'move', moveEvent: { event: D3ZoomEvent<HTMLDivElement, any>; flowTransform: ViewpaneTransform }): void
|
||||||
|
(event: 'moveEnd', moveEvent: { event: D3ZoomEvent<HTMLDivElement, any>; flowTransform: ViewpaneTransform }): void
|
||||||
|
(event: 'selectionDragStart', selectionEvent: NodeDragEvent): void
|
||||||
|
(event: 'selectionDrag', selectionEvent: NodeDragEvent): void
|
||||||
|
(event: 'selectionDragStop', selectionEvent: NodeDragEvent): void
|
||||||
|
(event: 'selectionContextMenu', selectionEvent: { event: MouseEvent; nodes: GraphNode[] }): void
|
||||||
|
(event: 'paneReady', paneEvent: VueFlowStore): void
|
||||||
|
(event: 'paneScroll', paneEvent: WheelEvent | undefined): void
|
||||||
|
(event: 'paneClick', paneEvent: MouseEvent): void
|
||||||
|
(event: 'paneContextMenu', paneEvent: MouseEvent): void
|
||||||
|
(event: 'edgeContextMenu', edgeMouseEvent: EdgeMouseEvent): void
|
||||||
|
(event: 'edgeMouseEnter', edgeMouseEvent: EdgeMouseEvent): void
|
||||||
|
(event: 'edgeMouseMove', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeMouseLeave', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeDoubleClick', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeClick', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeUpdateStart', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeUpdate', edgeMouseEvent: MouseEvent): void
|
||||||
|
(event: 'edgeUpdateEnd', edgeMouseEvent: MouseEvent): void
|
||||||
|
|
||||||
|
(event: 'update:modelValue', value: FlowElements): void
|
||||||
|
(event: 'update:nodes', value: GraphNode[]): void
|
||||||
|
(event: 'update:edges', value: GraphEdge[]): void
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user