Compare commits

..
11 Commits
17 changed files with 1163 additions and 166 deletions
+78 -8
View File
@@ -48,13 +48,14 @@ button { color: inherit; }
.upload-card { width: 100%; min-height: 100px; max-height: 260px; display: block; position: relative; overflow: hidden; border: 1px dashed #a5a59e; background: #ebe7dd; } .upload-card { width: 100%; min-height: 100px; max-height: 260px; display: block; position: relative; overflow: hidden; border: 1px dashed #a5a59e; background: #ebe7dd; }
.upload-card canvas { width: 100%; height: 100%; display: block; pointer-events: none; } .upload-card canvas { width: 100%; height: 100%; display: block; pointer-events: none; }
.upload-card .file-input { display: none; } .upload-card .file-input { display: none; }
.crop-preview { cursor: grab; touch-action: none; } .crop-preview { cursor: default; }
.crop-preview:active { cursor: grabbing; }
.crop-frame { position: absolute; inset: 0; border: 2px solid rgba(255,255,255,.85); box-shadow: inset 0 0 0 1px rgba(20,30,25,.22); pointer-events: none; }
.upload-overlay { position: absolute; z-index: 3; right: 10px; bottom: 10px; padding: 7px 10px; border: 0; color: white; background: rgba(22, 75, 57, .9); cursor: pointer; font-size: 12px; } .upload-overlay { position: absolute; z-index: 3; right: 10px; bottom: 10px; padding: 7px 10px; border: 0; color: white; background: rgba(22, 75, 57, .9); cursor: pointer; font-size: 12px; }
.file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin: 9px 0 24px; color: var(--muted); font-size: 11px; } .file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin: 9px 0 24px; color: var(--muted); font-size: 11px; }
.crop-zoom { margin-top: 0; } .crop-select-button { width: 100%; margin-top: 0; padding: 10px; border: 1px solid var(--ink); background: var(--lime); cursor: pointer; font-size: 12px; font-weight: 700; }
.reset-crop { width: 100%; margin: 7px 0 20px; padding: 7px; border: 1px solid var(--line); background: white; cursor: pointer; color: var(--muted); font-size: 11px; } .reset-crop { width: 100%; margin: 7px 0 0; padding: 7px; border: 1px solid var(--line); background: white; cursor: pointer; color: var(--muted); font-size: 11px; }
.pattern-import-button { width: 100%; margin: 7px 0 20px; padding: 9px; border: 1px solid #b6532d; background: #fff5ec; color: #8f3f20; cursor: pointer; font-size: 12px; font-weight: 700; }
.pattern-import-button:hover { background: #fee5d1; }
.pattern-import-file { display: none; }
.field-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: end; gap: 8px; } .field-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: end; gap: 8px; }
.field-row label, .field { display: flex; flex-direction: column; gap: 8px; } .field-row label, .field { display: flex; flex-direction: column; gap: 8px; }
.field-row label span, .field > span { color: var(--muted); font-size: 12px; } .field-row label span, .field > span { color: var(--muted); font-size: 12px; }
@@ -130,9 +131,9 @@ input[type="range"] { accent-color: var(--green); }
.history-heading p:not(.section-kicker) { margin: 8px 0 0; color: var(--muted); font-size: 12px; } .history-heading p:not(.section-kicker) { margin: 8px 0 0; color: var(--muted); font-size: 12px; }
.history-heading .search-box { width: min(330px, 100%); } .history-heading .search-box { width: min(330px, 100%); }
.history-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; padding-top: 18px; } .history-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; padding-top: 18px; }
.history-card { min-width: 0; padding: 16px; display: grid; grid-template-columns: 58px minmax(0,1fr); gap: 12px; border: 1px solid var(--line); background: var(--card); } .history-card { min-width: 0; padding: 16px; display: grid; grid-template-columns: 86px minmax(0,1fr); gap: 13px; border: 1px solid var(--line); background: var(--card); }
.history-swatches { width: 58px; height: 58px; display: flex; overflow: hidden; border: 1px solid var(--ink); } .history-thumbnail { width: 86px; height: 86px; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--ink); background-color: #f8f6f0; background-image: linear-gradient(45deg, #dedbd3 25%, transparent 25%), linear-gradient(-45deg, #dedbd3 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #dedbd3 75%), linear-gradient(-45deg, transparent 75%, #dedbd3 75%); background-position: 0 0, 0 5px, 5px -5px, -5px 0; background-size: 10px 10px; }
.history-swatches i { min-width: 3px; } .history-thumbnail canvas { display: block; width: auto; height: auto; max-width: 100%; max-height: 100%; image-rendering: pixelated; }
.history-card h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin: 3px 0 7px; font-size: 14px; } .history-card h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin: 3px 0 7px; font-size: 14px; }
.history-card p { margin: 0; color: var(--muted); font-size: 10px; } .history-card p { margin: 0; color: var(--muted); font-size: 10px; }
.history-actions { grid-column: 1 / -1; display: flex; gap: 7px; justify-content: flex-end; } .history-actions { grid-column: 1 / -1; display: flex; gap: 7px; justify-content: flex-end; }
@@ -140,6 +141,55 @@ input[type="range"] { accent-color: var(--green); }
.history-actions button:first-child { background: var(--lime); font-weight: 700; } .history-actions button:first-child { background: var(--lime); font-weight: 700; }
.history-empty { margin-top: 18px; padding: 44px 20px; color: var(--muted); text-align: center; border: 1px dashed var(--line); background: rgba(255,253,248,.5); font-size: 13px; } .history-empty { margin-top: 18px; padding: 44px 20px; color: var(--muted); text-align: center; border: 1px dashed var(--line); background: rgba(255,253,248,.5); font-size: 13px; }
.crop-dialog-backdrop { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 24px; background: rgba(17,24,21,.76); backdrop-filter: blur(5px); }
.crop-dialog { width: min(1280px, 96vw); max-height: 94vh; display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--ink); background: var(--card); box-shadow: 10px 10px 0 rgba(0,0,0,.32); }
.crop-dialog-header { flex: 0 0 auto; padding: 18px 22px; display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; border-bottom: 1px solid var(--line); }
.crop-dialog-header .section-kicker { margin-bottom: 5px; }
.crop-dialog-header h2 { margin: 0; font-size: 22px; }
.crop-dialog-header p:not(.section-kicker) { margin: 6px 0 0; color: var(--muted); font-size: 12px; }
.crop-dialog-header > button { width: 38px; height: 38px; flex: 0 0 auto; border: 1px solid var(--line); background: white; cursor: pointer; font-size: 25px; line-height: 1; }
.crop-mode-bar { min-height: 54px; padding: 8px 20px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid var(--line); background: #f7f4ec; color: var(--muted); font-size: 11px; }
.crop-mode-switch { display: inline-flex; border: 1px solid var(--ink); background: white; }
.crop-mode-switch button { min-height: 34px; padding: 0 15px; border: 0; border-right: 1px solid var(--ink); background: transparent; cursor: pointer; font-size: 12px; }
.crop-mode-switch button:last-child { border-right: 0; }
.crop-mode-switch button.active { background: var(--green); color: white; font-weight: 700; }
.pattern-import-dimensions { display: flex; align-items: flex-end; gap: 8px; }
.pattern-import-dimensions label { display: grid; gap: 3px; color: var(--muted); font-size: 10px; }
.pattern-import-dimensions input { width: 82px; height: 34px; padding: 0 8px; border: 1px solid var(--line); background: white; color: var(--ink); font-size: 12px; }
.pattern-import-dimensions b { padding-bottom: 9px; color: var(--muted); font-weight: 400; }
.pattern-import-empty-option { display: flex; align-items: center; gap: 6px; color: var(--ink); cursor: pointer; }
.pattern-import-empty-option input { accent-color: var(--green); }
.crop-size-readout { margin-left: auto; font-variant-numeric: tabular-nums; }
.crop-dialog-scroll { min-height: 0; flex: 1 1 auto; overflow: auto; padding: 18px; display: grid; place-items: center; background-color: #e9e5db; background-image: radial-gradient(#c8c3b8 .7px, transparent .7px); background-size: 12px 12px; }
.crop-dialog-stage { position: relative; width: max-content; max-width: 100%; line-height: 0; cursor: crosshair; touch-action: none; user-select: none; box-shadow: 0 0 0 1px var(--ink); }
.crop-dialog-stage.can-move-selection { cursor: move; }
.crop-dialog-stage.is-moving-selection { cursor: grabbing; }
.crop-dialog-stage.is-resizing-selection { cursor: nwse-resize; }
.crop-dialog-stage canvas { display: block; max-width: 100%; height: auto; pointer-events: none; }
.crop-selection-mask { position: absolute; z-index: 1; background: rgba(10,15,13,.55); pointer-events: none; }
.crop-selection-top { inset: 0 0 auto; }
.crop-selection-left { left: 0; }
.crop-selection-bottom { inset: auto 0 0; bottom: 0; }
.crop-selection-box { position: absolute; z-index: 2; border: 2px solid white; outline: 1px solid rgba(0,0,0,.75); box-shadow: inset 0 0 0 1px rgba(0,0,0,.18); pointer-events: none; }
.crop-selection-box::before, .crop-selection-box::after { content: ""; position: absolute; background: rgba(255,255,255,.62); }
.crop-selection-box::before { left: 33.333%; top: 0; bottom: 0; width: 1px; box-shadow: calc(33.333% * 1) 0 0 rgba(255,255,255,.62); }
.crop-selection-box::after { left: 0; right: 0; top: 33.333%; height: 1px; box-shadow: 0 calc(33.333% * 1) 0 rgba(255,255,255,.62); }
.pattern-import-grid-overlay { position: absolute; z-index: 3; inset: 0; display: block; pointer-events: none; background-image: linear-gradient(to right, rgba(255,102,0,.66) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,102,0,.66) 1px, transparent 1px); box-shadow: inset -1px -1px 0 rgba(255,102,0,.66); }
.crop-resize-handle { position: absolute; z-index: 4; width: 15px; height: 15px; display: block; border: 2px solid white; border-radius: 50%; background: var(--green); box-shadow: 0 1px 4px rgba(0,0,0,.48); pointer-events: auto; }
.handle-nw { left: 0; top: 0; transform: translate(-50%, -50%); cursor: nwse-resize; }
.handle-ne { right: 0; top: 0; transform: translate(50%, -50%); cursor: nesw-resize; }
.handle-sw { left: 0; bottom: 0; transform: translate(-50%, 50%); cursor: nesw-resize; }
.handle-se { right: 0; bottom: 0; transform: translate(50%, 50%); cursor: nwse-resize; }
.handle-n { left: 50%; top: 0; transform: translate(-50%, -50%); cursor: ns-resize; }
.handle-s { left: 50%; bottom: 0; transform: translate(-50%, 50%); cursor: ns-resize; }
.handle-w { left: 0; top: 50%; transform: translate(-50%, -50%); cursor: ew-resize; }
.handle-e { right: 0; top: 50%; transform: translate(50%, -50%); cursor: ew-resize; }
.crop-dialog-actions { flex: 0 0 auto; min-height: 66px; padding: 12px 20px; display: flex; align-items: center; justify-content: flex-end; gap: 10px; border-top: 1px solid var(--line); }
.crop-dialog-actions span { margin-right: auto; color: var(--muted); font-size: 11px; }
.crop-dialog-actions button { min-height: 40px; padding: 0 16px; border: 1px solid var(--ink); background: white; cursor: pointer; font-size: 12px; }
.crop-dialog-actions .confirm { background: var(--green); color: white; font-weight: 700; }
.crop-dialog-actions .confirm:disabled { opacity: .42; cursor: not-allowed; }
@media (max-width: 1160px) { @media (max-width: 1160px) {
.workspace { grid-template-columns: 260px minmax(500px, 1fr); } .workspace { grid-template-columns: 260px minmax(500px, 1fr); }
.color-panel { grid-column: 1 / -1; border-left: 0; border-top: 1px solid var(--ink); max-height: none; } .color-panel { grid-column: 1 / -1; border-left: 0; border-top: 1px solid var(--ink); max-height: none; }
@@ -175,6 +225,26 @@ input[type="range"] { accent-color: var(--green); }
.history-section { margin: 52px auto; } .history-section { margin: 52px auto; }
.history-heading .search-box { width: 100%; } .history-heading .search-box { width: 100%; }
.history-grid { grid-template-columns: 1fr; } .history-grid { grid-template-columns: 1fr; }
.crop-dialog-backdrop { padding: 0; }
.crop-dialog { width: 100vw; max-height: 100vh; height: 100vh; border: 0; box-shadow: none; }
.crop-dialog-header { padding: 14px 16px; }
.crop-dialog-header h2 { font-size: 18px; }
.crop-mode-bar { padding: 8px 12px; flex-wrap: wrap; gap: 7px 10px; }
.crop-mode-switch { flex: 1; }
.crop-mode-switch button { flex: 1; padding: 0 8px; }
.pattern-import-dimensions { width: 100%; }
.pattern-import-dimensions label { flex: 1; }
.pattern-import-dimensions input { width: 100%; min-height: 42px; }
.pattern-import-empty-option { width: 100%; min-height: 34px; }
.crop-size-readout { width: 100%; margin-left: 0; }
.crop-resize-handle { width: 28px; height: 28px; border-width: 3px; }
.crop-edge-handle { display: none; }
.crop-dialog-scroll { padding: 10px; }
.crop-dialog-stage { width: 100%; max-width: 100%; }
.crop-dialog-stage canvas { width: 100%; max-width: 100%; height: auto; }
.crop-dialog-actions { padding: 10px 12px; flex-wrap: wrap; }
.crop-dialog-actions span { width: 100%; order: -1; }
.crop-dialog-actions button { flex: 1; padding: 0 10px; }
input, select, button { font-size: 16px; } input, select, button { font-size: 16px; }
.search-box input, .color-item, .selection-tools button { font-size: 12px; } .search-box input, .color-item, .selection-tools button { font-size: 12px; }
} }
+1028 -71
View File
File diff suppressed because it is too large Load Diff
+12 -2
View File
@@ -1,4 +1,5 @@
import { appendFile, createReadStream, existsSync, mkdirSync, statSync } from "node:fs"; import { createReadStream, existsSync, mkdirSync, statSync } from "node:fs";
import { appendFile } from "node:fs/promises";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import { createServer } from "node:http"; import { createServer } from "node:http";
import { extname, join, normalize } from "node:path"; import { extname, join, normalize } from "node:path";
@@ -9,6 +10,13 @@ const port = Number(process.env.APP_PORT || 3200);
const dataDirectory = process.env.DATA_DIR || join(process.cwd(), "data"); const dataDirectory = process.env.DATA_DIR || join(process.cwd(), "data");
const usageLogPath = join(dataDirectory, "usage.jsonl"); const usageLogPath = join(dataDirectory, "usage.jsonl");
mkdirSync(dataDirectory, { recursive: true }); mkdirSync(dataDirectory, { recursive: true });
function formatChinaTimestamp(date) {
return new Date(date.getTime() + 8 * 60 * 60 * 1000)
.toISOString()
.replace("Z", "+08:00");
}
const mimeTypes = { const mimeTypes = {
".css": "text/css; charset=utf-8", ".css": "text/css; charset=utf-8",
".html": "text/html; charset=utf-8", ".html": "text/html; charset=utf-8",
@@ -40,8 +48,10 @@ function writeUsageLog(request, response) {
sendJson(response, 400, { error: "Invalid event" }); sendJson(response, 400, { error: "Invalid event" });
return; return;
} }
const now = new Date();
const entry = { const entry = {
timestamp: new Date().toISOString(), timestamp: now.toISOString(),
timestamp_cn: formatChinaTimestamp(now),
event: input.event, event: input.event,
client_hash: request.headers["x-forwarded-for"] || request.socket.remoteAddress client_hash: request.headers["x-forwarded-for"] || request.socket.remoteAddress
? createHash("sha256").update(String(request.headers["x-forwarded-for"] || request.socket.remoteAddress)).digest("hex").slice(0, 16) ? createHash("sha256").update(String(request.headers["x-forwarded-for"] || request.socket.remoteAddress)).digest("hex").slice(0, 16)
+3 -3
View File
@@ -11,7 +11,7 @@
"name": "rolldown-runtime" "name": "rolldown-runtime"
}, },
"app/page.tsx": { "app/page.tsx": {
"file": "_next/static/chunks/page-Clx65Ifw.js", "file": "_next/static/chunks/page-DBRuBfy6.js",
"name": "page", "name": "page",
"src": "app/page.tsx", "src": "app/page.tsx",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -21,7 +21,7 @@
] ]
}, },
"node_modules/vinext/dist/shims/layout-segment-context.js": { "node_modules/vinext/dist/shims/layout-segment-context.js": {
"file": "_next/static/chunks/layout-segment-context-BVJGUT0n.js", "file": "_next/static/chunks/layout-segment-context-rAphBSzO.js",
"name": "layout-segment-context", "name": "layout-segment-context",
"src": "node_modules/vinext/dist/shims/layout-segment-context.js", "src": "node_modules/vinext/dist/shims/layout-segment-context.js",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -32,7 +32,7 @@
] ]
}, },
"virtual:vinext-app-browser-entry": { "virtual:vinext-app-browser-entry": {
"file": "_next/static/chunks/index-iZ0Yke16.js", "file": "_next/static/chunks/index-VFVBI02U.js",
"name": "index", "name": "index",
"src": "virtual:vinext-app-browser-entry", "src": "virtual:vinext-app-browser-entry",
"isEntry": true, "isEntry": true,
+2 -2
View File
@@ -1,2 +1,2 @@
<!DOCTYPE html><html lang="zh-CN"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/index.DWpRPKap.css" data-rsc-css-href="/_next/static/css/index.DWpRPKap.css" data-precedence="vite-rsc/importer-resources"/><link rel="modulepreload" fetchPriority="low" href="/_next/static/chunks/index-iZ0Yke16.js"/><script src="/_next/static/chunks/rolldown-runtime-C60lm6uB.js" type="module" async=""></script><script src="/_next/static/chunks/framework-BgSIrAUN.js" type="module" async=""></script><meta name="robots" content="noindex"/><title>豆格工坊|图片转拼豆图纸</title><meta name="description" content="在浏览器中把图片转换为拼豆像素图纸,按色号、名称或像素格筛选颜色。"/><title>404: This page could not be found.</title><script>Object.assign(((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}),{params:{},nav:{"pathname":"/__vinext_nonexistent_for_404__","searchParams":[]}})</script><link rel="modulepreload" href="/_next/static/chunks/index-iZ0Yke16.js" /> <!DOCTYPE html><html lang="zh-CN"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/index.B4qFKCfT.css" data-rsc-css-href="/_next/static/css/index.B4qFKCfT.css" data-precedence="vite-rsc/importer-resources"/><link rel="modulepreload" fetchPriority="low" href="/_next/static/chunks/index-VFVBI02U.js"/><script src="/_next/static/chunks/rolldown-runtime-C60lm6uB.js" type="module" async=""></script><script src="/_next/static/chunks/framework-BgSIrAUN.js" type="module" async=""></script><meta name="robots" content="noindex"/><title>豆格工坊|图片转拼豆图纸</title><meta name="description" content="在浏览器中把图片转换为拼豆像素图纸,按色号、名称或像素格筛选颜色。"/><title>404: This page could not be found.</title><script>Object.assign(((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}),{params:{},nav:{"pathname":"/__vinext_nonexistent_for_404__","searchParams":[]}})</script><link rel="modulepreload" href="/_next/static/chunks/index-VFVBI02U.js" />
</head><body><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><script type="module" src="/_next/static/chunks/index-iZ0Yke16.js" id="_R_" async=""></script><script>((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}).rsc.push("1:\"$Sreact.fragment\"\n")</script><script>((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}).rsc.push(":HL[\"/_next/static/css/index.DWpRPKap.css\",\"style\" ]\n")</script><script>((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}).rsc.push("0:{\"__route\":\"route:/__vinext_nonexistent_for_404__\",\"__interceptionContext\":null,\"__layoutIds\":[],\"__rootLayout\":null,\"route:/__vinext_nonexistent_for_404__\":[[[\"$\",\"link\",\"css:/_next/static/css/index.DWpRPKap.css\",{\"rel\":\"stylesheet\",\"precedence\":\"vite-rsc/importer-resources\",\"href\":\"/_next/static/css/index.DWpRPKap.css\",\"data-rsc-css-href\":\"/_next/static/css/index.DWpRPKap.css\"}],\"$undefined\"],[\"$\",\"html\",null,{\"lang\":\"zh-CN\",\"children\":[\"$\",\"body\",null,{\"children\":[[\"$\",\"meta\",\"charset\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"robots\",{\"name\":\"robots\",\"content\":\"noindex\"}],[\"$\",\"$1\",\"metadata\",{\"children\":[[\"$\",\"title\",\"0\",{\"children\":\"豆格工坊|图片转拼豆图纸\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"在浏览器中把图片转换为拼豆像素图纸,按色号、名称或像素格筛选颜色。\"}]]}],[\"$\",\"$1\",\"viewport\",{\"children\":[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]}],[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]]]}]}]]}\n")</script><script>((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}).done=true</script></body></html> </head><body><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><script type="module" src="/_next/static/chunks/index-VFVBI02U.js" id="_R_" async=""></script><script>((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}).rsc.push("1:\"$Sreact.fragment\"\n")</script><script>((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}).rsc.push(":HL[\"/_next/static/css/index.B4qFKCfT.css\",\"style\" ]\n")</script><script>((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}).rsc.push("0:{\"__route\":\"route:/__vinext_nonexistent_for_404__\",\"__interceptionContext\":null,\"__layoutIds\":[],\"__rootLayout\":null,\"route:/__vinext_nonexistent_for_404__\":[[[\"$\",\"link\",\"css:/_next/static/css/index.B4qFKCfT.css\",{\"rel\":\"stylesheet\",\"precedence\":\"vite-rsc/importer-resources\",\"href\":\"/_next/static/css/index.B4qFKCfT.css\",\"data-rsc-css-href\":\"/_next/static/css/index.B4qFKCfT.css\"}],\"$undefined\"],[\"$\",\"html\",null,{\"lang\":\"zh-CN\",\"children\":[\"$\",\"body\",null,{\"children\":[[\"$\",\"meta\",\"charset\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"robots\",{\"name\":\"robots\",\"content\":\"noindex\"}],[\"$\",\"$1\",\"metadata\",{\"children\":[[\"$\",\"title\",\"0\",{\"children\":\"豆格工坊|图片转拼豆图纸\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"在浏览器中把图片转换为拼豆像素图纸,按色号、名称或像素格筛选颜色。\"}]]}],[\"$\",\"$1\",\"viewport\",{\"children\":[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]}],[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]]]}]}]]}\n")</script><script>((self[Symbol.for("vinext.navigationRuntime")]??={bootstrap:{routeManifest:null},functions:{}}).bootstrap.rsc??={rsc:[]}).done=true</script></body></html>
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{r as e}from"./rolldown-runtime-C60lm6uB.js";import{r as t}from"./framework-BgSIrAUN.js";import{t as n}from"./index-iZ0Yke16.js";var r=e(t(),1),i=new Map;function a(e,t){return e?{...e,...t}:t}function o({providerId:e,segmentMap:t,children:o}){let s=(0,r.useRef)(null),c=n(),l=a(s.current??(e?i.get(e)??null:null),t);return(0,r.useEffect)(()=>{s.current=l,e&&i.set(e,l)},[l,e]),c?(0,r.createElement)(c.Provider,{value:l},o):o}export{o as LayoutSegmentProvider,a as mergeLayoutSegmentMap}; import{r as e}from"./rolldown-runtime-C60lm6uB.js";import{r as t}from"./framework-BgSIrAUN.js";import{t as n}from"./index-VFVBI02U.js";var r=e(t(),1),i=new Map;function a(e,t){return e?{...e,...t}:t}function o({providerId:e,segmentMap:t,children:o}){let s=(0,r.useRef)(null),c=n(),l=a(s.current??(e?i.get(e)??null:null),t);return(0,r.useEffect)(()=>{s.current=l,e&&i.set(e,l)},[l,e]),c?(0,r.createElement)(c.Provider,{value:l},o):o}export{o as LayoutSegmentProvider,a as mergeLayoutSegmentMap};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -6,8 +6,8 @@
7:I["8c0f216c4604",[],"Slot",1] 7:I["8c0f216c4604",[],"Slot",1]
8:I["9276801271d6",[],"AppRouterScrollTarget",1] 8:I["9276801271d6",[],"AppRouterScrollTarget",1]
9:I["593f344dc510",[],"RedirectBoundary",1] 9:I["593f344dc510",[],"RedirectBoundary",1]
:HL["/_next/static/css/index.DWpRPKap.css","style" ] :HL["/_next/static/css/index.B4qFKCfT.css","style" ]
0:{"__route":"route:/","__interceptionContext":null,"__layoutIds":["layout:/"],"__rootLayout":"/","__sourcePage":"/page","page:/":"$L1","layout:/":[[[["$","link","css:/_next/static/css/index.DWpRPKap.css",{"rel":"stylesheet","precedence":"vite-rsc/importer-resources","href":"/_next/static/css/index.DWpRPKap.css","data-rsc-css-href":"/_next/static/css/index.DWpRPKap.css"}],"$undefined"],["$","html",null,{"lang":"zh-CN","children":["$","body",null,{"children":["$","$L2",null,{}]}]}]],null],"route:/":[[["$","meta",null,{"charSet":"utf-8"}],[["$","title","0",{"children":"豆格工坊|图片转拼豆图纸"}],["$","meta","1",{"name":"description","content":"在浏览器中把图片转换为拼豆像素图纸,按色号、名称或像素格筛选颜色。"}]],[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]],["$","$L3",null,{"fallback":"$4","children":["$","$L5",null,{"fallback":"$4","children":["$","$L6",null,{"providerId":"layout:/","segmentMap":{"children":[]},"children":["$","$L7",null,{"id":"layout:/","parallelSlots":"$undefined","children":["$","$L8",null,{"children":["$","$L9",null,{"children":[["$","$L6",null,{"providerId":"page:/","segmentMap":{"children":["__PAGE__"]},"children":["$","$L7",null,{"id":"page:/"}]}],null]}]}]}]}]}]}],null,null],"__layoutFlags":{"layout:/":"s"},"__artifactCompatibility":{"schemaVersion":1,"graphVersion":"app-route-graph:1177fd80f83fa7b0","deploymentVersion":"031ba822-b678-4785-9e41-6342ae4e309a","appElementsSchemaVersion":1,"rscPayloadSchemaVersion":1,"rootBoundaryId":"/","renderEpoch":null},"__renderObservation":{"schemaVersion":1,"output":{"kind":"app-rsc","mountedSlotsFingerprint":null,"renderEpoch":null,"rootBoundaryId":"/","routeId":"route:/"},"completeness":"partial","boundaryOutcome":{"kind":"unknown"},"requestApis":[{"kind":"connection","status":"unknown"},{"kind":"cookies","status":"unknown"},{"kind":"draftMode","status":"unknown"},{"kind":"headers","status":"unknown"},{"kind":"params","status":"unknown"},{"kind":"searchParams","status":"unknown"}],"dynamicFetches":[],"cacheTags":["/","_N_T_/","_N_T_/index","_N_T_/layout","_N_T_/page"],"pathTags":["/"],"cacheability":"unknown","downgrade":{"target":"freshRender","reasons":[{"code":"CP_DOWNGRADE_CACHEABILITY_UNKNOWN","target":"freshRender"},{"code":"CP_DOWNGRADE_INCOMPLETE_OBSERVATION","completeness":"partial","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"connection","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"cookies","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"draftMode","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"headers","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"params","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"searchParams","target":"freshRender"}],"fallback":{"kind":"breakerFallback","code":"CP_PRIVATE_DYNAMIC_DOWNGRADE","mode":"renderFresh","scope":"affectedOutput","fields":{"reasonCodes":["CP_DOWNGRADE_CACHEABILITY_UNKNOWN","CP_DOWNGRADE_INCOMPLETE_OBSERVATION","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API"],"target":"freshRender"}},"isPublicCacheCandidate":false}}} 0:{"__route":"route:/","__interceptionContext":null,"__layoutIds":["layout:/"],"__rootLayout":"/","__sourcePage":"/page","page:/":"$L1","layout:/":[[[["$","link","css:/_next/static/css/index.B4qFKCfT.css",{"rel":"stylesheet","precedence":"vite-rsc/importer-resources","href":"/_next/static/css/index.B4qFKCfT.css","data-rsc-css-href":"/_next/static/css/index.B4qFKCfT.css"}],"$undefined"],["$","html",null,{"lang":"zh-CN","children":["$","body",null,{"children":["$","$L2",null,{}]}]}]],null],"route:/":[[["$","meta",null,{"charSet":"utf-8"}],[["$","title","0",{"children":"豆格工坊|图片转拼豆图纸"}],["$","meta","1",{"name":"description","content":"在浏览器中把图片转换为拼豆像素图纸,按色号、名称或像素格筛选颜色。"}]],[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]],["$","$L3",null,{"fallback":"$4","children":["$","$L5",null,{"fallback":"$4","children":["$","$L6",null,{"providerId":"layout:/","segmentMap":{"children":[]},"children":["$","$L7",null,{"id":"layout:/","parallelSlots":"$undefined","children":["$","$L8",null,{"children":["$","$L9",null,{"children":[["$","$L6",null,{"providerId":"page:/","segmentMap":{"children":["__PAGE__"]},"children":["$","$L7",null,{"id":"page:/"}]}],null]}]}]}]}]}]}],null,null],"__layoutFlags":{"layout:/":"s"},"__artifactCompatibility":{"schemaVersion":1,"graphVersion":"app-route-graph:1177fd80f83fa7b0","deploymentVersion":"feb6bb1f-6465-425f-8535-1fdc32d89efe","appElementsSchemaVersion":1,"rscPayloadSchemaVersion":1,"rootBoundaryId":"/","renderEpoch":null},"__renderObservation":{"schemaVersion":1,"output":{"kind":"app-rsc","mountedSlotsFingerprint":null,"renderEpoch":null,"rootBoundaryId":"/","routeId":"route:/"},"completeness":"partial","boundaryOutcome":{"kind":"unknown"},"requestApis":[{"kind":"connection","status":"unknown"},{"kind":"cookies","status":"unknown"},{"kind":"draftMode","status":"unknown"},{"kind":"headers","status":"unknown"},{"kind":"params","status":"unknown"},{"kind":"searchParams","status":"unknown"}],"dynamicFetches":[],"cacheTags":["/","_N_T_/","_N_T_/index","_N_T_/layout","_N_T_/page"],"pathTags":["/"],"cacheability":"unknown","downgrade":{"target":"freshRender","reasons":[{"code":"CP_DOWNGRADE_CACHEABILITY_UNKNOWN","target":"freshRender"},{"code":"CP_DOWNGRADE_INCOMPLETE_OBSERVATION","completeness":"partial","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"connection","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"cookies","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"draftMode","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"headers","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"params","target":"freshRender"},{"code":"CP_DOWNGRADE_UNKNOWN_REQUEST_API","requestApi":"searchParams","target":"freshRender"}],"fallback":{"kind":"breakerFallback","code":"CP_PRIVATE_DYNAMIC_DOWNGRADE","mode":"renderFresh","scope":"affectedOutput","fields":{"reasonCodes":["CP_DOWNGRADE_CACHEABILITY_UNKNOWN","CP_DOWNGRADE_INCOMPLETE_OBSERVATION","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API","CP_DOWNGRADE_UNKNOWN_REQUEST_API"],"target":"freshRender"}},"isPublicCacheCandidate":false}}}
a:I["6efdf509a785",[],"default",1] a:I["6efdf509a785",[],"default",1]
1:["$","$La",null,{"params":"$@b","searchParams":"$@c"}] 1:["$","$La",null,{"params":"$@b","searchParams":"$@c"}]
b:{} b:{}
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"appBrowserEntry": "_next/static/chunks/index-iZ0Yke16.js" "appBrowserEntry": "_next/static/chunks/index-VFVBI02U.js"
} }
+28 -68
View File
@@ -1,91 +1,51 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { access, readFile, readdir } from "node:fs/promises"; import { readFile } from "node:fs/promises";
import test from "node:test"; import test from "node:test";
const developmentPreviewMeta =
/<meta(?=[^>]*\bname=["']codex-preview["'])(?=[^>]*\bcontent=["']development["'])[^>]*>/i;
const templateRoot = new URL("../", import.meta.url);
const previewRoot = new URL("../app/_sites-preview/", import.meta.url);
async function render() { async function render() {
const workerUrl = new URL("../dist/server/index.js", import.meta.url); const workerUrl = new URL("../dist/server/index.js", import.meta.url);
workerUrl.searchParams.set("test", `${process.pid}-${Date.now()}`); workerUrl.searchParams.set("test", `${process.pid}-${Date.now()}`);
const { default: worker } = await import(workerUrl.href); const { default: worker } = await import(workerUrl.href);
return worker.fetch( return worker.fetch(
new Request("http://localhost/", { new Request("http://localhost/", { headers: { accept: "text/html" } }),
headers: { accept: "text/html" }, { ASSETS: { fetch: async () => new Response("Not found", { status: 404 }) } },
}), { waitUntil() {}, passThroughOnException() {} },
{
ASSETS: {
fetch: async () => new Response("Not found", { status: 404 }),
},
},
{
waitUntil() {},
passThroughOnException() {},
},
); );
} }
test("server-renders the starter loading skeleton", async () => { test("server-renders the pixel bead application shell", async () => {
const response = await render(); const response = await render();
assert.equal(response.status, 200); assert.equal(response.status, 200);
assert.match(response.headers.get("content-type") ?? "", /^text\/html\b/i); assert.match(response.headers.get("content-type") ?? "", /^text\/html\b/i);
const html = await response.text(); const html = await response.text();
assert.match(html, developmentPreviewMeta); assert.match(html, /<title>豆格工坊|图片转拼豆图纸<\/title>/i);
assert.match(html, /<title>Your site is taking shape<\/title>/i); assert.doesNotMatch(html, /导入现成图纸/);
assert.match(html, /Building your site/); assert.match(html, /MARD 221 基础色卡/);
assert.match(html, /Your site is taking shape/); assert.match(html, /图片只在当前浏览器中处理/);
assert.match( assert.doesNotMatch(html, /codex-preview|Your site is taking shape/i);
html,
/Your first version will appear here automatically when its ready\./,
);
assert.doesNotMatch(html, /Codex/);
assert.match(html, /react-loading-skeleton/);
assert.match(html, /role="status"/);
}); });
test("keeps the loading skeleton scoped and disposable", async () => { test("keeps MARD pattern import wired to the crop and recognition flow", async () => {
const [preview, css, page, layout, packageJson, files] = await Promise.all([ const [page, css, palette] = await Promise.all([
readFile(new URL("SkeletonPreview.tsx", previewRoot), "utf8"),
readFile(new URL("preview.css", previewRoot), "utf8"),
readFile(new URL("../app/page.tsx", import.meta.url), "utf8"), readFile(new URL("../app/page.tsx", import.meta.url), "utf8"),
readFile(new URL("../app/layout.tsx", import.meta.url), "utf8"), readFile(new URL("../app/globals.css", import.meta.url), "utf8"),
readFile(new URL("../package.json", import.meta.url), "utf8"), readFile(new URL("../app/mard-palette.ts", import.meta.url), "utf8"),
readdir(previewRoot),
]); ]);
assert.deepEqual(files.sort(), ["SkeletonPreview.tsx", "preview.css"]); assert.match(page, /function importMardPatternImage\(/);
assert.match(preview, /from "react-loading-skeleton"/); assert.match(page, /const ENABLE_PATTERN_IMPORT = false/);
assert.match(preview, /baseColor="#eceae7"/); assert.match(page, /recognizedCodes/);
assert.match(preview, /highlightColor="#f9f8f6"/); assert.match(page, /missingCodeAsEmpty/);
assert.match(preview, /duration=\{2\.8\}/); assert.match(page, /没有识别到 MARD 编码的格子视为空白/);
assert.match(preview, /sites-skeleton-search-placeholder/); assert.match(page, /pattern-import-grid-overlay/);
assert.match(packageJson, /"react-loading-skeleton": "3\.5\.0"/); assert.match(page, /max="256"/);
assert.match(page, /maximumExportSide = 8192/);
const shellIndex = preview.indexOf('className="sites-skeleton-shell"'); assert.match(page, /color\.code.*color\.count.*颗/);
const statusIndex = preview.indexOf('className="sites-skeleton-status"'); assert.match(page, /下载高清 PNG/);
assert.ok(shellIndex >= 0 && statusIndex > shellIndex); assert.match(css, /\.pattern-import-grid-overlay/);
assert.match(css, /position:\s*fixed/); assert.match(css, /@media \(max-width: 760px\)/);
assert.match(css, /inset:\s*0/); assert.match(palette, /export const MARD_221/);
assert.match(css, /opacity:\s*0\.52/); assert.match(palette, /A:\s*"#[0-9A-F]{6}/);
assert.match(css, /prefers-reduced-motion:\s*reduce/);
assert.doesNotMatch(css, /#020617|canvas|pets|progress/i);
assert.doesNotMatch(
preview,
/loading-spinner|status-mark|status-progress|canvas|cookie|random/i,
);
assert.match(page, /export const metadata:\s*Metadata/);
assert.match(page, /"codex-preview": "development"/);
assert.match(page, /<SkeletonPreview \/>/);
assert.match(layout, /title:\s*"Starter Project"/);
assert.doesNotMatch(layout, /codex-preview|_sites-preview|themeColor|\bViewport\b/);
assert.doesNotMatch(css, /(^|\s)(html|body)\s*\{/m);
await assert.rejects(
access(new URL("public/_sites-preview", templateRoot)),
);
}); });