pptx-viewer-mcp 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/README.md +146 -0
- package/dist/codec/index.d.mts +26 -0
- package/dist/codec/index.d.ts +26 -0
- package/dist/codec/index.js +241 -0
- package/dist/codec/index.mjs +238 -0
- package/dist/index.d.mts +480 -0
- package/dist/index.d.ts +480 -0
- package/dist/index.js +1581 -0
- package/dist/index.mjs +1547 -0
- package/dist/mcp/index.d.mts +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +2339 -0
- package/dist/mcp/index.mjs +2337 -0
- package/dist/schemas/index.d.mts +728 -0
- package/dist/schemas/index.d.ts +728 -0
- package/dist/schemas/index.js +285 -0
- package/dist/schemas/index.mjs +259 -0
- package/package.json +87 -0
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# pptx-viewer-mcp
|
|
2
|
+
|
|
3
|
+
Pure tool functions, Zod schemas, a Y.Doc collaboration codec, and an MCP server for PPTX manipulation — all built on top of `pptx-viewer-core`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install pptx-viewer-mcp pptx-viewer-core
|
|
9
|
+
# optional — required only for the collaboration codec
|
|
10
|
+
npm install yjs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Exports
|
|
14
|
+
|
|
15
|
+
| Entry point | Contents |
|
|
16
|
+
| ---------------------------- | ------------------------------------------------------- |
|
|
17
|
+
| `pptx-viewer-mcp` | 24 tool functions + provider types + execution pipeline |
|
|
18
|
+
| `pptx-viewer-mcp/schemas` | Zod schemas for every tool input |
|
|
19
|
+
| `pptx-viewer-mcp/codec` | `PptxCodec` — Y.Doc ↔ PPTX bytes codec |
|
|
20
|
+
| `pptx-viewer-mcp/mcp` | `createServer()` — programmatic MCP server factory |
|
|
21
|
+
|
|
22
|
+
## Tool reference
|
|
23
|
+
|
|
24
|
+
**Slide (8):** `getSlide`, `addSlide`, `deleteSlides`, `reorderSlides`, `duplicateSlide`, `updateSlideProperties`, `setSlideTransition`, `setCanvasSize`
|
|
25
|
+
|
|
26
|
+
**Element (9):** `addElement`, `updateElement`, `deleteElements`, `arrangeElements`, `cloneElement`, `setElementAnimation`, `groupElements`, `ungroupElements`, `batchUpdateElements`
|
|
27
|
+
|
|
28
|
+
**Table (2):** `updateTableCells`, `manageTableStructure`
|
|
29
|
+
|
|
30
|
+
**Style (2):** `updateElementStyle`, `runAccessibilityCheck`
|
|
31
|
+
|
|
32
|
+
**Content (3):** `findText`, `replaceText`, `manageComments`
|
|
33
|
+
|
|
34
|
+
**Conversion (1):** `convertToMarkdown`
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
### 1. Using tool functions directly
|
|
41
|
+
|
|
42
|
+
Every tool is a pure function — no file I/O, no framework dependencies.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
46
|
+
import { addSlide, getSlide } from 'pptx-viewer-mcp';
|
|
47
|
+
|
|
48
|
+
// Load
|
|
49
|
+
const handler = new PptxHandler();
|
|
50
|
+
const bytes = await fs.readFile('deck.pptx');
|
|
51
|
+
const pptxData = await handler.load(bytes.buffer);
|
|
52
|
+
|
|
53
|
+
// Run a tool
|
|
54
|
+
const ctx = { pptxData };
|
|
55
|
+
const { pptxData: updated, dirty } = addSlide(ctx, { insertAfterIndex: 0 });
|
|
56
|
+
|
|
57
|
+
// Save when dirty
|
|
58
|
+
if (dirty) {
|
|
59
|
+
const out = await handler.save(updated.slides);
|
|
60
|
+
await fs.writeFile('deck.pptx', out);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Inspect a slide
|
|
64
|
+
const { result } = getSlide(ctx, { slideIndex: 0 });
|
|
65
|
+
console.log(result.elements);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Using the MCP server
|
|
69
|
+
|
|
70
|
+
Add to your MCP client configuration (Claude Desktop, Cursor, etc.):
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"pptx": {
|
|
76
|
+
"command": "npx",
|
|
77
|
+
"args": ["pptx-viewer-mcp"]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The binary is also available as `pptx-tools` after a global install. All 24 tools are exposed over stdio with the same names in snake_case (e.g. `add_slide`, `batch_update_elements`). Every tool accepts a `filePath` parameter — the server handles load/save internally.
|
|
84
|
+
|
|
85
|
+
### 3. Using `executeToolWithContext` with providers
|
|
86
|
+
|
|
87
|
+
`executeToolWithContext` wraps the load → tool → save cycle and routes through a live Y.Doc collaboration room when one is available.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import {
|
|
91
|
+
executeToolWithContext,
|
|
92
|
+
type ExecutionContext,
|
|
93
|
+
type FileSystemProvider,
|
|
94
|
+
type CollaborationProvider,
|
|
95
|
+
} from 'pptx-viewer-mcp';
|
|
96
|
+
import { replaceText } from 'pptx-viewer-mcp';
|
|
97
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
98
|
+
|
|
99
|
+
// Minimal file-system-only provider
|
|
100
|
+
const filesystem: FileSystemProvider = {
|
|
101
|
+
readFile: (p) => readFile(p),
|
|
102
|
+
writeFile: (p, data) => writeFile(p, data),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const execCtx: ExecutionContext = { filesystem };
|
|
106
|
+
|
|
107
|
+
const result = await executeToolWithContext('deck.pptx', execCtx, (ctx) =>
|
|
108
|
+
replaceText(ctx, { find: 'Draft', replace: 'Final', caseSensitive: false }),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
console.log(result.replacements, result.savedToDisk);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
When `execCtx.collaboration` is provided, `executeToolWithContext` automatically dehydrates the current Y.Doc state before running the tool and re-hydrates the room after saving, so remote peers receive the change without a file reload.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Architecture
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
ToolContext { pptxData }
|
|
122
|
+
│
|
|
123
|
+
▼
|
|
124
|
+
tool function → ToolResult { pptxData, result, dirty }
|
|
125
|
+
│
|
|
126
|
+
▼ (when dirty)
|
|
127
|
+
savePresentation()
|
|
128
|
+
├─ collaboration room → hydrate Y.Doc → broadcast
|
|
129
|
+
└─ no room → writeFile to disk
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`CollaborationProvider`, `FileSystemProvider`, and `ViewerProvider` are plain interfaces — implement them for any runtime (Node, Electron, browser, edge worker).
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
bun run build # tsup → dist/
|
|
140
|
+
bun run typecheck # tsc --noEmit
|
|
141
|
+
bun run test # vitest run
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Doc } from 'yjs';
|
|
2
|
+
|
|
3
|
+
declare const ORIGIN_FILE_LOAD = "file-load";
|
|
4
|
+
/**
|
|
5
|
+
* Bidirectional format codec: file bytes <-> Y.Doc shared types.
|
|
6
|
+
*/
|
|
7
|
+
interface FormatCodec {
|
|
8
|
+
readonly formatId: string;
|
|
9
|
+
readonly extensions: string[];
|
|
10
|
+
hydrate(ydoc: Doc, bytes: Uint8Array, origin?: string): Promise<void>;
|
|
11
|
+
dehydrate(ydoc: Doc, dirtyPaths?: string[]): Promise<Uint8Array>;
|
|
12
|
+
observe(ydoc: Doc, onChange: () => void): () => void;
|
|
13
|
+
}
|
|
14
|
+
declare class PptxCodec implements FormatCodec {
|
|
15
|
+
readonly formatId = "pptx";
|
|
16
|
+
readonly extensions: string[];
|
|
17
|
+
hydrate(ydoc: Doc, bytes: Uint8Array, origin?: string): Promise<void>;
|
|
18
|
+
dehydrate(ydoc: Doc, _dirtyPaths?: string[]): Promise<Uint8Array>;
|
|
19
|
+
observe(ydoc: Doc, onChange: () => void): () => void;
|
|
20
|
+
private _slideToYMap;
|
|
21
|
+
private _yMapToSlide;
|
|
22
|
+
private _elementToYMap;
|
|
23
|
+
private _yMapToElement;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { type FormatCodec, ORIGIN_FILE_LOAD, PptxCodec };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Doc } from 'yjs';
|
|
2
|
+
|
|
3
|
+
declare const ORIGIN_FILE_LOAD = "file-load";
|
|
4
|
+
/**
|
|
5
|
+
* Bidirectional format codec: file bytes <-> Y.Doc shared types.
|
|
6
|
+
*/
|
|
7
|
+
interface FormatCodec {
|
|
8
|
+
readonly formatId: string;
|
|
9
|
+
readonly extensions: string[];
|
|
10
|
+
hydrate(ydoc: Doc, bytes: Uint8Array, origin?: string): Promise<void>;
|
|
11
|
+
dehydrate(ydoc: Doc, dirtyPaths?: string[]): Promise<Uint8Array>;
|
|
12
|
+
observe(ydoc: Doc, onChange: () => void): () => void;
|
|
13
|
+
}
|
|
14
|
+
declare class PptxCodec implements FormatCodec {
|
|
15
|
+
readonly formatId = "pptx";
|
|
16
|
+
readonly extensions: string[];
|
|
17
|
+
hydrate(ydoc: Doc, bytes: Uint8Array, origin?: string): Promise<void>;
|
|
18
|
+
dehydrate(ydoc: Doc, _dirtyPaths?: string[]): Promise<Uint8Array>;
|
|
19
|
+
observe(ydoc: Doc, onChange: () => void): () => void;
|
|
20
|
+
private _slideToYMap;
|
|
21
|
+
private _yMapToSlide;
|
|
22
|
+
private _elementToYMap;
|
|
23
|
+
private _yMapToElement;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { type FormatCodec, ORIGIN_FILE_LOAD, PptxCodec };
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var pptxViewerCore = require('pptx-viewer-core');
|
|
4
|
+
var yjs = require('yjs');
|
|
5
|
+
|
|
6
|
+
// src/codec/pptx-codec.ts
|
|
7
|
+
var ORIGIN_FILE_LOAD = "file-load";
|
|
8
|
+
var SCALAR_ELEMENT_KEYS = /* @__PURE__ */ new Set([
|
|
9
|
+
"id",
|
|
10
|
+
"type",
|
|
11
|
+
"x",
|
|
12
|
+
"y",
|
|
13
|
+
"width",
|
|
14
|
+
"height",
|
|
15
|
+
"rotation",
|
|
16
|
+
"flipHorizontal",
|
|
17
|
+
"flipVertical",
|
|
18
|
+
"hidden",
|
|
19
|
+
"opacity",
|
|
20
|
+
"text",
|
|
21
|
+
"name",
|
|
22
|
+
"altText",
|
|
23
|
+
"shapeType",
|
|
24
|
+
"placeholder",
|
|
25
|
+
"imagePath",
|
|
26
|
+
"imageData",
|
|
27
|
+
"svgContent",
|
|
28
|
+
"inkSvg",
|
|
29
|
+
"sourceSlideId",
|
|
30
|
+
"mediaType",
|
|
31
|
+
"mediaPath",
|
|
32
|
+
"linkedTxbxId",
|
|
33
|
+
"linkedTxbxSeq",
|
|
34
|
+
"promptText"
|
|
35
|
+
]);
|
|
36
|
+
var COMPLEX_FIELD_MAP = {
|
|
37
|
+
textSegments: "_textSegments",
|
|
38
|
+
textStyle: "_textStyle",
|
|
39
|
+
shapeStyle: "_shapeStyle",
|
|
40
|
+
shapeAdjustments: "_shapeAdjustments",
|
|
41
|
+
adjustmentHandles: "_adjustmentHandles",
|
|
42
|
+
tableData: "_tableData",
|
|
43
|
+
chartData: "_chartData",
|
|
44
|
+
smartArtData: "_smartArtData",
|
|
45
|
+
connectionStart: "_connectionStart",
|
|
46
|
+
connectionEnd: "_connectionEnd",
|
|
47
|
+
animations: "_animations",
|
|
48
|
+
nativeAnimations: "_nativeAnimations",
|
|
49
|
+
children: "_children",
|
|
50
|
+
paragraphIndents: "_paragraphIndents",
|
|
51
|
+
rawXml: "_rawXml",
|
|
52
|
+
actionClick: "_actionClick",
|
|
53
|
+
actionHover: "_actionHover",
|
|
54
|
+
locks: "_locks",
|
|
55
|
+
imageEffects: "_imageEffects",
|
|
56
|
+
cropShape: "_cropShape",
|
|
57
|
+
mediaBookmarks: "_mediaBookmarks",
|
|
58
|
+
captionTracks: "_captionTracks"
|
|
59
|
+
};
|
|
60
|
+
var REVERSE_COMPLEX_MAP = {};
|
|
61
|
+
for (const [original, prefixed] of Object.entries(COMPLEX_FIELD_MAP)) {
|
|
62
|
+
REVERSE_COMPLEX_MAP[prefixed] = original;
|
|
63
|
+
}
|
|
64
|
+
var SCALAR_SLIDE_KEYS = /* @__PURE__ */ new Set([
|
|
65
|
+
"id",
|
|
66
|
+
"rId",
|
|
67
|
+
"sourceSlideId",
|
|
68
|
+
"layoutPath",
|
|
69
|
+
"layoutName",
|
|
70
|
+
"slideNumber",
|
|
71
|
+
"hidden",
|
|
72
|
+
"sectionName",
|
|
73
|
+
"sectionId",
|
|
74
|
+
"backgroundColor",
|
|
75
|
+
"backgroundImage",
|
|
76
|
+
"backgroundGradient",
|
|
77
|
+
"notes",
|
|
78
|
+
"backgroundShowAnimation",
|
|
79
|
+
"showMasterShapes",
|
|
80
|
+
"isDirty"
|
|
81
|
+
]);
|
|
82
|
+
var COMPLEX_SLIDE_FIELD_MAP = {
|
|
83
|
+
transition: "_transition",
|
|
84
|
+
animations: "_animations",
|
|
85
|
+
nativeAnimations: "_nativeAnimations",
|
|
86
|
+
rawTiming: "_rawTiming",
|
|
87
|
+
notesSegments: "_notesSegments",
|
|
88
|
+
comments: "_comments",
|
|
89
|
+
warnings: "_warnings",
|
|
90
|
+
rawXml: "_rawXml",
|
|
91
|
+
clrMapOverride: "_clrMapOverride",
|
|
92
|
+
guides: "_guides",
|
|
93
|
+
customerData: "_customerData",
|
|
94
|
+
activeXControls: "_activeXControls"
|
|
95
|
+
};
|
|
96
|
+
var REVERSE_COMPLEX_SLIDE_MAP = {};
|
|
97
|
+
for (const [original, prefixed] of Object.entries(COMPLEX_SLIDE_FIELD_MAP)) {
|
|
98
|
+
REVERSE_COMPLEX_SLIDE_MAP[prefixed] = original;
|
|
99
|
+
}
|
|
100
|
+
var PptxCodec = class {
|
|
101
|
+
formatId = "pptx";
|
|
102
|
+
extensions = [".pptx", ".ppt"];
|
|
103
|
+
async hydrate(ydoc, bytes, origin) {
|
|
104
|
+
const effectiveOrigin = origin ?? ORIGIN_FILE_LOAD;
|
|
105
|
+
const handler = new pptxViewerCore.PptxHandler();
|
|
106
|
+
const pptxData = await handler.load(bytes.buffer);
|
|
107
|
+
ydoc.transact(() => {
|
|
108
|
+
const meta = ydoc.getMap("pptx:meta");
|
|
109
|
+
meta.set("width", pptxData.width);
|
|
110
|
+
meta.set("height", pptxData.height);
|
|
111
|
+
if (pptxData.widthEmu !== void 0) {
|
|
112
|
+
meta.set("widthEmu", pptxData.widthEmu);
|
|
113
|
+
}
|
|
114
|
+
if (pptxData.heightEmu !== void 0) {
|
|
115
|
+
meta.set("heightEmu", pptxData.heightEmu);
|
|
116
|
+
}
|
|
117
|
+
const sourceBytesArr = new yjs.Array();
|
|
118
|
+
sourceBytesArr.insert(0, Array.from(bytes));
|
|
119
|
+
meta.set("sourceBytes", sourceBytesArr);
|
|
120
|
+
const slidesArray = ydoc.getArray("pptx:slides");
|
|
121
|
+
if (slidesArray.length > 0) {
|
|
122
|
+
slidesArray.delete(0, slidesArray.length);
|
|
123
|
+
}
|
|
124
|
+
for (const slide of pptxData.slides) {
|
|
125
|
+
const slideMap = this._slideToYMap(slide);
|
|
126
|
+
slidesArray.push([slideMap]);
|
|
127
|
+
}
|
|
128
|
+
}, effectiveOrigin);
|
|
129
|
+
}
|
|
130
|
+
async dehydrate(ydoc, _dirtyPaths) {
|
|
131
|
+
const meta = ydoc.getMap("pptx:meta");
|
|
132
|
+
const slidesArray = ydoc.getArray("pptx:slides");
|
|
133
|
+
const slides = [];
|
|
134
|
+
for (let i = 0; i < slidesArray.length; i++) {
|
|
135
|
+
const slideMap = slidesArray.get(i);
|
|
136
|
+
slides.push(this._yMapToSlide(slideMap));
|
|
137
|
+
}
|
|
138
|
+
const sourceBytesArr = meta.get("sourceBytes");
|
|
139
|
+
try {
|
|
140
|
+
if (!sourceBytesArr || sourceBytesArr.length === 0) {
|
|
141
|
+
throw new Error("No source bytes for PptxHandler");
|
|
142
|
+
}
|
|
143
|
+
const sourceBytes = new Uint8Array(sourceBytesArr.toArray());
|
|
144
|
+
const handler = new pptxViewerCore.PptxHandler();
|
|
145
|
+
await handler.load(sourceBytes.buffer);
|
|
146
|
+
return await handler.save(slides);
|
|
147
|
+
} catch {
|
|
148
|
+
if (sourceBytesArr && sourceBytesArr.length > 0) {
|
|
149
|
+
return new Uint8Array(sourceBytesArr.toArray());
|
|
150
|
+
}
|
|
151
|
+
throw new Error("No PPTX bytes available for dehydration");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
observe(ydoc, onChange) {
|
|
155
|
+
const slidesArray = ydoc.getArray("pptx:slides");
|
|
156
|
+
const meta = ydoc.getMap("pptx:meta");
|
|
157
|
+
const handler = () => onChange();
|
|
158
|
+
slidesArray.observeDeep(handler);
|
|
159
|
+
meta.observeDeep(handler);
|
|
160
|
+
return () => {
|
|
161
|
+
slidesArray.unobserveDeep(handler);
|
|
162
|
+
meta.unobserveDeep(handler);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
_slideToYMap(slide) {
|
|
166
|
+
const slideMap = new yjs.Map();
|
|
167
|
+
const slideRecord = slide;
|
|
168
|
+
for (const key of SCALAR_SLIDE_KEYS) {
|
|
169
|
+
const value = slideRecord[key];
|
|
170
|
+
if (value !== void 0) {
|
|
171
|
+
slideMap.set(key, value);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const [original, prefixed] of Object.entries(COMPLEX_SLIDE_FIELD_MAP)) {
|
|
175
|
+
const value = slideRecord[original];
|
|
176
|
+
if (value !== void 0) {
|
|
177
|
+
slideMap.set(prefixed, JSON.stringify(value));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const elementsArray = new yjs.Array();
|
|
181
|
+
for (const element of slide.elements) {
|
|
182
|
+
elementsArray.push([this._elementToYMap(element)]);
|
|
183
|
+
}
|
|
184
|
+
slideMap.set("elements", elementsArray);
|
|
185
|
+
return slideMap;
|
|
186
|
+
}
|
|
187
|
+
_yMapToSlide(slideMap) {
|
|
188
|
+
const slide = {};
|
|
189
|
+
for (const key of SCALAR_SLIDE_KEYS) {
|
|
190
|
+
const value = slideMap.get(key);
|
|
191
|
+
if (value !== void 0) {
|
|
192
|
+
slide[key] = value;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const [prefixed, original] of Object.entries(REVERSE_COMPLEX_SLIDE_MAP)) {
|
|
196
|
+
const value = slideMap.get(prefixed);
|
|
197
|
+
if (value !== void 0) {
|
|
198
|
+
slide[original] = JSON.parse(value);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const elementsArray = slideMap.get("elements");
|
|
202
|
+
const elements = [];
|
|
203
|
+
if (elementsArray) {
|
|
204
|
+
for (let i = 0; i < elementsArray.length; i++) {
|
|
205
|
+
const elemMap = elementsArray.get(i);
|
|
206
|
+
elements.push(this._yMapToElement(elemMap));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
slide["elements"] = elements;
|
|
210
|
+
return slide;
|
|
211
|
+
}
|
|
212
|
+
_elementToYMap(element) {
|
|
213
|
+
const elemMap = new yjs.Map();
|
|
214
|
+
const record = element;
|
|
215
|
+
for (const [key, value] of Object.entries(record)) {
|
|
216
|
+
if (value === void 0) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (SCALAR_ELEMENT_KEYS.has(key)) {
|
|
220
|
+
elemMap.set(key, value);
|
|
221
|
+
} else if (COMPLEX_FIELD_MAP[key]) {
|
|
222
|
+
elemMap.set(COMPLEX_FIELD_MAP[key], JSON.stringify(value));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return elemMap;
|
|
226
|
+
}
|
|
227
|
+
_yMapToElement(elemMap) {
|
|
228
|
+
const element = {};
|
|
229
|
+
elemMap.forEach((value, key) => {
|
|
230
|
+
if (REVERSE_COMPLEX_MAP[key]) {
|
|
231
|
+
element[REVERSE_COMPLEX_MAP[key]] = JSON.parse(value);
|
|
232
|
+
} else {
|
|
233
|
+
element[key] = value;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
return element;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
exports.ORIGIN_FILE_LOAD = ORIGIN_FILE_LOAD;
|
|
241
|
+
exports.PptxCodec = PptxCodec;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { PptxHandler } from 'pptx-viewer-core';
|
|
2
|
+
import { Array as Array$1, Map } from 'yjs';
|
|
3
|
+
|
|
4
|
+
// src/codec/pptx-codec.ts
|
|
5
|
+
var ORIGIN_FILE_LOAD = "file-load";
|
|
6
|
+
var SCALAR_ELEMENT_KEYS = /* @__PURE__ */ new Set([
|
|
7
|
+
"id",
|
|
8
|
+
"type",
|
|
9
|
+
"x",
|
|
10
|
+
"y",
|
|
11
|
+
"width",
|
|
12
|
+
"height",
|
|
13
|
+
"rotation",
|
|
14
|
+
"flipHorizontal",
|
|
15
|
+
"flipVertical",
|
|
16
|
+
"hidden",
|
|
17
|
+
"opacity",
|
|
18
|
+
"text",
|
|
19
|
+
"name",
|
|
20
|
+
"altText",
|
|
21
|
+
"shapeType",
|
|
22
|
+
"placeholder",
|
|
23
|
+
"imagePath",
|
|
24
|
+
"imageData",
|
|
25
|
+
"svgContent",
|
|
26
|
+
"inkSvg",
|
|
27
|
+
"sourceSlideId",
|
|
28
|
+
"mediaType",
|
|
29
|
+
"mediaPath",
|
|
30
|
+
"linkedTxbxId",
|
|
31
|
+
"linkedTxbxSeq",
|
|
32
|
+
"promptText"
|
|
33
|
+
]);
|
|
34
|
+
var COMPLEX_FIELD_MAP = {
|
|
35
|
+
textSegments: "_textSegments",
|
|
36
|
+
textStyle: "_textStyle",
|
|
37
|
+
shapeStyle: "_shapeStyle",
|
|
38
|
+
shapeAdjustments: "_shapeAdjustments",
|
|
39
|
+
adjustmentHandles: "_adjustmentHandles",
|
|
40
|
+
tableData: "_tableData",
|
|
41
|
+
chartData: "_chartData",
|
|
42
|
+
smartArtData: "_smartArtData",
|
|
43
|
+
connectionStart: "_connectionStart",
|
|
44
|
+
connectionEnd: "_connectionEnd",
|
|
45
|
+
animations: "_animations",
|
|
46
|
+
nativeAnimations: "_nativeAnimations",
|
|
47
|
+
children: "_children",
|
|
48
|
+
paragraphIndents: "_paragraphIndents",
|
|
49
|
+
rawXml: "_rawXml",
|
|
50
|
+
actionClick: "_actionClick",
|
|
51
|
+
actionHover: "_actionHover",
|
|
52
|
+
locks: "_locks",
|
|
53
|
+
imageEffects: "_imageEffects",
|
|
54
|
+
cropShape: "_cropShape",
|
|
55
|
+
mediaBookmarks: "_mediaBookmarks",
|
|
56
|
+
captionTracks: "_captionTracks"
|
|
57
|
+
};
|
|
58
|
+
var REVERSE_COMPLEX_MAP = {};
|
|
59
|
+
for (const [original, prefixed] of Object.entries(COMPLEX_FIELD_MAP)) {
|
|
60
|
+
REVERSE_COMPLEX_MAP[prefixed] = original;
|
|
61
|
+
}
|
|
62
|
+
var SCALAR_SLIDE_KEYS = /* @__PURE__ */ new Set([
|
|
63
|
+
"id",
|
|
64
|
+
"rId",
|
|
65
|
+
"sourceSlideId",
|
|
66
|
+
"layoutPath",
|
|
67
|
+
"layoutName",
|
|
68
|
+
"slideNumber",
|
|
69
|
+
"hidden",
|
|
70
|
+
"sectionName",
|
|
71
|
+
"sectionId",
|
|
72
|
+
"backgroundColor",
|
|
73
|
+
"backgroundImage",
|
|
74
|
+
"backgroundGradient",
|
|
75
|
+
"notes",
|
|
76
|
+
"backgroundShowAnimation",
|
|
77
|
+
"showMasterShapes",
|
|
78
|
+
"isDirty"
|
|
79
|
+
]);
|
|
80
|
+
var COMPLEX_SLIDE_FIELD_MAP = {
|
|
81
|
+
transition: "_transition",
|
|
82
|
+
animations: "_animations",
|
|
83
|
+
nativeAnimations: "_nativeAnimations",
|
|
84
|
+
rawTiming: "_rawTiming",
|
|
85
|
+
notesSegments: "_notesSegments",
|
|
86
|
+
comments: "_comments",
|
|
87
|
+
warnings: "_warnings",
|
|
88
|
+
rawXml: "_rawXml",
|
|
89
|
+
clrMapOverride: "_clrMapOverride",
|
|
90
|
+
guides: "_guides",
|
|
91
|
+
customerData: "_customerData",
|
|
92
|
+
activeXControls: "_activeXControls"
|
|
93
|
+
};
|
|
94
|
+
var REVERSE_COMPLEX_SLIDE_MAP = {};
|
|
95
|
+
for (const [original, prefixed] of Object.entries(COMPLEX_SLIDE_FIELD_MAP)) {
|
|
96
|
+
REVERSE_COMPLEX_SLIDE_MAP[prefixed] = original;
|
|
97
|
+
}
|
|
98
|
+
var PptxCodec = class {
|
|
99
|
+
formatId = "pptx";
|
|
100
|
+
extensions = [".pptx", ".ppt"];
|
|
101
|
+
async hydrate(ydoc, bytes, origin) {
|
|
102
|
+
const effectiveOrigin = origin ?? ORIGIN_FILE_LOAD;
|
|
103
|
+
const handler = new PptxHandler();
|
|
104
|
+
const pptxData = await handler.load(bytes.buffer);
|
|
105
|
+
ydoc.transact(() => {
|
|
106
|
+
const meta = ydoc.getMap("pptx:meta");
|
|
107
|
+
meta.set("width", pptxData.width);
|
|
108
|
+
meta.set("height", pptxData.height);
|
|
109
|
+
if (pptxData.widthEmu !== void 0) {
|
|
110
|
+
meta.set("widthEmu", pptxData.widthEmu);
|
|
111
|
+
}
|
|
112
|
+
if (pptxData.heightEmu !== void 0) {
|
|
113
|
+
meta.set("heightEmu", pptxData.heightEmu);
|
|
114
|
+
}
|
|
115
|
+
const sourceBytesArr = new Array$1();
|
|
116
|
+
sourceBytesArr.insert(0, Array.from(bytes));
|
|
117
|
+
meta.set("sourceBytes", sourceBytesArr);
|
|
118
|
+
const slidesArray = ydoc.getArray("pptx:slides");
|
|
119
|
+
if (slidesArray.length > 0) {
|
|
120
|
+
slidesArray.delete(0, slidesArray.length);
|
|
121
|
+
}
|
|
122
|
+
for (const slide of pptxData.slides) {
|
|
123
|
+
const slideMap = this._slideToYMap(slide);
|
|
124
|
+
slidesArray.push([slideMap]);
|
|
125
|
+
}
|
|
126
|
+
}, effectiveOrigin);
|
|
127
|
+
}
|
|
128
|
+
async dehydrate(ydoc, _dirtyPaths) {
|
|
129
|
+
const meta = ydoc.getMap("pptx:meta");
|
|
130
|
+
const slidesArray = ydoc.getArray("pptx:slides");
|
|
131
|
+
const slides = [];
|
|
132
|
+
for (let i = 0; i < slidesArray.length; i++) {
|
|
133
|
+
const slideMap = slidesArray.get(i);
|
|
134
|
+
slides.push(this._yMapToSlide(slideMap));
|
|
135
|
+
}
|
|
136
|
+
const sourceBytesArr = meta.get("sourceBytes");
|
|
137
|
+
try {
|
|
138
|
+
if (!sourceBytesArr || sourceBytesArr.length === 0) {
|
|
139
|
+
throw new Error("No source bytes for PptxHandler");
|
|
140
|
+
}
|
|
141
|
+
const sourceBytes = new Uint8Array(sourceBytesArr.toArray());
|
|
142
|
+
const handler = new PptxHandler();
|
|
143
|
+
await handler.load(sourceBytes.buffer);
|
|
144
|
+
return await handler.save(slides);
|
|
145
|
+
} catch {
|
|
146
|
+
if (sourceBytesArr && sourceBytesArr.length > 0) {
|
|
147
|
+
return new Uint8Array(sourceBytesArr.toArray());
|
|
148
|
+
}
|
|
149
|
+
throw new Error("No PPTX bytes available for dehydration");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
observe(ydoc, onChange) {
|
|
153
|
+
const slidesArray = ydoc.getArray("pptx:slides");
|
|
154
|
+
const meta = ydoc.getMap("pptx:meta");
|
|
155
|
+
const handler = () => onChange();
|
|
156
|
+
slidesArray.observeDeep(handler);
|
|
157
|
+
meta.observeDeep(handler);
|
|
158
|
+
return () => {
|
|
159
|
+
slidesArray.unobserveDeep(handler);
|
|
160
|
+
meta.unobserveDeep(handler);
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
_slideToYMap(slide) {
|
|
164
|
+
const slideMap = new Map();
|
|
165
|
+
const slideRecord = slide;
|
|
166
|
+
for (const key of SCALAR_SLIDE_KEYS) {
|
|
167
|
+
const value = slideRecord[key];
|
|
168
|
+
if (value !== void 0) {
|
|
169
|
+
slideMap.set(key, value);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const [original, prefixed] of Object.entries(COMPLEX_SLIDE_FIELD_MAP)) {
|
|
173
|
+
const value = slideRecord[original];
|
|
174
|
+
if (value !== void 0) {
|
|
175
|
+
slideMap.set(prefixed, JSON.stringify(value));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const elementsArray = new Array$1();
|
|
179
|
+
for (const element of slide.elements) {
|
|
180
|
+
elementsArray.push([this._elementToYMap(element)]);
|
|
181
|
+
}
|
|
182
|
+
slideMap.set("elements", elementsArray);
|
|
183
|
+
return slideMap;
|
|
184
|
+
}
|
|
185
|
+
_yMapToSlide(slideMap) {
|
|
186
|
+
const slide = {};
|
|
187
|
+
for (const key of SCALAR_SLIDE_KEYS) {
|
|
188
|
+
const value = slideMap.get(key);
|
|
189
|
+
if (value !== void 0) {
|
|
190
|
+
slide[key] = value;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const [prefixed, original] of Object.entries(REVERSE_COMPLEX_SLIDE_MAP)) {
|
|
194
|
+
const value = slideMap.get(prefixed);
|
|
195
|
+
if (value !== void 0) {
|
|
196
|
+
slide[original] = JSON.parse(value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const elementsArray = slideMap.get("elements");
|
|
200
|
+
const elements = [];
|
|
201
|
+
if (elementsArray) {
|
|
202
|
+
for (let i = 0; i < elementsArray.length; i++) {
|
|
203
|
+
const elemMap = elementsArray.get(i);
|
|
204
|
+
elements.push(this._yMapToElement(elemMap));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
slide["elements"] = elements;
|
|
208
|
+
return slide;
|
|
209
|
+
}
|
|
210
|
+
_elementToYMap(element) {
|
|
211
|
+
const elemMap = new Map();
|
|
212
|
+
const record = element;
|
|
213
|
+
for (const [key, value] of Object.entries(record)) {
|
|
214
|
+
if (value === void 0) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (SCALAR_ELEMENT_KEYS.has(key)) {
|
|
218
|
+
elemMap.set(key, value);
|
|
219
|
+
} else if (COMPLEX_FIELD_MAP[key]) {
|
|
220
|
+
elemMap.set(COMPLEX_FIELD_MAP[key], JSON.stringify(value));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return elemMap;
|
|
224
|
+
}
|
|
225
|
+
_yMapToElement(elemMap) {
|
|
226
|
+
const element = {};
|
|
227
|
+
elemMap.forEach((value, key) => {
|
|
228
|
+
if (REVERSE_COMPLEX_MAP[key]) {
|
|
229
|
+
element[REVERSE_COMPLEX_MAP[key]] = JSON.parse(value);
|
|
230
|
+
} else {
|
|
231
|
+
element[key] = value;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
return element;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export { ORIGIN_FILE_LOAD, PptxCodec };
|