@bubblydoo/uxp-toolkit 0.0.4 → 0.0.6

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.
@@ -0,0 +1,56 @@
1
+ import z from "zod";
2
+ import { createCommand } from "../core/command";
3
+ import type { PsLayerRef } from "../ut-tree/psLayerRef";
4
+
5
+ const layerSchema = z.object({
6
+ name: z.string(),
7
+ visible: z.boolean(),
8
+ group: z.boolean(),
9
+ layerSection: z.object({
10
+ _value: z.enum([
11
+ "layerSectionStart",
12
+ "layerSectionEnd",
13
+ "layerSectionContent",
14
+ ]),
15
+ _enum: z.literal("layerSectionType"),
16
+ }),
17
+ layerKind: z.number(),
18
+ itemIndex: z.number(),
19
+ background: z.boolean(),
20
+ mode: z.object({
21
+ _enum: z.literal("blendMode"),
22
+ _value: z.string(),
23
+ }),
24
+ layerID: z.number(),
25
+ layerEffects: z.record(z.string(), z.object({
26
+ // "scale" does not have an "enabled" property, that's why it's optional
27
+ enabled: z.boolean().optional(),
28
+ }).or(z.array(z.object({
29
+ enabled: z.boolean(),
30
+ })))).optional(),
31
+ });
32
+
33
+ export function createGetLayerCommand(layerRef: PsLayerRef) {
34
+ return createCommand({
35
+ modifying: false,
36
+ descriptor: {
37
+ _obj: "get",
38
+ _target: [
39
+ { _ref: "layer", _id: layerRef.id },
40
+ { _ref: "document", _id: layerRef.docId },
41
+ ],
42
+ },
43
+ schema: layerSchema,
44
+ });
45
+ }
46
+
47
+ export function createGetBackgroundLayerCommand(docId: number) {
48
+ return createCommand({
49
+ modifying: false,
50
+ descriptor: {
51
+ _obj: "get",
52
+ _target: { _ref: [{ _ref: "layer", "_property": "background" }, { _ref: "document", _id: docId }] },
53
+ },
54
+ schema: layerSchema,
55
+ });
56
+ }
@@ -0,0 +1,186 @@
1
+ {
2
+ "layerID": 1,
3
+ "group": false,
4
+ "name": "Background",
5
+ "color": {
6
+ "_enum": "color",
7
+ "_value": "none"
8
+ },
9
+ "visible": true,
10
+ "mode": {
11
+ "_enum": "blendMode",
12
+ "_value": "normal"
13
+ },
14
+ "opacity": 255,
15
+ "itemIndex": 1,
16
+ "count": 2,
17
+ "preserveTransparency": true,
18
+ "layerFXVisible": false,
19
+ "globalAngle": 90,
20
+ "background": true,
21
+ "layerSection": {
22
+ "_enum": "layerSectionType",
23
+ "_value": "layerSectionContent"
24
+ },
25
+ "layerLocking": {
26
+ "_obj": "layerLocking",
27
+ "protectTransparency": true,
28
+ "protectComposite": false,
29
+ "protectPosition": true,
30
+ "protectArtboardAutonest": true,
31
+ "protectAll": false
32
+ },
33
+ "targetChannels": [
34
+ {
35
+ "_ref": "channel",
36
+ "_index": 1
37
+ },
38
+ {
39
+ "_ref": "channel",
40
+ "_index": 2
41
+ },
42
+ {
43
+ "_ref": "channel",
44
+ "_index": 3
45
+ }
46
+ ],
47
+ "visibleChannels": [
48
+ {
49
+ "_ref": "channel",
50
+ "_index": 1
51
+ },
52
+ {
53
+ "_ref": "channel",
54
+ "_index": 2
55
+ },
56
+ {
57
+ "_ref": "channel",
58
+ "_index": 3
59
+ }
60
+ ],
61
+ "channelRestrictions": [
62
+ {
63
+ "_enum": "channel",
64
+ "_value": "red"
65
+ },
66
+ {
67
+ "_enum": "channel",
68
+ "_value": "grain"
69
+ },
70
+ {
71
+ "_enum": "channel",
72
+ "_value": "blue"
73
+ }
74
+ ],
75
+ "fillOpacity": 255,
76
+ "hasUserMask": false,
77
+ "hasVectorMask": false,
78
+ "proportionalScaling": false,
79
+ "layerKind": 1,
80
+ "hasFilterMask": false,
81
+ "userMaskDensity": 255,
82
+ "userMaskFeather": 0,
83
+ "vectorMaskDensity": 255,
84
+ "vectorMaskFeather": 0,
85
+ "filterMaskDensity": 255,
86
+ "filterMaskFeather": 0,
87
+ "bounds": {
88
+ "_obj": "rectangle",
89
+ "top": {
90
+ "_unit": "pixelsUnit",
91
+ "_value": 0
92
+ },
93
+ "left": {
94
+ "_unit": "pixelsUnit",
95
+ "_value": 0
96
+ },
97
+ "bottom": {
98
+ "_unit": "pixelsUnit",
99
+ "_value": 100
100
+ },
101
+ "right": {
102
+ "_unit": "pixelsUnit",
103
+ "_value": 100
104
+ },
105
+ "width": {
106
+ "_unit": "pixelsUnit",
107
+ "_value": 100
108
+ },
109
+ "height": {
110
+ "_unit": "pixelsUnit",
111
+ "_value": 100
112
+ }
113
+ },
114
+ "boundsNoEffects": {
115
+ "_obj": "rectangle",
116
+ "top": {
117
+ "_unit": "pixelsUnit",
118
+ "_value": 0
119
+ },
120
+ "left": {
121
+ "_unit": "pixelsUnit",
122
+ "_value": 0
123
+ },
124
+ "bottom": {
125
+ "_unit": "pixelsUnit",
126
+ "_value": 100
127
+ },
128
+ "right": {
129
+ "_unit": "pixelsUnit",
130
+ "_value": 100
131
+ },
132
+ "width": {
133
+ "_unit": "pixelsUnit",
134
+ "_value": 100
135
+ },
136
+ "height": {
137
+ "_unit": "pixelsUnit",
138
+ "_value": 100
139
+ }
140
+ },
141
+ "boundsNoMask": {
142
+ "_obj": "rectangle",
143
+ "top": {
144
+ "_unit": "pixelsUnit",
145
+ "_value": 0
146
+ },
147
+ "left": {
148
+ "_unit": "pixelsUnit",
149
+ "_value": 0
150
+ },
151
+ "bottom": {
152
+ "_unit": "pixelsUnit",
153
+ "_value": 100
154
+ },
155
+ "right": {
156
+ "_unit": "pixelsUnit",
157
+ "_value": 100
158
+ },
159
+ "width": {
160
+ "_unit": "pixelsUnit",
161
+ "_value": 100
162
+ },
163
+ "height": {
164
+ "_unit": "pixelsUnit",
165
+ "_value": 100
166
+ }
167
+ },
168
+ "useAlignedRendering": false,
169
+ "generatorSettings": {
170
+ "_obj": "generatorSettings"
171
+ },
172
+ "keyOriginType": [],
173
+ "fillEnabled": false,
174
+ "animationProtection": {
175
+ "_obj": "animationProtection",
176
+ "animationUnifyPosition": false,
177
+ "animationUnifyEffects": false,
178
+ "animationUnifyVisibility": false,
179
+ "animationPropagate": true
180
+ },
181
+ "isSyntheticFill": false,
182
+ "artboardEnabled": false,
183
+ "vectorMaskEmpty": true,
184
+ "textWarningLevel": 0,
185
+ "parentLayerID": -1
186
+ }
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ import { createCommand } from "../core/command";
3
+
4
+ export function createMultiGetDocumentCommand(docId: number) {
5
+ return createCommand({
6
+ modifying: false,
7
+ descriptor: {
8
+ _obj: "multiGet",
9
+ _target: { _ref: [{ _ref: "document", _id: docId }] },
10
+ extendedReference: [
11
+ ["name", "layerID", "visible", "group", "layerSection", "layerKind", "itemIndex", "background", "mode", "layerEffects"],
12
+ { _obj: "layer", index: 1, count: -1 },
13
+ ],
14
+ },
15
+ schema: z.object({
16
+ list: z.array(
17
+ z.object({
18
+ name: z.string(),
19
+ layerID: z.number(),
20
+ visible: z.boolean(),
21
+ group: z.boolean(),
22
+ layerSection: z.object({
23
+ _value: z.enum([
24
+ "layerSectionStart",
25
+ "layerSectionEnd",
26
+ "layerSectionContent",
27
+ ]),
28
+ _enum: z.literal("layerSectionType"),
29
+ }),
30
+ layerKind: z.number(),
31
+ itemIndex: z.number(),
32
+ background: z.boolean(),
33
+ mode: z.object({
34
+ _enum: z.literal("blendMode"),
35
+ _value: z.string(),
36
+ }),
37
+ layerEffects: z.record(z.string(), z.object({
38
+ // "scale" does not have an "enabled" property, that's why it's optional
39
+ enabled: z.boolean().optional(),
40
+ }).or(z.array(z.object({
41
+ enabled: z.boolean(),
42
+ })))).optional(),
43
+ })
44
+ )
45
+ })
46
+ });
47
+ }
@@ -1,13 +1,13 @@
1
1
  import { executeAsModal } from "../core/executeAsModal";
2
2
  import { openFileByPath } from "../filesystem/openFileByPath";
3
- import { getLayerProperties } from "../ut-tree/getLayerProperties";
3
+ import { getDocumentLayerDescriptors } from "../ut-tree/getDocumentLayerDescriptors";
4
4
  import type { Test } from "@bubblydoo/uxp-test-framework";
5
5
  import { expect } from "chai";
6
6
  import { app } from "photoshop";
7
7
  import { createRenameLayerCommand } from "./renameLayer";
8
8
 
9
9
  async function getFirstLayer() {
10
- const allLayers = await getLayerProperties(app.activeDocument.id);
10
+ const allLayers = await getDocumentLayerDescriptors(app.activeDocument.id);
11
11
  return {
12
12
  ref: {
13
13
  id: allLayers[0]!.layerID,
@@ -15,7 +15,7 @@ async function getFirstLayer() {
15
15
  },
16
16
  name: allLayers[0]!.name,
17
17
  };
18
- }
18
+ }
19
19
 
20
20
  export const renameLayerTest: Test = {
21
21
  name: "Rename Layer",
@@ -29,4 +29,4 @@ export const renameLayerTest: Test = {
29
29
  const layer2 = await getFirstLayer();
30
30
  expect(layer2.name).to.equal("New Name");
31
31
  },
32
- };
32
+ };
@@ -0,0 +1,25 @@
1
+ import { describe, expectTypeOf, test } from "vitest";
2
+ import { z } from "zod";
3
+ import {
4
+ createCommand,
5
+ type UTCommandResult,
6
+ } from "./command";
7
+
8
+ describe("UTCommandResult type tests", () => {
9
+ test("extracts result type from non-modifying command", () => {
10
+ const command = createCommand({
11
+ descriptor: { _obj: "get" },
12
+ schema: z.object({
13
+ name: z.string(),
14
+ count: z.number(),
15
+ }),
16
+ modifying: false,
17
+ });
18
+
19
+ type Result = UTCommandResult<typeof command>;
20
+
21
+ expectTypeOf<Result>().toEqualTypeOf<{
22
+ name: string;
23
+ count: number;
24
+ }>();
25
+ });});
package/src/index.ts CHANGED
@@ -18,14 +18,21 @@ export {
18
18
  type CorrectExecuteAsModalOptions,
19
19
  type ExtendedExecutionContext,
20
20
  } from "./core/executeAsModal";
21
- export { suspendHistory, type SuspendHistoryContext } from "./core/suspendHistory";
21
+ export {
22
+ suspendHistory,
23
+ type SuspendHistoryContext,
24
+ } from "./core/suspendHistory";
22
25
 
23
26
  // Core wrappers
24
27
  export { executeAsModalAndSuspendHistory } from "./core-wrappers/executeAsModalAndSuspendHistory";
25
28
 
26
29
  // Commands library
27
- export { getLayerProperties } from "./commands-library/getLayerProperties";
28
30
  export { createRenameLayerCommand } from "./commands-library/renameLayer";
31
+ export {
32
+ createGetDocumentCommand,
33
+ createGetDocumentHasBackgroundLayerCommand,
34
+ } from "./commands-library/getDocument";
35
+ export { createGetBackgroundLayerCommand } from "./commands-library/getLayer";
29
36
 
30
37
  // DOM – layers
31
38
  export { getFlattenedDomLayersList } from "./dom/getFlattenedDomLayersList";
@@ -47,27 +54,29 @@ export { copyToClipboard, readFromClipboard } from "./other/clipboard";
47
54
  export { uxpEntrypointsSchema } from "./other/uxpEntrypoints";
48
55
 
49
56
  // UT tree – layer descriptors & Photoshop tree
57
+ export { createMultiGetDocumentCommand } from "./commands-library/multiGetDocument";
58
+ export { getDocumentLayerDescriptors } from "./ut-tree/getDocumentLayerDescriptors";
59
+ export { getLayerEffects } from "./ut-tree/getLayerEffects";
50
60
  export {
51
- getFlattenedLayerDescriptorsList,
52
- type LayerDescriptor,
53
- } from "./ut-tree/getFlattenedLayerDescriptorsList";
54
- export {
55
- createGetLayerPropertiesCommand,
56
- getLayerProperties as getLayerPropertiesFromUtTree,
57
- } from "./ut-tree/getLayerProperties";
58
- export {
59
- createGetLayerCommand as createGetLayerEffectsCommand,
60
- getLayerEffects,
61
- } from "./ut-tree/getLayerEffects";
62
- export {
63
- photoshopLayerDescriptorsToUTLayers,
64
61
  type UTLayer,
62
+ photoshopLayerDescriptorsToUTLayers,
65
63
  } from "./ut-tree/photoshopLayerDescriptorsToUTLayers";
66
64
  export { type PsLayerRef } from "./ut-tree/psLayerRef";
67
- export { utLayersToTree, type UTLayerWithoutChildren } from "./ut-tree/utLayersToTree";
65
+ export {
66
+ utLayersToTree,
67
+ type UTLayerWithoutChildren,
68
+ } from "./ut-tree/utLayersToTree";
69
+
70
+ // UT tree – text
71
+ export { utLayersToText as utTreeToText } from "./ut-tree/utLayersToText";
68
72
 
69
73
  // Util
70
74
  export { utLayerToDomLayer, utLayersToDomLayers } from "./util/utLayerToLayer";
75
+ export { type UTLayerPickKeys } from "./util/utLayerPickKeysType";
71
76
 
72
77
  // Error sourcemaps
73
- export { parseUxpErrorSourcemaps, getBasicStackFrameAbsoluteFilePath, type BasicStackFrame } from "./error-sourcemaps/sourcemaps";
78
+ export {
79
+ parseUxpErrorSourcemaps,
80
+ getBasicStackFrameAbsoluteFilePath,
81
+ type BasicStackFrame,
82
+ } from "./error-sourcemaps/sourcemaps";
@@ -0,0 +1,29 @@
1
+ import { batchPlayCommand, batchPlayCommands } from "../core/command";
2
+ import { createMultiGetDocumentCommand } from "../commands-library/multiGetDocument";
3
+ import { createGetDocumentHasBackgroundLayerCommand } from "../commands-library/getDocument";
4
+ import { createGetBackgroundLayerCommand } from "../commands-library/getLayer";
5
+
6
+ // get layer properties like name and layerID for all layers in the document (by index)
7
+ export const getDocumentLayerDescriptors = async (documentId: number) => {
8
+ const [layersResult, documentHasBackgroundLayerResult] = await batchPlayCommands([
9
+ createMultiGetDocumentCommand(documentId),
10
+ createGetDocumentHasBackgroundLayerCommand(documentId),
11
+ ]);
12
+
13
+ const backgroundLayerResult = documentHasBackgroundLayerResult.hasBackgroundLayer ? await batchPlayCommand(createGetBackgroundLayerCommand(documentId)) : null;
14
+
15
+ const list = [...layersResult.list].reverse();
16
+ if (backgroundLayerResult) {
17
+ list.push(backgroundLayerResult);
18
+ }
19
+
20
+ // Reverse to get bottom-up order
21
+ return list.map((layerProp) => {
22
+ return {
23
+ ...layerProp,
24
+ docId: documentId,
25
+ };
26
+ });
27
+ };
28
+
29
+ export type LayerDescriptor = Awaited<ReturnType<typeof getDocumentLayerDescriptors>>[number];
@@ -1,36 +1,13 @@
1
1
 
2
2
  import { type PsLayerRef } from "./psLayerRef";
3
- import { batchPlayCommand, createCommand } from "../core/command";
4
- import { z } from "zod";
5
-
6
- export function createGetLayerCommand(layerRef: PsLayerRef) {
7
- return createCommand({
8
- modifying: false,
9
- descriptor: {
10
- _obj: "get",
11
- _target: [
12
- { _ref: "layer", _id: layerRef.id },
13
- { _ref: "document", _id: layerRef.docId },
14
- ],
15
- },
16
- schema: z.object({
17
- layerID: z.number(),
18
- group: z.boolean().optional(),
19
- layerEffects: z.record(z.string(), z.object({
20
- // "scale" does not have an "enabled" property, that's why it's optional
21
- enabled: z.boolean().optional(),
22
- }).or(z.array(z.object({
23
- enabled: z.boolean(),
24
- })))).optional(),
25
- }),
26
- });
27
- }
3
+ import { batchPlayCommand } from "../core/command";
4
+ import { createGetLayerCommand } from "../commands-library/getLayer";
28
5
 
29
6
  export async function getLayerEffects(layerRef: PsLayerRef) {
30
7
  const result = await batchPlayCommand(createGetLayerCommand(layerRef));
31
8
 
32
9
  const data = result.layerEffects || {};
33
-
10
+
34
11
  const effects: Record<string, boolean> = {};
35
12
 
36
13
  for (const effect in data) {
@@ -0,0 +1,42 @@
1
+ import { app } from "photoshop";
2
+ import { openFileByPath } from "../filesystem/openFileByPath";
3
+ import { batchPlayCommand } from "../core/command";
4
+ import { expect } from "chai";
5
+ import { createGetDocumentHasBackgroundLayerCommand } from "../commands-library/getDocument";
6
+ import type { Test } from "@bubblydoo/uxp-test-framework";
7
+ import { createGetBackgroundLayerCommand } from "../commands-library/getLayer";
8
+ import { executeAsModal } from "../core/executeAsModal";
9
+
10
+ export const backgroundLayerTest: Test = {
11
+ name: "Has/Get Background Layer",
12
+ async run() {
13
+ const doc = await openFileByPath("plugin:/fixtures/one-layer-with-bg.psd");
14
+ const hasBackgroundLayer = await batchPlayCommand(
15
+ createGetDocumentHasBackgroundLayerCommand(doc.id)
16
+ )
17
+ expect(hasBackgroundLayer.hasBackgroundLayer).to.be.true;
18
+ const layer = await batchPlayCommand(
19
+ createGetBackgroundLayerCommand(app.activeDocument.id)
20
+ )
21
+ expect(layer.name).to.equal("Background");
22
+ await executeAsModal("Close Document", async () => await doc.close(0));
23
+
24
+ const doc2 = await openFileByPath("plugin:/fixtures/one-layer.psd");
25
+
26
+ const hasBackgroundLayer2 = await batchPlayCommand(
27
+ createGetDocumentHasBackgroundLayerCommand(doc2.id)
28
+ )
29
+ expect(hasBackgroundLayer2.hasBackgroundLayer).to.be.false;
30
+ let rejected = false;
31
+ try {
32
+ await batchPlayCommand(
33
+ createGetBackgroundLayerCommand(doc2.id)
34
+ )
35
+ } catch (e) {
36
+ rejected = true;
37
+ }
38
+ expect(rejected).to.be.true;
39
+
40
+ await executeAsModal("Close Document", async () => await doc2.close(0));
41
+ },
42
+ };
@@ -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
+ });