@jant/core 0.2.4 → 0.2.6
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/client/.vite/manifest.json +17 -0
- package/dist/client/assets/client-DKznU4dt.js +3434 -0
- package/dist/client/assets/style-D2PQnJEV.css +2 -0
- package/dist/client.d.ts +3 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -4
- package/dist/jant/.dev.vars +1 -0
- package/dist/jant/.vite/manifest.json +58 -0
- package/dist/jant/assets/bun-sqlite-dialect-DmKw6ndf.js +155 -0
- package/dist/jant/assets/index-1wipnXVC.js +212 -0
- package/dist/jant/assets/index-DhnBrk-n.js +295 -0
- package/dist/jant/assets/node-sqlite-dialect-BzlVRd6O.js +155 -0
- package/dist/jant/assets/worker-entry-CAzIsRGm.js +66701 -0
- package/dist/jant/index.js +2 -0
- package/dist/jant/wrangler.json +1 -0
- package/dist/lib/image-processor.d.ts +30 -0
- package/dist/lib/image-processor.d.ts.map +1 -0
- package/dist/lib/image-processor.js +191 -0
- package/dist/routes/dash/media.d.ts.map +1 -1
- package/dist/routes/dash/media.js +2 -8
- package/dist/theme/layouts/BaseLayout.d.ts +2 -0
- package/dist/theme/layouts/BaseLayout.d.ts.map +1 -1
- package/dist/theme/layouts/BaseLayout.js +9 -10
- package/package.json +3 -3
- package/src/client.ts +3 -4
- package/src/lib/image-processor.ts +218 -0
- package/src/preset.css +10 -2
- package/src/routes/dash/media.tsx +1 -7
- package/src/style.css +15 -0
- package/src/theme/layouts/BaseLayout.tsx +6 -8
- package/src/lib/assets.ts +0 -49
- package/static/assets/image-processor.js +0 -234
package/src/style.css
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jant/core Development Styles
|
|
3
|
+
*
|
|
4
|
+
* This file is used for developing/testing @jant/core directly.
|
|
5
|
+
* User projects should NOT use this file - use preset.css instead.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* Tailwind CSS v4 */
|
|
9
|
+
@import "tailwindcss";
|
|
10
|
+
|
|
11
|
+
/* Jant Core preset (basecoat, component styles, theme, source scanning) */
|
|
12
|
+
@import "./preset.css";
|
|
13
|
+
|
|
14
|
+
/* Scan local project files */
|
|
15
|
+
@source "./**/*.{ts,tsx}";
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides the HTML shell with meta tags, styles, and scripts.
|
|
5
5
|
* If Context is provided, automatically wraps children with I18nProvider.
|
|
6
|
+
*
|
|
7
|
+
* Uses vite-ssr-components for automatic dev/prod asset path resolution.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import type { FC, PropsWithChildren } from "hono/jsx";
|
|
9
11
|
import type { Context } from "hono";
|
|
10
|
-
import {
|
|
12
|
+
import { Script, Link, ViteClient } from "vite-ssr-components/hono";
|
|
11
13
|
import { I18nProvider } from "../../i18n/index.js";
|
|
12
14
|
|
|
13
15
|
export interface BaseLayoutProps {
|
|
@@ -24,9 +26,6 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
24
26
|
c,
|
|
25
27
|
children,
|
|
26
28
|
}) => {
|
|
27
|
-
// Get assets at render time (supports runtime manifest loading)
|
|
28
|
-
const assets = getAssets();
|
|
29
|
-
|
|
30
29
|
// Automatically wrap with I18nProvider if Context is provided
|
|
31
30
|
const content = c ? <I18nProvider c={c}>{children}</I18nProvider> : children;
|
|
32
31
|
|
|
@@ -37,10 +36,9 @@ export const BaseLayout: FC<PropsWithChildren<BaseLayoutProps>> = ({
|
|
|
37
36
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
38
37
|
<title>{title}</title>
|
|
39
38
|
{description && <meta name="description" content={description} />}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<script type="module" src={assets.client} defer />
|
|
39
|
+
<ViteClient />
|
|
40
|
+
<Link href="/src/style.css" rel="stylesheet" />
|
|
41
|
+
<Script src="/src/client.ts" />
|
|
44
42
|
</head>
|
|
45
43
|
<body class="bg-background text-foreground antialiased">
|
|
46
44
|
{content}
|
package/src/lib/assets.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Asset paths for SSR
|
|
3
|
-
*
|
|
4
|
-
* Development: Paths injected via vite.config.ts `define`
|
|
5
|
-
* Production: Paths replaced at build time with hashed filenames
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
interface Assets {
|
|
9
|
-
/** CSS path */
|
|
10
|
-
styles: string;
|
|
11
|
-
/** Main client bundle (includes Datastar + BaseCoat) */
|
|
12
|
-
client: string;
|
|
13
|
-
/** Image processor script (lazy-loaded on media page) */
|
|
14
|
-
imageProcessor: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Injected by vite.config.ts via `define`
|
|
18
|
-
declare const __JANT_DEV_STYLES__: string;
|
|
19
|
-
declare const __JANT_DEV_CLIENT__: string;
|
|
20
|
-
declare const __JANT_DEV_IMAGE_PROCESSOR__: string;
|
|
21
|
-
|
|
22
|
-
// Production paths - replaced at build time
|
|
23
|
-
const PROD_ASSETS: Assets = {
|
|
24
|
-
styles: "__JANT_ASSET_STYLES__",
|
|
25
|
-
client: "__JANT_ASSET_CLIENT__",
|
|
26
|
-
imageProcessor: "__JANT_ASSET_IMAGE_PROCESSOR__",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get assets based on environment
|
|
31
|
-
*/
|
|
32
|
-
export function getAssets(): Assets {
|
|
33
|
-
try {
|
|
34
|
-
if (import.meta.env?.DEV) {
|
|
35
|
-
return {
|
|
36
|
-
styles: __JANT_DEV_STYLES__,
|
|
37
|
-
client: __JANT_DEV_CLIENT__,
|
|
38
|
-
imageProcessor: __JANT_DEV_IMAGE_PROCESSOR__,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
} catch {
|
|
42
|
-
// import.meta.env may not exist in all environments
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return PROD_ASSETS;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// For static imports
|
|
49
|
-
export const ASSETS = PROD_ASSETS;
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Client-side Image Processor
|
|
3
|
-
*
|
|
4
|
-
* Processes images before upload:
|
|
5
|
-
* - Corrects EXIF orientation
|
|
6
|
-
* - Resizes to max dimensions
|
|
7
|
-
* - Strips all metadata (privacy)
|
|
8
|
-
* - Converts to WebP format
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
window.ImageProcessor = (() => {
|
|
12
|
-
const DEFAULT_OPTIONS = {
|
|
13
|
-
maxWidth: 1920,
|
|
14
|
-
maxHeight: 1920,
|
|
15
|
-
quality: 0.85,
|
|
16
|
-
mimeType: 'image/webp',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* EXIF Orientation values and their transformations
|
|
21
|
-
* @see https://exiftool.org/TagNames/EXIF.html
|
|
22
|
-
*/
|
|
23
|
-
const ORIENTATIONS = {
|
|
24
|
-
1: { rotate: 0, flip: false }, // Normal
|
|
25
|
-
2: { rotate: 0, flip: true }, // Flipped horizontally
|
|
26
|
-
3: { rotate: 180, flip: false }, // Rotated 180°
|
|
27
|
-
4: { rotate: 180, flip: true }, // Flipped vertically
|
|
28
|
-
5: { rotate: 90, flip: true }, // Rotated 90° CCW + flipped
|
|
29
|
-
6: { rotate: 90, flip: false }, // Rotated 90° CW
|
|
30
|
-
7: { rotate: 270, flip: true }, // Rotated 90° CW + flipped
|
|
31
|
-
8: { rotate: 270, flip: false }, // Rotated 90° CCW
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Read EXIF orientation from JPEG file
|
|
36
|
-
* @param {ArrayBuffer} buffer - File buffer
|
|
37
|
-
* @returns {number} Orientation value (1-8), defaults to 1
|
|
38
|
-
*/
|
|
39
|
-
function readExifOrientation(buffer) {
|
|
40
|
-
const view = new DataView(buffer);
|
|
41
|
-
|
|
42
|
-
// Check for JPEG SOI marker
|
|
43
|
-
if (view.getUint16(0) !== 0xFFD8) return 1;
|
|
44
|
-
|
|
45
|
-
let offset = 2;
|
|
46
|
-
const length = view.byteLength;
|
|
47
|
-
|
|
48
|
-
while (offset < length) {
|
|
49
|
-
if (view.getUint8(offset) !== 0xFF) return 1;
|
|
50
|
-
|
|
51
|
-
const marker = view.getUint8(offset + 1);
|
|
52
|
-
|
|
53
|
-
// APP1 marker (EXIF)
|
|
54
|
-
if (marker === 0xE1) {
|
|
55
|
-
const exifOffset = offset + 4;
|
|
56
|
-
|
|
57
|
-
// Check for "Exif\0\0"
|
|
58
|
-
if (
|
|
59
|
-
view.getUint32(exifOffset) !== 0x45786966 ||
|
|
60
|
-
view.getUint16(exifOffset + 4) !== 0x0000
|
|
61
|
-
) {
|
|
62
|
-
return 1;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const tiffOffset = exifOffset + 6;
|
|
66
|
-
const littleEndian = view.getUint16(tiffOffset) === 0x4949;
|
|
67
|
-
|
|
68
|
-
// Validate TIFF header
|
|
69
|
-
if (view.getUint16(tiffOffset + 2, littleEndian) !== 0x002A) return 1;
|
|
70
|
-
|
|
71
|
-
const ifdOffset = view.getUint32(tiffOffset + 4, littleEndian);
|
|
72
|
-
const numEntries = view.getUint16(tiffOffset + ifdOffset, littleEndian);
|
|
73
|
-
|
|
74
|
-
// Search for orientation tag (0x0112)
|
|
75
|
-
for (let i = 0; i < numEntries; i++) {
|
|
76
|
-
const entryOffset = tiffOffset + ifdOffset + 2 + i * 12;
|
|
77
|
-
const tag = view.getUint16(entryOffset, littleEndian);
|
|
78
|
-
|
|
79
|
-
if (tag === 0x0112) {
|
|
80
|
-
return view.getUint16(entryOffset + 8, littleEndian);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return 1;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Skip to next marker
|
|
88
|
-
if (marker === 0xD8 || marker === 0xD9) {
|
|
89
|
-
offset += 2;
|
|
90
|
-
} else {
|
|
91
|
-
offset += 2 + view.getUint16(offset + 2);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return 1;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Load image from file
|
|
100
|
-
* @param {File} file - Image file
|
|
101
|
-
* @returns {Promise<HTMLImageElement>}
|
|
102
|
-
*/
|
|
103
|
-
function loadImage(file) {
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
const img = new Image();
|
|
106
|
-
img.onload = () => {
|
|
107
|
-
URL.revokeObjectURL(img.src);
|
|
108
|
-
resolve(img);
|
|
109
|
-
};
|
|
110
|
-
img.onerror = () => reject(new Error('Failed to load image'));
|
|
111
|
-
img.src = URL.createObjectURL(file);
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Calculate output dimensions maintaining aspect ratio
|
|
117
|
-
* @param {number} width - Original width
|
|
118
|
-
* @param {number} height - Original height
|
|
119
|
-
* @param {number} maxWidth - Maximum width
|
|
120
|
-
* @param {number} maxHeight - Maximum height
|
|
121
|
-
* @returns {{ width: number, height: number }}
|
|
122
|
-
*/
|
|
123
|
-
function calculateDimensions(width, height, maxWidth, maxHeight) {
|
|
124
|
-
if (width <= maxWidth && height <= maxHeight) {
|
|
125
|
-
return { width, height };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const ratio = Math.min(maxWidth / width, maxHeight / height);
|
|
129
|
-
return {
|
|
130
|
-
width: Math.round(width * ratio),
|
|
131
|
-
height: Math.round(height * ratio),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Process image file
|
|
137
|
-
* @param {File} file - Image file to process
|
|
138
|
-
* @param {Object} options - Processing options
|
|
139
|
-
* @returns {Promise<Blob>} Processed image as WebP blob
|
|
140
|
-
*/
|
|
141
|
-
async function process(file, options = {}) {
|
|
142
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
143
|
-
|
|
144
|
-
// Read file buffer for EXIF
|
|
145
|
-
const buffer = await file.arrayBuffer();
|
|
146
|
-
const orientation = readExifOrientation(buffer);
|
|
147
|
-
const transform = ORIENTATIONS[orientation] || ORIENTATIONS[1];
|
|
148
|
-
|
|
149
|
-
// Load image
|
|
150
|
-
const img = await loadImage(file);
|
|
151
|
-
|
|
152
|
-
// For 90° or 270° rotation, swap dimensions
|
|
153
|
-
const isRotated = transform.rotate === 90 || transform.rotate === 270;
|
|
154
|
-
const srcWidth = isRotated ? img.height : img.width;
|
|
155
|
-
const srcHeight = isRotated ? img.width : img.height;
|
|
156
|
-
|
|
157
|
-
// Calculate output size
|
|
158
|
-
const { width, height } = calculateDimensions(
|
|
159
|
-
srcWidth,
|
|
160
|
-
srcHeight,
|
|
161
|
-
opts.maxWidth,
|
|
162
|
-
opts.maxHeight
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
// Create canvas
|
|
166
|
-
const canvas = document.createElement('canvas');
|
|
167
|
-
canvas.width = width;
|
|
168
|
-
canvas.height = height;
|
|
169
|
-
|
|
170
|
-
const ctx = canvas.getContext('2d');
|
|
171
|
-
if (!ctx) throw new Error('Failed to get canvas context');
|
|
172
|
-
|
|
173
|
-
// Apply transformations
|
|
174
|
-
ctx.save();
|
|
175
|
-
|
|
176
|
-
// Move to center for rotation
|
|
177
|
-
ctx.translate(width / 2, height / 2);
|
|
178
|
-
|
|
179
|
-
// Apply rotation
|
|
180
|
-
if (transform.rotate) {
|
|
181
|
-
ctx.rotate((transform.rotate * Math.PI) / 180);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Apply flip
|
|
185
|
-
if (transform.flip) {
|
|
186
|
-
ctx.scale(-1, 1);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Draw image centered
|
|
190
|
-
const drawWidth = isRotated ? height : width;
|
|
191
|
-
const drawHeight = isRotated ? width : height;
|
|
192
|
-
ctx.drawImage(img, -drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight);
|
|
193
|
-
|
|
194
|
-
ctx.restore();
|
|
195
|
-
|
|
196
|
-
// Export as WebP
|
|
197
|
-
return new Promise((resolve, reject) => {
|
|
198
|
-
canvas.toBlob(
|
|
199
|
-
(blob) => {
|
|
200
|
-
if (blob) {
|
|
201
|
-
resolve(blob);
|
|
202
|
-
} else {
|
|
203
|
-
reject(new Error('Failed to create blob'));
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
opts.mimeType,
|
|
207
|
-
opts.quality
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Process file and create a new File object
|
|
214
|
-
* @param {File} file - Original file
|
|
215
|
-
* @param {Object} options - Processing options
|
|
216
|
-
* @returns {Promise<File>} Processed file with .webp extension
|
|
217
|
-
*/
|
|
218
|
-
async function processToFile(file, options = {}) {
|
|
219
|
-
const blob = await process(file, options);
|
|
220
|
-
|
|
221
|
-
// Generate new filename with .webp extension
|
|
222
|
-
const originalName = file.name.replace(/\.[^.]+$/, '');
|
|
223
|
-
const newName = `${originalName}.webp`;
|
|
224
|
-
|
|
225
|
-
return new File([blob], newName, { type: 'image/webp' });
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return { process, processToFile };
|
|
229
|
-
})();
|
|
230
|
-
|
|
231
|
-
// Also export for module systems
|
|
232
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
233
|
-
module.exports = window.ImageProcessor;
|
|
234
|
-
}
|