@veltra/desktop 1.7.7 → 1.7.9
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/dist/components/cascade/use-check.js +5 -2
- package/dist/components/cascade/use-check.js.map +1 -1
- package/dist/components/cascade/use-select.js +5 -1
- package/dist/components/cascade/use-select.js.map +1 -1
- package/dist/components/code-editor/langs/bash/index.js +1 -1
- package/dist/components/code-editor/langs/powershell/index.js +1 -1
- package/dist/components/image-cropper/draw-crop.js +63 -0
- package/dist/components/image-cropper/draw-crop.js.map +1 -0
- package/dist/components/image-cropper/image-cropper.js +344 -0
- package/dist/components/image-cropper/image-cropper.js.map +1 -0
- package/dist/components/image-cropper/style.d.ts +1 -0
- package/dist/components/image-cropper/style.js +1 -0
- package/dist/components/image-cropper/style2.css +205 -0
- package/dist/components/image-cropper/use-image-loader.js +59 -0
- package/dist/components/image-cropper/use-image-loader.js.map +1 -0
- package/dist/components/image-cropper/use-preview.js +44 -0
- package/dist/components/image-cropper/use-preview.js.map +1 -0
- package/dist/components/image-cropper/use-selection.js +183 -0
- package/dist/components/image-cropper/use-selection.js.map +1 -0
- package/dist/components/image-cropper/use-transform.js +182 -0
- package/dist/components/image-cropper/use-transform.js.map +1 -0
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.js +3 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/table/style2.css +1 -1
- package/dist/index.d.ts +47 -45
- package/dist/index.js +46 -45
- package/dist/node_modules/.bun/{@codemirror_legacy-modes@6.5.3 → @codemirror_legacy-modes@6.5.4}/node_modules/@codemirror/legacy-modes/mode/powershell.js +1 -1
- package/dist/node_modules/.bun/{@codemirror_legacy-modes@6.5.3 → @codemirror_legacy-modes@6.5.4}/node_modules/@codemirror/legacy-modes/mode/powershell.js.map +1 -1
- package/dist/node_modules/.bun/{@codemirror_legacy-modes@6.5.3 → @codemirror_legacy-modes@6.5.4}/node_modules/@codemirror/legacy-modes/mode/shell.js +1 -1
- package/dist/node_modules/.bun/{@codemirror_legacy-modes@6.5.3 → @codemirror_legacy-modes@6.5.4}/node_modules/@codemirror/legacy-modes/mode/shell.js.map +1 -1
- package/dist/types/cascade.d.ts +6 -2
- package/dist/types/image-cropper.d.ts +68 -0
- package/dist/types/index.d.ts +2 -1
- package/package.json +15 -15
- package/src/components/auto-complete/style.scss +3 -15
- package/src/components/cascade/__test__/cascade.test.ts +103 -0
- package/src/components/cascade/use-check.ts +5 -2
- package/src/components/cascade/use-select.ts +2 -1
- package/src/components/contextmenu/style.scss +4 -20
- package/src/components/image-cropper/__test__/draw-crop.test.ts +131 -0
- package/src/components/image-cropper/__test__/image-cropper.test.ts +744 -0
- package/src/components/image-cropper/__test__/use-transform.test.ts +229 -0
- package/src/components/image-cropper/draw-crop.ts +112 -0
- package/src/components/image-cropper/image-cropper.vue +302 -0
- package/src/components/image-cropper/index.ts +1 -0
- package/src/components/image-cropper/style.scss +232 -0
- package/src/components/image-cropper/style.ts +1 -0
- package/src/components/image-cropper/use-image-loader.ts +79 -0
- package/src/components/image-cropper/use-preview.ts +58 -0
- package/src/components/image-cropper/use-selection.ts +255 -0
- package/src/components/image-cropper/use-transform.ts +256 -0
- package/src/components/index.ts +2 -0
- package/src/components/select/style.scss +3 -15
- package/src/components/table/style.scss +1 -1
- package/src/types/cascade.ts +10 -2
- package/src/types/image-cropper.ts +73 -0
- package/src/types/index.ts +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createApp, h, nextTick, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import UCascade from '../cascade.vue'
|
|
5
|
+
|
|
6
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
7
|
+
|
|
8
|
+
const cascadeData = [
|
|
9
|
+
{
|
|
10
|
+
label: '浙江省',
|
|
11
|
+
value: 'zhejiang',
|
|
12
|
+
children: [
|
|
13
|
+
{
|
|
14
|
+
label: '杭州市',
|
|
15
|
+
value: 'hangzhou',
|
|
16
|
+
children: [
|
|
17
|
+
{ label: '西湖区', value: 'xihu' },
|
|
18
|
+
{ label: '余杭区', value: 'yuhang' }
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: '江苏省',
|
|
25
|
+
value: 'jiangsu',
|
|
26
|
+
children: [
|
|
27
|
+
{ label: '南京市', value: 'nanjing', children: [{ label: '玄武区', value: 'xuanwu' }] }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
function queryCascadeItems() {
|
|
33
|
+
return [...document.body.querySelectorAll<HTMLElement>('.u-cascade-panel-item__option')]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function openDropdown(host: HTMLElement) {
|
|
37
|
+
const trigger = (host.querySelector('.u-cascade') ||
|
|
38
|
+
host.querySelector('.u-dropdown') ||
|
|
39
|
+
host.firstElementChild) as HTMLElement
|
|
40
|
+
trigger.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
41
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
42
|
+
await nextTick()
|
|
43
|
+
await sleep(25)
|
|
44
|
+
if (queryCascadeItems().length > 0) return
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function clickItemByLabel(label: string) {
|
|
49
|
+
const item = queryCascadeItems().find((el) => el.textContent?.includes(label))
|
|
50
|
+
expect(item, `cascade item "${label}" not found`).toBeTruthy()
|
|
51
|
+
item!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
52
|
+
await nextTick()
|
|
53
|
+
await sleep(30)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('UCascade change event fullLabel', () => {
|
|
57
|
+
it('should emit fullLabel in change event for single select', async () => {
|
|
58
|
+
const host = document.createElement('div')
|
|
59
|
+
document.body.appendChild(host)
|
|
60
|
+
|
|
61
|
+
const model = ref<string | undefined>()
|
|
62
|
+
let changeResult: { item: any; fullLabel?: string } | undefined
|
|
63
|
+
|
|
64
|
+
const app = createApp({
|
|
65
|
+
render() {
|
|
66
|
+
return h(UCascade, {
|
|
67
|
+
data: cascadeData,
|
|
68
|
+
modelValue: model.value,
|
|
69
|
+
'onUpdate:modelValue': (val: string) => {
|
|
70
|
+
model.value = val
|
|
71
|
+
},
|
|
72
|
+
onChange: (item: any, fullLabel?: string) => {
|
|
73
|
+
changeResult = { item, fullLabel }
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
app.mount(host)
|
|
80
|
+
await nextTick()
|
|
81
|
+
|
|
82
|
+
await openDropdown(host)
|
|
83
|
+
|
|
84
|
+
// 点击第一列 "浙江省"
|
|
85
|
+
await clickItemByLabel('浙江省')
|
|
86
|
+
|
|
87
|
+
// 点击第二列 "杭州市"
|
|
88
|
+
await clickItemByLabel('杭州市')
|
|
89
|
+
|
|
90
|
+
// 点击第三列 "西湖区"
|
|
91
|
+
await clickItemByLabel('西湖区')
|
|
92
|
+
|
|
93
|
+
expect(changeResult).toBeDefined()
|
|
94
|
+
expect(changeResult?.fullLabel).toBe('浙江省/杭州市/西湖区')
|
|
95
|
+
expect(changeResult?.item).toBeDefined()
|
|
96
|
+
expect(changeResult?.item.value).toBe('xihu')
|
|
97
|
+
expect(changeResult?.item.label).toBe('西湖区')
|
|
98
|
+
expect(changeResult?.item.fullLabel).toBe('浙江省/杭州市/西湖区')
|
|
99
|
+
|
|
100
|
+
app.unmount()
|
|
101
|
+
host.remove()
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -68,10 +68,13 @@ export function useCheck(options: CheckOptions): UseCheckReturned {
|
|
|
68
68
|
const checkedArr = Array.from(checkedSet.value)
|
|
69
69
|
const targetValues = checkedArr.map((item) => item.value)
|
|
70
70
|
const targetLabels = checkedArr.map((item) => item.label)
|
|
71
|
-
const targetItems = checkedArr.map((item) =>
|
|
71
|
+
const targetItems = checkedArr.map((item) => {
|
|
72
|
+
const data = item.data as Record<string, any>
|
|
73
|
+
return data ? Object.assign({}, data, { fullLabel: item.label }) : data
|
|
74
|
+
})
|
|
72
75
|
emit('update:modelValue', targetValues)
|
|
73
76
|
emit('update:label', targetLabels.length ? targetLabels : undefined)
|
|
74
|
-
emit('change', targetItems)
|
|
77
|
+
emit('change', targetItems, targetLabels.length ? targetLabels : undefined)
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
const restTag = computed(() => {
|
|
@@ -115,10 +115,11 @@ export function useSelect(options: SelectOptions): UseSelectReturned {
|
|
|
115
115
|
: leafLabel
|
|
116
116
|
|
|
117
117
|
const leafNode = leafKey ? dataMap.value.get(leafKey) : undefined
|
|
118
|
+
const changeItem = leafNode?.data ? { ...leafNode.data, fullLabel: targetLabel } : undefined
|
|
118
119
|
|
|
119
120
|
emit('update:modelValue', targetValue)
|
|
120
121
|
emit('update:label', targetLabel)
|
|
121
|
-
emit('change',
|
|
122
|
+
emit('change', changeItem, targetLabel)
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
/**
|
|
@@ -92,31 +92,15 @@ $arrow-size: 12px;
|
|
|
92
92
|
|
|
93
93
|
#{$b}__item:not(.is-disabled):not(.is-loading) > & {
|
|
94
94
|
&:hover {
|
|
95
|
-
background-color: fn.component-var(
|
|
96
|
-
|
|
97
|
-
item-hover-bg,
|
|
98
|
-
fn.use-var(bg-color, hover)
|
|
99
|
-
);
|
|
100
|
-
color: fn.component-var(
|
|
101
|
-
contextmenu,
|
|
102
|
-
item-hover-color,
|
|
103
|
-
fn.use-var(text-color, title)
|
|
104
|
-
);
|
|
95
|
+
background-color: fn.component-var(contextmenu, item-hover-bg, fn.use-var(bg-color, hover));
|
|
96
|
+
color: fn.component-var(contextmenu, item-hover-color, fn.use-var(text-color, title));
|
|
105
97
|
}
|
|
106
98
|
}
|
|
107
99
|
}
|
|
108
100
|
|
|
109
101
|
#{$b}__item.is-open:not(.is-disabled) > #{$b}__item-content {
|
|
110
|
-
background-color: fn.component-var(
|
|
111
|
-
|
|
112
|
-
item-hover-bg,
|
|
113
|
-
fn.use-var(bg-color, hover)
|
|
114
|
-
);
|
|
115
|
-
color: fn.component-var(
|
|
116
|
-
contextmenu,
|
|
117
|
-
item-hover-color,
|
|
118
|
-
fn.use-var(text-color, title)
|
|
119
|
-
);
|
|
102
|
+
background-color: fn.component-var(contextmenu, item-hover-bg, fn.use-var(bg-color, hover));
|
|
103
|
+
color: fn.component-var(contextmenu, item-hover-color, fn.use-var(text-color, title));
|
|
120
104
|
}
|
|
121
105
|
|
|
122
106
|
@include m.e(icon-place) {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { cropToResult, drawCropToCanvas } from '../draw-crop'
|
|
4
|
+
|
|
5
|
+
/** 记录调用参数的 2d 上下文桩 */
|
|
6
|
+
function createCtxStub() {
|
|
7
|
+
return { translate: vi.fn(), rotate: vi.fn(), scale: vi.fn(), drawImage: vi.fn() }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createCanvasStub(ctx: ReturnType<typeof createCtxStub>) {
|
|
11
|
+
return { width: 0, height: 0, getContext: () => ctx } as unknown as HTMLCanvasElement
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const img = {} as HTMLImageElement
|
|
15
|
+
const selection = { x: 10, y: 20, width: 200, height: 100 }
|
|
16
|
+
const identity = { rotation: 0, flipX: false, flipY: false }
|
|
17
|
+
|
|
18
|
+
describe('drawCropToCanvas', () => {
|
|
19
|
+
it('无变换时按选区原图像素尺寸输出', () => {
|
|
20
|
+
const ctx = createCtxStub()
|
|
21
|
+
const canvas = createCanvasStub(ctx)
|
|
22
|
+
|
|
23
|
+
drawCropToCanvas(img, selection, identity, canvas)
|
|
24
|
+
|
|
25
|
+
expect(canvas.width).toBe(200)
|
|
26
|
+
expect(canvas.height).toBe(100)
|
|
27
|
+
expect(ctx.rotate).toHaveBeenCalledWith(0)
|
|
28
|
+
expect(ctx.scale).toHaveBeenCalledWith(1, 1)
|
|
29
|
+
expect(ctx.drawImage).toHaveBeenCalledWith(img, 10, 20, 200, 100, -100, -50, 200, 100)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('旋转 90° / 270° 时输出尺寸交换宽高', () => {
|
|
33
|
+
const ctx = createCtxStub()
|
|
34
|
+
const canvas = createCanvasStub(ctx)
|
|
35
|
+
|
|
36
|
+
drawCropToCanvas(img, selection, { ...identity, rotation: 90 }, canvas)
|
|
37
|
+
|
|
38
|
+
expect(canvas.width).toBe(100)
|
|
39
|
+
expect(canvas.height).toBe(200)
|
|
40
|
+
expect(ctx.rotate).toHaveBeenCalledWith(Math.PI / 2)
|
|
41
|
+
// 变换顺序与舞台一致:先旋转后缩放(翻转)
|
|
42
|
+
expect(ctx.translate).toHaveBeenCalledWith(50, 100)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('翻转以负缩放体现', () => {
|
|
46
|
+
const ctx = createCtxStub()
|
|
47
|
+
const canvas = createCanvasStub(ctx)
|
|
48
|
+
|
|
49
|
+
drawCropToCanvas(img, selection, { ...identity, flipX: true, flipY: true }, canvas)
|
|
50
|
+
|
|
51
|
+
expect(ctx.scale).toHaveBeenCalledWith(-1, -1)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('传入输出尺寸时按该尺寸缩放输出', () => {
|
|
55
|
+
const ctx = createCtxStub()
|
|
56
|
+
const canvas = createCanvasStub(ctx)
|
|
57
|
+
|
|
58
|
+
drawCropToCanvas(img, selection, identity, canvas, { width: 400, height: 200 })
|
|
59
|
+
|
|
60
|
+
expect(canvas.width).toBe(400)
|
|
61
|
+
expect(canvas.height).toBe(200)
|
|
62
|
+
expect(ctx.scale).toHaveBeenCalledWith(2, 2)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('输出尺寸只传一个维度时另一维按选区比例推算', () => {
|
|
66
|
+
const ctx = createCtxStub()
|
|
67
|
+
const canvas = createCanvasStub(ctx)
|
|
68
|
+
|
|
69
|
+
drawCropToCanvas(img, selection, identity, canvas, { width: 100 })
|
|
70
|
+
expect(canvas.width).toBe(100)
|
|
71
|
+
expect(canvas.height).toBe(50)
|
|
72
|
+
|
|
73
|
+
drawCropToCanvas(img, selection, identity, canvas, { height: 400 })
|
|
74
|
+
expect(canvas.width).toBe(800)
|
|
75
|
+
expect(canvas.height).toBe(400)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('选区像素为小数时输出尺寸取整', () => {
|
|
79
|
+
const ctx = createCtxStub()
|
|
80
|
+
const canvas = createCanvasStub(ctx)
|
|
81
|
+
|
|
82
|
+
drawCropToCanvas(img, { x: 0, y: 0, width: 100.4, height: 50.6 }, identity, canvas)
|
|
83
|
+
|
|
84
|
+
expect(canvas.width).toBe(100)
|
|
85
|
+
expect(canvas.height).toBe(51)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('cropToResult', () => {
|
|
90
|
+
afterEach(() => {
|
|
91
|
+
vi.restoreAllMocks()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('默认按原图选区像素导出 Blob + base64 dataURL', async () => {
|
|
95
|
+
const toBlobSpy = vi.spyOn(HTMLCanvasElement.prototype, 'toBlob')
|
|
96
|
+
|
|
97
|
+
const { blob, base64 } = await cropToResult(img, selection, identity)
|
|
98
|
+
|
|
99
|
+
// 离屏画布按选区 200×100 输出
|
|
100
|
+
const canvas = toBlobSpy.mock.instances[0] as unknown as HTMLCanvasElement
|
|
101
|
+
expect(canvas.width).toBe(200)
|
|
102
|
+
expect(canvas.height).toBe(100)
|
|
103
|
+
expect(blob).toBeInstanceOf(Blob)
|
|
104
|
+
expect(blob.type).toBe('image/png')
|
|
105
|
+
expect(base64.startsWith('data:image/png;base64,')).toBe(true)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('传入输出尺寸时按该尺寸缩放导出', async () => {
|
|
109
|
+
const toBlobSpy = vi.spyOn(HTMLCanvasElement.prototype, 'toBlob')
|
|
110
|
+
|
|
111
|
+
await cropToResult(img, selection, identity, { width: 100 })
|
|
112
|
+
|
|
113
|
+
const canvas = toBlobSpy.mock.instances[0] as unknown as HTMLCanvasElement
|
|
114
|
+
expect(canvas.width).toBe(100)
|
|
115
|
+
expect(canvas.height).toBe(50)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('canvas 被跨域图片污染(toBlob 抛错)时拒绝为可捕获错误', async () => {
|
|
119
|
+
vi.spyOn(HTMLCanvasElement.prototype, 'toBlob').mockImplementation(() => {
|
|
120
|
+
throw new DOMException('The canvas has been tainted by cross-origin data.', 'SecurityError')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
await expect(cropToResult(img, selection, identity)).rejects.toThrow(/tainted/)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('toBlob 未产出数据时拒绝而不是静默返回', async () => {
|
|
127
|
+
vi.spyOn(HTMLCanvasElement.prototype, 'toBlob').mockImplementation((cb) => cb(null))
|
|
128
|
+
|
|
129
|
+
await expect(cropToResult(img, selection, identity)).rejects.toThrow(/got null/)
|
|
130
|
+
})
|
|
131
|
+
})
|