@leviosa-ai/detail-page-editor 0.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/components/cardnews/color-input.tsx +380 -0
- package/components/detail-page/ai-generate-panel.tsx +763 -0
- package/components/detail-page/annotation-canvas.tsx +860 -0
- package/components/detail-page/annotation-dialog.tsx +397 -0
- package/components/detail-page/bubble-tail-overlay.tsx +209 -0
- package/components/detail-page/canvas-context-menu.tsx +234 -0
- package/components/detail-page/canvas-observer.tsx +68 -0
- package/components/detail-page/canvas-overlay-host.tsx +44 -0
- package/components/detail-page/canvas-store-facade.ts +51 -0
- package/components/detail-page/chart-inspector.tsx +531 -0
- package/components/detail-page/design-reference-picker.tsx +383 -0
- package/components/detail-page/detail-page-basic-shapes.tsx +180 -0
- package/components/detail-page/detail-page-brand-gifs-panel.tsx +261 -0
- package/components/detail-page/detail-page-brand-kit-panel.tsx +77 -0
- package/components/detail-page/detail-page-brand-panel-header.tsx +44 -0
- package/components/detail-page/detail-page-charts-panel.tsx +104 -0
- package/components/detail-page/detail-page-decorations-panel.tsx +125 -0
- package/components/detail-page/detail-page-download-dialog.tsx +524 -0
- package/components/detail-page/detail-page-editor.tsx +974 -0
- package/components/detail-page/detail-page-elements-panel.tsx +117 -0
- package/components/detail-page/detail-page-font-picker.tsx +231 -0
- package/components/detail-page/detail-page-history-buttons.tsx +87 -0
- package/components/detail-page/detail-page-host-context.tsx +465 -0
- package/components/detail-page/detail-page-icons-panel.tsx +251 -0
- package/components/detail-page/detail-page-konva-json-preview.tsx +474 -0
- package/components/detail-page/detail-page-layers-panel.tsx +618 -0
- package/components/detail-page/detail-page-my-images-panel.tsx +179 -0
- package/components/detail-page/detail-page-my-shapes-panel.tsx +174 -0
- package/components/detail-page/detail-page-page-toolbar.tsx +171 -0
- package/components/detail-page/detail-page-pages-panel.tsx +299 -0
- package/components/detail-page/detail-page-pages-timeline.tsx +79 -0
- package/components/detail-page/detail-page-photos-panel.tsx +257 -0
- package/components/detail-page/detail-page-properties-panel.tsx +2680 -0
- package/components/detail-page/detail-page-qr-panel.tsx +137 -0
- package/components/detail-page/detail-page-references-panel.tsx +166 -0
- package/components/detail-page/detail-page-selection.ts +44 -0
- package/components/detail-page/detail-page-shapes-panel.tsx +16 -0
- package/components/detail-page/detail-page-sidebar-sections.tsx +324 -0
- package/components/detail-page/detail-page-tables-panel.tsx +106 -0
- package/components/detail-page/detail-page-text-panel.tsx +179 -0
- package/components/detail-page/detail-page-thumbnail-bus.ts +42 -0
- package/components/detail-page/edit-quota-ui.tsx +150 -0
- package/components/detail-page/editor-history.ts +56 -0
- package/components/detail-page/editor-hotkeys.tsx +165 -0
- package/components/detail-page/element-recents-strip.tsx +105 -0
- package/components/detail-page/element-rects.ts +82 -0
- package/components/detail-page/fill-control.tsx +261 -0
- package/components/detail-page/find-replace-panel.tsx +252 -0
- package/components/detail-page/generation-progress-fill.tsx +57 -0
- package/components/detail-page/gif-animator.tsx +184 -0
- package/components/detail-page/gif-effect-picker.tsx +136 -0
- package/components/detail-page/group-drill-in.tsx +219 -0
- package/components/detail-page/group-prompt-edit-panel.tsx +154 -0
- package/components/detail-page/hover-highlight-overlay.tsx +111 -0
- package/components/detail-page/hovered-layer.ts +34 -0
- package/components/detail-page/inspector-controls.tsx +175 -0
- package/components/detail-page/leviosa-canvas-workspace.tsx +313 -0
- package/components/detail-page/nested-selection-overlay.tsx +364 -0
- package/components/detail-page/panel-search-input.tsx +49 -0
- package/components/detail-page/prompt-edit-panel.tsx +146 -0
- package/components/detail-page/section-height-handle.tsx +276 -0
- package/components/detail-page/section-reauthor-controller.tsx +210 -0
- package/components/detail-page/spec-prompt-edit-panel.tsx +178 -0
- package/components/detail-page/spec-resize-absorber.tsx +149 -0
- package/components/detail-page/svg-prompt-edit-panel.tsx +139 -0
- package/components/detail-page/table-canvas-overlay.tsx +707 -0
- package/components/detail-page/table-inspector.tsx +586 -0
- package/components/ui/button.tsx +64 -0
- package/components/ui/command.tsx +175 -0
- package/components/ui/dialog.tsx +167 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/select.tsx +190 -0
- package/components/ui/slider.tsx +63 -0
- package/components/ui/switch.tsx +35 -0
- package/components/ui/tooltip.tsx +57 -0
- package/config/detail-page-bundle-font-tags.json +39 -0
- package/config/detail-page-fonts.json +373 -0
- package/index.ts +24 -0
- package/lib/cardnews/font-loader.ts +306 -0
- package/lib/detail-page/animation-sniff.ts +101 -0
- package/lib/detail-page/asset-bytes-url.ts +70 -0
- package/lib/detail-page/basic-shapes.ts +603 -0
- package/lib/detail-page/canvas-menu.ts +182 -0
- package/lib/detail-page/chart/defaults.ts +283 -0
- package/lib/detail-page/chart/format.ts +66 -0
- package/lib/detail-page/chart/normalize.ts +149 -0
- package/lib/detail-page/chart/palette.ts +85 -0
- package/lib/detail-page/chart/paste.ts +82 -0
- package/lib/detail-page/chart/preview.ts +78 -0
- package/lib/detail-page/chart/render.ts +188 -0
- package/lib/detail-page/chart/renderers/bar-h.ts +103 -0
- package/lib/detail-page/chart/renderers/bar-v.ts +213 -0
- package/lib/detail-page/chart/renderers/donut.ts +186 -0
- package/lib/detail-page/chart/renderers/gauge.ts +96 -0
- package/lib/detail-page/chart/renderers/line.ts +143 -0
- package/lib/detail-page/chart/renderers/shared.ts +263 -0
- package/lib/detail-page/chart/renderers/stack.ts +121 -0
- package/lib/detail-page/chart/sync.ts +92 -0
- package/lib/detail-page/chart/types.ts +145 -0
- package/lib/detail-page/css-color.ts +34 -0
- package/lib/detail-page/data-gif-payload.ts +167 -0
- package/lib/detail-page/design-reference.ts +244 -0
- package/lib/detail-page/distribute.ts +89 -0
- package/lib/detail-page/editor-routes.ts +10 -0
- package/lib/detail-page/element-recents.ts +167 -0
- package/lib/detail-page/find-replace.ts +120 -0
- package/lib/detail-page/format-painter.ts +159 -0
- package/lib/detail-page/gif-frames.ts +216 -0
- package/lib/detail-page/gif-timeline.ts +65 -0
- package/lib/detail-page/group-action.ts +62 -0
- package/lib/detail-page/hangul-roman.ts +58 -0
- package/lib/detail-page/icon-curated.ts +290 -0
- package/lib/detail-page/icon-fuzzy.ts +250 -0
- package/lib/detail-page/icon-keywords.generated.ts +3766 -0
- package/lib/detail-page/icon-keywords.ko.ts +524 -0
- package/lib/detail-page/icon-search.ts +257 -0
- package/lib/detail-page/icons.ts +67 -0
- package/lib/detail-page/image-credit.ts +121 -0
- package/lib/detail-page/image-data-uri.ts +29 -0
- package/lib/detail-page/insert-image.ts +75 -0
- package/lib/detail-page/insert-shape.ts +49 -0
- package/lib/detail-page/korean-dictionary.ts +187 -0
- package/lib/detail-page/layer-move.ts +257 -0
- package/lib/detail-page/new-product-draft-store.ts +105 -0
- package/lib/detail-page/new-product-draft.ts +283 -0
- package/lib/detail-page/personal-images-refresh.ts +30 -0
- package/lib/detail-page/product-source.ts +587 -0
- package/lib/detail-page/qr-code.ts +214 -0
- package/lib/detail-page/reference-image.ts +103 -0
- package/lib/detail-page/reference-upload.ts +181 -0
- package/lib/detail-page/replace-page.ts +35 -0
- package/lib/detail-page/replace-with-gif.ts +251 -0
- package/lib/detail-page/section-height.ts +131 -0
- package/lib/detail-page/section-reauthor-bus.ts +61 -0
- package/lib/detail-page/shape-to-image.ts +219 -0
- package/lib/detail-page/spec-group/clipboard.ts +35 -0
- package/lib/detail-page/spec-group/sync.ts +377 -0
- package/lib/detail-page/spec-group/text-metrics.ts +48 -0
- package/lib/detail-page/stock-photos.ts +88 -0
- package/lib/detail-page/svg-colors.ts +74 -0
- package/lib/detail-page/table/defaults.ts +344 -0
- package/lib/detail-page/table/edit.ts +312 -0
- package/lib/detail-page/table/harvest.ts +103 -0
- package/lib/detail-page/table/normalize.ts +125 -0
- package/lib/detail-page/table/paste.ts +49 -0
- package/lib/detail-page/table/preview.ts +66 -0
- package/lib/detail-page/table/render.ts +388 -0
- package/lib/detail-page/table/sync.ts +107 -0
- package/lib/detail-page/table/types.ts +102 -0
- package/lib/detail-page/text-gif-layout.ts +245 -0
- package/lib/detail-page/text-gif-payload.ts +67 -0
- package/lib/detail-page/text-presets.ts +230 -0
- package/lib/detail-page/use-fake-progress.ts +67 -0
- package/lib/detail-page/z-order.ts +82 -0
- package/lib/detail-page-canvas/bubble-path.ts +190 -0
- package/lib/detail-page-canvas/canvas-key.ts +22 -0
- package/lib/detail-page-canvas/clip-rect.ts +65 -0
- package/lib/detail-page-canvas/custom-props-adapter.ts +219 -0
- package/lib/detail-page-canvas/editor-fonts.ts +161 -0
- package/lib/detail-page-canvas/export/ai.ts +413 -0
- package/lib/detail-page-canvas/export/color.ts +35 -0
- package/lib/detail-page-canvas/export/document-model.ts +78 -0
- package/lib/detail-page-canvas/export/export-files.ts +290 -0
- package/lib/detail-page-canvas/export/font-subset.ts +264 -0
- package/lib/detail-page-canvas/export/gif-export.ts +310 -0
- package/lib/detail-page-canvas/export/gif-plan.ts +95 -0
- package/lib/detail-page-canvas/export/mp4-encode.ts +158 -0
- package/lib/detail-page-canvas/export/mp4-support.ts +22 -0
- package/lib/detail-page-canvas/export/pdf/geometry.ts +309 -0
- package/lib/detail-page-canvas/export/pdf/inline-svg.ts +399 -0
- package/lib/detail-page-canvas/export/pdf/resources.ts +443 -0
- package/lib/detail-page-canvas/export/pdf/text-ops.ts +81 -0
- package/lib/detail-page-canvas/export/pdf/writer.ts +109 -0
- package/lib/detail-page-canvas/export/psd.ts +354 -0
- package/lib/detail-page-canvas/export/raster.ts +255 -0
- package/lib/detail-page-canvas/export/svg.ts +479 -0
- package/lib/detail-page-canvas/export/text-layout.ts +142 -0
- package/lib/detail-page-canvas/font-catalog.ts +177 -0
- package/lib/detail-page-canvas/font-tags.ts +47 -0
- package/lib/detail-page-canvas/gif-web-fonts.ts +90 -0
- package/lib/detail-page-canvas/konva-fallback.ts +477 -0
- package/lib/detail-page-canvas/text-background-plan.ts +124 -0
- package/lib/detail-page-canvas/text-highlight-bands.ts +127 -0
- package/lib/utils.ts +10 -0
- package/package.json +68 -0
- package/types/commerce.ts +171 -0
- package/types/detail-page-api.ts +200 -0
- package/types/detail-page-canvas.ts +20 -0
- package/types/fontkit.d.ts +42 -0
- package/types/gifenc.d.ts +51 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import Konva from "konva";
|
|
2
|
+
|
|
3
|
+
import { isTransparentColor } from "./text-background-plan";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A per-line "marker" highlight: instead of one box behind the whole text (which,
|
|
7
|
+
* with a loose paragraph line-height, balloons into a solid block that swallows the
|
|
8
|
+
* gap between wrapped lines), we paint ONE band per visual line, each hugging that
|
|
9
|
+
* line's own text width. That reads as a highlighter pen — bands differ in width and
|
|
10
|
+
* a gap shows between lines.
|
|
11
|
+
*
|
|
12
|
+
* The line breaks MUST match what the editor/preview actually render, so we measure
|
|
13
|
+
* with an offscreen ``Konva.Text`` configured identically (same font, width, wrap,
|
|
14
|
+
* line-height). Konva populates ``textArr`` (its wrapped-line layout) in the
|
|
15
|
+
* constructor without needing a stage, so this is synchronous and deterministic —
|
|
16
|
+
* and, being the same engine, it wraps Korean no-space runs by character exactly as
|
|
17
|
+
* the visible text does.
|
|
18
|
+
*
|
|
19
|
+
* The band is stored on the element as ``custom.highlightColor`` (a single source of
|
|
20
|
+
* truth); the stock editor's native ``backgroundEnabled`` box is left off so the two never
|
|
21
|
+
* double up. Both renderers — the SDK editor (``BackgroundAwareText``) and the Konva
|
|
22
|
+
* export/preview — draw bands from the same computation here.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export type HighlightBand = {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
cornerRadius: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type HighlightBandInput = {
|
|
34
|
+
text: string;
|
|
35
|
+
fontSize: number;
|
|
36
|
+
fontFamily?: string;
|
|
37
|
+
fontWeight?: unknown;
|
|
38
|
+
fontStyle?: unknown;
|
|
39
|
+
/** The text box width the visible text wraps against. */
|
|
40
|
+
boxWidth: number;
|
|
41
|
+
/** Unitless line-height ratio (px strings must be resolved by the caller). */
|
|
42
|
+
lineHeightRatio: number;
|
|
43
|
+
align?: string;
|
|
44
|
+
color?: unknown;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Band geometry, all relative to fontSize so it scales with the text.
|
|
48
|
+
const PAD_X_RATIO = 0.16; // horizontal overhang past the glyphs on each side
|
|
49
|
+
const BAND_H_RATIO = 1.3; // band height around the glyphs (tight, not the line box)
|
|
50
|
+
|
|
51
|
+
/** Resolve a Canvas/CSS line-height (number ratio or ``"30.78px"``) to a ratio. */
|
|
52
|
+
export function lineHeightRatioFor(raw: unknown, fontSize: number): number {
|
|
53
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return raw;
|
|
54
|
+
if (typeof raw === "string") {
|
|
55
|
+
const px = /^(-?\d*\.?\d+)px$/.exec(raw.trim());
|
|
56
|
+
if (px && fontSize > 0) {
|
|
57
|
+
const ratio = parseFloat(px[1]) / fontSize;
|
|
58
|
+
if (Number.isFinite(ratio) && ratio > 0) return ratio;
|
|
59
|
+
}
|
|
60
|
+
const n = parseFloat(raw);
|
|
61
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
62
|
+
}
|
|
63
|
+
return 1.2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function konvaFontStyle(fontWeight: unknown, fontStyle: unknown): string {
|
|
67
|
+
const bold = /(bold|[6-9]00)/i.test(String(fontWeight ?? ""));
|
|
68
|
+
const italic = String(fontStyle ?? "") === "italic";
|
|
69
|
+
return [bold ? "bold" : "", italic ? "italic" : ""].join(" ").trim() || "normal";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Wrapped lines with their pixel widths, via an offscreen Konva.Text. */
|
|
73
|
+
export function measureHighlightLines(
|
|
74
|
+
input: HighlightBandInput,
|
|
75
|
+
): { text: string; width: number }[] {
|
|
76
|
+
const node = new Konva.Text({
|
|
77
|
+
text: String(input.text ?? ""),
|
|
78
|
+
fontSize: input.fontSize,
|
|
79
|
+
fontFamily: input.fontFamily || "Arial",
|
|
80
|
+
fontStyle: konvaFontStyle(input.fontWeight, input.fontStyle),
|
|
81
|
+
lineHeight: input.lineHeightRatio,
|
|
82
|
+
width: input.boxWidth > 0 ? input.boxWidth : undefined,
|
|
83
|
+
wrap: input.boxWidth > 0 ? "word" : "none",
|
|
84
|
+
});
|
|
85
|
+
const arr = (node.textArr ?? []) as Array<{ text?: string; width?: number }>;
|
|
86
|
+
const lines = arr.map((l) => ({
|
|
87
|
+
text: String(l.text ?? ""),
|
|
88
|
+
width: Number(l.width) || 0,
|
|
89
|
+
}));
|
|
90
|
+
const fallbackWidth = (() => {
|
|
91
|
+
try {
|
|
92
|
+
return node.getTextWidth();
|
|
93
|
+
} catch {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
node.destroy();
|
|
98
|
+
return lines.length
|
|
99
|
+
? lines
|
|
100
|
+
: [{ text: String(input.text ?? ""), width: fallbackWidth }];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Per-line marker bands in the text's LOCAL coordinate space (origin at the text
|
|
105
|
+
* box's top-left, before the element's own x/y/rotation transform). Blank lines
|
|
106
|
+
* produce no band. Returns [] when there is nothing to paint.
|
|
107
|
+
*/
|
|
108
|
+
export function computeHighlightBands(input: HighlightBandInput): HighlightBand[] {
|
|
109
|
+
const fs = input.fontSize;
|
|
110
|
+
if (!(fs > 0) || isTransparentColor(input.color)) return [];
|
|
111
|
+
const lineH = fs * input.lineHeightRatio;
|
|
112
|
+
const padX = fs * PAD_X_RATIO;
|
|
113
|
+
const bandH = Math.min(fs * BAND_H_RATIO, lineH); // never taller than the line box
|
|
114
|
+
const cornerRadius = Math.min(bandH * 0.28, 6);
|
|
115
|
+
const align = String(input.align ?? "left").toLowerCase();
|
|
116
|
+
const boxWidth = input.boxWidth;
|
|
117
|
+
|
|
118
|
+
return measureHighlightLines(input).flatMap((line, i) => {
|
|
119
|
+
if (!line.text.trim()) return [];
|
|
120
|
+
const w = Math.max(0, line.width) + padX * 2;
|
|
121
|
+
let x = -padX; // left / start
|
|
122
|
+
if (align === "center") x = (boxWidth - line.width) / 2 - padX;
|
|
123
|
+
else if (align === "right" || align === "end") x = boxWidth - line.width - padX;
|
|
124
|
+
const yCenter = i * lineH + lineH / 2;
|
|
125
|
+
return [{ x, y: yCenter - bandH / 2, width: w, height: bandH, cornerRadius }];
|
|
126
|
+
});
|
|
127
|
+
}
|
package/lib/utils.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx"
|
|
2
|
+
import { twMerge } from "tailwind-merge"
|
|
3
|
+
|
|
4
|
+
export function cn(...inputs: ClassValue[]) {
|
|
5
|
+
return twMerge(clsx(inputs))
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function clamp(value: number, min: number, max: number): number {
|
|
9
|
+
return Math.max(min, Math.min(max, value));
|
|
10
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leviosa-ai/detail-page-editor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Leviosa detail-page editor shell. Source-only: consumers compile it through Next transpilePackages. Every app coupling (sourcing server, brand store, cache keys, toast) arrives through the DetailPageHost interface.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Leviosa-AI/leviosa-canvas.git",
|
|
10
|
+
"directory": "packages/detail-page-editor"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public",
|
|
14
|
+
"provenance": true
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./index.ts",
|
|
19
|
+
"./package.json": "./package.json",
|
|
20
|
+
"./host": "./components/detail-page/detail-page-host-context.tsx",
|
|
21
|
+
"./types": "./types/detail-page-canvas.ts",
|
|
22
|
+
"./api-types": "./types/detail-page-api.ts",
|
|
23
|
+
"./commerce-types": "./types/commerce.ts",
|
|
24
|
+
"./components/canvas-store-facade": "./components/detail-page/canvas-store-facade.ts",
|
|
25
|
+
"./components/editor-history": "./components/detail-page/editor-history.ts",
|
|
26
|
+
"./components/hovered-layer": "./components/detail-page/hovered-layer.ts",
|
|
27
|
+
"./components/element-rects": "./components/detail-page/element-rects.ts",
|
|
28
|
+
"./components/detail-page-selection": "./components/detail-page/detail-page-selection.ts",
|
|
29
|
+
"./components/detail-page-thumbnail-bus": "./components/detail-page/detail-page-thumbnail-bus.ts",
|
|
30
|
+
"./components/*": "./components/detail-page/*.tsx",
|
|
31
|
+
"./lib/*": "./lib/detail-page/*.ts",
|
|
32
|
+
"./canvas/*": "./lib/detail-page-canvas/*.ts"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"index.ts",
|
|
36
|
+
"components",
|
|
37
|
+
"config",
|
|
38
|
+
"lib",
|
|
39
|
+
"types",
|
|
40
|
+
"!**/__tests__"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@dnd-kit/core": "^6.3.1",
|
|
44
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
45
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
46
|
+
"ag-psd": "^28.5.1",
|
|
47
|
+
"fontkit": "^2.0.4",
|
|
48
|
+
"gifenc": "^1.0.3",
|
|
49
|
+
"gifuct-js": "^2.1.2",
|
|
50
|
+
"jszip": "^3.10.1",
|
|
51
|
+
"mp4-muxer": "^5.2.2",
|
|
52
|
+
"qrcode-generator": "^2.0.4",
|
|
53
|
+
"woff2-encoder": "2.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@leviosa-ai/canvas": "^0.1.1",
|
|
57
|
+
"@leviosa-ai/konva": "^0.8.0",
|
|
58
|
+
"@tanstack/react-query": "^5.100.3",
|
|
59
|
+
"konva": "^10.0.0",
|
|
60
|
+
"lucide-react": "^0.563.0",
|
|
61
|
+
"next": "^16.0.0",
|
|
62
|
+
"radix-ui": "^1.4.3",
|
|
63
|
+
"react": "^19.0.0",
|
|
64
|
+
"react-dom": "^19.0.0",
|
|
65
|
+
"react-i18next": "^16.0.0",
|
|
66
|
+
"react-konva": "^19.0.0"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 편집기가 커머스 플랫폼 응답에서 **실제로 읽는 것**만.
|
|
3
|
+
*
|
|
4
|
+
* 프리필(업로드 3루트)이 카페24·네이버·쿠팡 상품을 읽어 설문을 채운다. 그래서 편집기는
|
|
5
|
+
* 이 응답들의 모양을 어느 정도 알아야 한다 — 완전히 불투명하게 두면 프리필이 못 선다.
|
|
6
|
+
*
|
|
7
|
+
* 다만 앱의 커머스 타입 전체(수백 줄)를 여기 옮기지는 않았다. 편집기는 카페24 클라이언트가
|
|
8
|
+
* 아니고, 소비자(agency)는 그 타입을 갖고 있지도 않다. 그래서 **읽는 필드만** 선언한다.
|
|
9
|
+
*
|
|
10
|
+
* ## 드리프트는 어떻게 잡히나
|
|
11
|
+
*
|
|
12
|
+
* 앱이 자기 진짜 타입으로 `DetailPageHost` 를 구현할 때 tsc 가 대입 가능성을 잰다.
|
|
13
|
+
* 앱 쪽에서 여기 적힌 필드가 사라지면 그 자리에서 즉시 죽는다 — 우리가 앱 타입을
|
|
14
|
+
* 직접 가져오지 않고도 계약이 지켜지는 이유다. 반대로 앱에 필드가 **더** 있는 것은
|
|
15
|
+
* 아무 문제가 없다(구조적 타이핑).
|
|
16
|
+
*
|
|
17
|
+
* 여는 쪽은 전부 옵셔널로 뒀다. 편집기 코드가 이미 옵셔널 체이닝과 방어적 문자열
|
|
18
|
+
* 추출로 읽고 있어서, 여기서 필수로 못 박으면 앱의 진짜 타입이 더 느슨할 때 대입이
|
|
19
|
+
* 깨진다. 이 파일은 "최소한 이만큼은 온다"가 아니라 "이만큼을 본다"는 선언이다.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** 진열·판매 플래그. */
|
|
23
|
+
export type Cafe24Flag = "T" | "F";
|
|
24
|
+
|
|
25
|
+
// ── 카페24 ────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
export interface Cafe24ProductListParams {
|
|
28
|
+
page?: number;
|
|
29
|
+
page_size?: number;
|
|
30
|
+
query?: string;
|
|
31
|
+
category_name?: string;
|
|
32
|
+
include_sub_category?: boolean;
|
|
33
|
+
display?: Cafe24Flag;
|
|
34
|
+
selling?: Cafe24Flag;
|
|
35
|
+
include_details?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 목록 한 줄.
|
|
40
|
+
*
|
|
41
|
+
* 번호와 이름이 **필수**인 것은 우리가 그렇게 정해서가 아니라, 호스트가 꽂아 줄
|
|
42
|
+
* `getCafe24ProductNo` 가 그 둘을 요구하기 때문이다. 여기서 옵셔널로 열어 두면 앱이
|
|
43
|
+
* 자기 함수를 못 꽂는다 — 인자는 반변이라 우리 쪽이 더 느슨하면 대입이 깨진다.
|
|
44
|
+
*/
|
|
45
|
+
export interface Cafe24ProductSummary {
|
|
46
|
+
product_no: number;
|
|
47
|
+
origin_product_no: number;
|
|
48
|
+
product_name: string;
|
|
49
|
+
name: string;
|
|
50
|
+
list_image?: string | null;
|
|
51
|
+
tiny_image?: string | null;
|
|
52
|
+
small_image?: string | null;
|
|
53
|
+
detail_image?: string | null;
|
|
54
|
+
/**
|
|
55
|
+
* 카페24 목록은 계정 설정에 따라 필드가 늘고 준다. 앱의 타입도 이 서명을 갖고 있고,
|
|
56
|
+
* 없으면 앱 함수를 못 꽂는다 — 색인 서명이 있는 타입에는 없는 타입을 못 넣는다.
|
|
57
|
+
*/
|
|
58
|
+
[key: string]: unknown;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface Cafe24ProductListPage {
|
|
62
|
+
items: Cafe24ProductSummary[];
|
|
63
|
+
page_size: number;
|
|
64
|
+
total: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 상세. 나머지 필드는 코드가 `Record<string, unknown>` 으로 캐스팅해 방어적으로 읽으므로
|
|
69
|
+
* 여기 적힌 것이 편집기가 **타입으로** 아는 전부다.
|
|
70
|
+
*/
|
|
71
|
+
export interface Cafe24ProductDetailPayload {
|
|
72
|
+
_cafe24_raw?: unknown;
|
|
73
|
+
originProduct?: {
|
|
74
|
+
name?: string | null;
|
|
75
|
+
summary_description?: string | null;
|
|
76
|
+
simple_description?: string | null;
|
|
77
|
+
detailContent?: string | null;
|
|
78
|
+
images?: { representativeImage?: { url?: string | null } | null } | null;
|
|
79
|
+
} | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 새로고침 힌트. 상세·옵션·카테고리 조회가 공유한다. */
|
|
83
|
+
export interface Cafe24RefreshParams {
|
|
84
|
+
force_refresh?: boolean;
|
|
85
|
+
known_product_info_version?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── 네이버 ────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
export interface NaverProductListParams {
|
|
91
|
+
page?: number;
|
|
92
|
+
size?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface NaverProductListPage {
|
|
96
|
+
contents: Array<{
|
|
97
|
+
originProductNo: number | string;
|
|
98
|
+
channelProducts?: Array<{
|
|
99
|
+
name?: string | null;
|
|
100
|
+
representativeImage?: { url?: string | null } | null;
|
|
101
|
+
}> | null;
|
|
102
|
+
}>;
|
|
103
|
+
totalPages?: number | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface NaverProductDetailPayload {
|
|
107
|
+
originProduct?: {
|
|
108
|
+
name?: string | null;
|
|
109
|
+
detailContent?: string | null;
|
|
110
|
+
leafCategoryId?: string | number | null;
|
|
111
|
+
images?: {
|
|
112
|
+
representativeImage?: { url?: string | null } | null;
|
|
113
|
+
optionalImages?: Array<{ url?: string | null } | null> | null;
|
|
114
|
+
} | null;
|
|
115
|
+
detailAttribute?: {
|
|
116
|
+
optionInfo?: {
|
|
117
|
+
optionSimple?: Array<{
|
|
118
|
+
groupName?: string | null;
|
|
119
|
+
name?: string | null;
|
|
120
|
+
}> | null;
|
|
121
|
+
} | null;
|
|
122
|
+
originAreaInfo?: { content?: string | null } | null;
|
|
123
|
+
naverShoppingSearchInfo?: { brandName?: string | null } | null;
|
|
124
|
+
seoInfo?: { sellerTags?: Array<{ text?: string | null }> | null } | null;
|
|
125
|
+
} | null;
|
|
126
|
+
deliveryInfo?: {
|
|
127
|
+
deliveryCompany?: string | null;
|
|
128
|
+
deliveryFee?: { baseFee?: number | string | null } | null;
|
|
129
|
+
claimDeliveryInfo?: { returnDeliveryFee?: number | string | null } | null;
|
|
130
|
+
} | null;
|
|
131
|
+
} | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── 쿠팡 ──────────────────────────────────────────────────────
|
|
135
|
+
//
|
|
136
|
+
// 쿠팡만 소싱 서버가 아니라 자체 클라이언트다. 그래서 호출 모양도 다르다
|
|
137
|
+
// (`{ signal }` 을 옵션 객체로 받는다).
|
|
138
|
+
|
|
139
|
+
export interface CoupangListListingsParams {
|
|
140
|
+
nextToken?: string;
|
|
141
|
+
maxPerPage?: number;
|
|
142
|
+
name?: string;
|
|
143
|
+
signal?: AbortSignal;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface CoupangListingListPage {
|
|
147
|
+
data: Array<{
|
|
148
|
+
sellerProductId: number | string;
|
|
149
|
+
sellerProductName?: string | null;
|
|
150
|
+
representativeImage?: string | null;
|
|
151
|
+
}>;
|
|
152
|
+
nextToken?: string | null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface CoupangListingDetail {
|
|
156
|
+
data: {
|
|
157
|
+
sellerProductName?: string | null;
|
|
158
|
+
displayCategoryCode?: string | number | null;
|
|
159
|
+
items?: Array<{
|
|
160
|
+
attributes?: Array<{
|
|
161
|
+
attributeTypeName?: string | null;
|
|
162
|
+
attributeValueName?: string | null;
|
|
163
|
+
}> | null;
|
|
164
|
+
}> | null;
|
|
165
|
+
images?: Array<{
|
|
166
|
+
imageOrder?: number | null;
|
|
167
|
+
cdnPath?: string | null;
|
|
168
|
+
vendorPath?: string | null;
|
|
169
|
+
}> | null;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 편집기가 소싱 서버와 주고받는 모양.
|
|
3
|
+
*
|
|
4
|
+
* 이 타입들은 **편집기의 도메인**이다 — 상세페이지 판독 결과, 프롬프트 편집 결과,
|
|
5
|
+
* 도형 라이브러리, 편집 한도. 그래서 정의가 여기 산다. 소싱 서버 클라이언트를 쓰는
|
|
6
|
+
* 앱(leviosa-frontend)은 이걸 패키지에서 가져다 쓴다. 정의를 양쪽에 두면 그날부터
|
|
7
|
+
* 갈라지므로, 옮긴 것이지 복제한 것이 아니다.
|
|
8
|
+
*
|
|
9
|
+
* 호출 **방법**(base URL·토큰·재시도)은 여기 없다. 그건 호스트가 안다 —
|
|
10
|
+
* `components/detail-page/detail-page-host-context.tsx`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface DetailPagePersonalShapeItem {
|
|
14
|
+
asset_id: string;
|
|
15
|
+
svg: string;
|
|
16
|
+
view_box: string;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
origin?: string;
|
|
20
|
+
created_at?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type DetailPageReferenceImageUploadUrlResponse = Record<string, unknown>;
|
|
24
|
+
|
|
25
|
+
export interface DetailPagePromptEditResult {
|
|
26
|
+
slot_role: string;
|
|
27
|
+
text: string;
|
|
28
|
+
edits_used?: number;
|
|
29
|
+
edit_limit?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DetailPageSvgPromptEditResult {
|
|
33
|
+
slot_role: string;
|
|
34
|
+
svg: string;
|
|
35
|
+
edits_used?: number;
|
|
36
|
+
edit_limit?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DetailPageEditUsageResult {
|
|
40
|
+
svg_used: number;
|
|
41
|
+
svg_limit: number;
|
|
42
|
+
text_used: number;
|
|
43
|
+
text_limit: number;
|
|
44
|
+
unlimited: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 편집 한도 초과(429) 오류 본문. 프론트 하드 차단 + 추가구매 안내에 사용. */
|
|
48
|
+
export interface DetailPageEditQuotaDetail {
|
|
49
|
+
error: "edit_quota_exceeded";
|
|
50
|
+
kind: "svg" | "text";
|
|
51
|
+
used: number;
|
|
52
|
+
limit: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 그룹 프롬프트 편집: 대상 요소 한 개(텍스트/도형/차트·표)의 입력 형태. */
|
|
56
|
+
export interface DetailPageGroupEditItem {
|
|
57
|
+
id: string;
|
|
58
|
+
kind: "text" | "svg" | "data";
|
|
59
|
+
current_text?: string;
|
|
60
|
+
current_svg?: string;
|
|
61
|
+
slot_role?: string;
|
|
62
|
+
render_kind?: string;
|
|
63
|
+
max_length?: number;
|
|
64
|
+
/**
|
|
65
|
+
* 차트·표(``kind: "data"``)일 때의 스펙.
|
|
66
|
+
*
|
|
67
|
+
* 자식 글자가 아니라 스펙을 주고받는다 — 차트·표의 자식은 스펙에서 매번 다시
|
|
68
|
+
* 그려지므로, 글자를 고쳐 받으면 다음 편집 한 번에 덮인다.
|
|
69
|
+
*/
|
|
70
|
+
spec_kind?: "chart" | "table";
|
|
71
|
+
current_spec?: unknown;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 그룹 프롬프트 편집 결과: 요소별 수정 결과. */
|
|
75
|
+
export interface DetailPageGroupEditResultItem {
|
|
76
|
+
id: string;
|
|
77
|
+
kind: "text" | "svg" | "data";
|
|
78
|
+
text?: string | null;
|
|
79
|
+
svg?: string | null;
|
|
80
|
+
/** data일 때 수정된 스펙. 변경이 없으면 안 온다(원본 유지). */
|
|
81
|
+
spec?: unknown;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface DetailPageGroupPromptEditResult {
|
|
85
|
+
results: DetailPageGroupEditResultItem[];
|
|
86
|
+
text_used: number;
|
|
87
|
+
text_limit: number;
|
|
88
|
+
svg_used: number;
|
|
89
|
+
svg_limit: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface DetailPageImageEditResult {
|
|
93
|
+
success: boolean;
|
|
94
|
+
message?: string;
|
|
95
|
+
slot_role?: string;
|
|
96
|
+
asset_id?: string;
|
|
97
|
+
url?: string;
|
|
98
|
+
s3_key?: string;
|
|
99
|
+
tier?: string;
|
|
100
|
+
provider_model?: string;
|
|
101
|
+
credit_feature?: string;
|
|
102
|
+
credit_remaining?: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 크레딧 부족(402) 오류 본문. 프론트 부족 안내 + 추가구매 CTA에 사용. */
|
|
106
|
+
export interface DetailPageInsufficientCreditsDetail {
|
|
107
|
+
error: "insufficient_credits";
|
|
108
|
+
feature: string;
|
|
109
|
+
remaining: number;
|
|
110
|
+
message: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- 도형(SVG) 라이브러리 -----------------------------------------------------
|
|
114
|
+
|
|
115
|
+
/** 공용 도형 라이브러리 한 건(정적 카탈로그, 인라인 SVG 마크업). */
|
|
116
|
+
export interface DetailPageShapeLibraryItem {
|
|
117
|
+
id: string;
|
|
118
|
+
category: string;
|
|
119
|
+
view_box: string;
|
|
120
|
+
width: number;
|
|
121
|
+
height: number;
|
|
122
|
+
svg: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** 도형 저장 결과(동일 도형은 duplicate=true로 기존 재사용). */
|
|
126
|
+
export interface DetailPagePersonalShapeSaveResult
|
|
127
|
+
extends DetailPagePersonalShapeItem {
|
|
128
|
+
success: boolean;
|
|
129
|
+
message?: string;
|
|
130
|
+
duplicate?: boolean;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 레퍼런스 한 장 + 그 장에서 따라갈 축.
|
|
135
|
+
*
|
|
136
|
+
* ``aspects`` 는 닫힌 어휘다(palette · typography · layout · content · mood ·
|
|
137
|
+
* decoration). 비우면 예전처럼 배치와 구성만 참고한다. ``content`` 는 문구가 아니라
|
|
138
|
+
* **다루는 정보 항목과 순서**다.
|
|
139
|
+
*/
|
|
140
|
+
export interface DesignReferenceInput {
|
|
141
|
+
url: string;
|
|
142
|
+
aspects?: string[];
|
|
143
|
+
note?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 레퍼런스 판독 결과 — 설문이 이미 아는 어휘로 옮긴 것. 빈 값은 "모른다"다. */
|
|
147
|
+
export interface DetailPageDesignBrief {
|
|
148
|
+
tone: string;
|
|
149
|
+
density: string;
|
|
150
|
+
bg_color: string;
|
|
151
|
+
primary_colors: string[];
|
|
152
|
+
typography: string;
|
|
153
|
+
layout: string;
|
|
154
|
+
/** 다루는 정보 항목과 순서 한 줄. 문구가 아니다. */
|
|
155
|
+
content: string;
|
|
156
|
+
summary: string;
|
|
157
|
+
reference_count: number;
|
|
158
|
+
aspects: string[][];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 판독 응답. 차감 크레딧은 화면이 셀러에게 되돌려 줘야 하는 숫자다. */
|
|
162
|
+
export interface DetailPageDesignBriefResult {
|
|
163
|
+
brief: DetailPageDesignBrief;
|
|
164
|
+
model: string;
|
|
165
|
+
/** 차감한 크레딧(붙인 그림 크기로 정해짐). 과금이 꺼져 있으면 0. */
|
|
166
|
+
credit_charged?: number;
|
|
167
|
+
/** 차감 뒤 잔여 크레딧. 과금을 건너뛰었으면 -1. */
|
|
168
|
+
credit_remaining?: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** 브랜드 버킷에 저장해 둔 레퍼런스 자산 한 건. */
|
|
172
|
+
export interface DetailPageBrandReferenceItem {
|
|
173
|
+
asset_id: string;
|
|
174
|
+
url: string;
|
|
175
|
+
/** 만료 없는 서명 경로. 문서에 박을 주소는 이쪽이어야 다시 열 때 403 이 안 난다. */
|
|
176
|
+
stable_path: string;
|
|
177
|
+
display_name: string;
|
|
178
|
+
mime: string;
|
|
179
|
+
created_at: string;
|
|
180
|
+
/** screen(화면 캡쳐) | document(편집기 문서). 그림으로 걸 수 있는 것은 screen 뿐이다. */
|
|
181
|
+
role: string;
|
|
182
|
+
reference_group: string;
|
|
183
|
+
reference_name: string;
|
|
184
|
+
generated_id: string;
|
|
185
|
+
template_id: string;
|
|
186
|
+
screen_label: string;
|
|
187
|
+
screen_index: number;
|
|
188
|
+
screen_count: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** 화면 재저작 결과: 갈아 끼울 Canvas 페이지 하나 + 규약 린트 리포트. */
|
|
192
|
+
export interface DetailPageSectionReauthorResult {
|
|
193
|
+
label: string;
|
|
194
|
+
page: Record<string, unknown>;
|
|
195
|
+
lint_ok: boolean;
|
|
196
|
+
lint_findings: Array<Record<string, unknown>>;
|
|
197
|
+
rounds: number;
|
|
198
|
+
text_used: number;
|
|
199
|
+
text_limit: number;
|
|
200
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type CanvasSlotBinding = {
|
|
2
|
+
page_id?: string | null;
|
|
3
|
+
element_id: string;
|
|
4
|
+
kind: "text" | "image" | "number" | "html" | "rich";
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type LeviosaCanvasDocument = {
|
|
8
|
+
schema_version: "leviosa-canvas-detail-page-v1";
|
|
9
|
+
renderer: "leviosa_canvas_detail_page";
|
|
10
|
+
template_id?: string | null;
|
|
11
|
+
template_version?: number | null;
|
|
12
|
+
canvas: {
|
|
13
|
+
width: number;
|
|
14
|
+
background: string;
|
|
15
|
+
};
|
|
16
|
+
slot_bindings: Record<string, CanvasSlotBinding>;
|
|
17
|
+
canvas_json: Record<string, unknown>;
|
|
18
|
+
fonts: Array<Record<string, unknown>>;
|
|
19
|
+
source: "leviosa_canvas_editor";
|
|
20
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
declare module "fontkit" {
|
|
2
|
+
export type FontkitGlyph = {
|
|
3
|
+
id: number;
|
|
4
|
+
advanceWidth: number;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type FontkitSubset = {
|
|
8
|
+
includeGlyph(glyph: FontkitGlyph | number): number;
|
|
9
|
+
encode(): Uint8Array;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type FontkitFont = {
|
|
13
|
+
postscriptName?: string | null;
|
|
14
|
+
unitsPerEm: number;
|
|
15
|
+
ascent: number;
|
|
16
|
+
descent: number;
|
|
17
|
+
capHeight: number;
|
|
18
|
+
italicAngle: number;
|
|
19
|
+
bbox: {
|
|
20
|
+
minX: number;
|
|
21
|
+
minY: number;
|
|
22
|
+
maxX: number;
|
|
23
|
+
maxY: number;
|
|
24
|
+
};
|
|
25
|
+
glyphForCodePoint(codePoint: number): FontkitGlyph;
|
|
26
|
+
createSubset(): FontkitSubset;
|
|
27
|
+
directory?: { tables: Record<string, unknown> };
|
|
28
|
+
_glyphs?: unknown[];
|
|
29
|
+
readonly ["OS/2"]?: {
|
|
30
|
+
fsType?: {
|
|
31
|
+
noEmbedding?: boolean;
|
|
32
|
+
viewOnly?: boolean;
|
|
33
|
+
editable?: boolean;
|
|
34
|
+
noSubsetting?: boolean;
|
|
35
|
+
bitmapOnly?: boolean;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
readonly ["CFF "]?: unknown;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function create(buffer: Uint8Array): FontkitFont;
|
|
42
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Minimal typings for gifenc (ships no declarations). Covers only the surface
|
|
2
|
+
// the detail-page GIF exporter uses: quantize → applyPalette → GIFEncoder.
|
|
3
|
+
declare module "gifenc" {
|
|
4
|
+
type PixelBuffer = Uint8Array | Uint8ClampedArray;
|
|
5
|
+
type Palette = number[][];
|
|
6
|
+
type PixelFormat = "rgb565" | "rgb444" | "rgba4444";
|
|
7
|
+
|
|
8
|
+
export function quantize(
|
|
9
|
+
rgba: PixelBuffer,
|
|
10
|
+
maxColors: number,
|
|
11
|
+
options?: {
|
|
12
|
+
format?: PixelFormat;
|
|
13
|
+
oneBitAlpha?: boolean | number;
|
|
14
|
+
clearAlpha?: boolean;
|
|
15
|
+
clearAlphaThreshold?: number;
|
|
16
|
+
clearAlphaColor?: number;
|
|
17
|
+
},
|
|
18
|
+
): Palette;
|
|
19
|
+
|
|
20
|
+
export function applyPalette(
|
|
21
|
+
rgba: PixelBuffer,
|
|
22
|
+
palette: Palette,
|
|
23
|
+
format?: PixelFormat,
|
|
24
|
+
): Uint8Array;
|
|
25
|
+
|
|
26
|
+
export interface GifEncoderInstance {
|
|
27
|
+
writeFrame(
|
|
28
|
+
index: Uint8Array,
|
|
29
|
+
width: number,
|
|
30
|
+
height: number,
|
|
31
|
+
options?: {
|
|
32
|
+
palette?: Palette;
|
|
33
|
+
delay?: number;
|
|
34
|
+
repeat?: number;
|
|
35
|
+
transparent?: boolean;
|
|
36
|
+
transparentIndex?: number;
|
|
37
|
+
dispose?: number;
|
|
38
|
+
first?: boolean;
|
|
39
|
+
},
|
|
40
|
+
): void;
|
|
41
|
+
finish(): void;
|
|
42
|
+
bytes(): Uint8Array;
|
|
43
|
+
bytesView(): Uint8Array;
|
|
44
|
+
reset(): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function GIFEncoder(options?: {
|
|
48
|
+
auto?: boolean;
|
|
49
|
+
initialCapacity?: number;
|
|
50
|
+
}): GifEncoderInstance;
|
|
51
|
+
}
|