@textcortex/slidewise 1.0.1 → 1.2.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/index.mjs +8085 -7881
- package/dist/index.mjs.map +1 -1
- package/dist/slidewise.css +1 -1
- package/package.json +4 -19
- package/src/SlidewiseEditor.css +121 -4
- package/src/SlidewiseEditor.tsx +93 -165
- package/src/SlidewiseFileEditor.tsx +109 -11
- package/src/components/editor/TopBar.tsx +37 -24
- package/src/compound/HostContext.tsx +29 -0
- package/src/compound/IconContext.tsx +42 -0
- package/src/compound/ReadOnlyContext.tsx +23 -0
- package/src/compound/SlidewiseRoot.tsx +325 -0
- package/src/compound/index.ts +51 -0
- package/src/compound/parts.tsx +160 -0
- package/src/css.d.ts +4 -0
- package/src/index.ts +43 -0
- package/src/lib/__tests__/history.test.ts +164 -0
- package/src/lib/store.ts +81 -4
- package/README.md +0 -112
- package/dist/file.svg +0 -1
- package/dist/globe.svg +0 -1
- package/dist/types/SlidewiseEditor.d.ts +0 -47
- package/dist/types/SlidewiseFileEditor.d.ts +0 -54
- package/dist/types/components/editor/BottomToolbar.d.ts +0 -1
- package/dist/types/components/editor/Canvas.d.ts +0 -1
- package/dist/types/components/editor/Editor.d.ts +0 -8
- package/dist/types/components/editor/ElementView.d.ts +0 -6
- package/dist/types/components/editor/FloatingToolbar.d.ts +0 -6
- package/dist/types/components/editor/GridView.d.ts +0 -1
- package/dist/types/components/editor/PlayMode.d.ts +0 -1
- package/dist/types/components/editor/SelectionFrame.d.ts +0 -8
- package/dist/types/components/editor/SlideRail.d.ts +0 -1
- package/dist/types/components/editor/SlideView.d.ts +0 -5
- package/dist/types/components/editor/TopBar.d.ts +0 -7
- package/dist/types/index.d.ts +0 -7
- package/dist/types/lib/StoreProvider.d.ts +0 -8
- package/dist/types/lib/fonts.d.ts +0 -9
- package/dist/types/lib/pptx/deckToPptx.d.ts +0 -9
- package/dist/types/lib/pptx/index.d.ts +0 -3
- package/dist/types/lib/pptx/pptxToDeck.d.ts +0 -18
- package/dist/types/lib/pptx/types.d.ts +0 -15
- package/dist/types/lib/pptx/units.d.ts +0 -25
- package/dist/types/lib/schema/migrate.d.ts +0 -25
- package/dist/types/lib/seed.d.ts +0 -2
- package/dist/types/lib/store.d.ts +0 -55
- package/dist/types/lib/types.d.ts +0 -141
- package/dist/window.svg +0 -1
- package/src/App.tsx +0 -261
- package/src/components/editor/Editor.tsx +0 -53
- package/src/index.css +0 -13
- package/src/lib/seed.ts +0 -777
- package/src/main.tsx +0 -10
- package/src/vite-env.d.ts +0 -3
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Deck } from "@/lib/types";
|
|
2
|
-
/**
|
|
3
|
-
* Parse a PPTX blob into a Slidewise Deck. Coverage:
|
|
4
|
-
* - Slide background (solid + theme color)
|
|
5
|
-
* - Text boxes with placeholder inheritance from layout/master, theme-color
|
|
6
|
-
* resolution, multi-run formatting, paragraph alignment, lineHeight
|
|
7
|
-
* - Preset shapes (rect, roundRect, ellipse, triangle, diamond, star — and
|
|
8
|
-
* many other prsts mapped to the closest available kind so they at least
|
|
9
|
-
* render with correct fill/position)
|
|
10
|
-
* - Images (embedded media → data URLs, srcRect crop preserved)
|
|
11
|
-
* - Connector lines (cxnSp) and shapes authored as prst="line"
|
|
12
|
-
* - Tables (basic row/cell content + header/body fills)
|
|
13
|
-
* - Group shapes (recursed and flattened with the group transform applied)
|
|
14
|
-
* - Anything else (charts, SmartArt, embedded video) is preserved as
|
|
15
|
-
* UnknownElement carrying its raw OOXML so a save round-trip can re-emit
|
|
16
|
-
* it without data loss.
|
|
17
|
-
*/
|
|
18
|
-
export declare function parsePptx(blob: Blob | ArrayBuffer): Promise<Deck>;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { SlideElement } from "@/lib/types";
|
|
2
|
-
/**
|
|
3
|
-
* Result of parsing a PPTX archive. Surfaced to the caller so they can
|
|
4
|
-
* decide whether to warn about lossy fields (animations, transitions,
|
|
5
|
-
* unknown elements that fell into UnknownElement).
|
|
6
|
-
*/
|
|
7
|
-
export interface ParseDiagnostics {
|
|
8
|
-
unknownElementCount: number;
|
|
9
|
-
droppedAnimations: number;
|
|
10
|
-
warnings: string[];
|
|
11
|
-
}
|
|
12
|
-
export interface ParseResult {
|
|
13
|
-
diagnostics: ParseDiagnostics;
|
|
14
|
-
elements: SlideElement[];
|
|
15
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit conversions between Slidewise pixels and PPTX EMU/inches/points.
|
|
3
|
-
*
|
|
4
|
-
* Slidewise authors at a fixed 1920×1080 px canvas. PPTX widescreen layout is
|
|
5
|
-
* 13.333 × 7.5 inches (12,192,000 × 6,858,000 EMU). The mapping is linear:
|
|
6
|
-
* 1920 px ↔ 12,192,000 EMU ↔ 13.333 in
|
|
7
|
-
* 1080 px ↔ 6,858,000 EMU ↔ 7.5 in
|
|
8
|
-
*
|
|
9
|
-
* That gives 6350 EMU per pixel (and 144 px per inch, 0.5 pt per px).
|
|
10
|
-
*/
|
|
11
|
-
export declare const EMU_PER_INCH = 914400;
|
|
12
|
-
export declare const EMU_PER_POINT = 12700;
|
|
13
|
-
export declare const PX_PER_INCH = 144;
|
|
14
|
-
export declare const EMU_PER_PX: number;
|
|
15
|
-
export declare const POINTS_PER_PX = 0.5;
|
|
16
|
-
export declare const PPTX_SLIDE_W_INCHES: number;
|
|
17
|
-
export declare const PPTX_SLIDE_H_INCHES: number;
|
|
18
|
-
export declare const PPTX_SLIDE_W_EMU: number;
|
|
19
|
-
export declare const PPTX_SLIDE_H_EMU: number;
|
|
20
|
-
export declare const pxToEmu: (px: number) => number;
|
|
21
|
-
export declare const emuToPx: (emu: number) => number;
|
|
22
|
-
export declare const pxToInches: (px: number) => number;
|
|
23
|
-
export declare const inchesToPx: (inches: number) => number;
|
|
24
|
-
export declare const pxToPoints: (px: number) => number;
|
|
25
|
-
export declare const pointsToPx: (pt: number) => number;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Deck } from "@/lib/types";
|
|
2
|
-
/**
|
|
3
|
-
* Slidewise deck schema version. Bump this whenever the persisted shape of a
|
|
4
|
-
* Deck changes in a way old hosts cannot read transparently — adding a new
|
|
5
|
-
* required field, renaming a key, restructuring an element type, etc.
|
|
6
|
-
*
|
|
7
|
-
* Bumping always pairs with a migrator at `MIGRATIONS[oldVersion]` that takes
|
|
8
|
-
* a deck shaped at the previous version and returns one shaped at this one.
|
|
9
|
-
* Migrations are run forward in order, so a v0 deck becomes v1 then v2 etc.
|
|
10
|
-
*
|
|
11
|
-
* Never delete an old migrator — published decks in the wild may still be at
|
|
12
|
-
* any historical version, and they all need a path forward.
|
|
13
|
-
*/
|
|
14
|
-
export declare const CURRENT_DECK_VERSION = 1;
|
|
15
|
-
/**
|
|
16
|
-
* Normalise an external deck (from PPTX import, JSON import, localStorage,
|
|
17
|
-
* a host prop, etc.) to the current schema. Stamps the current version on
|
|
18
|
-
* its way out so downstream code can rely on `deck.version`.
|
|
19
|
-
*
|
|
20
|
-
* Throws if the input is missing the basic shape, or if its `version` is
|
|
21
|
-
* higher than `CURRENT_DECK_VERSION` — that means the deck was written by
|
|
22
|
-
* a newer Slidewise and the host should upgrade rather than silently render
|
|
23
|
-
* a degraded version.
|
|
24
|
-
*/
|
|
25
|
-
export declare function migrate(input: unknown): Deck;
|
package/dist/types/lib/seed.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { type StoreApi } from "zustand/vanilla";
|
|
2
|
-
import type { Deck, Slide, SlideElement, ElementDraft, ShapeKind } from "./types";
|
|
3
|
-
import { SLIDE_W, SLIDE_H } from "./types";
|
|
4
|
-
type Tool = "select" | "text" | "shape" | "line" | "image" | "table" | "formula" | "icon" | "embed";
|
|
5
|
-
interface HistorySnapshot {
|
|
6
|
-
deck: Deck;
|
|
7
|
-
currentSlideId: string;
|
|
8
|
-
}
|
|
9
|
-
type Theme = "light" | "dark";
|
|
10
|
-
type View = "editor" | "grid";
|
|
11
|
-
export interface EditorState {
|
|
12
|
-
deck: Deck;
|
|
13
|
-
currentSlideId: string;
|
|
14
|
-
selectedIds: string[];
|
|
15
|
-
tool: Tool;
|
|
16
|
-
zoom: number;
|
|
17
|
-
fitMode: "fit" | "fill" | "manual";
|
|
18
|
-
playing: boolean;
|
|
19
|
-
theme: Theme;
|
|
20
|
-
view: View;
|
|
21
|
-
history: HistorySnapshot[];
|
|
22
|
-
future: HistorySnapshot[];
|
|
23
|
-
currentSlide: () => Slide;
|
|
24
|
-
setTool: (t: Tool) => void;
|
|
25
|
-
setTitle: (t: string) => void;
|
|
26
|
-
setZoom: (z: number) => void;
|
|
27
|
-
setFitMode: (f: "fit" | "fill" | "manual") => void;
|
|
28
|
-
selectSlide: (id: string) => void;
|
|
29
|
-
selectElement: (id: string | null, additive?: boolean) => void;
|
|
30
|
-
clearSelection: () => void;
|
|
31
|
-
addSlide: (afterId?: string) => void;
|
|
32
|
-
duplicateSlide: (id: string) => void;
|
|
33
|
-
deleteSlide: (id: string) => void;
|
|
34
|
-
reorderSlide: (id: string, toIndex: number) => void;
|
|
35
|
-
addElement: (partial: ElementDraft) => string;
|
|
36
|
-
updateElement: (id: string, patch: Partial<SlideElement>) => void;
|
|
37
|
-
deleteElement: (id: string) => void;
|
|
38
|
-
bringForward: (id: string) => void;
|
|
39
|
-
sendBackward: (id: string) => void;
|
|
40
|
-
setBackground: (color: string) => void;
|
|
41
|
-
play: () => void;
|
|
42
|
-
stop: () => void;
|
|
43
|
-
undo: () => void;
|
|
44
|
-
redo: () => void;
|
|
45
|
-
pushHistory: () => void;
|
|
46
|
-
setDeck: (deck: Deck) => void;
|
|
47
|
-
setTheme: (t: Theme) => void;
|
|
48
|
-
toggleTheme: () => void;
|
|
49
|
-
setView: (v: View) => void;
|
|
50
|
-
}
|
|
51
|
-
export type EditorStore = StoreApi<EditorState>;
|
|
52
|
-
export declare function createEditorStore(initialDeck: Deck): EditorStore;
|
|
53
|
-
export type { Tool };
|
|
54
|
-
export { SLIDE_W, SLIDE_H };
|
|
55
|
-
export declare const presetShapes: ShapeKind[];
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
export declare const SLIDE_W = 1920;
|
|
2
|
-
export declare const SLIDE_H = 1080;
|
|
3
|
-
export type ElementType = "text" | "shape" | "image" | "line" | "table" | "icon" | "embed" | "unknown";
|
|
4
|
-
export type EnterAnim = "none" | "fade" | "slide-up" | "slide-down" | "slide-left" | "slide-right" | "scale" | "draw";
|
|
5
|
-
export interface BaseElement {
|
|
6
|
-
id: string;
|
|
7
|
-
type: ElementType;
|
|
8
|
-
x: number;
|
|
9
|
-
y: number;
|
|
10
|
-
w: number;
|
|
11
|
-
h: number;
|
|
12
|
-
rotation: number;
|
|
13
|
-
z: number;
|
|
14
|
-
locked?: boolean;
|
|
15
|
-
enter?: EnterAnim;
|
|
16
|
-
delay?: number;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* One styled fragment within a text element. Any field left undefined falls
|
|
20
|
-
* back to the parent TextElement's flat default. Run text may contain "\n" —
|
|
21
|
-
* which becomes a paragraph break in both renderer and PPTX writer.
|
|
22
|
-
*/
|
|
23
|
-
export interface TextRun {
|
|
24
|
-
text: string;
|
|
25
|
-
fontFamily?: string;
|
|
26
|
-
fontSize?: number;
|
|
27
|
-
fontWeight?: number;
|
|
28
|
-
italic?: boolean;
|
|
29
|
-
underline?: boolean;
|
|
30
|
-
strike?: boolean;
|
|
31
|
-
color?: string;
|
|
32
|
-
letterSpacing?: number;
|
|
33
|
-
}
|
|
34
|
-
export interface TextElement extends BaseElement {
|
|
35
|
-
type: "text";
|
|
36
|
-
text: string;
|
|
37
|
-
fontFamily: string;
|
|
38
|
-
fontSize: number;
|
|
39
|
-
fontWeight: number;
|
|
40
|
-
italic: boolean;
|
|
41
|
-
underline: boolean;
|
|
42
|
-
strike: boolean;
|
|
43
|
-
color: string;
|
|
44
|
-
align: "left" | "center" | "right";
|
|
45
|
-
vAlign: "top" | "middle" | "bottom";
|
|
46
|
-
lineHeight: number;
|
|
47
|
-
letterSpacing: number;
|
|
48
|
-
/**
|
|
49
|
-
* Optional rich-text breakdown. When present, the renderer and PPTX writer
|
|
50
|
-
* use these per-run styles; the flat fields above act as defaults for any
|
|
51
|
-
* field a run leaves unset. Editing the text via the contentEditable surface
|
|
52
|
-
* collapses runs back to the flat representation.
|
|
53
|
-
*/
|
|
54
|
-
runs?: TextRun[];
|
|
55
|
-
}
|
|
56
|
-
export type ShapeKind = "rect" | "rounded" | "circle" | "triangle" | "star" | "diamond";
|
|
57
|
-
export interface ShapeElement extends BaseElement {
|
|
58
|
-
type: "shape";
|
|
59
|
-
shape: ShapeKind;
|
|
60
|
-
fill: string;
|
|
61
|
-
stroke?: string;
|
|
62
|
-
strokeWidth?: number;
|
|
63
|
-
radius?: number;
|
|
64
|
-
}
|
|
65
|
-
export interface ImageElement extends BaseElement {
|
|
66
|
-
type: "image";
|
|
67
|
-
src: string;
|
|
68
|
-
alt?: string;
|
|
69
|
-
fit: "cover" | "contain" | "fill";
|
|
70
|
-
radius?: number;
|
|
71
|
-
/**
|
|
72
|
-
* PPTX <a:srcRect> source crop, expressed as fractions (0..1) of the source
|
|
73
|
-
* image to chop from each edge before placing into the bounding box.
|
|
74
|
-
* Slidewise applies it via background-image / background-position so the
|
|
75
|
-
* final paint matches PowerPoint's "crop + stretch" behaviour.
|
|
76
|
-
*/
|
|
77
|
-
crop?: {
|
|
78
|
-
l: number;
|
|
79
|
-
r: number;
|
|
80
|
-
t: number;
|
|
81
|
-
b: number;
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
export interface LineElement extends BaseElement {
|
|
85
|
-
type: "line";
|
|
86
|
-
stroke: string;
|
|
87
|
-
strokeWidth: number;
|
|
88
|
-
arrow?: boolean;
|
|
89
|
-
dashed?: boolean;
|
|
90
|
-
}
|
|
91
|
-
export interface TableElement extends BaseElement {
|
|
92
|
-
type: "table";
|
|
93
|
-
rows: string[][];
|
|
94
|
-
headerFill: string;
|
|
95
|
-
rowFill: string;
|
|
96
|
-
textColor: string;
|
|
97
|
-
fontSize: number;
|
|
98
|
-
}
|
|
99
|
-
export interface IconElement extends BaseElement {
|
|
100
|
-
type: "icon";
|
|
101
|
-
icon: string;
|
|
102
|
-
color: string;
|
|
103
|
-
}
|
|
104
|
-
export interface EmbedElement extends BaseElement {
|
|
105
|
-
type: "embed";
|
|
106
|
-
url: string;
|
|
107
|
-
label: string;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Opaque OOXML element preserved for round-trip when reading a PPTX
|
|
111
|
-
* containing constructs Slidewise does not yet model (charts, SmartArt,
|
|
112
|
-
* embedded media, etc.). Position/size is editable; the inner XML is
|
|
113
|
-
* re-emitted on write so the user does not lose data.
|
|
114
|
-
*/
|
|
115
|
-
export interface UnknownElement extends BaseElement {
|
|
116
|
-
type: "unknown";
|
|
117
|
-
/** Tag name of the wrapped OOXML node, e.g. "p:graphicFrame". */
|
|
118
|
-
ooxmlTag: string;
|
|
119
|
-
/** Raw OOXML serialized as a string, re-emitted verbatim on save. */
|
|
120
|
-
ooxmlXml: string;
|
|
121
|
-
/** Human-readable label for the editor UI, e.g. "Chart" or "SmartArt". */
|
|
122
|
-
label?: string;
|
|
123
|
-
}
|
|
124
|
-
export type SlideElement = TextElement | ShapeElement | ImageElement | LineElement | TableElement | IconElement | EmbedElement | UnknownElement;
|
|
125
|
-
export interface Slide {
|
|
126
|
-
id: string;
|
|
127
|
-
background: string;
|
|
128
|
-
elements: SlideElement[];
|
|
129
|
-
}
|
|
130
|
-
export interface Deck {
|
|
131
|
-
/**
|
|
132
|
-
* Schema version this deck conforms to. Stamped by `migrate()` and by
|
|
133
|
-
* internal Deck constructors (seed, PPTX import). Hosts should not set
|
|
134
|
-
* this manually — pass an external deck through `migrate()` and read the
|
|
135
|
-
* version off the result.
|
|
136
|
-
*/
|
|
137
|
-
version: number;
|
|
138
|
-
title: string;
|
|
139
|
-
slides: Slide[];
|
|
140
|
-
}
|
|
141
|
-
export type ElementDraft<T extends SlideElement = SlideElement> = T extends SlideElement ? Omit<T, "id" | "z"> : never;
|
package/dist/window.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
package/src/App.tsx
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { Upload, RotateCcw } from "lucide-react";
|
|
3
|
-
import { SlidewiseEditor, type SlidewiseEditorHandle } from "./SlidewiseEditor";
|
|
4
|
-
import { seedDeck } from "@/lib/seed";
|
|
5
|
-
import { parsePptx, serializeDeck } from "@/lib/pptx";
|
|
6
|
-
import type { Deck } from "@/lib/types";
|
|
7
|
-
|
|
8
|
-
const STORAGE_KEY = "slidewise-deck";
|
|
9
|
-
|
|
10
|
-
function loadInitialDeck(): Deck {
|
|
11
|
-
try {
|
|
12
|
-
const raw = localStorage.getItem(STORAGE_KEY);
|
|
13
|
-
if (raw) {
|
|
14
|
-
const parsed = JSON.parse(raw) as Deck;
|
|
15
|
-
if (parsed && Array.isArray(parsed.slides) && parsed.slides.length) {
|
|
16
|
-
return parsed;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
} catch {}
|
|
20
|
-
return seedDeck;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function App() {
|
|
24
|
-
const [deck, setDeck] = useState<Deck>(() => loadInitialDeck());
|
|
25
|
-
const editorRef = useRef<SlidewiseEditorHandle>(null);
|
|
26
|
-
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
27
|
-
const [overlay, setOverlay] = useState<string | null>(null);
|
|
28
|
-
const [sourceLabel, setSourceLabel] = useState<string>("Seed deck");
|
|
29
|
-
|
|
30
|
-
const loadFromFile = async (file: File) => {
|
|
31
|
-
if (!file.name.toLowerCase().endsWith(".pptx")) {
|
|
32
|
-
setOverlay(`Not a .pptx file: ${file.name}`);
|
|
33
|
-
setTimeout(() => setOverlay(null), 1800);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
setOverlay(`Loading ${file.name}…`);
|
|
38
|
-
const next = await parsePptx(file);
|
|
39
|
-
setDeck(next);
|
|
40
|
-
setSourceLabel(file.name);
|
|
41
|
-
setOverlay(`Loaded ${next.slides.length} slides from ${file.name}`);
|
|
42
|
-
setTimeout(() => setOverlay(null), 1600);
|
|
43
|
-
} catch (err) {
|
|
44
|
-
console.error("[slidewise] PPTX parse failed:", err);
|
|
45
|
-
setOverlay("Failed to parse .pptx — see console");
|
|
46
|
-
setTimeout(() => setOverlay(null), 2400);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
let dragDepth = 0;
|
|
52
|
-
const onDragEnter = (e: DragEvent) => {
|
|
53
|
-
if (!e.dataTransfer?.types.includes("Files")) return;
|
|
54
|
-
dragDepth++;
|
|
55
|
-
setOverlay("Drop a .pptx to load it");
|
|
56
|
-
};
|
|
57
|
-
const onDragLeave = () => {
|
|
58
|
-
dragDepth = Math.max(0, dragDepth - 1);
|
|
59
|
-
if (dragDepth === 0) setOverlay(null);
|
|
60
|
-
};
|
|
61
|
-
const onDragOver = (e: DragEvent) => {
|
|
62
|
-
if (e.dataTransfer?.types.includes("Files")) {
|
|
63
|
-
e.preventDefault();
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
const onDrop = async (e: DragEvent) => {
|
|
67
|
-
e.preventDefault();
|
|
68
|
-
dragDepth = 0;
|
|
69
|
-
setOverlay(null);
|
|
70
|
-
const file = e.dataTransfer?.files?.[0];
|
|
71
|
-
if (!file) return;
|
|
72
|
-
await loadFromFile(file);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
window.addEventListener("dragenter", onDragEnter);
|
|
76
|
-
window.addEventListener("dragleave", onDragLeave);
|
|
77
|
-
window.addEventListener("dragover", onDragOver);
|
|
78
|
-
window.addEventListener("drop", onDrop);
|
|
79
|
-
return () => {
|
|
80
|
-
window.removeEventListener("dragenter", onDragEnter);
|
|
81
|
-
window.removeEventListener("dragleave", onDragLeave);
|
|
82
|
-
window.removeEventListener("dragover", onDragOver);
|
|
83
|
-
window.removeEventListener("drop", onDrop);
|
|
84
|
-
};
|
|
85
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
86
|
-
}, []);
|
|
87
|
-
|
|
88
|
-
const handleSave = (next: Deck) => {
|
|
89
|
-
try {
|
|
90
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
91
|
-
} catch (err) {
|
|
92
|
-
console.error("Failed to persist deck", err);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const handleExportPptx = async (current: Deck) => {
|
|
97
|
-
try {
|
|
98
|
-
const blob = await serializeDeck(current);
|
|
99
|
-
const url = URL.createObjectURL(blob);
|
|
100
|
-
const a = document.createElement("a");
|
|
101
|
-
a.href = url;
|
|
102
|
-
a.download = `${(current.title || "deck").replace(/[^a-z0-9-_]+/gi, "-")}.pptx`;
|
|
103
|
-
a.click();
|
|
104
|
-
URL.revokeObjectURL(url);
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.error("[slidewise] PPTX export failed:", err);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const resetToSeed = () => {
|
|
111
|
-
try {
|
|
112
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
113
|
-
} catch {}
|
|
114
|
-
setDeck(seedDeck);
|
|
115
|
-
setSourceLabel("Seed deck");
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<div
|
|
120
|
-
style={{
|
|
121
|
-
display: "flex",
|
|
122
|
-
flexDirection: "column",
|
|
123
|
-
width: "100%",
|
|
124
|
-
height: "100%",
|
|
125
|
-
background: "#0E1330",
|
|
126
|
-
}}
|
|
127
|
-
>
|
|
128
|
-
<div
|
|
129
|
-
style={{
|
|
130
|
-
flex: "0 0 44px",
|
|
131
|
-
display: "flex",
|
|
132
|
-
alignItems: "center",
|
|
133
|
-
gap: 12,
|
|
134
|
-
padding: "0 14px",
|
|
135
|
-
background: "#0E1330",
|
|
136
|
-
color: "#fff",
|
|
137
|
-
fontFamily: "Inter, system-ui, sans-serif",
|
|
138
|
-
borderBottom: "1px solid rgba(255,255,255,0.08)",
|
|
139
|
-
}}
|
|
140
|
-
>
|
|
141
|
-
<span style={{ fontSize: 12, fontWeight: 700, letterSpacing: 0.6, opacity: 0.7 }}>
|
|
142
|
-
SLIDEWISE DEV
|
|
143
|
-
</span>
|
|
144
|
-
<span
|
|
145
|
-
style={{
|
|
146
|
-
fontSize: 12,
|
|
147
|
-
opacity: 0.55,
|
|
148
|
-
padding: "2px 8px",
|
|
149
|
-
borderRadius: 999,
|
|
150
|
-
background: "rgba(255,255,255,0.06)",
|
|
151
|
-
}}
|
|
152
|
-
title="Source of the currently loaded deck"
|
|
153
|
-
>
|
|
154
|
-
{sourceLabel}
|
|
155
|
-
</span>
|
|
156
|
-
<div style={{ flex: 1 }} />
|
|
157
|
-
<button
|
|
158
|
-
type="button"
|
|
159
|
-
onClick={resetToSeed}
|
|
160
|
-
style={chipBtn(false)}
|
|
161
|
-
onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(255,255,255,0.10)")}
|
|
162
|
-
onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
|
|
163
|
-
title="Reset to the built-in seed deck"
|
|
164
|
-
>
|
|
165
|
-
<RotateCcw size={14} />
|
|
166
|
-
Reset
|
|
167
|
-
</button>
|
|
168
|
-
<button
|
|
169
|
-
type="button"
|
|
170
|
-
onClick={() => fileInputRef.current?.click()}
|
|
171
|
-
style={chipBtn(true)}
|
|
172
|
-
onMouseEnter={(e) =>
|
|
173
|
-
(e.currentTarget.style.background = "rgba(138, 150, 240, 0.95)")
|
|
174
|
-
}
|
|
175
|
-
onMouseLeave={(e) =>
|
|
176
|
-
(e.currentTarget.style.background = "rgba(138, 150, 240, 0.85)")
|
|
177
|
-
}
|
|
178
|
-
>
|
|
179
|
-
<Upload size={14} />
|
|
180
|
-
Open .pptx
|
|
181
|
-
</button>
|
|
182
|
-
<input
|
|
183
|
-
ref={fileInputRef}
|
|
184
|
-
type="file"
|
|
185
|
-
accept=".pptx,application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
186
|
-
onChange={async (e) => {
|
|
187
|
-
const file = e.target.files?.[0];
|
|
188
|
-
if (file) await loadFromFile(file);
|
|
189
|
-
e.target.value = "";
|
|
190
|
-
}}
|
|
191
|
-
style={{ display: "none" }}
|
|
192
|
-
/>
|
|
193
|
-
</div>
|
|
194
|
-
|
|
195
|
-
<div style={{ flex: 1, position: "relative", minHeight: 0 }}>
|
|
196
|
-
<SlidewiseEditor
|
|
197
|
-
ref={editorRef}
|
|
198
|
-
deck={deck}
|
|
199
|
-
onChange={(next) => {
|
|
200
|
-
if (import.meta.env.DEV) {
|
|
201
|
-
console.debug("[slidewise] onChange", next.slides.length, "slides");
|
|
202
|
-
}
|
|
203
|
-
}}
|
|
204
|
-
onSave={handleSave}
|
|
205
|
-
onExport={handleExportPptx}
|
|
206
|
-
/>
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
{overlay && (
|
|
210
|
-
<div
|
|
211
|
-
style={{
|
|
212
|
-
position: "fixed",
|
|
213
|
-
inset: 0,
|
|
214
|
-
display: "flex",
|
|
215
|
-
alignItems: "center",
|
|
216
|
-
justifyContent: "center",
|
|
217
|
-
background: "rgba(15, 23, 42, 0.45)",
|
|
218
|
-
color: "#fff",
|
|
219
|
-
fontFamily: "Inter, system-ui, sans-serif",
|
|
220
|
-
fontSize: 18,
|
|
221
|
-
fontWeight: 600,
|
|
222
|
-
zIndex: 999,
|
|
223
|
-
pointerEvents: "none",
|
|
224
|
-
}}
|
|
225
|
-
>
|
|
226
|
-
<div
|
|
227
|
-
style={{
|
|
228
|
-
padding: "16px 28px",
|
|
229
|
-
background: "rgba(15, 23, 42, 0.85)",
|
|
230
|
-
borderRadius: 14,
|
|
231
|
-
border: "1px dashed rgba(255,255,255,0.45)",
|
|
232
|
-
backdropFilter: "blur(12px)",
|
|
233
|
-
}}
|
|
234
|
-
>
|
|
235
|
-
{overlay}
|
|
236
|
-
</div>
|
|
237
|
-
</div>
|
|
238
|
-
)}
|
|
239
|
-
</div>
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function chipBtn(primary: boolean): React.CSSProperties {
|
|
244
|
-
return {
|
|
245
|
-
display: "inline-flex",
|
|
246
|
-
alignItems: "center",
|
|
247
|
-
gap: 6,
|
|
248
|
-
height: 28,
|
|
249
|
-
padding: "0 12px",
|
|
250
|
-
borderRadius: 999,
|
|
251
|
-
border: primary
|
|
252
|
-
? "1px solid rgba(138, 150, 240, 0.85)"
|
|
253
|
-
: "1px solid rgba(255,255,255,0.16)",
|
|
254
|
-
background: primary ? "rgba(138, 150, 240, 0.85)" : "transparent",
|
|
255
|
-
color: primary ? "#0E1330" : "#fff",
|
|
256
|
-
fontSize: 12,
|
|
257
|
-
fontWeight: 600,
|
|
258
|
-
cursor: "pointer",
|
|
259
|
-
fontFamily: "inherit",
|
|
260
|
-
};
|
|
261
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { useEditor } from "@/lib/StoreProvider";
|
|
2
|
-
import type { Deck } from "@/lib/types";
|
|
3
|
-
import { TopBar } from "./TopBar";
|
|
4
|
-
import { SlideRail } from "./SlideRail";
|
|
5
|
-
import { Canvas } from "./Canvas";
|
|
6
|
-
import { BottomToolbar } from "./BottomToolbar";
|
|
7
|
-
import { PlayMode } from "./PlayMode";
|
|
8
|
-
import { GridView } from "./GridView";
|
|
9
|
-
|
|
10
|
-
interface EditorProps {
|
|
11
|
-
showTopBar?: boolean;
|
|
12
|
-
onSave?: (deck: Deck) => void | Promise<void>;
|
|
13
|
-
onExport?: (deck: Deck) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function Editor({ showTopBar = true, onSave, onExport }: EditorProps = {}) {
|
|
17
|
-
const playing = useEditor((s) => s.playing);
|
|
18
|
-
const view = useEditor((s) => s.view);
|
|
19
|
-
const theme = useEditor((s) => s.theme);
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<div
|
|
23
|
-
className={`slidewise-editor theme-${theme}`}
|
|
24
|
-
style={{
|
|
25
|
-
width: "100%",
|
|
26
|
-
height: "100%",
|
|
27
|
-
display: "flex",
|
|
28
|
-
flexDirection: "column",
|
|
29
|
-
background: "var(--app-bg)",
|
|
30
|
-
color: "var(--ink)",
|
|
31
|
-
overflow: "hidden",
|
|
32
|
-
}}
|
|
33
|
-
>
|
|
34
|
-
{showTopBar && <TopBar onSave={onSave} onExport={onExport} />}
|
|
35
|
-
<div
|
|
36
|
-
style={{
|
|
37
|
-
flex: 1,
|
|
38
|
-
display: "flex",
|
|
39
|
-
overflow: "hidden",
|
|
40
|
-
position: "relative",
|
|
41
|
-
}}
|
|
42
|
-
>
|
|
43
|
-
<SlideRail />
|
|
44
|
-
<div style={{ flex: 1, display: "flex", position: "relative" }}>
|
|
45
|
-
<Canvas />
|
|
46
|
-
<BottomToolbar />
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
{view === "grid" && <GridView />}
|
|
50
|
-
{playing && <PlayMode />}
|
|
51
|
-
</div>
|
|
52
|
-
);
|
|
53
|
-
}
|