Skip to content

Commit

Permalink
feat: add <Table>
Browse files Browse the repository at this point in the history
  • Loading branch information
wyozi committed Aug 16, 2023
1 parent 36e114d commit e0a9558
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 37 deletions.
24 changes: 17 additions & 7 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
import { transform } from "buble";
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as ReactPPTX from "react-pptx";
import {
Image,
Line,
MasterSlide,
Presentation,
Shape,
Slide,
MasterSlide,
Image,
Table,
Text,
Shape,
Line,
normalizeJsx,
} from "react-pptx";
import Preview from "react-pptx/preview";
import { transform } from "buble";

declare var __LATEST_GIT_TAG__: string;
declare var __LATEST_GIT_COMMIT_HASH__: string;

const primitives = { Presentation, Slide, MasterSlide, Image, Text, Shape, Line };
const primitives = {
Presentation,
Slide,
MasterSlide,
Image,
Text,
Shape,
Line,
Table,
};

const transpile = (code, callback, onError) => {
try {
Expand Down Expand Up @@ -57,11 +67,11 @@ const transpile = (code, callback, onError) => {
},
};

import "monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution";
import "monaco-editor/esm/vs/editor/browser/controller/coreCommands.js";
import "monaco-editor/esm/vs/editor/contrib/hover/hover.js";
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
import "monaco-editor/esm/vs/language/typescript/monaco.contribution";
import "monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution";

// @ts-ignore
import coreDefs from "!!raw-loader!../node_modules/typescript/lib/lib.es5.d.ts";
Expand Down
37 changes: 31 additions & 6 deletions src/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import PPTX2Json from "pptx2json";
import * as React from "react";
import { render } from "./index";
import PPTX2Json from "pptx2json";
import fs = require("fs");
import {
Presentation,
Slide,
Shape,
Text,
Image,
Line,
MasterSlide,
Presentation,
Shape,
Slide,
Table,
Text,
} from "./nodes";
import fs = require("fs");

describe("test render", () => {
it("ok", async () => {
Expand Down Expand Up @@ -227,6 +228,30 @@ describe("test render", () => {
style={{ x: "10%", y: "10%", w: "80%", h: "80%" }}
/>
</Slide>
<Slide>
<Table
rows={[
["foo", <Text style={{ align: "right" }}>bar</Text>],
[
<Text style={{ verticalAlign: "bottom" }}>
what about a{" "}
<Text.Link url="https://www.youtube.com/watch?v=6IqKEeRS90A">
link
</Text.Link>
</Text>,
"xyz",
],
]}
style={{
x: "10%",
y: "10%",
w: "80%",
h: "80%",
borderWidth: 1,
borderColor: "#ff0000",
}}
/>
</Slide>
</Presentation>
);
const rendered = await render(test, { outputType: "nodebuffer" });
Expand Down
17 changes: 17 additions & 0 deletions src/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export enum NodeTypes {
SLIDE = "slide",
MASTER_SLIDE = "master-slide",
IMAGE = "image",
TABLE = "table",
PRESENTATION = "presentation",
}

Expand Down Expand Up @@ -144,6 +145,22 @@ export const isShape = (
return el.type === NodeTypes.SHAPE;
};

export type TableProps = VisualBaseProps & {
rows: Array<Array<string | React.ReactElement<TextProps>>>;
style?: {
borderWidth?: number;
borderColor?: string;
width?: number;
};
};
export const Table: React.FC<TableProps> =
NodeTypes.TABLE as unknown as React.FC;
export const isTable = (
el: React.ReactElement
): el is React.ReactElement<TableProps> => {
return el.type === NodeTypes.TABLE;
};

export type LineProps = {
x1: number;
x2: number;
Expand Down
57 changes: 46 additions & 11 deletions src/normalizer.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
// Normalizer converts and normalizes JSX Presentation trees into internal nodes
// that roughly match what pptxgenjs will want to ingest

import type PptxGenJs from "pptxgenjs";
import Color from "color";
import { flattenChildren, isReactElementOrElementArray } from "./util";
import type PptxGenJs from "pptxgenjs";
import React, { ReactElement } from "react";
import {
MasterSlideProps,
NodeTypes,
PresentationProps,
SlideProps,
TextBulletProps,
TextChild,
TextLinkProps,
VisualProps,
isText,
isImage,
isLine,
isShape,
TextChild,
TextLinkProps,
TextBulletProps,
isTextLink,
isTable,
isText,
isTextBullet,
isLine,
NodeTypes,
MasterSlideProps,
isTextLink,
} from "./nodes";
import React, { ReactElement } from "react";
import { flattenChildren, isReactElementOrElementArray } from "./util";

export type HexColor = string; // 6-Character hex (without prefix hash)
export type ComplexColor = {
Expand Down Expand Up @@ -113,6 +114,14 @@ export type InternalShape = ObjectBase & {
borderWidth: number | null;
};
};
export type InternalTable = ObjectBase & {
kind: "table";
rows: Array<Array<InternalText>>;
style: {
borderColor: HexColor | null;
borderWidth: number | null;
};
};
export type InternalLine = {
kind: "line";
x1: number;
Expand All @@ -129,6 +138,7 @@ export type InternalSlideObject =
| InternalText
| InternalImage
| InternalShape
| InternalTable
| InternalLine;

export type InternalImageSrc =
Expand Down Expand Up @@ -381,6 +391,31 @@ const normalizeSlideObject = (
borderWidth: node.props.style.borderWidth ?? null,
},
};
} else if (isTable(node)) {
const normalized: InternalText[][] = node.props.rows.map((row) =>
row.map((cell) => {
if (typeof cell === "string") {
return {
kind: "text",
text: [{ text: cell, style: {} }],
style: { x: 0, y: 0, w: 0, h: 0, color: null },
};
} else {
return normalizeSlideObject(cell) as InternalText;
}
})
);
return {
kind: "table",
rows: normalized,
style: {
...normalizedCoordinates,
borderColor: node.props.style.borderColor
? normalizeHexColor(node.props.style.borderColor)
: null,
borderWidth: node.props.style.borderWidth ?? null,
},
};
} else {
throw new Error("unknown slide object");
}
Expand Down
36 changes: 29 additions & 7 deletions src/preview/Preview.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import * as React from "react";
import { PresentationProps } from "../nodes";
import {
normalizeJsx,
InternalSlide,
InternalSlideObject,
HexColor,
ComplexColor,
HexColor,
InternalImage,
InternalMasterSlide,
InternalShape,
InternalSlide,
InternalSlideObject,
InternalTextPart,
InternalImage,
InternalTextPartBaseStyle,
InternalMasterSlide,
normalizeJsx,
} from "../normalizer";
import { layoutToInches, POINTS_TO_INCHES } from "../util";
import { POINTS_TO_INCHES, layoutToInches } from "../util";

const normalizedColorToCSS = (color: HexColor | ComplexColor) => {
if (typeof color === "string") {
Expand Down Expand Up @@ -358,6 +358,28 @@ const SlideObjectPreview = ({
objectFit: constrainObjectFit(object.style.sizing),
}}
/>
) : object.kind === "table" ? (
<table style={{ width: "100%", height: "100%" }}>
<tbody>
{object.rows.map((row, i) => (
<tr key={i}>
{row.map((cell, i) => {
return (
<td key={i}>
<TextPreview
parts={cell.text}
subscript={cell.style.subscript}
superscript={cell.style.superscript}
dimensions={dimensions} // TODO: these should be divided by table rows/cols
slideWidth={slideWidth}
/>
</td>
);
})}
</tr>
))}
</tbody>
</table>
) : (
<SlideObjectShape shape={object} />
)}
Expand Down
44 changes: 38 additions & 6 deletions src/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// Renderer renders normalized nodes into pptxgenjs presentations

import pptxgen from "pptxgenjs";
import fetch from "cross-fetch";
import type PptxGenJs from "pptxgenjs";
import pptxgen from "pptxgenjs";
import { PresentationProps } from "./nodes";
import {
normalizeJsx,
ComplexColor,
HexColor,
InternalImage,
InternalMasterSlide,
InternalSlide,
InternalSlideObject,
InternalTextPart,
InternalMasterSlide,
HexColor,
ComplexColor,
InternalImage,
normalizeJsx,
} from "./normalizer";

const normalizedColorToPptxgenShapeFill = (
Expand Down Expand Up @@ -150,6 +150,38 @@ const renderSlideObject = async (
},
});
}
} else if (object.kind === "table") {
const style = object.style;

const mapped: PptxGenJs.TableRow[] = object.rows.map((row) =>
row.map((cell) => {
const { color, verticalAlign, backgroundColor, ...style } = cell.style;
// this is super weird, but works?
return {
text: renderTextParts(cell.text),
options: {
...style,
fill: backgroundColor
? normalizedColorToPptxgenShapeFill(backgroundColor)
: undefined,
color: color ?? undefined,
valign: verticalAlign,
breakLine: true,
},
};
})
);

slide.addTable(mapped, {
x,
y,
w,
h,
border: {
pt: style.borderWidth ?? undefined,
color: style.borderColor ?? undefined,
},
});
}
};

Expand Down

0 comments on commit e0a9558

Please sign in to comment.