@xen-orchestra/web-core 0.56.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/assets/css/base.pcss +5 -0
- package/lib/components/input-wrapper/VtsInputWrapper.vue +2 -2
- package/lib/components/layout/VtsLayoutSidebar.vue +116 -43
- package/lib/components/menu/MenuItem.vue +9 -1
- package/lib/components/menu/MenuList.vue +4 -2
- package/lib/components/menu/VtsActionsMenu.vue +24 -24
- package/lib/components/modal/VtsActionModal.vue +44 -9
- package/lib/components/modal/VtsBlockedModal.vue +1 -1
- package/lib/components/modal/VtsDeleteModal.vue +1 -1
- package/lib/components/overlay/VtsOverlayButton.vue +35 -0
- package/lib/components/overlay/VtsOverlayCancelButton.vue +16 -0
- package/lib/components/overlay/VtsOverlayConfirmButton.vue +25 -0
- package/lib/components/overlay/VtsOverlayList.vue +82 -0
- package/lib/components/progress-bar/VtsProgressBar.vue +5 -3
- package/lib/components/select/VtsSelect.vue +0 -3
- package/lib/components/space-card/VtsSpaceCard.vue +1 -1
- package/lib/components/table/cells/VtsProgressBarCell.vue +1 -1
- package/lib/components/table/cells/VtsUserNameCell.vue +29 -0
- package/lib/components/task/VtsQuickTaskButton.vue +1 -1
- package/lib/components/task/VtsQuickTaskList.vue +1 -1
- package/lib/components/tree/VtsTreeItem.vue +4 -4
- package/lib/components/ui/head-bar/UiHeadBar.vue +1 -1
- package/lib/components/ui/input/UiInput.vue +53 -9
- package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +1 -15
- package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +1 -1
- package/lib/components/ui/quick-task-panel/UiQuickTaskPanel.vue +1 -1
- package/lib/components/ui/task-item/UiTaskItem.vue +14 -6
- package/lib/components/ui/task-list/UiTaskList.vue +1 -1
- package/lib/components/ui/text-area/UiTextarea.vue +12 -14
- package/lib/components/vif-connection-toggle-modal/VtsVifConnectionToggleModal.vue +43 -0
- package/lib/composables/table/multi-select.composable.md +33 -0
- package/lib/layouts/CoreLayout.vue +74 -62
- package/lib/locales/en.json +63 -2
- package/lib/locales/fr.json +63 -2
- package/lib/packages/form-validation/custom-rules/ip-addresses.rule.ts +15 -0
- package/lib/packages/form-validation/custom-rules/ip.regex.ts +26 -0
- package/lib/packages/form-validation/custom-rules/ipv4-or-cidr.rule.ts +2 -3
- package/lib/packages/form-validation/index.ts +2 -0
- package/lib/packages/hide-permanently/README.md +60 -0
- package/lib/packages/hide-permanently/types.ts +3 -0
- package/lib/packages/hide-permanently/use-hide-permanently.ts +15 -0
- package/lib/packages/overlay/OverlayComponent.vue +18 -0
- package/lib/packages/overlay/README.md +285 -0
- package/lib/packages/overlay/create-event-handler.ts +80 -0
- package/lib/packages/overlay/injection-keys.ts +3 -0
- package/lib/packages/overlay/is-thenable.ts +3 -0
- package/lib/packages/overlay/symbols.ts +5 -0
- package/lib/packages/overlay/types.ts +94 -0
- package/lib/packages/overlay/use-overlay-escape.ts +34 -0
- package/lib/packages/overlay/use-overlay-store.ts +33 -0
- package/lib/packages/overlay/use-overlay-trigger.ts +33 -0
- package/lib/packages/overlay/use-overlay.ts +99 -0
- package/lib/packages/progress/use-progress.ts +12 -2
- package/lib/packages/sidebar/index.ts +2 -0
- package/lib/packages/sidebar/sidebar.store.ts +55 -0
- package/lib/packages/sidebar/types.ts +21 -0
- package/lib/packages/sidebar/use-sidebar-resize.ts +47 -0
- package/lib/packages/sidebar/use-sidebar-responsive-expand.ts +19 -0
- package/lib/tables/column-definitions/user-name-column.ts +10 -0
- package/lib/tables/column-sets/server-columns.ts +2 -2
- package/lib/tables/column-sets/user-columns.ts +16 -0
- package/lib/types/object.type.ts +9 -1
- package/lib/types/task.type.ts +5 -12
- package/lib/utils/injection-keys.util.ts +3 -1
- package/lib/utils/ip-address.utils.ts +7 -0
- package/lib/utils/sr.utils.ts +4 -0
- package/package.json +2 -1
- package/lib/stores/sidebar.store.ts +0 -79
|
@@ -2,16 +2,26 @@ import { toComputed } from '@core/utils/to-computed.util.ts'
|
|
|
2
2
|
import type { Progress } from './types.ts'
|
|
3
3
|
import { computed, type MaybeRefOrGetter } from 'vue'
|
|
4
4
|
|
|
5
|
-
export function useProgress(
|
|
5
|
+
export function useProgress(
|
|
6
|
+
rawCurrent: MaybeRefOrGetter<number>,
|
|
7
|
+
rawTotal: MaybeRefOrGetter<number>,
|
|
8
|
+
rawNoRuler: MaybeRefOrGetter<boolean> = false
|
|
9
|
+
): Progress {
|
|
6
10
|
const current = toComputed(rawCurrent)
|
|
7
11
|
|
|
8
12
|
const total = toComputed(rawTotal)
|
|
9
13
|
|
|
14
|
+
const noRuler = toComputed(rawNoRuler)
|
|
15
|
+
|
|
10
16
|
const percentage = computed(() => (total.value === 0 ? 0 : (current.value / total.value) * 100))
|
|
11
17
|
|
|
12
18
|
const percentageCap = computed(() => Math.max(100, Math.ceil(percentage.value / 100) * 100))
|
|
13
19
|
|
|
14
|
-
const fillWidth = computed(() =>
|
|
20
|
+
const fillWidth = computed(() => {
|
|
21
|
+
const max = noRuler.value ? 100 : percentageCap.value
|
|
22
|
+
|
|
23
|
+
return `${(Math.min(percentage.value, max) / max) * 100}%`
|
|
24
|
+
})
|
|
15
25
|
|
|
16
26
|
return {
|
|
17
27
|
current,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { SIDEBAR_DEFAULTS, type SidebarSide } from '@core/packages/sidebar/types.ts'
|
|
2
|
+
import { useSidebarResize } from '@core/packages/sidebar/use-sidebar-resize.ts'
|
|
3
|
+
import { useSidebarResponsiveExpand } from '@core/packages/sidebar/use-sidebar-responsive-expand.ts'
|
|
4
|
+
import { useUiStore } from '@core/stores/ui.store.ts'
|
|
5
|
+
import { useLocalStorage, useToggle } from '@vueuse/core'
|
|
6
|
+
import { defineStore } from 'pinia'
|
|
7
|
+
import { computed } from 'vue'
|
|
8
|
+
|
|
9
|
+
function createSidebar(side: SidebarSide) {
|
|
10
|
+
const uiStore = useUiStore()
|
|
11
|
+
const prefix = `sidebar.${side}`
|
|
12
|
+
const defaults = SIDEBAR_DEFAULTS[side]
|
|
13
|
+
|
|
14
|
+
const isExpanded = useLocalStorage(`${prefix}.expanded`, defaults.isExpanded)
|
|
15
|
+
const isLocked = useLocalStorage(`${prefix}.locked`, defaults.isLocked)
|
|
16
|
+
const width = useLocalStorage(`${prefix}.width`, defaults.width)
|
|
17
|
+
|
|
18
|
+
const toggleExpand = useToggle(isExpanded)
|
|
19
|
+
const toggleLock = useToggle(isLocked)
|
|
20
|
+
const { startResize, isResizing } = useSidebarResize(side, width)
|
|
21
|
+
|
|
22
|
+
useSidebarResponsiveExpand(isExpanded)
|
|
23
|
+
|
|
24
|
+
const cssWidth = computed(() => (uiStore.isSmall ? '100%' : `${width.value}px`))
|
|
25
|
+
|
|
26
|
+
const cssLockedOffset = computed(() => (isExpanded.value ? '0' : `-${cssWidth.value}`))
|
|
27
|
+
|
|
28
|
+
const cssUnlockedOffset = computed(() => {
|
|
29
|
+
if (isExpanded.value) {
|
|
30
|
+
return '0'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return side === 'left' ? `-${cssWidth.value}` : cssWidth.value
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
isExpanded,
|
|
38
|
+
isLocked,
|
|
39
|
+
width,
|
|
40
|
+
toggleExpand,
|
|
41
|
+
toggleLock,
|
|
42
|
+
startResize,
|
|
43
|
+
isResizing,
|
|
44
|
+
cssWidth,
|
|
45
|
+
cssLockedOffset,
|
|
46
|
+
cssUnlockedOffset,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const useLeftSidebarStore = defineStore('sidebar-left', () => createSidebar('left'))
|
|
51
|
+
export const useRightSidebarStore = defineStore('sidebar-right', () => createSidebar('right'))
|
|
52
|
+
|
|
53
|
+
export function useSidebar(side: SidebarSide = 'left') {
|
|
54
|
+
return side === 'left' ? useLeftSidebarStore() : useRightSidebarStore()
|
|
55
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const SIDES = ['left', 'right'] as const
|
|
2
|
+
|
|
3
|
+
export type SidebarSide = (typeof SIDES)[number]
|
|
4
|
+
|
|
5
|
+
export type SidebarState = {
|
|
6
|
+
isExpanded: boolean
|
|
7
|
+
isLocked: boolean
|
|
8
|
+
width: number
|
|
9
|
+
toggleExpand: (value?: boolean) => boolean
|
|
10
|
+
toggleLock: (value?: boolean) => boolean
|
|
11
|
+
startResize: (event: MouseEvent) => void
|
|
12
|
+
isResizing: boolean
|
|
13
|
+
cssWidth: string
|
|
14
|
+
cssLockedOffset: string | number
|
|
15
|
+
cssUnlockedOffset: string | number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SIDEBAR_DEFAULTS = {
|
|
19
|
+
left: { isExpanded: true, isLocked: true, width: 350 },
|
|
20
|
+
right: { isExpanded: false, isLocked: false, width: 350 },
|
|
21
|
+
} satisfies Record<SidebarSide, Pick<SidebarState, 'isExpanded' | 'isLocked' | 'width'>>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { SidebarSide } from '@core/packages/sidebar/types.ts'
|
|
2
|
+
import { ifElse } from '@core/utils/if-else.utils.ts'
|
|
3
|
+
import { useRafFn, useStyleTag } from '@vueuse/core'
|
|
4
|
+
import { ref, type Ref } from 'vue'
|
|
5
|
+
|
|
6
|
+
export function useSidebarResize(side: SidebarSide, width: Ref<number>) {
|
|
7
|
+
const isResizing = ref(false)
|
|
8
|
+
|
|
9
|
+
let initialX: number
|
|
10
|
+
let initialWidth: number
|
|
11
|
+
let clientX: number
|
|
12
|
+
|
|
13
|
+
function startResize(event: MouseEvent) {
|
|
14
|
+
clientX = event.clientX
|
|
15
|
+
initialX = clientX
|
|
16
|
+
initialWidth = width.value
|
|
17
|
+
isResizing.value = true
|
|
18
|
+
document.addEventListener('mousemove', handleMouseMove)
|
|
19
|
+
document.addEventListener('mouseup', handleMouseUp)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function handleMouseMove(event: MouseEvent) {
|
|
23
|
+
clientX = event.clientX
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handleMouseUp() {
|
|
27
|
+
isResizing.value = false
|
|
28
|
+
document.removeEventListener('mousemove', handleMouseMove)
|
|
29
|
+
document.removeEventListener('mouseup', handleMouseUp)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { load, unload } = useStyleTag('body { cursor: col-resize !important; }', { immediate: false })
|
|
33
|
+
|
|
34
|
+
const resizeDelta = () => (side === 'left' ? initialWidth + clientX - initialX : initialWidth + initialX - clientX)
|
|
35
|
+
|
|
36
|
+
const { pause, resume } = useRafFn(
|
|
37
|
+
() => (width.value = Math.min(500, window.innerWidth * 0.3, Math.max(200, resizeDelta()))),
|
|
38
|
+
{ immediate: false }
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
ifElse(isResizing, [load, resume], [pause, unload])
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
startResize,
|
|
45
|
+
isResizing,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useUiStore } from '@core/stores/ui.store.ts'
|
|
2
|
+
import { watch, type Ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
export function useSidebarResponsiveExpand(isExpanded: Ref<boolean>) {
|
|
5
|
+
const uiStore = useUiStore()
|
|
6
|
+
|
|
7
|
+
let desktopExpanded = isExpanded.value
|
|
8
|
+
|
|
9
|
+
watch(
|
|
10
|
+
() => uiStore.isSmall,
|
|
11
|
+
(isSmall, wasSmall) => {
|
|
12
|
+
if (isSmall && !wasSmall) {
|
|
13
|
+
desktopExpanded = isExpanded.value
|
|
14
|
+
} else if (!isSmall && wasSmall) {
|
|
15
|
+
isExpanded.value = desktopExpanded
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import VtsUserNameCell from '@core/components/table/cells/VtsUserNameCell.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column.ts'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell.ts'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
import { h } from 'vue'
|
|
6
|
+
|
|
7
|
+
export const useUserNameColumn = defineColumn((config?: HeaderConfig) => ({
|
|
8
|
+
renderHead: () => renderHeadCell(config?.headerLabel),
|
|
9
|
+
renderBody: (content: string, props?: { href?: string }) => h(VtsUserNameCell, { content, href: props?.href }),
|
|
10
|
+
}))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineColumns } from '@core/packages/table/define-columns.ts'
|
|
2
|
+
import { useActionColumn } from '@core/tables/column-definitions/action-column.ts'
|
|
2
3
|
import { useAddressColumn } from '@core/tables/column-definitions/address-column.ts'
|
|
3
4
|
import { useLinkColumn } from '@core/tables/column-definitions/link-column'
|
|
4
|
-
import { useSelectItemColumn } from '@core/tables/column-definitions/select-item-column'
|
|
5
5
|
import { useStatusColumn } from '@core/tables/column-definitions/status-column.ts'
|
|
6
6
|
import { useI18n } from 'vue-i18n'
|
|
7
7
|
|
|
@@ -13,6 +13,6 @@ export const useServerColumns = defineColumns(() => {
|
|
|
13
13
|
hostIp: useAddressColumn({ headerLabel: () => t('ip-address') }),
|
|
14
14
|
status: useStatusColumn({ headerLabel: () => t('status') }),
|
|
15
15
|
primaryHost: useLinkColumn({ headerLabel: () => t('master') }),
|
|
16
|
-
|
|
16
|
+
actions: useActionColumn({}),
|
|
17
17
|
}
|
|
18
18
|
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineColumns } from '@core/packages/table/define-columns.ts'
|
|
2
|
+
import { useSelectItemColumn } from '@core/tables/column-definitions/select-item-column.ts'
|
|
3
|
+
import { useTruncatedTextColumn } from '@core/tables/column-definitions/truncated-text-column.ts'
|
|
4
|
+
import { useUserNameColumn } from '@core/tables/column-definitions/user-name-column.ts'
|
|
5
|
+
import { useI18n } from 'vue-i18n'
|
|
6
|
+
|
|
7
|
+
export const useUserColumns = defineColumns(() => {
|
|
8
|
+
const { t } = useI18n()
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
username: useUserNameColumn({ headerLabel: () => t('username') }),
|
|
12
|
+
email: useTruncatedTextColumn({ headerLabel: () => t('email') }),
|
|
13
|
+
providers: useTruncatedTextColumn({ headerLabel: () => t('providers') }),
|
|
14
|
+
selectItem: useSelectItemColumn(),
|
|
15
|
+
}
|
|
16
|
+
})
|
package/lib/types/object.type.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export type ObjectType = 'vm'
|
|
1
|
+
export type ObjectType = 'vm' | 'host'
|
|
2
|
+
|
|
2
3
|
export type VmActions = 'reboot' | 'shutdown' | 'force-reboot' | 'force-shutdown'
|
|
4
|
+
export type HostActions = 'enable' | 'disable'
|
|
5
|
+
|
|
3
6
|
export type VmBlockedOperations =
|
|
4
7
|
| 'clean_shutdown'
|
|
5
8
|
| 'hard_shutdown'
|
|
@@ -8,3 +11,8 @@ export type VmBlockedOperations =
|
|
|
8
11
|
| 'hard_reboot'
|
|
9
12
|
| 'suspend'
|
|
10
13
|
| 'destroy'
|
|
14
|
+
|
|
15
|
+
export type ActionsByObject = {
|
|
16
|
+
vm: VmActions
|
|
17
|
+
host: HostActions
|
|
18
|
+
}
|
package/lib/types/task.type.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
2
|
+
|
|
1
3
|
export type TaskStatus = 'failure' | 'interrupted' | 'pending' | 'success'
|
|
2
4
|
|
|
3
|
-
export type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
name?: string
|
|
7
|
-
progress?: number
|
|
8
|
-
tag?: string
|
|
9
|
-
userName?: string
|
|
10
|
-
start?: number
|
|
11
|
-
end?: number
|
|
12
|
-
status: TaskStatus
|
|
13
|
-
subtasks?: Task[]
|
|
14
|
-
warnings?: { data: unknown; message: string }[]
|
|
5
|
+
export type TaskObjectSegment = {
|
|
6
|
+
text: string
|
|
7
|
+
to?: RouteLocationRaw
|
|
15
8
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { InputWrapperController } from '@core/components/input-wrapper/VtsInputWrapper.vue'
|
|
2
2
|
import type { ModalAccent } from '@core/components/ui/modal/UiModal.vue'
|
|
3
|
-
import type { ValueFormatter } from '@core/types/chart'
|
|
3
|
+
import type { ValueFormatter } from '@core/types/chart.ts'
|
|
4
4
|
import type { ComputedRef, InjectionKey, Ref } from 'vue'
|
|
5
5
|
|
|
6
6
|
export const IK_CHART_VALUE_FORMATTER = Symbol('IK_CHART_VALUE_FORMATTER') as InjectionKey<ComputedRef<ValueFormatter>>
|
|
@@ -24,3 +24,5 @@ export const IK_DISABLED = Symbol('IK_DISABLED') as InjectionKey<ComputedRef<boo
|
|
|
24
24
|
export const IK_INPUT_WRAPPER_CONTROLLER = Symbol('IK_INPUT_WRAPPER_CONTROLLER') as InjectionKey<InputWrapperController>
|
|
25
25
|
|
|
26
26
|
export const IK_MODAL_ACCENT = Symbol('IK_MODAL_ACCENT') as InjectionKey<ComputedRef<ModalAccent>>
|
|
27
|
+
|
|
28
|
+
export const IK_OVERLAY_ACCENT = Symbol('IK_OVERLAY_ACCENT') as InjectionKey<ComputedRef<ModalAccent>>
|
|
@@ -16,3 +16,10 @@ export const getUniqueIpAddressesForDevice = (
|
|
|
16
16
|
|
|
17
17
|
return [...new Set(getIpAddressesByDevice(addresses)[device])]
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
export function parseIpList(value: string, separator = ';'): string[] {
|
|
21
|
+
return value
|
|
22
|
+
.split(separator)
|
|
23
|
+
.map(ip => ip.trim())
|
|
24
|
+
.filter(ip => ip !== '')
|
|
25
|
+
}
|
package/lib/utils/sr.utils.ts
CHANGED
|
@@ -32,3 +32,7 @@ export function getSrModalInfoVariant(scope: SrScope, accessMode: SrAccessMode):
|
|
|
32
32
|
|
|
33
33
|
return 'pool-shared'
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
export function shouldShowTargetCount(scope: SrScope, targetCount: number) {
|
|
37
|
+
return targetCount > (scope.type === SR_SCOPE_TYPE.POOL ? 0 : 1)
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xen-orchestra/web-core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.57.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"exports": {
|
|
7
7
|
"./*": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"ndjson-readablestream": "^1.3.0",
|
|
35
35
|
"placement.js": "^1.0.0-beta.5",
|
|
36
36
|
"simple-icons": "^14.14.0",
|
|
37
|
+
"vue-component-type-helpers": "~3.3.7",
|
|
37
38
|
"vue-echarts": "^6.6.8",
|
|
38
39
|
"vue-virtual-scroller": "^2.0.0-beta.8"
|
|
39
40
|
},
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { useUiStore } from '@core/stores/ui.store'
|
|
2
|
-
import { ifElse } from '@core/utils/if-else.utils'
|
|
3
|
-
import { useLocalStorage, useRafFn, useStyleTag, useToggle } from '@vueuse/core'
|
|
4
|
-
import { defineStore } from 'pinia'
|
|
5
|
-
import { computed, ref, watch } from 'vue'
|
|
6
|
-
|
|
7
|
-
export const useSidebarStore = defineStore('layout', () => {
|
|
8
|
-
const uiStore = useUiStore()
|
|
9
|
-
const isExpanded = useLocalStorage('sidebar.expanded', true)
|
|
10
|
-
const isLocked = useLocalStorage('sidebar.locked', true)
|
|
11
|
-
const width = useLocalStorage('sidebar.width', 350)
|
|
12
|
-
const toggleExpand = useToggle(isExpanded)
|
|
13
|
-
const toggleLock = useToggle(isLocked)
|
|
14
|
-
const isResizing = ref(false)
|
|
15
|
-
let desktopState = false
|
|
16
|
-
|
|
17
|
-
let initialX: number
|
|
18
|
-
let initialWidth: number
|
|
19
|
-
let clientX: number
|
|
20
|
-
|
|
21
|
-
function startResize(event: MouseEvent) {
|
|
22
|
-
clientX = event.clientX
|
|
23
|
-
initialX = clientX
|
|
24
|
-
initialWidth = width.value
|
|
25
|
-
isResizing.value = true
|
|
26
|
-
document.addEventListener('mousemove', handleMouseMove)
|
|
27
|
-
document.addEventListener('mouseup', handleMouseUp)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function handleMouseMove(event: MouseEvent) {
|
|
31
|
-
clientX = event.clientX
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function handleMouseUp() {
|
|
35
|
-
isResizing.value = false
|
|
36
|
-
document.removeEventListener('mousemove', handleMouseMove)
|
|
37
|
-
document.removeEventListener('mouseup', handleMouseUp)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const cssWidth = computed(() => (uiStore.isSmall ? '100%' : `${width.value}px`))
|
|
41
|
-
|
|
42
|
-
const cssOffset = computed(() => (isExpanded.value ? 0 : `-${cssWidth.value}`))
|
|
43
|
-
|
|
44
|
-
const { load, unload } = useStyleTag('body { cursor: col-resize !important; }', { immediate: false })
|
|
45
|
-
|
|
46
|
-
const { pause, resume } = useRafFn(
|
|
47
|
-
() => (width.value = Math.min(500, Math.max(200, initialWidth + clientX - initialX))),
|
|
48
|
-
{ immediate: false }
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
ifElse(isResizing, [load, resume], [pause, unload])
|
|
52
|
-
|
|
53
|
-
watch(
|
|
54
|
-
() => ({
|
|
55
|
-
isSmall: uiStore.isSmall,
|
|
56
|
-
isLarge: uiStore.isLarge,
|
|
57
|
-
}),
|
|
58
|
-
({ isSmall, isLarge }) => {
|
|
59
|
-
if (isSmall) {
|
|
60
|
-
desktopState = isExpanded.value
|
|
61
|
-
} else {
|
|
62
|
-
isExpanded.value = isLarge ? true : desktopState
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
{ immediate: true }
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
isExpanded,
|
|
70
|
-
isLocked,
|
|
71
|
-
width,
|
|
72
|
-
toggleExpand,
|
|
73
|
-
toggleLock,
|
|
74
|
-
startResize,
|
|
75
|
-
isResizing,
|
|
76
|
-
cssWidth,
|
|
77
|
-
cssOffset,
|
|
78
|
-
}
|
|
79
|
-
})
|