ts-visio 1.0.2 → 1.1.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/dist/Layer.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { VisioPackage } from './VisioPackage';
2
+ import { ShapeModifier } from './ShapeModifier';
2
3
  export declare class Layer {
3
4
  name: string;
4
5
  index: number;
5
6
  private pageId?;
6
7
  private pkg?;
7
- constructor(name: string, index: number, pageId?: string | undefined, pkg?: VisioPackage | undefined);
8
+ private modifier;
9
+ constructor(name: string, index: number, pageId?: string | undefined, pkg?: VisioPackage | undefined, modifier?: ShapeModifier);
8
10
  setVisible(visible: boolean): Promise<this>;
9
11
  setLocked(locked: boolean): Promise<this>;
10
12
  hide(): Promise<this>;
package/dist/Layer.js CHANGED
@@ -3,26 +3,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Layer = void 0;
4
4
  const ShapeModifier_1 = require("./ShapeModifier");
5
5
  class Layer {
6
- constructor(name, index, pageId, pkg) {
6
+ constructor(name, index, pageId, pkg, modifier) {
7
7
  this.name = name;
8
8
  this.index = index;
9
9
  this.pageId = pageId;
10
10
  this.pkg = pkg;
11
+ this.modifier = modifier ?? (pkg ? new ShapeModifier_1.ShapeModifier(pkg) : null);
11
12
  }
12
13
  async setVisible(visible) {
13
- if (!this.pageId || !this.pkg) {
14
+ if (!this.pageId || !this.modifier) {
14
15
  throw new Error('Layer was not created with page context. Cannot update properties.');
15
16
  }
16
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
17
- await modifier.updateLayerProperty(this.pageId, this.index, 'Visible', visible ? '1' : '0');
17
+ await this.modifier.updateLayerProperty(this.pageId, this.index, 'Visible', visible ? '1' : '0');
18
18
  return this;
19
19
  }
20
20
  async setLocked(locked) {
21
- if (!this.pageId || !this.pkg) {
21
+ if (!this.pageId || !this.modifier) {
22
22
  throw new Error('Layer was not created with page context. Cannot update properties.');
23
23
  }
24
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
25
- await modifier.updateLayerProperty(this.pageId, this.index, 'Lock', locked ? '1' : '0');
24
+ await this.modifier.updateLayerProperty(this.pageId, this.index, 'Lock', locked ? '1' : '0');
26
25
  return this;
27
26
  }
28
27
  async hide() {
package/dist/Page.d.ts CHANGED
@@ -12,6 +12,8 @@ export declare class Page {
12
12
  private media;
13
13
  private rels;
14
14
  private modifier;
15
+ /** Resolved OPC part path for this page's XML file. */
16
+ private pagePath;
15
17
  constructor(internalPage: VisioPage, pkg: VisioPackage, media?: MediaManager, rels?: RelsManager, modifier?: ShapeModifier);
16
18
  get id(): string;
17
19
  get name(): string;
package/dist/Page.js CHANGED
@@ -12,9 +12,13 @@ class Page {
12
12
  constructor(internalPage, pkg, media, rels, modifier) {
13
13
  this.internalPage = internalPage;
14
14
  this.pkg = pkg;
15
+ // Prefer the relationship-resolved path over the ID-derived fallback so
16
+ // that loaded files with non-sequential page filenames work correctly.
17
+ this.pagePath = internalPage.xmlPath ?? `visio/pages/page${internalPage.ID}.xml`;
15
18
  this.media = media || new MediaManager_1.MediaManager(pkg);
16
19
  this.rels = rels || new RelsManager_1.RelsManager(pkg);
17
20
  this.modifier = modifier || new ShapeModifier_1.ShapeModifier(pkg);
21
+ this.modifier.registerPage(internalPage.ID, this.pagePath);
18
22
  }
19
23
  get id() {
20
24
  return this.internalPage.ID;
@@ -24,11 +28,9 @@ class Page {
24
28
  }
25
29
  getShapes() {
26
30
  const reader = new ShapeReader_1.ShapeReader(this.pkg);
27
- // Assuming standard path mapping for now
28
- const pagePath = `visio/pages/page${this.id}.xml`;
29
31
  try {
30
- const internalShapes = reader.readShapes(pagePath);
31
- return internalShapes.map(s => new Shape_1.Shape(s, this.id, this.pkg));
32
+ const internalShapes = reader.readShapes(this.pagePath);
33
+ return internalShapes.map(s => new Shape_1.Shape(s, this.id, this.pkg, this.modifier));
32
34
  }
33
35
  catch (e) {
34
36
  // If page file doesn't exist or is empty, return empty array
@@ -53,7 +55,7 @@ class Page {
53
55
  'LocPinY': props.height / 2
54
56
  }
55
57
  });
56
- return new Shape_1.Shape(internalStub, this.id, this.pkg);
58
+ return new Shape_1.Shape(internalStub, this.id, this.pkg, this.modifier);
57
59
  }
58
60
  async connectShapes(fromShape, toShape, beginArrow, endArrow) {
59
61
  await this.modifier.addConnector(this.id, fromShape.id, toShape.id, beginArrow, endArrow);
@@ -61,8 +63,8 @@ class Page {
61
63
  async addImage(data, name, x, y, width, height) {
62
64
  // 1. Upload Media
63
65
  const mediaPath = this.media.addMedia(name, data);
64
- // 2. Link Page to Media
65
- const rId = await this.rels.addPageImageRel(this.id, mediaPath);
66
+ // 2. Link Page to Media (use resolved path so loaded files work correctly)
67
+ const rId = await this.rels.addImageRelationship(this.pagePath, mediaPath);
66
68
  // 3. Create Shape
67
69
  const newId = await this.modifier.addShape(this.id, {
68
70
  text: '',
@@ -80,7 +82,7 @@ class Page {
80
82
  'PinY': y
81
83
  }
82
84
  });
83
- return new Shape_1.Shape(internalStub, this.id, this.pkg);
85
+ return new Shape_1.Shape(internalStub, this.id, this.pkg, this.modifier);
84
86
  }
85
87
  async addContainer(props) {
86
88
  const newId = await this.modifier.addContainer(this.id, props);
@@ -94,7 +96,7 @@ class Page {
94
96
  'PinY': props.y
95
97
  }
96
98
  });
97
- return new Shape_1.Shape(internalStub, this.id, this.pkg);
99
+ return new Shape_1.Shape(internalStub, this.id, this.pkg, this.modifier);
98
100
  }
99
101
  async addList(props, direction = 'vertical') {
100
102
  const newId = await this.modifier.addList(this.id, props, direction);
@@ -108,7 +110,7 @@ class Page {
108
110
  'PinY': props.y
109
111
  }
110
112
  });
111
- return new Shape_1.Shape(internalStub, this.id, this.pkg);
113
+ return new Shape_1.Shape(internalStub, this.id, this.pkg, this.modifier);
112
114
  }
113
115
  /**
114
116
  * Creates a Swimlane Pool (which is technically a Vertical List of Containers).
@@ -179,7 +181,7 @@ class Page {
179
181
  }
180
182
  async addLayer(name, options) {
181
183
  const info = await this.modifier.addLayer(this.id, name, options);
182
- return new Layer_1.Layer(info.name, info.index, this.id, this.pkg);
184
+ return new Layer_1.Layer(info.name, info.index, this.id, this.pkg, this.modifier);
183
185
  }
184
186
  }
185
187
  exports.Page = Page;
package/dist/Shape.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { VisioShape } from './types/VisioTypes';
2
2
  import { VisioPackage } from './VisioPackage';
3
- import { ShapeStyle } from './ShapeModifier';
3
+ import { ShapeModifier, ShapeStyle } from './ShapeModifier';
4
4
  import { VisioPropType } from './types/VisioTypes';
5
5
  import { Layer } from './Layer';
6
6
  export interface ShapeData {
@@ -13,7 +13,8 @@ export declare class Shape {
13
13
  private internalShape;
14
14
  private pageId;
15
15
  private pkg;
16
- constructor(internalShape: VisioShape, pageId: string, pkg: VisioPackage);
16
+ private modifier;
17
+ constructor(internalShape: VisioShape, pageId: string, pkg: VisioPackage, modifier?: ShapeModifier);
17
18
  get id(): string;
18
19
  get name(): string;
19
20
  get text(): string;
@@ -65,4 +66,5 @@ export declare class Shape {
65
66
  * Alias for assignLayer. Adds this shape to a layer.
66
67
  */
67
68
  addToLayer(layer: Layer | number): Promise<this>;
69
+ private setLocalCoord;
68
70
  }
package/dist/Shape.js CHANGED
@@ -3,11 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Shape = void 0;
4
4
  const ShapeModifier_1 = require("./ShapeModifier");
5
5
  const VisioTypes_1 = require("./types/VisioTypes");
6
+ /** Round a coordinate to 10 decimal places to prevent float-to-string-to-float precision drift. */
7
+ function fmtCoord(n) {
8
+ return parseFloat(n.toFixed(10)).toString();
9
+ }
6
10
  class Shape {
7
- constructor(internalShape, pageId, pkg) {
11
+ constructor(internalShape, pageId, pkg, modifier) {
8
12
  this.internalShape = internalShape;
9
13
  this.pageId = pageId;
10
14
  this.pkg = pkg;
15
+ this.modifier = modifier ?? new ShapeModifier_1.ShapeModifier(pkg);
11
16
  }
12
17
  get id() {
13
18
  return this.internalShape.ID;
@@ -19,9 +24,7 @@ class Shape {
19
24
  return this.internalShape.Text || '';
20
25
  }
21
26
  async setText(newText) {
22
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
23
- await modifier.updateShapeText(this.pageId, this.id, newText);
24
- // Update local state to reflect change
27
+ await this.modifier.updateShapeText(this.pageId, this.id, newText);
25
28
  this.internalShape.Text = newText;
26
29
  }
27
30
  get width() {
@@ -37,64 +40,43 @@ class Shape {
37
40
  return this.internalShape.Cells['PinY'] ? Number(this.internalShape.Cells['PinY'].V) : 0;
38
41
  }
39
42
  async connectTo(targetShape, beginArrow, endArrow) {
40
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
41
- await modifier.addConnector(this.pageId, this.id, targetShape.id, beginArrow, endArrow);
43
+ await this.modifier.addConnector(this.pageId, this.id, targetShape.id, beginArrow, endArrow);
42
44
  return this;
43
45
  }
44
46
  async setStyle(style) {
45
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
46
- await modifier.updateShapeStyle(this.pageId, this.id, style);
47
- // Minimal local state update to reflect changes if necessary
48
- // For now, valid XML is the priority.
47
+ await this.modifier.updateShapeStyle(this.pageId, this.id, style);
49
48
  return this;
50
49
  }
51
50
  async placeRightOf(targetShape, options = { gap: 1 }) {
52
- const newX = targetShape.x + targetShape.width + options.gap;
53
- const newY = targetShape.y; // Keep same Y (per instructions, aligns center-to-center if PinY is center)
54
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
55
- await modifier.updateShapePosition(this.pageId, this.id, newX, newY);
56
- // Update local state is crucial for chaining successive placements
57
- if (this.internalShape.Cells['PinX'])
58
- this.internalShape.Cells['PinX'].V = newX.toString();
59
- else
60
- this.internalShape.Cells['PinX'] = { V: newX.toString(), N: 'PinX' };
61
- if (this.internalShape.Cells['PinY'])
62
- this.internalShape.Cells['PinY'].V = newY.toString();
63
- else
64
- this.internalShape.Cells['PinY'] = { V: newY.toString(), N: 'PinY' };
51
+ // PinX is the shape centre, so right edge of target = target.x + target.width/2;
52
+ // left edge of this = newX - this.width/2. Set left edge = right edge of target + gap.
53
+ const newX = targetShape.x + (targetShape.width / 2) + options.gap + (this.width / 2);
54
+ const newY = targetShape.y; // Align centres vertically
55
+ await this.modifier.updateShapePosition(this.pageId, this.id, newX, newY);
56
+ // Update local state — rounded to avoid float precision drift in chained placements
57
+ this.setLocalCoord('PinX', newX);
58
+ this.setLocalCoord('PinY', newY);
65
59
  return this;
66
60
  }
67
61
  async placeBelow(targetShape, options = { gap: 1 }) {
68
- const newX = targetShape.x; // Align Centers
69
- // Target Bottom = target.y - target.height / 2
70
- // My Top = Target Bottom - gap
71
- // My Center = My Top - my.height / 2
72
- // My Center = target.y - target.height/2 - gap - my.height/2
62
+ const newX = targetShape.x; // Align centres horizontally
63
+ // Target bottom edge = target.y - target.height/2
64
+ // This centre = target bottom - gap - this.height/2
73
65
  const newY = targetShape.y - (targetShape.height + this.height) / 2 - options.gap;
74
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
75
- await modifier.updateShapePosition(this.pageId, this.id, newX, newY);
76
- if (this.internalShape.Cells['PinX'])
77
- this.internalShape.Cells['PinX'].V = newX.toString();
78
- else
79
- this.internalShape.Cells['PinX'] = { V: newX.toString(), N: 'PinX' };
80
- if (this.internalShape.Cells['PinY'])
81
- this.internalShape.Cells['PinY'].V = newY.toString();
82
- else
83
- this.internalShape.Cells['PinY'] = { V: newY.toString(), N: 'PinY' };
66
+ await this.modifier.updateShapePosition(this.pageId, this.id, newX, newY);
67
+ this.setLocalCoord('PinX', newX);
68
+ this.setLocalCoord('PinY', newY);
84
69
  return this;
85
70
  }
86
71
  addPropertyDefinition(name, type, options = {}) {
87
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
88
- modifier.addPropertyDefinition(this.pageId, this.id, name, type, options);
72
+ this.modifier.addPropertyDefinition(this.pageId, this.id, name, type, options);
89
73
  return this;
90
74
  }
91
75
  setPropertyValue(name, value) {
92
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
93
- modifier.setPropertyValue(this.pageId, this.id, name, value);
76
+ this.modifier.setPropertyValue(this.pageId, this.id, name, value);
94
77
  return this;
95
78
  }
96
79
  addData(key, data) {
97
- // Auto-detect type if not provided
98
80
  let type = data.type;
99
81
  if (type === undefined) {
100
82
  if (data.value instanceof Date) {
@@ -110,59 +92,37 @@ class Shape {
110
92
  type = VisioTypes_1.VisioPropType.String;
111
93
  }
112
94
  }
113
- // 1. Define Property
114
- this.addPropertyDefinition(key, type, {
115
- label: data.label,
116
- invisible: data.hidden
117
- });
118
- // 2. Set Value
95
+ this.addPropertyDefinition(key, type, { label: data.label, invisible: data.hidden });
119
96
  this.setPropertyValue(key, data.value);
120
97
  return this;
121
98
  }
122
99
  async addMember(memberShape) {
123
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
124
- // Type="Container" is the standard for Container relationships
125
- await modifier.addRelationship(this.pageId, this.id, memberShape.id, 'Container');
100
+ await this.modifier.addRelationship(this.pageId, this.id, memberShape.id, 'Container');
126
101
  return this;
127
102
  }
128
103
  async addListItem(item) {
129
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
130
- await modifier.addListItem(this.pageId, this.id, item.id);
131
- // Refresh local state after modifer updates (resizeToFit called internally)
104
+ await this.modifier.addListItem(this.pageId, this.id, item.id);
132
105
  await this.refreshLocalState();
133
106
  return this;
134
107
  }
135
108
  async resizeToFit(padding = 0.25) {
136
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
137
- await modifier.resizeContainerToFit(this.pageId, this.id, padding);
109
+ await this.modifier.resizeContainerToFit(this.pageId, this.id, padding);
138
110
  await this.refreshLocalState();
139
111
  return this;
140
112
  }
141
113
  async refreshLocalState() {
142
- // Reloads internal Cells from modifier's fresh XML
143
- // This is a bit expensive but ensures consistency
144
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
145
- const geo = modifier.getShapeGeometry(this.pageId, this.id);
146
- const update = (n, v) => {
147
- if (this.internalShape.Cells[n])
148
- this.internalShape.Cells[n].V = v;
149
- else
150
- this.internalShape.Cells[n] = { V: v, N: n };
151
- };
152
- update('PinX', geo.x.toString());
153
- update('PinY', geo.y.toString());
154
- update('Width', geo.width.toString());
155
- update('Height', geo.height.toString());
114
+ const geo = this.modifier.getShapeGeometry(this.pageId, this.id);
115
+ this.setLocalCoord('PinX', geo.x);
116
+ this.setLocalCoord('PinY', geo.y);
117
+ this.setLocalCoord('Width', geo.width);
118
+ this.setLocalCoord('Height', geo.height);
156
119
  }
157
120
  async addHyperlink(address, description) {
158
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
159
- await modifier.addHyperlink(this.pageId, this.id, { address, description });
121
+ await this.modifier.addHyperlink(this.pageId, this.id, { address, description });
160
122
  return this;
161
123
  }
162
124
  async linkToPage(targetPage, description) {
163
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
164
- // Internal links use SubAddress='PageName' and empty Address
165
- await modifier.addHyperlink(this.pageId, this.id, {
125
+ await this.modifier.addHyperlink(this.pageId, this.id, {
166
126
  address: '',
167
127
  subAddress: targetPage.name,
168
128
  description
@@ -189,8 +149,7 @@ class Shape {
189
149
  }
190
150
  async assignLayer(layer) {
191
151
  const index = typeof layer === 'number' ? layer : layer.index;
192
- const modifier = new ShapeModifier_1.ShapeModifier(this.pkg);
193
- await modifier.assignLayer(this.pageId, this.id, index);
152
+ await this.modifier.assignLayer(this.pageId, this.id, index);
194
153
  return this;
195
154
  }
196
155
  /**
@@ -199,5 +158,12 @@ class Shape {
199
158
  async addToLayer(layer) {
200
159
  return this.assignLayer(layer);
201
160
  }
161
+ setLocalCoord(name, value) {
162
+ const v = fmtCoord(value);
163
+ if (this.internalShape.Cells[name])
164
+ this.internalShape.Cells[name].V = v;
165
+ else
166
+ this.internalShape.Cells[name] = { V: v, N: name };
167
+ }
202
168
  }
203
169
  exports.Shape = Shape;
@@ -9,9 +9,18 @@ export declare class ShapeModifier {
9
9
  private relsManager;
10
10
  private pageCache;
11
11
  private dirtyPages;
12
+ private shapeCache;
13
+ private pagePathRegistry;
12
14
  autoSave: boolean;
15
+ /**
16
+ * Register the resolved OPC part path for a page ID.
17
+ * Must be called before any operation on a loaded file to ensure the
18
+ * correct file is targeted rather than the ID-derived fallback name.
19
+ */
20
+ registerPage(pageId: string, xmlPath: string): void;
13
21
  constructor(pkg: VisioPackage);
14
22
  private getPagePath;
23
+ private getShapeMap;
15
24
  private getAllShapes;
16
25
  private getNextId;
17
26
  private ensurePageSheet;