@bubblydoo/uxp-toolkit 0.0.3 → 0.0.5
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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +13 -0
- package/dist/index.d.ts +77 -58
- package/dist/index.js +196 -276
- package/package.json +6 -3
- package/src/commands-library/getDocument.ts +36 -0
- package/src/commands-library/getDocument.txt +62 -0
- package/src/commands-library/getLayer.ts +56 -0
- package/src/commands-library/getLayer.txt +186 -0
- package/src/commands-library/renameLayer.uxp-test.ts +4 -4
- package/src/core/command.test-d.ts +25 -0
- package/src/index.ts +22 -17
- package/src/ut-tree/getLayerEffects.ts +3 -26
- package/src/ut-tree/getLayerProperties.ts +46 -7
- package/src/ut-tree/hasBackgroundLayer.uxp-test.ts +42 -0
- package/src/ut-tree/photoshopLayerDescriptorsToUTLayers.test.ts +114 -0
- package/src/ut-tree/photoshopLayerDescriptorsToUTLayers.ts +30 -58
- package/src/ut-tree/photoshopLayerDescriptorsToUTLayers.uxp-test.ts +36 -3
- package/src/ut-tree/utLayersToText.test.ts +94 -0
- package/src/ut-tree/utLayersToText.ts +32 -0
- package/src/ut-tree/utLayersToTree.ts +2 -1
- package/src/util/utLayerPickKeysType.ts +5 -0
- package/test/fixtures/one-layer-with-bg.psd +0 -0
- package/test/index.ts +4 -1
- package/vitest-photoshop-alias-plugin.ts +41 -0
- package/vitest.config.ts +17 -0
- package/src/commands-library/getLayerProperties.ts +0 -32
- package/src/ut-tree/getFlattenedLayerDescriptorsList.ts +0 -72
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { expect, it } from "vitest";
|
|
2
|
+
import { photoshopLayerDescriptorsToUTLayers } from "./photoshopLayerDescriptorsToUTLayers";
|
|
3
|
+
import { utLayersToText } from "./utLayersToText";
|
|
4
|
+
|
|
5
|
+
it("parses a flat list correctly", async () => {
|
|
6
|
+
expect(
|
|
7
|
+
utLayersToText(
|
|
8
|
+
photoshopLayerDescriptorsToUTLayers(
|
|
9
|
+
[
|
|
10
|
+
{
|
|
11
|
+
name: "circle",
|
|
12
|
+
layerID: 4,
|
|
13
|
+
mode: {
|
|
14
|
+
_enum: "blendMode",
|
|
15
|
+
_value: "normal",
|
|
16
|
+
},
|
|
17
|
+
background: false,
|
|
18
|
+
itemIndex: 5,
|
|
19
|
+
visible: true,
|
|
20
|
+
layerKind: 1,
|
|
21
|
+
layerSection: {
|
|
22
|
+
_value: "layerSectionContent",
|
|
23
|
+
_enum: "layerSectionType",
|
|
24
|
+
},
|
|
25
|
+
docId: 70,
|
|
26
|
+
layerEffects: {},
|
|
27
|
+
group: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "group",
|
|
31
|
+
layerID: 6,
|
|
32
|
+
mode: {
|
|
33
|
+
_enum: "blendMode",
|
|
34
|
+
_value: "passThrough",
|
|
35
|
+
},
|
|
36
|
+
background: false,
|
|
37
|
+
itemIndex: 4,
|
|
38
|
+
visible: true,
|
|
39
|
+
layerKind: 7,
|
|
40
|
+
layerSection: {
|
|
41
|
+
_value: "layerSectionStart",
|
|
42
|
+
_enum: "layerSectionType",
|
|
43
|
+
},
|
|
44
|
+
docId: 70,
|
|
45
|
+
layerEffects: {},
|
|
46
|
+
group: false,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "green square",
|
|
50
|
+
layerID: 3,
|
|
51
|
+
mode: {
|
|
52
|
+
_enum: "blendMode",
|
|
53
|
+
_value: "normal",
|
|
54
|
+
},
|
|
55
|
+
background: false,
|
|
56
|
+
itemIndex: 3,
|
|
57
|
+
visible: true,
|
|
58
|
+
layerKind: 1,
|
|
59
|
+
layerSection: {
|
|
60
|
+
_value: "layerSectionContent",
|
|
61
|
+
_enum: "layerSectionType",
|
|
62
|
+
},
|
|
63
|
+
docId: 70,
|
|
64
|
+
layerEffects: {},
|
|
65
|
+
group: true,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "red square",
|
|
69
|
+
layerID: 2,
|
|
70
|
+
mode: {
|
|
71
|
+
_enum: "blendMode",
|
|
72
|
+
_value: "normal",
|
|
73
|
+
},
|
|
74
|
+
background: false,
|
|
75
|
+
itemIndex: 2,
|
|
76
|
+
visible: true,
|
|
77
|
+
layerKind: 1,
|
|
78
|
+
layerSection: {
|
|
79
|
+
_value: "layerSectionContent",
|
|
80
|
+
_enum: "layerSectionType",
|
|
81
|
+
},
|
|
82
|
+
docId: 70,
|
|
83
|
+
layerEffects: {},
|
|
84
|
+
group: false,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "</Layer group>",
|
|
88
|
+
layerID: 7,
|
|
89
|
+
mode: {
|
|
90
|
+
_enum: "blendMode",
|
|
91
|
+
_value: "passThrough",
|
|
92
|
+
},
|
|
93
|
+
background: false,
|
|
94
|
+
itemIndex: 1,
|
|
95
|
+
visible: true,
|
|
96
|
+
layerKind: 13,
|
|
97
|
+
layerSection: {
|
|
98
|
+
_value: "layerSectionEnd",
|
|
99
|
+
_enum: "layerSectionType",
|
|
100
|
+
},
|
|
101
|
+
docId: 70,
|
|
102
|
+
layerEffects: {},
|
|
103
|
+
group: true,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
).toMatchInlineSnapshot(`
|
|
109
|
+
"◯ ⬐ circle
|
|
110
|
+
◯ ▾ group
|
|
111
|
+
◯ ⬐ green square
|
|
112
|
+
◯ red square"
|
|
113
|
+
`);
|
|
114
|
+
});
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { executeAsModal } from "../core/executeAsModal";
|
|
3
|
-
import { createGetLayerCommand } from "./getLayerEffects";
|
|
1
|
+
import type { LayerDescriptor } from "./getLayerProperties";
|
|
4
2
|
|
|
5
3
|
type UTLayerKind = "pixel" | "adjustment-layer" | "text" | "curves" | "smartObject" | "video" | "group" | "threeD" | "gradientFill" | "pattern" | "solidColor" | "background";
|
|
6
4
|
|
|
@@ -15,6 +13,7 @@ type UTLayerBuilder = {
|
|
|
15
13
|
blendMode: UTBlendMode;
|
|
16
14
|
effects: Record<string, boolean>;
|
|
17
15
|
isClippingMask: boolean;
|
|
16
|
+
background: boolean;
|
|
18
17
|
layers?: UTLayerBuilder[];
|
|
19
18
|
};
|
|
20
19
|
|
|
@@ -22,6 +21,8 @@ export type UTLayer = Readonly<Omit<UTLayerBuilder, "layers">> & {
|
|
|
22
21
|
layers?: UTLayer[];
|
|
23
22
|
};
|
|
24
23
|
|
|
24
|
+
export type UTLayerMultiGetOnly = Omit<UTLayer, "effects">;
|
|
25
|
+
|
|
25
26
|
const layerKindMap = new Map<number, UTLayerKind>([
|
|
26
27
|
[1, "pixel"],
|
|
27
28
|
[2, "adjustment-layer"], // All adjustment layers
|
|
@@ -68,20 +69,6 @@ const blendModes: string[] = [
|
|
|
68
69
|
"passThrough",
|
|
69
70
|
] satisfies UTBlendMode[];
|
|
70
71
|
|
|
71
|
-
const getLayerSectionValue = (layer: LayerDescriptor): string | undefined => {
|
|
72
|
-
if (typeof layer.layerSection === "string") {
|
|
73
|
-
return layer.layerSection;
|
|
74
|
-
}
|
|
75
|
-
if (
|
|
76
|
-
layer.layerSection &&
|
|
77
|
-
typeof layer.layerSection === "object" &&
|
|
78
|
-
"_value" in layer.layerSection
|
|
79
|
-
) {
|
|
80
|
-
return layer.layerSection._value;
|
|
81
|
-
}
|
|
82
|
-
return undefined;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
72
|
const getLayerKind = (layer: LayerDescriptor): UTLayerKind => {
|
|
86
73
|
const kind = layerKindMap.get(layer.layerKind);
|
|
87
74
|
if (!kind) {
|
|
@@ -98,54 +85,16 @@ const getBlendMode = (layer: LayerDescriptor): UTBlendMode => {
|
|
|
98
85
|
return mode as UTBlendMode;
|
|
99
86
|
};
|
|
100
87
|
|
|
101
|
-
|
|
102
|
-
const section = getLayerSectionValue(layer);
|
|
103
|
-
const isGroupEnd =
|
|
104
|
-
layer.name === "</Layer group>" ||
|
|
105
|
-
layer.name === "</Layer set>" ||
|
|
106
|
-
section === "layerSectionEnd";
|
|
107
|
-
|
|
108
|
-
const isGroupStart = section === "layerSectionStart";
|
|
109
|
-
return isGroupStart ? "start" : isGroupEnd ? "end" : "normal";
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// Generate a tree from a flat list of layer descriptors
|
|
113
|
-
export const photoshopLayerDescriptorsToUTLayers = async (layers: LayerDescriptor[]): Promise<UTLayer[]> => {
|
|
88
|
+
export function photoshopLayerDescriptorsToUTLayers(layers: LayerDescriptor[]): UTLayer[] {
|
|
114
89
|
const root: UTLayerBuilder[] = [];
|
|
115
90
|
const stack: {
|
|
116
91
|
layers: UTLayerBuilder[];
|
|
117
92
|
}[] = [{ layers: root }];
|
|
118
93
|
|
|
119
|
-
// 1. Prepare a single batch request for all layers
|
|
120
|
-
const commands = layers.map((layer) => createGetLayerCommand({ docId: layer.docId, id: layer.layerID }));
|
|
121
|
-
|
|
122
|
-
// 2. Execute one batch command instead of N commands
|
|
123
|
-
const batchResults = await executeAsModal("Get Layer Effects Data", async (ctx) => {
|
|
124
|
-
return await ctx.batchPlayCommands(commands);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// 3. Create a fast lookup map for the results
|
|
128
|
-
const effectsMap = new Map<number, UTLayerBuilder["effects"]>();
|
|
129
|
-
batchResults.forEach((result, index) => {
|
|
130
|
-
const layerId = layers[index]!.layerID;
|
|
131
|
-
const data = result.layerEffects;
|
|
132
|
-
const effects: UTLayerBuilder["effects"] = {};
|
|
133
|
-
if (data) {
|
|
134
|
-
for (const effect in data) {
|
|
135
|
-
effects[effect] = Array.isArray(data[effect]) ? data[effect].some((e) => e.enabled) : !!data[effect]?.enabled;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
effectsMap.set(layerId, effects);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
94
|
for (const layer of layers) {
|
|
142
95
|
// Determine if the layer is a group start or end
|
|
143
96
|
const sectionType = determineLayerSection(layer);
|
|
144
97
|
|
|
145
|
-
const isClippingMask = !!batchResults.find((res, index) => {
|
|
146
|
-
return layer.layerID === res.layerID;
|
|
147
|
-
})?.group;
|
|
148
|
-
|
|
149
98
|
// Handle group end
|
|
150
99
|
if (sectionType === "end") {
|
|
151
100
|
if (stack.length > 1) {
|
|
@@ -153,6 +102,7 @@ export const photoshopLayerDescriptorsToUTLayers = async (layers: LayerDescripto
|
|
|
153
102
|
}
|
|
154
103
|
continue;
|
|
155
104
|
}
|
|
105
|
+
|
|
156
106
|
// Create the node
|
|
157
107
|
const node: UTLayerBuilder = {
|
|
158
108
|
name: layer.name,
|
|
@@ -161,8 +111,9 @@ export const photoshopLayerDescriptorsToUTLayers = async (layers: LayerDescripto
|
|
|
161
111
|
visible: layer.visible,
|
|
162
112
|
kind: getLayerKind(layer),
|
|
163
113
|
blendMode: getBlendMode(layer),
|
|
164
|
-
isClippingMask,
|
|
165
|
-
effects:
|
|
114
|
+
isClippingMask: layer.group,
|
|
115
|
+
effects: getEffects(layer),
|
|
116
|
+
background: layer.background,
|
|
166
117
|
};
|
|
167
118
|
|
|
168
119
|
// Add the node to the current level
|
|
@@ -180,3 +131,24 @@ export const photoshopLayerDescriptorsToUTLayers = async (layers: LayerDescripto
|
|
|
180
131
|
// Cast to the readonly Tree type
|
|
181
132
|
return root as UTLayer[];
|
|
182
133
|
};
|
|
134
|
+
|
|
135
|
+
const determineLayerSection = (layer: LayerDescriptor): "start" | "end" | "normal" => {
|
|
136
|
+
const section = layer.layerSection._value;
|
|
137
|
+
const isGroupEnd =
|
|
138
|
+
layer.name === "</Layer group>" ||
|
|
139
|
+
layer.name === "</Layer set>" ||
|
|
140
|
+
section === "layerSectionEnd";
|
|
141
|
+
|
|
142
|
+
const isGroupStart = section === "layerSectionStart";
|
|
143
|
+
return isGroupStart ? "start" : isGroupEnd ? "end" : "normal";
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
function getEffects(layer: LayerDescriptor): Record<string, boolean> {
|
|
147
|
+
const effects: Record<string, boolean> = {};
|
|
148
|
+
if (layer.layerEffects) {
|
|
149
|
+
for (const effect in layer.layerEffects) {
|
|
150
|
+
effects[effect] = Array.isArray(layer.layerEffects[effect]) ? layer.layerEffects[effect].some((e) => e.enabled) : !!layer.layerEffects[effect]?.enabled;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return effects;
|
|
154
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { Test } from "@bubblydoo/uxp-test-framework";
|
|
2
2
|
import { photoshopLayerDescriptorsToUTLayers } from "./photoshopLayerDescriptorsToUTLayers";
|
|
3
3
|
import { openFileByPath } from "../filesystem/openFileByPath";
|
|
4
|
-
import { getFlattenedLayerDescriptorsList } from "./getFlattenedLayerDescriptorsList";
|
|
5
4
|
import { expect } from "chai";
|
|
5
|
+
import { getDocumentLayerDescriptors } from "./getLayerProperties";
|
|
6
6
|
|
|
7
7
|
export const photoshopLayerDescriptorsToUTLayersTest: Test = {
|
|
8
8
|
name: "photoshopLayerDescriptorsToUTLayers",
|
|
9
9
|
description: "Test the photoshopLayerDescriptorsToUTLayers function",
|
|
10
10
|
run: async () => {
|
|
11
11
|
const doc = await openFileByPath("plugin:/fixtures/clipping-layers.psd");
|
|
12
|
-
const descriptors = await
|
|
13
|
-
|
|
12
|
+
const descriptors = await getDocumentLayerDescriptors(doc.id);
|
|
13
|
+
|
|
14
|
+
console.log(descriptors);
|
|
15
|
+
|
|
16
|
+
const layers = photoshopLayerDescriptorsToUTLayers(descriptors);
|
|
14
17
|
expect(layers).to.containSubset([
|
|
15
18
|
{
|
|
16
19
|
name: "circle",
|
|
@@ -50,3 +53,33 @@ export const photoshopLayerDescriptorsToUTLayersTest: Test = {
|
|
|
50
53
|
]);
|
|
51
54
|
},
|
|
52
55
|
};
|
|
56
|
+
|
|
57
|
+
export const photoshopLayerDescriptorsToUTLayersTest2: Test = {
|
|
58
|
+
name: "photoshopLayerDescriptorsToUTLayers",
|
|
59
|
+
description: "Test the photoshopLayerDescriptorsToUTLayers function",
|
|
60
|
+
run: async () => {
|
|
61
|
+
const doc = await openFileByPath("plugin:/fixtures/one-layer-with-bg.psd");
|
|
62
|
+
const descriptors = await getDocumentLayerDescriptors(doc.id);
|
|
63
|
+
console.log(descriptors);
|
|
64
|
+
const layers = photoshopLayerDescriptorsToUTLayers(descriptors);
|
|
65
|
+
console.log(layers);
|
|
66
|
+
expect(layers).to.containSubset([
|
|
67
|
+
{
|
|
68
|
+
name: "Layer 1",
|
|
69
|
+
visible: true,
|
|
70
|
+
kind: "pixel",
|
|
71
|
+
blendMode: "normal",
|
|
72
|
+
isClippingMask: false,
|
|
73
|
+
effects: {},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "Background",
|
|
77
|
+
visible: true,
|
|
78
|
+
kind: "background",
|
|
79
|
+
blendMode: "normal",
|
|
80
|
+
isClippingMask: false,
|
|
81
|
+
effects: {},
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { expect, it } from "vitest";
|
|
2
|
+
import { utLayersToText } from "./utLayersToText";
|
|
3
|
+
|
|
4
|
+
it("converts a single layer to text", () => {
|
|
5
|
+
expect(utLayersToText([
|
|
6
|
+
{
|
|
7
|
+
name: "circle",
|
|
8
|
+
effects: {},
|
|
9
|
+
blendMode: "normal",
|
|
10
|
+
isClippingMask: false,
|
|
11
|
+
kind: "pixel",
|
|
12
|
+
visible: true,
|
|
13
|
+
},
|
|
14
|
+
])).toMatchInlineSnapshot(`"◯ circle"`)
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("converts a nested layer to text", () => {
|
|
18
|
+
expect(utLayersToText([
|
|
19
|
+
{
|
|
20
|
+
name: "group",
|
|
21
|
+
effects: {},
|
|
22
|
+
blendMode: "passThrough",
|
|
23
|
+
isClippingMask: false,
|
|
24
|
+
kind: "group",
|
|
25
|
+
visible: true,
|
|
26
|
+
layers: [
|
|
27
|
+
{
|
|
28
|
+
name: "circle",
|
|
29
|
+
effects: {},
|
|
30
|
+
blendMode: "normal",
|
|
31
|
+
isClippingMask: false,
|
|
32
|
+
kind: "pixel",
|
|
33
|
+
visible: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "square",
|
|
37
|
+
effects: {},
|
|
38
|
+
blendMode: "normal",
|
|
39
|
+
isClippingMask: false,
|
|
40
|
+
kind: "pixel",
|
|
41
|
+
visible: true,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "other",
|
|
45
|
+
effects: {},
|
|
46
|
+
blendMode: "passThrough",
|
|
47
|
+
isClippingMask: false,
|
|
48
|
+
kind: "group",
|
|
49
|
+
visible: true,
|
|
50
|
+
layers: [
|
|
51
|
+
{
|
|
52
|
+
name: "nested",
|
|
53
|
+
effects: {},
|
|
54
|
+
blendMode: "normal",
|
|
55
|
+
isClippingMask: false,
|
|
56
|
+
kind: "pixel",
|
|
57
|
+
visible: true,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
])).toMatchInlineSnapshot(`
|
|
64
|
+
"◯ ▾ group
|
|
65
|
+
◯ circle
|
|
66
|
+
◯ square
|
|
67
|
+
◯ ▾ other
|
|
68
|
+
◯ nested"
|
|
69
|
+
`)
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("converts a nested layer with a clipping mask to text", () => {
|
|
73
|
+
expect(utLayersToText([
|
|
74
|
+
{
|
|
75
|
+
name: "clipper",
|
|
76
|
+
effects: {},
|
|
77
|
+
blendMode: "normal",
|
|
78
|
+
isClippingMask: true,
|
|
79
|
+
kind: "pixel",
|
|
80
|
+
visible: true,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "circle",
|
|
84
|
+
effects: {},
|
|
85
|
+
blendMode: "normal",
|
|
86
|
+
isClippingMask: false,
|
|
87
|
+
kind: "pixel",
|
|
88
|
+
visible: true,
|
|
89
|
+
},
|
|
90
|
+
])).toMatchInlineSnapshot(`
|
|
91
|
+
"◯ ⬐ clipper
|
|
92
|
+
◯ circle"
|
|
93
|
+
`)
|
|
94
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { UTLayerPickKeys } from "../util/utLayerPickKeysType";
|
|
2
|
+
import type { UTLayer } from "./photoshopLayerDescriptorsToUTLayers";
|
|
3
|
+
|
|
4
|
+
const VISIBLE_ICON = "◯";
|
|
5
|
+
const INVISIBLE_ICON = "⊘";
|
|
6
|
+
const CLIPPING_MASK_ICON = "⬐";
|
|
7
|
+
const GROUP_ICON = "▾";
|
|
8
|
+
const EFFECTS_ICON = "ƒ";
|
|
9
|
+
const BLEND_ICON = "⁕"
|
|
10
|
+
|
|
11
|
+
type MinimalUTLayer = UTLayerPickKeys<"effects" | "visible" | "isClippingMask" | "kind" | "blendMode" | "name">;
|
|
12
|
+
|
|
13
|
+
export function utLayersToText(tree: MinimalUTLayer[], depth = 0): string {
|
|
14
|
+
return tree.map((layer) => {
|
|
15
|
+
const prefix = " ".repeat(depth * 2);
|
|
16
|
+
const name = layer.name;
|
|
17
|
+
const effects = Object.keys(layer.effects).length > 0 ? EFFECTS_ICON : "";
|
|
18
|
+
const blend = isSpecialBlendMode(layer) ? BLEND_ICON : "";
|
|
19
|
+
const clippingMask = layer.isClippingMask ? CLIPPING_MASK_ICON : "";
|
|
20
|
+
const group = layer.kind === "group" ? GROUP_ICON : "";
|
|
21
|
+
const visible = layer.visible ? VISIBLE_ICON : INVISIBLE_ICON;
|
|
22
|
+
const line = [visible, prefix, clippingMask, group, name, effects, blend].filter(Boolean).join(" ");
|
|
23
|
+
if (layer.layers) {
|
|
24
|
+
return line + "\n" + utLayersToText(layer.layers, depth + 1);
|
|
25
|
+
}
|
|
26
|
+
return line;
|
|
27
|
+
}).join("\n");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isSpecialBlendMode(layer: Pick<UTLayer, "kind" | "blendMode">): boolean {
|
|
31
|
+
return layer.kind === "group" ? layer.blendMode !== "passThrough" : layer.blendMode !== "normal";
|
|
32
|
+
}
|
|
@@ -14,8 +14,9 @@ export function utLayersToTree(layer: UTLayer[]): Tree<UTLayerWithoutChildren> {
|
|
|
14
14
|
blendMode: layer.blendMode,
|
|
15
15
|
isClippingMask: layer.isClippingMask,
|
|
16
16
|
effects: layer.effects,
|
|
17
|
+
background: layer.background,
|
|
17
18
|
},
|
|
18
19
|
name: layer.name,
|
|
19
20
|
children: layer.layers ? utLayersToTree(layer.layers) : undefined,
|
|
20
21
|
}));
|
|
21
|
-
}
|
|
22
|
+
}
|
|
Binary file
|
package/test/index.ts
CHANGED
|
@@ -5,9 +5,10 @@ import { executeAsModalErrorTest } from "./meta-tests/executeAsModal.uxp-test";
|
|
|
5
5
|
import { suspendHistoryTest } from "../src/core/suspendHistory.uxp-test";
|
|
6
6
|
import { sourcemapsTest } from "../src/error-sourcemaps/sourcemaps.uxp-test";
|
|
7
7
|
import { clipboardTest } from "../src/other/clipboard.uxp-test";
|
|
8
|
-
import { photoshopLayerDescriptorsToUTLayersTest } from "../src/ut-tree/photoshopLayerDescriptorsToUTLayers.uxp-test";
|
|
8
|
+
import { photoshopLayerDescriptorsToUTLayersTest, photoshopLayerDescriptorsToUTLayersTest2 } from "../src/ut-tree/photoshopLayerDescriptorsToUTLayers.uxp-test";
|
|
9
9
|
import { metadataStorageTest } from "../src/metadata-storage/metadataStorage.uxp-test";
|
|
10
10
|
import { builtinModulesTest } from "./meta-tests/builtinModules.uxp-test";
|
|
11
|
+
import { backgroundLayerTest } from "../src/ut-tree/hasBackgroundLayer.uxp-test";
|
|
11
12
|
|
|
12
13
|
export const tests = [
|
|
13
14
|
applicationInfoTest,
|
|
@@ -18,6 +19,8 @@ export const tests = [
|
|
|
18
19
|
sourcemapsTest,
|
|
19
20
|
clipboardTest,
|
|
20
21
|
photoshopLayerDescriptorsToUTLayersTest,
|
|
22
|
+
photoshopLayerDescriptorsToUTLayersTest2,
|
|
23
|
+
backgroundLayerTest,
|
|
21
24
|
metadataStorageTest,
|
|
22
25
|
builtinModulesTest,
|
|
23
26
|
];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Plugin } from "vitest/config";
|
|
2
|
+
import dedent from "dedent";
|
|
3
|
+
|
|
4
|
+
const nativeModules = [
|
|
5
|
+
"photoshop",
|
|
6
|
+
"uxp",
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const pluginPrefix = "photoshop-builtin"
|
|
10
|
+
|
|
11
|
+
const minimalModules = {
|
|
12
|
+
photoshop: dedent`
|
|
13
|
+
export const core = UNIMPLEMENTED;
|
|
14
|
+
export const action = UNIMPLEMENTED;
|
|
15
|
+
export const app = UNIMPLEMENTED;
|
|
16
|
+
`,
|
|
17
|
+
uxp: dedent`
|
|
18
|
+
export const entrypoints = UNIMPLEMENTED;
|
|
19
|
+
export const storage = UNIMPLEMENTED;
|
|
20
|
+
`,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const vitestPhotoshopAliasPlugin = (): Plugin => ({
|
|
24
|
+
name: "vitest-photoshop-alias",
|
|
25
|
+
resolveId(id) {
|
|
26
|
+
if (nativeModules.includes(id)) {
|
|
27
|
+
return `${id}?${pluginPrefix}`
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
load(id) {
|
|
31
|
+
if (id.endsWith(`?${pluginPrefix}`)) {
|
|
32
|
+
const origModuleId = id.replace(`?${pluginPrefix}`, "");
|
|
33
|
+
const mod = dedent`
|
|
34
|
+
// This is a photoshop builtin module, not available in Vitest. This is an alias to provide minimal compatibility.
|
|
35
|
+
const UNIMPLEMENTED = { __vitest_photoshop_alias_unimplemented__: true };
|
|
36
|
+
${minimalModules[origModuleId]}
|
|
37
|
+
`;
|
|
38
|
+
return mod;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
import { vitestPhotoshopAliasPlugin } from "./vitest-photoshop-alias-plugin";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
include: ["src/**/*.test.ts"],
|
|
7
|
+
reporters: process.env.CI ? ["default", "junit"] : ["default"],
|
|
8
|
+
outputFile: {
|
|
9
|
+
junit: "./test-results/junit.xml",
|
|
10
|
+
},
|
|
11
|
+
typecheck: {
|
|
12
|
+
enabled: true,
|
|
13
|
+
include: ["src/**/*.test-d.ts"],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
plugins: [vitestPhotoshopAliasPlugin()],
|
|
17
|
+
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { Document } from "photoshop/dom/Document";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { batchPlayCommand, createCommand } from "../core/command";
|
|
4
|
-
|
|
5
|
-
// get layer properties like name and layerID for all layers in the document (by index)
|
|
6
|
-
export const getLayerProperties = async (document: Document) => {
|
|
7
|
-
const command = createCommand({
|
|
8
|
-
modifying: false,
|
|
9
|
-
descriptor: {
|
|
10
|
-
_obj: "multiGet",
|
|
11
|
-
_target: { _ref: [{ _ref: "document", _id: document.id }] },
|
|
12
|
-
extendedReference: [
|
|
13
|
-
["name", "layerID", "visible"],
|
|
14
|
-
{ _obj: "layer", index: 1, count: -1 },
|
|
15
|
-
],
|
|
16
|
-
},
|
|
17
|
-
schema: z.object({
|
|
18
|
-
list: z.array(
|
|
19
|
-
z.object({
|
|
20
|
-
name: z.string(),
|
|
21
|
-
layerID: z.number(),
|
|
22
|
-
visible: z.boolean().optional(),
|
|
23
|
-
})
|
|
24
|
-
)
|
|
25
|
-
})
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const result = await batchPlayCommand(command);
|
|
29
|
-
|
|
30
|
-
// Reverse to get bottom-up order
|
|
31
|
-
return [...result.list].reverse();
|
|
32
|
-
};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { getLayerProperties } from "./getLayerProperties";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { batchPlayCommands, createCommand } from "../core/command";
|
|
4
|
-
|
|
5
|
-
// export interface LayerDescriptor {
|
|
6
|
-
// name: string;
|
|
7
|
-
// layerID: number;
|
|
8
|
-
// layerSection?: string | { _value: string; _enum: string };
|
|
9
|
-
// [key: string]: any;
|
|
10
|
-
// }
|
|
11
|
-
|
|
12
|
-
const layerDescriptorSchema = z.object({
|
|
13
|
-
name: z.string(),
|
|
14
|
-
// id: z.number(),
|
|
15
|
-
layerID: z.number(),
|
|
16
|
-
// _docId: z.number(),
|
|
17
|
-
mode: z.object({
|
|
18
|
-
_enum: z.literal("blendMode"),
|
|
19
|
-
_value: z.string(), // passThrough, normal, multiply, screen, overlay, etc.
|
|
20
|
-
}),
|
|
21
|
-
background: z.boolean(),
|
|
22
|
-
itemIndex: z.number(),
|
|
23
|
-
visible: z.boolean(),
|
|
24
|
-
layerKind: z.number(),
|
|
25
|
-
layerSection: z.object({
|
|
26
|
-
_value: z.enum([
|
|
27
|
-
"layerSectionStart",
|
|
28
|
-
"layerSectionEnd",
|
|
29
|
-
"layerSectionContent",
|
|
30
|
-
]),
|
|
31
|
-
_enum: z.literal("layerSectionType"),
|
|
32
|
-
}),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
export type LayerDescriptor = z.infer<typeof layerDescriptorSchema> & {
|
|
36
|
-
docId: number;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// get all layers (including nested in groups)
|
|
40
|
-
export const getFlattenedLayerDescriptorsList = async (
|
|
41
|
-
documentId: number
|
|
42
|
-
) => {
|
|
43
|
-
const layerProperties = await getLayerProperties(documentId);
|
|
44
|
-
|
|
45
|
-
const commands = layerProperties.map((layerProp) =>
|
|
46
|
-
createCommand({
|
|
47
|
-
modifying: false,
|
|
48
|
-
descriptor: {
|
|
49
|
-
_obj: "get",
|
|
50
|
-
_target: [
|
|
51
|
-
{
|
|
52
|
-
_ref: "layer",
|
|
53
|
-
_id: layerProp.layerID,
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
makeVisible: false,
|
|
57
|
-
layerID: [layerProp.layerID],
|
|
58
|
-
_isCommand: false,
|
|
59
|
-
},
|
|
60
|
-
schema: layerDescriptorSchema,
|
|
61
|
-
})
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const layerDescriptors = await batchPlayCommands(commands);
|
|
65
|
-
|
|
66
|
-
return layerDescriptors.map((desc) => {
|
|
67
|
-
return {
|
|
68
|
-
...desc,
|
|
69
|
-
docId: documentId,
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
};
|