pptx-angular-viewer 1.1.42 → 1.1.44
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.
|
@@ -16519,6 +16519,60 @@ class EditorHistory {
|
|
|
16519
16519
|
}
|
|
16520
16520
|
}
|
|
16521
16521
|
|
|
16522
|
+
/**
|
|
16523
|
+
* ole-actions.ts - framework-agnostic helpers for the OLE download/open UI.
|
|
16524
|
+
*
|
|
16525
|
+
* Shared by the binding OLE renderers to format the recovered embedded payload
|
|
16526
|
+
* size and decide whether its MIME type can be opened directly in a browser tab
|
|
16527
|
+
* (vs download-only). Pure functions, no framework or DOM dependencies.
|
|
16528
|
+
*
|
|
16529
|
+
* @module shared/render/ole-actions
|
|
16530
|
+
*/
|
|
16531
|
+
const BYTE_UNITS = ['bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
16532
|
+
/**
|
|
16533
|
+
* Human-readable binary file size (1024-based), e.g. `1 byte`, `0 bytes`,
|
|
16534
|
+
* `12 MB`. Returns `undefined` for missing, negative, or non-finite input so
|
|
16535
|
+
* callers can simply omit the size when it is unknown.
|
|
16536
|
+
*/
|
|
16537
|
+
function formatBytes$1(bytes) {
|
|
16538
|
+
if (bytes === undefined || !Number.isFinite(bytes) || bytes < 0) {
|
|
16539
|
+
return undefined;
|
|
16540
|
+
}
|
|
16541
|
+
if (bytes === 1) {
|
|
16542
|
+
return '1 byte';
|
|
16543
|
+
}
|
|
16544
|
+
if (bytes < 1024) {
|
|
16545
|
+
return `${bytes} bytes`;
|
|
16546
|
+
}
|
|
16547
|
+
let value = bytes;
|
|
16548
|
+
let unit = 0;
|
|
16549
|
+
while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
|
|
16550
|
+
value /= 1024;
|
|
16551
|
+
unit++;
|
|
16552
|
+
}
|
|
16553
|
+
// One decimal place, trimming a trailing `.0`.
|
|
16554
|
+
const rounded = Math.round(value * 10) / 10;
|
|
16555
|
+
const text = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
|
|
16556
|
+
return `${text} ${BYTE_UNITS[unit]}`;
|
|
16557
|
+
}
|
|
16558
|
+
/**
|
|
16559
|
+
* Whether a payload of the given MIME type can be opened directly in a browser
|
|
16560
|
+
* tab (PDF, images, plain text, JSON, XML). Everything else is download-only.
|
|
16561
|
+
*/
|
|
16562
|
+
function isBrowserOpenableMime$1(mime) {
|
|
16563
|
+
if (!mime) {
|
|
16564
|
+
return false;
|
|
16565
|
+
}
|
|
16566
|
+
const value = mime.trim().toLowerCase();
|
|
16567
|
+
if (value === 'application/pdf') {
|
|
16568
|
+
return true;
|
|
16569
|
+
}
|
|
16570
|
+
if (value.startsWith('image/') || value.startsWith('text/')) {
|
|
16571
|
+
return true;
|
|
16572
|
+
}
|
|
16573
|
+
return value === 'application/json' || value === 'application/xml';
|
|
16574
|
+
}
|
|
16575
|
+
|
|
16522
16576
|
/**
|
|
16523
16577
|
* Pure snap-and-alignment-guide geometry shared by the React, Vue, and Angular
|
|
16524
16578
|
* editor bindings. No framework imports, no DOM, no side effects — only data
|
|
@@ -23966,6 +24020,516 @@ function buildSmartArt3DModel(layout, options = {}) {
|
|
|
23966
24020
|
return options.spatial ? applySpatialLayout(model) : model;
|
|
23967
24021
|
}
|
|
23968
24022
|
|
|
24023
|
+
/**
|
|
24024
|
+
* Vanilla three.js GLTF/GLB model scene controller (framework-agnostic).
|
|
24025
|
+
*
|
|
24026
|
+
* Mounts an interactive 3D model into a caller-provided container element:
|
|
24027
|
+
* dynamically imports `three` plus its `GLTFLoader` and `OrbitControls` addons,
|
|
24028
|
+
* builds a scene (perspective camera, lights), auto-centres and fits the model
|
|
24029
|
+
* to the view via a `Box3`, renders with a RAF loop, and exposes `resize()` /
|
|
24030
|
+
* `dispose()` for deterministic teardown of GPU resources.
|
|
24031
|
+
*
|
|
24032
|
+
* `three` is an OPTIONAL peer dependency: every import is dynamic and guarded.
|
|
24033
|
+
* When `three` is not installed, {@link mountModel3D} resolves to a sentinel
|
|
24034
|
+
* handle ({@link THREE_UNAVAILABLE}) so the caller can fall back to a poster.
|
|
24035
|
+
* No framework imports - the React, Vue, and Angular bindings can all mount it.
|
|
24036
|
+
*/
|
|
24037
|
+
/**
|
|
24038
|
+
* Sentinel handle returned when `three` is unavailable or the model fails to
|
|
24039
|
+
* load. All methods are no-ops; `ok` is `false` so callers can show a poster.
|
|
24040
|
+
*/
|
|
24041
|
+
const THREE_UNAVAILABLE = {
|
|
24042
|
+
ok: false,
|
|
24043
|
+
resize: () => { },
|
|
24044
|
+
setInteractive: () => { },
|
|
24045
|
+
dispose: () => { },
|
|
24046
|
+
};
|
|
24047
|
+
const FOV$1 = 50;
|
|
24048
|
+
/** Dynamically load `three`; returns `null` when the package is not installed. */
|
|
24049
|
+
async function loadThree$1() {
|
|
24050
|
+
try {
|
|
24051
|
+
return (await import('three'));
|
|
24052
|
+
}
|
|
24053
|
+
catch {
|
|
24054
|
+
return null;
|
|
24055
|
+
}
|
|
24056
|
+
}
|
|
24057
|
+
/** Dynamically load the GLTFLoader addon; returns `null` when unavailable. */
|
|
24058
|
+
async function loadGltfLoaderCtor() {
|
|
24059
|
+
try {
|
|
24060
|
+
const mod = await import('three/examples/jsm/loaders/GLTFLoader.js');
|
|
24061
|
+
return mod.GLTFLoader;
|
|
24062
|
+
}
|
|
24063
|
+
catch {
|
|
24064
|
+
return null;
|
|
24065
|
+
}
|
|
24066
|
+
}
|
|
24067
|
+
/** Dynamically load the OrbitControls addon; returns `null` when unavailable. */
|
|
24068
|
+
async function loadOrbitControlsCtor$1() {
|
|
24069
|
+
try {
|
|
24070
|
+
const mod = await import('three/examples/jsm/controls/OrbitControls.js');
|
|
24071
|
+
return mod.OrbitControls;
|
|
24072
|
+
}
|
|
24073
|
+
catch {
|
|
24074
|
+
return null;
|
|
24075
|
+
}
|
|
24076
|
+
}
|
|
24077
|
+
/** Promisified `GLTFLoader.load`. */
|
|
24078
|
+
function loadGltf(loader, url) {
|
|
24079
|
+
return new Promise((resolve, reject) => {
|
|
24080
|
+
loader.load(url, resolve, undefined, reject);
|
|
24081
|
+
});
|
|
24082
|
+
}
|
|
24083
|
+
/**
|
|
24084
|
+
* Auto-centre the model at the origin and uniformly scale it so its largest
|
|
24085
|
+
* dimension fills a 2-unit cube, mirroring the previous react-three-fiber fit.
|
|
24086
|
+
*/
|
|
24087
|
+
function centerAndFit(three, root) {
|
|
24088
|
+
const box = new three.Box3().setFromObject(root);
|
|
24089
|
+
const size = new three.Vector3();
|
|
24090
|
+
box.getSize(size);
|
|
24091
|
+
const maxDim = Math.max(size.x, size.y, size.z);
|
|
24092
|
+
if (maxDim > 0) {
|
|
24093
|
+
root.scale.setScalar(2 / maxDim);
|
|
24094
|
+
}
|
|
24095
|
+
const center = new three.Vector3();
|
|
24096
|
+
box.getCenter(center);
|
|
24097
|
+
root.position.sub(center.multiplyScalar(root.scale.x));
|
|
24098
|
+
}
|
|
24099
|
+
/**
|
|
24100
|
+
* Mount an interactive GLTF/GLB model into `container` and start rendering.
|
|
24101
|
+
*
|
|
24102
|
+
* Resolves to {@link THREE_UNAVAILABLE} (a no-op handle with `ok === false`)
|
|
24103
|
+
* when `three` or its addons cannot be loaded, or when the model fails to parse.
|
|
24104
|
+
*/
|
|
24105
|
+
async function mountModel3D(container, modelUrl, options) {
|
|
24106
|
+
const three = await loadThree$1();
|
|
24107
|
+
const GltfLoaderCtor = three ? await loadGltfLoaderCtor() : null;
|
|
24108
|
+
if (!three || !GltfLoaderCtor) {
|
|
24109
|
+
return THREE_UNAVAILABLE;
|
|
24110
|
+
}
|
|
24111
|
+
const width = Math.max(1, options.width);
|
|
24112
|
+
const height = Math.max(1, options.height);
|
|
24113
|
+
const renderer = new three.WebGLRenderer({ antialias: true, alpha: !options.background });
|
|
24114
|
+
renderer.setPixelRatio(Math.min(typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1, options.maxPixelRatio ?? 2));
|
|
24115
|
+
renderer.setSize(width, height, false);
|
|
24116
|
+
const canvas = renderer.domElement;
|
|
24117
|
+
canvas.style.width = `${width}px`;
|
|
24118
|
+
canvas.style.height = `${height}px`;
|
|
24119
|
+
canvas.style.display = 'block';
|
|
24120
|
+
canvas.style.willChange = 'transform';
|
|
24121
|
+
container.appendChild(canvas);
|
|
24122
|
+
const scene = new three.Scene();
|
|
24123
|
+
if (options.background) {
|
|
24124
|
+
scene.background = new three.Color(options.background);
|
|
24125
|
+
}
|
|
24126
|
+
const camera = new three.PerspectiveCamera(FOV$1, width / height, 0.1, 1000);
|
|
24127
|
+
camera.position.set(0, 0, 5);
|
|
24128
|
+
camera.lookAt(0, 0, 0);
|
|
24129
|
+
scene.add(new three.AmbientLight(0xffffff, 0.5));
|
|
24130
|
+
const key = new three.DirectionalLight(0xffffff, 1);
|
|
24131
|
+
key.position.set(5, 5, 5);
|
|
24132
|
+
scene.add(key);
|
|
24133
|
+
const fill = new three.DirectionalLight(0xffffff, 0.3);
|
|
24134
|
+
fill.position.set(-3, -3, 2);
|
|
24135
|
+
scene.add(fill);
|
|
24136
|
+
let gltf;
|
|
24137
|
+
try {
|
|
24138
|
+
gltf = await loadGltf(new GltfLoaderCtor(), modelUrl);
|
|
24139
|
+
}
|
|
24140
|
+
catch {
|
|
24141
|
+
renderer.dispose();
|
|
24142
|
+
canvas.remove();
|
|
24143
|
+
return THREE_UNAVAILABLE;
|
|
24144
|
+
}
|
|
24145
|
+
const model = gltf.scene;
|
|
24146
|
+
centerAndFit(three, model);
|
|
24147
|
+
scene.add(model);
|
|
24148
|
+
let controls = null;
|
|
24149
|
+
const OrbitCtrlCtor = await loadOrbitControlsCtor$1();
|
|
24150
|
+
const enableControls = (on) => {
|
|
24151
|
+
if (on && !controls && OrbitCtrlCtor) {
|
|
24152
|
+
controls = new OrbitCtrlCtor(camera, canvas);
|
|
24153
|
+
controls.enablePan = false;
|
|
24154
|
+
controls.enableZoom = true;
|
|
24155
|
+
controls.enableRotate = true;
|
|
24156
|
+
controls.minDistance = 2;
|
|
24157
|
+
controls.maxDistance = 20;
|
|
24158
|
+
controls.update();
|
|
24159
|
+
}
|
|
24160
|
+
else if (!on && controls) {
|
|
24161
|
+
controls.dispose();
|
|
24162
|
+
controls = null;
|
|
24163
|
+
}
|
|
24164
|
+
};
|
|
24165
|
+
enableControls(options.interactive ?? true);
|
|
24166
|
+
let frame = 0;
|
|
24167
|
+
let disposed = false;
|
|
24168
|
+
const renderLoop = () => {
|
|
24169
|
+
if (disposed) {
|
|
24170
|
+
return;
|
|
24171
|
+
}
|
|
24172
|
+
frame = requestAnimationFrame(renderLoop);
|
|
24173
|
+
controls?.update();
|
|
24174
|
+
renderer.render(scene, camera);
|
|
24175
|
+
};
|
|
24176
|
+
frame = requestAnimationFrame(renderLoop);
|
|
24177
|
+
/** Recursively dispose every geometry/material/texture under the model. */
|
|
24178
|
+
const disposeModel = () => {
|
|
24179
|
+
model.traverse((obj) => {
|
|
24180
|
+
const mesh = obj;
|
|
24181
|
+
mesh.geometry?.dispose();
|
|
24182
|
+
const material = mesh.material;
|
|
24183
|
+
if (Array.isArray(material)) {
|
|
24184
|
+
for (const m of material) {
|
|
24185
|
+
m.dispose();
|
|
24186
|
+
}
|
|
24187
|
+
}
|
|
24188
|
+
else {
|
|
24189
|
+
material?.dispose();
|
|
24190
|
+
}
|
|
24191
|
+
});
|
|
24192
|
+
};
|
|
24193
|
+
return {
|
|
24194
|
+
ok: true,
|
|
24195
|
+
resize(w, h) {
|
|
24196
|
+
const nextW = Math.max(1, w);
|
|
24197
|
+
const nextH = Math.max(1, h);
|
|
24198
|
+
camera.aspect = nextW / nextH;
|
|
24199
|
+
camera.updateProjectionMatrix();
|
|
24200
|
+
renderer.setSize(nextW, nextH, false);
|
|
24201
|
+
canvas.style.width = `${nextW}px`;
|
|
24202
|
+
canvas.style.height = `${nextH}px`;
|
|
24203
|
+
},
|
|
24204
|
+
setInteractive(on) {
|
|
24205
|
+
enableControls(on);
|
|
24206
|
+
},
|
|
24207
|
+
dispose() {
|
|
24208
|
+
if (disposed) {
|
|
24209
|
+
return;
|
|
24210
|
+
}
|
|
24211
|
+
disposed = true;
|
|
24212
|
+
cancelAnimationFrame(frame);
|
|
24213
|
+
controls?.dispose();
|
|
24214
|
+
disposeModel();
|
|
24215
|
+
scene.clear();
|
|
24216
|
+
renderer.dispose();
|
|
24217
|
+
canvas.remove();
|
|
24218
|
+
},
|
|
24219
|
+
};
|
|
24220
|
+
}
|
|
24221
|
+
|
|
24222
|
+
/**
|
|
24223
|
+
* Pure surface-chart 3D geometry helpers (framework- and three-agnostic).
|
|
24224
|
+
*
|
|
24225
|
+
* The vanilla three scene controller ({@link mountSurfaceChart3D}) feeds these
|
|
24226
|
+
* the loaded `three` module so the heavy library stays a dynamic, optional
|
|
24227
|
+
* import while the grid maths (plane subdivision -> height/colour displacement,
|
|
24228
|
+
* world-space label anchors) lives here as small, separately testable units.
|
|
24229
|
+
*/
|
|
24230
|
+
/** Half-unit spacing between adjacent grid points, in world units. */
|
|
24231
|
+
const GRID_SPACING = 0.5;
|
|
24232
|
+
/** Maximum height displacement (world units) for a normalised value of 1. */
|
|
24233
|
+
const MAX_HEIGHT = 1.5;
|
|
24234
|
+
/** Compute the world-space width/depth of the data grid. */
|
|
24235
|
+
function computeGridExtent(cols, rows) {
|
|
24236
|
+
return {
|
|
24237
|
+
gridWidth: Math.max(cols - 1, 1) * GRID_SPACING,
|
|
24238
|
+
gridDepth: Math.max(rows - 1, 1) * GRID_SPACING,
|
|
24239
|
+
};
|
|
24240
|
+
}
|
|
24241
|
+
/**
|
|
24242
|
+
* Build the surface mesh + wireframe geometries from the normalised height and
|
|
24243
|
+
* colour maps. The plane is subdivided to match the data grid, rotated into the
|
|
24244
|
+
* XZ plane, then each vertex is displaced in Y by its height and tinted by its
|
|
24245
|
+
* colour. Returns disposable geometries the caller adds to the scene.
|
|
24246
|
+
*/
|
|
24247
|
+
function buildSurfaceGeometry(three, cols, rows, heightMap, colorMap) {
|
|
24248
|
+
const widthSegs = Math.max(cols - 1, 0);
|
|
24249
|
+
const depthSegs = Math.max(rows - 1, 0);
|
|
24250
|
+
const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
|
|
24251
|
+
const geo = new three.PlaneGeometry(gridWidth, gridDepth, widthSegs, depthSegs);
|
|
24252
|
+
// PlaneGeometry lies in the XY plane by default; rotate it flat into XZ.
|
|
24253
|
+
geo.rotateX(-Math.PI / 2);
|
|
24254
|
+
const pos = geo.attributes.position;
|
|
24255
|
+
const vertexCount = pos.count;
|
|
24256
|
+
const colors = new Float32Array(vertexCount * 3);
|
|
24257
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
24258
|
+
// After the rotation, the (widthSegs+1) x (depthSegs+1) vertices stay in
|
|
24259
|
+
// row-major order, so col = i % cols and row = floor(i / cols) map onto the
|
|
24260
|
+
// data grid directly.
|
|
24261
|
+
const row = Math.floor(i / cols);
|
|
24262
|
+
const col = i % cols;
|
|
24263
|
+
const idx = row * cols + col;
|
|
24264
|
+
const h = idx < heightMap.length ? heightMap[idx] : 0;
|
|
24265
|
+
pos.setY(i, h * MAX_HEIGHT);
|
|
24266
|
+
const ci = idx * 3;
|
|
24267
|
+
colors[i * 3] = ci < colorMap.length ? colorMap[ci] : 0.5;
|
|
24268
|
+
colors[i * 3 + 1] = ci + 1 < colorMap.length ? colorMap[ci + 1] : 0.5;
|
|
24269
|
+
colors[i * 3 + 2] = ci + 2 < colorMap.length ? colorMap[ci + 2] : 0.5;
|
|
24270
|
+
}
|
|
24271
|
+
geo.setAttribute('color', new three.BufferAttribute(colors, 3));
|
|
24272
|
+
geo.computeVertexNormals();
|
|
24273
|
+
pos.needsUpdate = true;
|
|
24274
|
+
const wireGeometry = new three.WireframeGeometry(geo);
|
|
24275
|
+
return { geometry: geo, wireGeometry };
|
|
24276
|
+
}
|
|
24277
|
+
/**
|
|
24278
|
+
* Build the axis labels (category along the front edge, series along the right
|
|
24279
|
+
* edge, plus a single "Value" Y-axis label) as world-anchored {@link
|
|
24280
|
+
* SurfaceLabel}s. Category/series labels are thinned to at most `maxCat`/`maxSer`
|
|
24281
|
+
* entries to avoid clutter, mirroring the prior drei `Html` layout.
|
|
24282
|
+
*/
|
|
24283
|
+
function buildSurfaceLabels(cols, rows, categoryLabels, seriesNames, maxCat = 8, maxSer = 6) {
|
|
24284
|
+
const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
|
|
24285
|
+
const labels = [];
|
|
24286
|
+
const catStep = Math.max(1, Math.ceil(categoryLabels.length / maxCat));
|
|
24287
|
+
for (let i = 0; i < categoryLabels.length; i += catStep) {
|
|
24288
|
+
const x = -gridWidth / 2 + (i / Math.max(cols - 1, 1)) * gridWidth;
|
|
24289
|
+
labels.push({
|
|
24290
|
+
key: `cat-${i}`,
|
|
24291
|
+
text: categoryLabels[i],
|
|
24292
|
+
anchor: [x, -0.15, gridDepth / 2 + 0.25],
|
|
24293
|
+
axis: 'category',
|
|
24294
|
+
});
|
|
24295
|
+
}
|
|
24296
|
+
const serStep = Math.max(1, Math.ceil(seriesNames.length / maxSer));
|
|
24297
|
+
for (let i = 0; i < seriesNames.length; i += serStep) {
|
|
24298
|
+
const z = -gridDepth / 2 + (i / Math.max(rows - 1, 1)) * gridDepth;
|
|
24299
|
+
labels.push({
|
|
24300
|
+
key: `ser-${i}`,
|
|
24301
|
+
text: seriesNames[i],
|
|
24302
|
+
anchor: [gridWidth / 2 + 0.3, -0.15, z],
|
|
24303
|
+
axis: 'series',
|
|
24304
|
+
});
|
|
24305
|
+
}
|
|
24306
|
+
labels.push({
|
|
24307
|
+
key: 'value-axis',
|
|
24308
|
+
text: 'Value',
|
|
24309
|
+
anchor: [-gridWidth / 2 - 0.35, 0.75, -gridDepth / 2],
|
|
24310
|
+
axis: 'value',
|
|
24311
|
+
});
|
|
24312
|
+
return labels;
|
|
24313
|
+
}
|
|
24314
|
+
/** Isometric-like camera placement that frames the whole grid. */
|
|
24315
|
+
function computeCameraPlacement(cols, rows) {
|
|
24316
|
+
const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
|
|
24317
|
+
const maxExtent = Math.max(gridWidth, gridDepth, MAX_HEIGHT);
|
|
24318
|
+
const dist = maxExtent * 1.8;
|
|
24319
|
+
return {
|
|
24320
|
+
position: [dist * 0.8, dist * 0.7, dist * 0.8],
|
|
24321
|
+
target: [0, 0.3, 0],
|
|
24322
|
+
};
|
|
24323
|
+
}
|
|
24324
|
+
|
|
24325
|
+
/**
|
|
24326
|
+
* Vanilla three.js 3D surface-chart scene controller (framework-agnostic).
|
|
24327
|
+
*
|
|
24328
|
+
* Mounts an interactive surface chart into a caller-provided container element:
|
|
24329
|
+
* dynamically imports `three` plus its `OrbitControls` addon, builds a colour-
|
|
24330
|
+
* displaced surface mesh (with optional wireframe), grid floor, lights, and an
|
|
24331
|
+
* isometric camera, renders with a RAF loop, and overlays axis labels as DOM
|
|
24332
|
+
* nodes that are re-projected to screen each frame. Exposes `dispose()` for
|
|
24333
|
+
* deterministic teardown of GPU resources, listeners, and overlay nodes.
|
|
24334
|
+
*
|
|
24335
|
+
* `three` is an OPTIONAL peer dependency: every import is dynamic and guarded.
|
|
24336
|
+
* When `three` (or its OrbitControls addon) is missing, {@link mountSurfaceChart3D}
|
|
24337
|
+
* resolves to a sentinel handle ({@link SURFACE_THREE_UNAVAILABLE}) so the caller
|
|
24338
|
+
* can fall back to the 2D renderer. No framework imports - React, Vue, and
|
|
24339
|
+
* Angular bindings can all mount it.
|
|
24340
|
+
*/
|
|
24341
|
+
/** No-op sentinel returned when `three` or its OrbitControls addon is missing. */
|
|
24342
|
+
const SURFACE_THREE_UNAVAILABLE = {
|
|
24343
|
+
ok: false,
|
|
24344
|
+
resize: () => { },
|
|
24345
|
+
dispose: () => { },
|
|
24346
|
+
};
|
|
24347
|
+
const FOV = 45;
|
|
24348
|
+
/** Dynamically load `three`; returns `null` when the package is not installed. */
|
|
24349
|
+
async function loadThree() {
|
|
24350
|
+
try {
|
|
24351
|
+
return (await import('three'));
|
|
24352
|
+
}
|
|
24353
|
+
catch {
|
|
24354
|
+
return null;
|
|
24355
|
+
}
|
|
24356
|
+
}
|
|
24357
|
+
/** Dynamically load the OrbitControls addon; returns `null` when unavailable. */
|
|
24358
|
+
async function loadOrbitControlsCtor() {
|
|
24359
|
+
try {
|
|
24360
|
+
const mod = await import('three/examples/jsm/controls/OrbitControls.js');
|
|
24361
|
+
return mod.OrbitControls;
|
|
24362
|
+
}
|
|
24363
|
+
catch {
|
|
24364
|
+
return null;
|
|
24365
|
+
}
|
|
24366
|
+
}
|
|
24367
|
+
/** Create the DOM overlay nodes for the axis labels, returned with the layer. */
|
|
24368
|
+
function createLabelOverlay(doc, labels) {
|
|
24369
|
+
const layer = doc.createElement('div');
|
|
24370
|
+
Object.assign(layer.style, {
|
|
24371
|
+
position: 'absolute',
|
|
24372
|
+
inset: '0',
|
|
24373
|
+
pointerEvents: 'none',
|
|
24374
|
+
overflow: 'hidden',
|
|
24375
|
+
});
|
|
24376
|
+
const nodes = labels.map((label) => {
|
|
24377
|
+
const node = doc.createElement('div');
|
|
24378
|
+
node.textContent = label.text;
|
|
24379
|
+
const color = label.axis === 'value' ? '#999' : '#666';
|
|
24380
|
+
Object.assign(node.style, {
|
|
24381
|
+
position: 'absolute',
|
|
24382
|
+
fontSize: '9px',
|
|
24383
|
+
color,
|
|
24384
|
+
whiteSpace: 'nowrap',
|
|
24385
|
+
userSelect: 'none',
|
|
24386
|
+
transform: 'translate(-50%, -50%)',
|
|
24387
|
+
willChange: 'left, top',
|
|
24388
|
+
});
|
|
24389
|
+
if (label.axis === 'value') {
|
|
24390
|
+
node.style.writingMode = 'vertical-rl';
|
|
24391
|
+
}
|
|
24392
|
+
layer.appendChild(node);
|
|
24393
|
+
return node;
|
|
24394
|
+
});
|
|
24395
|
+
return { layer, nodes };
|
|
24396
|
+
}
|
|
24397
|
+
/**
|
|
24398
|
+
* Mount an interactive 3D surface chart into `container` and start rendering.
|
|
24399
|
+
*
|
|
24400
|
+
* Resolves to {@link SURFACE_THREE_UNAVAILABLE} when `three` or its OrbitControls
|
|
24401
|
+
* addon cannot be loaded, so the caller can fall back to a 2D surface renderer.
|
|
24402
|
+
*/
|
|
24403
|
+
async function mountSurfaceChart3D(container, options) {
|
|
24404
|
+
const three = await loadThree();
|
|
24405
|
+
const OrbitCtrlCtor = three ? await loadOrbitControlsCtor() : null;
|
|
24406
|
+
if (!three || !OrbitCtrlCtor) {
|
|
24407
|
+
return SURFACE_THREE_UNAVAILABLE;
|
|
24408
|
+
}
|
|
24409
|
+
const { cols, rows } = options;
|
|
24410
|
+
let width = Math.max(1, options.width);
|
|
24411
|
+
let height = Math.max(1, options.height);
|
|
24412
|
+
const renderer = new three.WebGLRenderer({ antialias: true, alpha: true });
|
|
24413
|
+
renderer.setPixelRatio(Math.min(typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1, options.maxPixelRatio ?? 2));
|
|
24414
|
+
renderer.setSize(width, height, false);
|
|
24415
|
+
const canvas = renderer.domElement;
|
|
24416
|
+
canvas.style.width = `${width}px`;
|
|
24417
|
+
canvas.style.height = `${height}px`;
|
|
24418
|
+
canvas.style.display = 'block';
|
|
24419
|
+
canvas.style.willChange = 'transform';
|
|
24420
|
+
container.appendChild(canvas);
|
|
24421
|
+
const scene = new three.Scene();
|
|
24422
|
+
scene.add(new three.AmbientLight(0xffffff, 0.6));
|
|
24423
|
+
const key = new three.DirectionalLight(0xffffff, 0.8);
|
|
24424
|
+
key.position.set(5, 8, 5);
|
|
24425
|
+
scene.add(key);
|
|
24426
|
+
const fill = new three.DirectionalLight(0xffffff, 0.3);
|
|
24427
|
+
fill.position.set(-3, 4, -2);
|
|
24428
|
+
scene.add(fill);
|
|
24429
|
+
const camera = new three.PerspectiveCamera(FOV, width / height, 0.1, 1000);
|
|
24430
|
+
const placement = computeCameraPlacement(cols, rows);
|
|
24431
|
+
camera.position.set(...placement.position);
|
|
24432
|
+
const target = new three.Vector3(...placement.target);
|
|
24433
|
+
camera.lookAt(target);
|
|
24434
|
+
// Grid floor under the surface.
|
|
24435
|
+
const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
|
|
24436
|
+
const floorSize = Math.max(gridWidth, gridDepth) * 1.2;
|
|
24437
|
+
const gridFloor = new three.GridHelper(floorSize, Math.max(cols, rows), 0xcccccc, 0xe8e8e8);
|
|
24438
|
+
gridFloor.position.y = -0.02;
|
|
24439
|
+
scene.add(gridFloor);
|
|
24440
|
+
// Surface mesh + optional wireframe.
|
|
24441
|
+
const { geometry, wireGeometry } = buildSurfaceGeometry(three, cols, rows, options.heightMap, options.colorMap);
|
|
24442
|
+
const surfaceMaterial = new three.MeshPhongMaterial({
|
|
24443
|
+
vertexColors: true,
|
|
24444
|
+
side: three.DoubleSide,
|
|
24445
|
+
shininess: 30,
|
|
24446
|
+
transparent: true,
|
|
24447
|
+
opacity: 0.92,
|
|
24448
|
+
});
|
|
24449
|
+
const surfaceMesh = new three.Mesh(geometry, surfaceMaterial);
|
|
24450
|
+
scene.add(surfaceMesh);
|
|
24451
|
+
let wireMaterial = null;
|
|
24452
|
+
if (options.wireframe) {
|
|
24453
|
+
wireMaterial = new three.LineBasicMaterial({
|
|
24454
|
+
color: 0x333333,
|
|
24455
|
+
transparent: true,
|
|
24456
|
+
opacity: 0.25,
|
|
24457
|
+
});
|
|
24458
|
+
scene.add(new three.LineSegments(wireGeometry, wireMaterial));
|
|
24459
|
+
}
|
|
24460
|
+
const controls = new OrbitCtrlCtor(camera, canvas);
|
|
24461
|
+
controls.enablePan = true;
|
|
24462
|
+
controls.enableZoom = true;
|
|
24463
|
+
controls.enableRotate = true;
|
|
24464
|
+
controls.minDistance = 1;
|
|
24465
|
+
controls.maxDistance = 20;
|
|
24466
|
+
controls.maxPolarAngle = Math.PI / 2 + 0.3;
|
|
24467
|
+
controls.target.copy(target);
|
|
24468
|
+
controls.update();
|
|
24469
|
+
// Axis-label DOM overlay, re-projected to screen each frame.
|
|
24470
|
+
const doc = container.ownerDocument ?? document;
|
|
24471
|
+
const labels = buildSurfaceLabels(cols, rows, options.categoryLabels, options.seriesNames);
|
|
24472
|
+
const { layer, nodes } = createLabelOverlay(doc, labels);
|
|
24473
|
+
container.appendChild(layer);
|
|
24474
|
+
const anchors = labels.map((l) => new three.Vector3(...l.anchor));
|
|
24475
|
+
const projected = new three.Vector3();
|
|
24476
|
+
const updateLabels = () => {
|
|
24477
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
24478
|
+
projected.copy(anchors[i]).project(camera);
|
|
24479
|
+
const node = nodes[i];
|
|
24480
|
+
// Hide labels behind the camera.
|
|
24481
|
+
if (projected.z > 1) {
|
|
24482
|
+
node.style.display = 'none';
|
|
24483
|
+
continue;
|
|
24484
|
+
}
|
|
24485
|
+
node.style.display = '';
|
|
24486
|
+
node.style.left = `${((projected.x + 1) / 2) * width}px`;
|
|
24487
|
+
node.style.top = `${((-projected.y + 1) / 2) * height}px`;
|
|
24488
|
+
}
|
|
24489
|
+
};
|
|
24490
|
+
let frame = 0;
|
|
24491
|
+
let disposed = false;
|
|
24492
|
+
const renderLoop = () => {
|
|
24493
|
+
if (disposed) {
|
|
24494
|
+
return;
|
|
24495
|
+
}
|
|
24496
|
+
frame = requestAnimationFrame(renderLoop);
|
|
24497
|
+
controls.update();
|
|
24498
|
+
renderer.render(scene, camera);
|
|
24499
|
+
updateLabels();
|
|
24500
|
+
};
|
|
24501
|
+
frame = requestAnimationFrame(renderLoop);
|
|
24502
|
+
return {
|
|
24503
|
+
ok: true,
|
|
24504
|
+
resize(w, h) {
|
|
24505
|
+
width = Math.max(1, w);
|
|
24506
|
+
height = Math.max(1, h);
|
|
24507
|
+
camera.aspect = width / height;
|
|
24508
|
+
camera.updateProjectionMatrix();
|
|
24509
|
+
renderer.setSize(width, height, false);
|
|
24510
|
+
canvas.style.width = `${width}px`;
|
|
24511
|
+
canvas.style.height = `${height}px`;
|
|
24512
|
+
},
|
|
24513
|
+
dispose() {
|
|
24514
|
+
if (disposed) {
|
|
24515
|
+
return;
|
|
24516
|
+
}
|
|
24517
|
+
disposed = true;
|
|
24518
|
+
cancelAnimationFrame(frame);
|
|
24519
|
+
controls.dispose();
|
|
24520
|
+
geometry.dispose();
|
|
24521
|
+
wireGeometry.dispose();
|
|
24522
|
+
surfaceMaterial.dispose();
|
|
24523
|
+
wireMaterial?.dispose();
|
|
24524
|
+
gridFloor.dispose();
|
|
24525
|
+
scene.clear();
|
|
24526
|
+
renderer.dispose();
|
|
24527
|
+
canvas.remove();
|
|
24528
|
+
layer.remove();
|
|
24529
|
+
},
|
|
24530
|
+
};
|
|
24531
|
+
}
|
|
24532
|
+
|
|
23969
24533
|
/**
|
|
23970
24534
|
* smartart-drawing.ts: Drawing-shape view-model helpers for the SmartArt
|
|
23971
24535
|
* renderer, shared across the React, Vue, and Angular bindings.
|
|
@@ -44684,6 +45248,112 @@ function getPlaceholderStyle(type) {
|
|
|
44684
45248
|
'background-color': `${color}0d`,
|
|
44685
45249
|
};
|
|
44686
45250
|
}
|
|
45251
|
+
// ==========================================================================
|
|
45252
|
+
// Embedded-payload size + MIME helpers
|
|
45253
|
+
// ==========================================================================
|
|
45254
|
+
/**
|
|
45255
|
+
* Format a byte count as a short human-readable string (e.g. `"1.5 KB"`,
|
|
45256
|
+
* `"2.3 MB"`) using binary (1024) units. Returns `undefined` for missing,
|
|
45257
|
+
* non-finite, or negative input so callers can omit the size row entirely.
|
|
45258
|
+
*
|
|
45259
|
+
* Kept local to the Angular binding for now: the equivalent shared helper is
|
|
45260
|
+
* being churned by a parallel session, so this avoids a flaky cross-package
|
|
45261
|
+
* dependency. Extract to `pptx-viewer-shared` once that settles.
|
|
45262
|
+
*/
|
|
45263
|
+
function formatBytes(bytes) {
|
|
45264
|
+
if (bytes === undefined || !Number.isFinite(bytes) || bytes < 0) {
|
|
45265
|
+
return undefined;
|
|
45266
|
+
}
|
|
45267
|
+
if (bytes < 1024) {
|
|
45268
|
+
return bytes === 1 ? '1 byte' : `${Math.round(bytes)} bytes`;
|
|
45269
|
+
}
|
|
45270
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
45271
|
+
let value = bytes / 1024;
|
|
45272
|
+
let unitIndex = 0;
|
|
45273
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
45274
|
+
value /= 1024;
|
|
45275
|
+
unitIndex += 1;
|
|
45276
|
+
}
|
|
45277
|
+
const rounded = Math.round(value * 10) / 10;
|
|
45278
|
+
const text = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
|
|
45279
|
+
return `${text} ${units[unitIndex]}`;
|
|
45280
|
+
}
|
|
45281
|
+
/**
|
|
45282
|
+
* Whether a payload of the given MIME type can be opened (previewed) inline in
|
|
45283
|
+
* a new browser tab rather than only downloaded. PDFs, plain text / HTML / CSV /
|
|
45284
|
+
* JSON, and any `image/*` or `text/*` type qualify; binary office formats do
|
|
45285
|
+
* not. Matching is case-insensitive and ignores any `; charset=...` parameter.
|
|
45286
|
+
*/
|
|
45287
|
+
function isBrowserOpenableMime(mimeType) {
|
|
45288
|
+
if (!mimeType) {
|
|
45289
|
+
return false;
|
|
45290
|
+
}
|
|
45291
|
+
const normalized = mimeType.split(';', 1)[0]?.trim().toLowerCase() ?? '';
|
|
45292
|
+
if (normalized.length === 0) {
|
|
45293
|
+
return false;
|
|
45294
|
+
}
|
|
45295
|
+
if (normalized.startsWith('image/') || normalized.startsWith('text/')) {
|
|
45296
|
+
return true;
|
|
45297
|
+
}
|
|
45298
|
+
return (normalized === 'application/pdf' ||
|
|
45299
|
+
normalized === 'application/json' ||
|
|
45300
|
+
normalized === 'application/xml' ||
|
|
45301
|
+
normalized === 'application/xhtml+xml');
|
|
45302
|
+
}
|
|
45303
|
+
// ==========================================================================
|
|
45304
|
+
// Embedded-payload actions (Download / Open) + richer info
|
|
45305
|
+
// ==========================================================================
|
|
45306
|
+
/**
|
|
45307
|
+
* Resolve the file name to use for the download anchor's `download` attribute:
|
|
45308
|
+
* the recovered embedded name, then the authored file name, then a generic
|
|
45309
|
+
* fallback so the saved file is never nameless.
|
|
45310
|
+
*/
|
|
45311
|
+
function getOleDownloadFileName(el) {
|
|
45312
|
+
return el.oleEmbeddedFileName ?? el.fileName ?? 'embedded-object';
|
|
45313
|
+
}
|
|
45314
|
+
/**
|
|
45315
|
+
* Build the descriptive info rows shown in the OLE caption.
|
|
45316
|
+
*
|
|
45317
|
+
* Rows: object type label, original file name (`oleEmbeddedFileName ?? fileName`),
|
|
45318
|
+
* human-readable size (`oleEmbeddedByteSize`), and application (`oleProgId`).
|
|
45319
|
+
* A row is omitted when its value is unknown, so the result may contain only
|
|
45320
|
+
* the always-present type row.
|
|
45321
|
+
*/
|
|
45322
|
+
function buildOleInfoRows(el) {
|
|
45323
|
+
const rows = [
|
|
45324
|
+
{ key: 'type', label: 'Type', value: getOleTypeLabel(resolveOleType(el)) },
|
|
45325
|
+
];
|
|
45326
|
+
const fileName = el.oleEmbeddedFileName ?? el.fileName;
|
|
45327
|
+
if (fileName) {
|
|
45328
|
+
rows.push({ key: 'file', label: 'File', value: fileName });
|
|
45329
|
+
}
|
|
45330
|
+
const sizeLabel = formatBytes(el.oleEmbeddedByteSize);
|
|
45331
|
+
if (sizeLabel) {
|
|
45332
|
+
rows.push({ key: 'size', label: 'Size', value: sizeLabel });
|
|
45333
|
+
}
|
|
45334
|
+
if (el.oleProgId) {
|
|
45335
|
+
rows.push({ key: 'application', label: 'Application', value: el.oleProgId });
|
|
45336
|
+
}
|
|
45337
|
+
return rows;
|
|
45338
|
+
}
|
|
45339
|
+
/**
|
|
45340
|
+
* Build the full action model for an OLE element. Download is offered whenever a
|
|
45341
|
+
* non-empty embedded data-URL exists; Open is additionally offered when the
|
|
45342
|
+
* payload's MIME type is one a browser can render inline (PDF / image / text).
|
|
45343
|
+
*/
|
|
45344
|
+
function buildOleActionModel(el) {
|
|
45345
|
+
const downloadHref = el.oleEmbeddedData;
|
|
45346
|
+
const canDownload = typeof downloadHref === 'string' && downloadHref.length > 0;
|
|
45347
|
+
const canOpen = canDownload && isBrowserOpenableMime(el.oleEmbeddedMimeType);
|
|
45348
|
+
return {
|
|
45349
|
+
canDownload,
|
|
45350
|
+
canOpen,
|
|
45351
|
+
downloadHref,
|
|
45352
|
+
downloadFileName: getOleDownloadFileName(el),
|
|
45353
|
+
sizeLabel: formatBytes(el.oleEmbeddedByteSize),
|
|
45354
|
+
info: buildOleInfoRows(el),
|
|
45355
|
+
};
|
|
45356
|
+
}
|
|
44687
45357
|
|
|
44688
45358
|
/**
|
|
44689
45359
|
* OleRendererComponent: Angular port of the React `renderOleElement`
|
|
@@ -44751,6 +45421,39 @@ class OleRendererComponent {
|
|
|
44751
45421
|
/** Border + background style for the placeholder box. */
|
|
44752
45422
|
placeholderStyle = computed(() => getPlaceholderStyle(this.oleType()), /* @ts-ignore */
|
|
44753
45423
|
...(ngDevMode ? [{ debugName: "placeholderStyle" }] : /* istanbul ignore next */ []));
|
|
45424
|
+
/**
|
|
45425
|
+
* Download / Open action model derived from the recovered embedded payload.
|
|
45426
|
+
* When the input is not an OLE element, every action is disabled.
|
|
45427
|
+
*/
|
|
45428
|
+
actions = computed(() => {
|
|
45429
|
+
const el = this.ole();
|
|
45430
|
+
if (!el) {
|
|
45431
|
+
return {
|
|
45432
|
+
canDownload: false,
|
|
45433
|
+
canOpen: false,
|
|
45434
|
+
downloadHref: undefined,
|
|
45435
|
+
downloadFileName: 'embedded-object',
|
|
45436
|
+
sizeLabel: undefined,
|
|
45437
|
+
info: [],
|
|
45438
|
+
};
|
|
45439
|
+
}
|
|
45440
|
+
return buildOleActionModel(el);
|
|
45441
|
+
}, /* @ts-ignore */
|
|
45442
|
+
...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
|
|
45443
|
+
/**
|
|
45444
|
+
* Descriptive tooltip for the wrapper: the info rows joined as
|
|
45445
|
+
* "Label: value" pairs (e.g. "Type: Excel Spreadsheet, File: budget.xlsx,
|
|
45446
|
+
* Size: 2.3 KB, Application: Excel.Sheet.12"). Falls back to the aria label
|
|
45447
|
+
* when no rows are available.
|
|
45448
|
+
*/
|
|
45449
|
+
infoTitle = computed(() => {
|
|
45450
|
+
const rows = this.actions().info;
|
|
45451
|
+
if (rows.length === 0) {
|
|
45452
|
+
return this.ariaLabel();
|
|
45453
|
+
}
|
|
45454
|
+
return rows.map((row) => `${row.label}: ${row.value}`).join(', ');
|
|
45455
|
+
}, /* @ts-ignore */
|
|
45456
|
+
...(ngDevMode ? [{ debugName: "infoTitle" }] : /* istanbul ignore next */ []));
|
|
44754
45457
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OleRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
44755
45458
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: OleRendererComponent, isStandalone: true, selector: "pptx-ole-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
44756
45459
|
<div
|
|
@@ -44759,7 +45462,7 @@ class OleRendererComponent {
|
|
|
44759
45462
|
[attr.data-element-id]="element().id"
|
|
44760
45463
|
role="img"
|
|
44761
45464
|
[attr.aria-label]="ariaLabel()"
|
|
44762
|
-
title="
|
|
45465
|
+
[attr.title]="infoTitle()"
|
|
44763
45466
|
>
|
|
44764
45467
|
@if (previewSrc()) {
|
|
44765
45468
|
<!-- Preview image with type-badge overlay -->
|
|
@@ -45004,8 +45707,43 @@ class OleRendererComponent {
|
|
|
45004
45707
|
}
|
|
45005
45708
|
</div>
|
|
45006
45709
|
}
|
|
45710
|
+
@if (actions().canDownload) {
|
|
45711
|
+
<!--
|
|
45712
|
+
Download / Open actions for the recovered embedded payload.
|
|
45713
|
+
Pointer events are isolated so clicking an action never starts a
|
|
45714
|
+
selection/drag of the underlying element; the controls are
|
|
45715
|
+
keyboard-focusable and only paint on hover / focus-within.
|
|
45716
|
+
-->
|
|
45717
|
+
<div
|
|
45718
|
+
class="pptx-ng-ole-actions"
|
|
45719
|
+
(pointerdown)="$event.stopPropagation()"
|
|
45720
|
+
(mousedown)="$event.stopPropagation()"
|
|
45721
|
+
>
|
|
45722
|
+
<a
|
|
45723
|
+
class="pptx-ng-ole-action"
|
|
45724
|
+
[href]="actions().downloadHref"
|
|
45725
|
+
[attr.download]="actions().downloadFileName"
|
|
45726
|
+
[attr.aria-label]="'Download ' + actions().downloadFileName"
|
|
45727
|
+
(click)="$event.stopPropagation()"
|
|
45728
|
+
>
|
|
45729
|
+
Download
|
|
45730
|
+
</a>
|
|
45731
|
+
@if (actions().canOpen) {
|
|
45732
|
+
<a
|
|
45733
|
+
class="pptx-ng-ole-action"
|
|
45734
|
+
[href]="actions().downloadHref"
|
|
45735
|
+
target="_blank"
|
|
45736
|
+
rel="noopener noreferrer"
|
|
45737
|
+
[attr.aria-label]="'Open ' + actions().downloadFileName + ' in a new tab'"
|
|
45738
|
+
(click)="$event.stopPropagation()"
|
|
45739
|
+
>
|
|
45740
|
+
Open
|
|
45741
|
+
</a>
|
|
45742
|
+
}
|
|
45743
|
+
</div>
|
|
45744
|
+
}
|
|
45007
45745
|
</div>
|
|
45008
|
-
`, isInline: true, styles: [".pptx-ng-ole-preview{position:relative;width:100%;height:100%}.pptx-ng-ole-img{width:100%;height:100%;object-fit:contain;pointer-events:none;-webkit-user-select:none;user-select:none;display:block}.pptx-ng-ole-badge{position:absolute;bottom:4px;right:4px;z-index:10}.pptx-ng-ole-placeholder{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;box-sizing:border-box}.pptx-ng-ole-name{margin-top:8px;font-size:12px;font-weight:500;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-ole-sublabel{margin-top:2px;font-size:10px;color:#00000073;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
45746
|
+
`, isInline: true, styles: [".pptx-ng-ole-preview{position:relative;width:100%;height:100%}.pptx-ng-ole-img{width:100%;height:100%;object-fit:contain;pointer-events:none;-webkit-user-select:none;user-select:none;display:block}.pptx-ng-ole-badge{position:absolute;bottom:4px;right:4px;z-index:10}.pptx-ng-ole-placeholder{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;box-sizing:border-box}.pptx-ng-ole-name{margin-top:8px;font-size:12px;font-weight:500;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-ole-sublabel{margin-top:2px;font-size:10px;color:#00000073;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-ole-actions{position:absolute;bottom:4px;left:4px;display:flex;gap:4px;z-index:11;opacity:0;transition:opacity .12s ease-in-out}.pptx-ng-ole:hover .pptx-ng-ole-actions,.pptx-ng-ole-actions:focus-within{opacity:1}.pptx-ng-ole-action{font-size:11px;line-height:1;padding:4px 8px;border-radius:4px;background-color:#000000b8;color:#fff;text-decoration:none;cursor:pointer;white-space:nowrap;pointer-events:auto}.pptx-ng-ole-action:hover{background-color:#000000d9}.pptx-ng-ole-action:focus-visible{outline:2px solid #fff;outline-offset:1px}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
45009
45747
|
}
|
|
45010
45748
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: OleRendererComponent, decorators: [{
|
|
45011
45749
|
type: Component,
|
|
@@ -45016,7 +45754,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
45016
45754
|
[attr.data-element-id]="element().id"
|
|
45017
45755
|
role="img"
|
|
45018
45756
|
[attr.aria-label]="ariaLabel()"
|
|
45019
|
-
title="
|
|
45757
|
+
[attr.title]="infoTitle()"
|
|
45020
45758
|
>
|
|
45021
45759
|
@if (previewSrc()) {
|
|
45022
45760
|
<!-- Preview image with type-badge overlay -->
|
|
@@ -45261,8 +45999,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
45261
45999
|
}
|
|
45262
46000
|
</div>
|
|
45263
46001
|
}
|
|
46002
|
+
@if (actions().canDownload) {
|
|
46003
|
+
<!--
|
|
46004
|
+
Download / Open actions for the recovered embedded payload.
|
|
46005
|
+
Pointer events are isolated so clicking an action never starts a
|
|
46006
|
+
selection/drag of the underlying element; the controls are
|
|
46007
|
+
keyboard-focusable and only paint on hover / focus-within.
|
|
46008
|
+
-->
|
|
46009
|
+
<div
|
|
46010
|
+
class="pptx-ng-ole-actions"
|
|
46011
|
+
(pointerdown)="$event.stopPropagation()"
|
|
46012
|
+
(mousedown)="$event.stopPropagation()"
|
|
46013
|
+
>
|
|
46014
|
+
<a
|
|
46015
|
+
class="pptx-ng-ole-action"
|
|
46016
|
+
[href]="actions().downloadHref"
|
|
46017
|
+
[attr.download]="actions().downloadFileName"
|
|
46018
|
+
[attr.aria-label]="'Download ' + actions().downloadFileName"
|
|
46019
|
+
(click)="$event.stopPropagation()"
|
|
46020
|
+
>
|
|
46021
|
+
Download
|
|
46022
|
+
</a>
|
|
46023
|
+
@if (actions().canOpen) {
|
|
46024
|
+
<a
|
|
46025
|
+
class="pptx-ng-ole-action"
|
|
46026
|
+
[href]="actions().downloadHref"
|
|
46027
|
+
target="_blank"
|
|
46028
|
+
rel="noopener noreferrer"
|
|
46029
|
+
[attr.aria-label]="'Open ' + actions().downloadFileName + ' in a new tab'"
|
|
46030
|
+
(click)="$event.stopPropagation()"
|
|
46031
|
+
>
|
|
46032
|
+
Open
|
|
46033
|
+
</a>
|
|
46034
|
+
}
|
|
46035
|
+
</div>
|
|
46036
|
+
}
|
|
45264
46037
|
</div>
|
|
45265
|
-
`, styles: [".pptx-ng-ole-preview{position:relative;width:100%;height:100%}.pptx-ng-ole-img{width:100%;height:100%;object-fit:contain;pointer-events:none;-webkit-user-select:none;user-select:none;display:block}.pptx-ng-ole-badge{position:absolute;bottom:4px;right:4px;z-index:10}.pptx-ng-ole-placeholder{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;box-sizing:border-box}.pptx-ng-ole-name{margin-top:8px;font-size:12px;font-weight:500;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-ole-sublabel{margin-top:2px;font-size:10px;color:#00000073;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"] }]
|
|
46038
|
+
`, styles: [".pptx-ng-ole-preview{position:relative;width:100%;height:100%}.pptx-ng-ole-img{width:100%;height:100%;object-fit:contain;pointer-events:none;-webkit-user-select:none;user-select:none;display:block}.pptx-ng-ole-badge{position:absolute;bottom:4px;right:4px;z-index:10}.pptx-ng-ole-placeholder{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;box-sizing:border-box}.pptx-ng-ole-name{margin-top:8px;font-size:12px;font-weight:500;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-ole-sublabel{margin-top:2px;font-size:10px;color:#00000073;max-width:90%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pptx-ng-ole-actions{position:absolute;bottom:4px;left:4px;display:flex;gap:4px;z-index:11;opacity:0;transition:opacity .12s ease-in-out}.pptx-ng-ole:hover .pptx-ng-ole-actions,.pptx-ng-ole-actions:focus-within{opacity:1}.pptx-ng-ole-action{font-size:11px;line-height:1;padding:4px 8px;border-radius:4px;background-color:#000000b8;color:#fff;text-decoration:none;cursor:pointer;white-space:nowrap;pointer-events:auto}.pptx-ng-ole-action:hover{background-color:#000000d9}.pptx-ng-ole-action:focus-visible{outline:2px solid #fff;outline-offset:1px}\n"] }]
|
|
45266
46039
|
}], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }] } });
|
|
45267
46040
|
|
|
45268
46041
|
/**
|