From 1985566ae5665f0742c05e733601e278fd075305 Mon Sep 17 00:00:00 2001 From: NaitLee Date: Tue, 17 Dec 2024 23:33:24 +0800 Subject: [PATCH] add junks produced these days --- entemu-legacy/index.html | 47 +++++ entemu-legacy/main.css | 36 ++++ entemu-legacy/main.js | 189 +++++++++++++++++ entemu/.gitignore | 13 ++ entemu/.npmrc | 2 + entemu/.vscode/extensions.json | 5 + entemu/.vscode/settings.json | 17 ++ entemu/README.md | 16 ++ entemu/common/date.ts | 14 ++ entemu/common/plan.ts | 365 +++++++++++++++++++++++++++++++++ entemu/deno.json | 44 ++++ entemu/deno.lock | 229 +++++++++++++++++++++ entemu/dev.ts | 15 ++ entemu/islands/Calendar.tsx | 161 +++++++++++++++ entemu/islands/PlanDB.tsx | 35 ++++ entemu/islands/Planner.tsx | 72 +++++++ entemu/junkrc | 8 + entemu/main.ts | 33 +++ entemu/routes/_app.tsx | 17 ++ entemu/routes/api/[name].tsx | 10 + entemu/routes/index.tsx | 6 + entemu/static/favicon.ico | Bin 0 -> 22382 bytes entemu/static/logo.svg | 6 + entemu/static/styles.css | 160 +++++++++++++++ entemu/utils.ts | 6 + go.mod | 3 + index.html | 26 +++ junk.css | 23 +++ main.go | 166 +++++++++++++++ 29 files changed, 1724 insertions(+) create mode 100644 entemu-legacy/index.html create mode 100644 entemu-legacy/main.css create mode 100644 entemu-legacy/main.js create mode 100644 entemu/.gitignore create mode 100644 entemu/.npmrc create mode 100644 entemu/.vscode/extensions.json create mode 100644 entemu/.vscode/settings.json create mode 100644 entemu/README.md create mode 100644 entemu/common/date.ts create mode 100644 entemu/common/plan.ts create mode 100644 entemu/deno.json create mode 100644 entemu/deno.lock create mode 100644 entemu/dev.ts create mode 100644 entemu/islands/Calendar.tsx create mode 100644 entemu/islands/PlanDB.tsx create mode 100644 entemu/islands/Planner.tsx create mode 100755 entemu/junkrc create mode 100644 entemu/main.ts create mode 100644 entemu/routes/_app.tsx create mode 100644 entemu/routes/api/[name].tsx create mode 100644 entemu/routes/index.tsx create mode 100644 entemu/static/favicon.ico create mode 100644 entemu/static/logo.svg create mode 100644 entemu/static/styles.css create mode 100644 entemu/utils.ts create mode 100644 go.mod create mode 100644 index.html create mode 100644 junk.css create mode 100644 main.go diff --git a/entemu-legacy/index.html b/entemu-legacy/index.html new file mode 100644 index 0000000..53354e8 --- /dev/null +++ b/entemu-legacy/index.html @@ -0,0 +1,47 @@ + + + + + + 产品规划 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
厂房建成日:
生产效率:
模拟次数:
产出量:
产品: + +
+
+ + + \ No newline at end of file diff --git a/entemu-legacy/main.css b/entemu-legacy/main.css new file mode 100644 index 0000000..f9efc41 --- /dev/null +++ b/entemu-legacy/main.css @@ -0,0 +1,36 @@ +body { + font-size: large; + margin: 1em auto; + width: 29.7cm; +} +input, button, select { + font: inherit; +} +table.dashboard { + float: right; +} +table.dashboard td:nth-child(1) { + text-align: end; +} +#tables { + display: flex; + max-width: 100%; +} +table.result { + text-align: center; + margin: 0 1em; +} +table.result td { + min-width: 6em; + padding: 4px 8px; +} +table.result tr.highlight { + background-color: yellow !important; +} +table.result thead tr { + background-color: aquamarine; + font-weight: bold; +} +table.result tbody tr:nth-of-type(2n) { + background-color: #ededed; +} \ No newline at end of file diff --git a/entemu-legacy/main.js b/entemu-legacy/main.js new file mode 100644 index 0000000..30b14e4 --- /dev/null +++ b/entemu-legacy/main.js @@ -0,0 +1,189 @@ + +/** + * + * @param {string} s + * @returns {HTMLElement} + */ +function E(s) { + return document.querySelector(s); +} + +/** + * + * @param {string} tag + * @returns {HTMLElement} + */ +function A(tag) { + return document.createElement(tag); +} + +/** + * + * @param {string} s + * @returns {[number, number, number]} + */ +function parse_date(s) { + return s.split("/").map(x => parseInt(x)); +} + +/** + * + * @param {[number, number, number]} d + * @returns {string} + */ +function fmt_date(d) { + return "" + d[0] + "/" + d[1] + "/" + d[2]; +} + +/** + * + * @param {[number, number, number]} d + * @param {number} n + * @returns {[number, number, number]} + */ +function add_day(d, n) { + d = d.concat(); + d[2] += n; + d[1] += d[2] / 30 | 0; + d[2] = d[2] % 30; + d[0] += d[1] / 12 | 0; + d[1] = d[1] % 12; + if (d[2] < 1) { + d[2] += 30; + d[1] -= 1; + } + if (d[1] < 1) { + d[1] += 12; + d[0] -= 1; + } + return d; +} + +const db = { + rprice: { + R1: 12, + R2: 12, + R3: 12, + R4: 12, + }, + rdelay: { + R1: 30, + R2: 30, + R3: 60, + R4: 60, + }, + pprice: { + P1: 50, + P2: 70, + P3: 90, + P4: 100, + P5: 100, + }, + pdelay: { + P1: 56, + P2: 56, + P3: 56, + P4: 56, + P5: 56, + }, + pr: { + P1: { R1: 1 }, + P2: { R2: 1, R3: 1 }, + P3: { R1: 1, R3: 1, R4: 1 }, + P4: { R2: 1, R3: 1, R4: 2 }, + P5: { P2: 1, R4: 1 }, + }, +}; + +/** + * + * @param {string} p + * @param {[number, number, number]} d + * @return {{ [r: string]: [number, [number, number, number]] }} + */ +function buy_date_of(p, d) { + const r = db.pr[p]; + const s = {}; + for (const key in r) { + if (key[0] === "R") { + s[key] = [r[key], add_day(d, -db.rdelay[key])]; + } else if (key[0] === "P") { + s[key] = [r[key], add_day(d, -db.pdelay[key])]; + Object.assign(s, buy_date_of(key, add_day(d, -db.pdelay[key]))); + } + } + return s; +} + +const order = ["P2", "P4", "P1", "P5", "P3"]; + +document.addEventListener("DOMContentLoaded", function() { + const datestart_input = E("#datestart"); + const efficiency_input = E("#efficiency"); + const product_select = E("#product"); + const limit_input = E("#limit"); + const amount_input = E("#amount"); + const ok_button = E("#ok"); + const tables = E("#tables"); + function generate() { + tables.innerHTML = ""; + const datestart = parse_date(datestart_input.value); + const limit = parseInt(limit_input.value); + const efficiency = parseInt(efficiency_input.value); + const amount = parseInt(amount_input.value); + const p = product_select.value; + if (order.indexOf(p) === -1) return; + // for (const p of order) { + const table = A("table"); + table.classList.add("result"); + tables.appendChild(table); + const thead = A("thead"); + table.appendChild(thead); + let tr = A("tr"); + thead.append(tr); + const plabel = A("td"); + plabel.textContent = p + " 规划"; + E("title").textContent = p + ": " + fmt_date(datestart); + tr.appendChild(plabel); + tr = A("tr"); + thead.appendChild(tr); + const d0 = buy_date_of(p, datestart); + plabel.colSpan = Object.keys(d0).length + 2; + for (const r in d0) { + const td = A("td"); + td.textContent = "购买 " + r + " * " + (d0[r][0] * amount); + tr.appendChild(td); + } + let td = A("td"); + td.textContent = p + " 开产日"; + tr.appendChild(td); + td = A("td"); + td.textContent = p + " 产出 * " + amount; + tr.appendChild(td); + const tbody = A("tbody"); + table.appendChild(tbody); + let next_date = datestart; + for (let i = 0; i < limit; ++i) { + const tr = A("tr"); + tr.addEventListener("click", () => tr.classList.toggle("highlight")); + tbody.appendChild(tr); + const d = buy_date_of(p, next_date); + for (const r in d) { + const td = A("td"); + td.textContent = fmt_date(d[r][1]); + tr.appendChild(td); + } + let td = A("td"); + td.textContent = fmt_date(next_date); + tr.appendChild(td); + td = A("td"); + td.textContent = fmt_date(add_day(next_date, efficiency /*db.pdelay[p]*/)); + tr.appendChild(td); + next_date = add_day(next_date, efficiency /*db.pdelay[p]*/); + } + // } + } + ok_button.addEventListener("click", () => generate()); + product_select.addEventListener("change", () => generate()); + generate(); +}); diff --git a/entemu/.gitignore b/entemu/.gitignore new file mode 100644 index 0000000..0735100 --- /dev/null +++ b/entemu/.gitignore @@ -0,0 +1,13 @@ +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# Fresh build directory +_fresh/ +# npm + other dependencies +node_modules/ +vendor/ + diff --git a/entemu/.npmrc b/entemu/.npmrc new file mode 100644 index 0000000..b892fb4 --- /dev/null +++ b/entemu/.npmrc @@ -0,0 +1,2 @@ +# for China Mainland users +registry=https://registry.npmmirror.com diff --git a/entemu/.vscode/extensions.json b/entemu/.vscode/extensions.json new file mode 100644 index 0000000..09cf720 --- /dev/null +++ b/entemu/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "denoland.vscode-deno" + ] +} diff --git a/entemu/.vscode/settings.json b/entemu/.vscode/settings.json new file mode 100644 index 0000000..a5f0701 --- /dev/null +++ b/entemu/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "deno.enable": true, + "deno.lint": true, + "editor.defaultFormatter": "denoland.vscode-deno", + "[typescriptreact]": { + "editor.defaultFormatter": "denoland.vscode-deno" + }, + "[typescript]": { + "editor.defaultFormatter": "denoland.vscode-deno" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "denoland.vscode-deno" + }, + "[javascript]": { + "editor.defaultFormatter": "denoland.vscode-deno" + } +} diff --git a/entemu/README.md b/entemu/README.md new file mode 100644 index 0000000..ee7e5f9 --- /dev/null +++ b/entemu/README.md @@ -0,0 +1,16 @@ +# Fresh project + +Your new Fresh project is ready to go. You can follow the Fresh "Getting +Started" guide here: https://fresh.deno.dev/docs/getting-started + +### Usage + +Make sure to install Deno: https://deno.land/manual/getting_started/installation + +Then start the project in development mode: + +``` +deno task dev +``` + +This will watch the project directory and restart as necessary. diff --git a/entemu/common/date.ts b/entemu/common/date.ts new file mode 100644 index 0000000..44a0804 --- /dev/null +++ b/entemu/common/date.ts @@ -0,0 +1,14 @@ + +export type DateTuple = [number, number, number]; + +export function datetuple(d: number): DateTuple { + return [d / 12 / 30 | 0, (d / 30 | 0) % 12, d % 30] +} + +export function datefmt(d: number): string { + return datetuple(d).map(n => n + 1).join("/"); +} + +export function datefrom(y: number, m: number, d: number) { + return (y - 1) * 12 * 30 + (m - 1) * 30 + (d - 1); +} diff --git a/entemu/common/plan.ts b/entemu/common/plan.ts new file mode 100644 index 0000000..6e5694f --- /dev/null +++ b/entemu/common/plan.ts @@ -0,0 +1,365 @@ +import { datefrom } from "./date.ts"; + +export interface Plan { + name: string; + times: number; + price: number; + start: number; + amount: number; + period: number; + nextonce: boolean; + role: string; + next: Partial[]; +} + +export const dplan: Plan = { + name: "新策划", + times: 1, + price: 0, + start: 0, + amount: 1, + period: 0, + nextonce: false, + role: "", + next: [], +}; + +export interface IPlan { + date: number; + plan: Plan; + order: number; + cost: number; + current: number; +} + +export function bplan(...plan: Partial[]): Plan { + const p = { ...dplan }; + for (const p1 of plan) { + Object.assign(p, p1); + } + return p; +} + +export function addplan(cal: Record, p: Plan): number[] { + const e: number[] = []; + let d = p.start; + for (let i = 0; i < p.times; i++) { + if (cal[d] === undefined) + cal[d] = []; + cal[d].push({ + date: d, + plan: p, + order: i, + cost: p.price * p.amount, + current: 0, + }); + if (!p.nextonce || (p.nextonce && i === 0)) + for (const n of p.next) { + e.push(...addplan(cal, bplan(n, { + start: d + (n.start || 0), + amount: (p.amount || 1) * (n.amount || 1), + }))); + } + e.push(d); + d += p.period; + } + return e; +} + +export function expandplan(d: number, p: Plan): Plan[] { + const l: Plan[] = []; + for (let i = 0; i < p.times; ++i) { + l.push({ ...p, times: 1, start: d + p.period * i }); + } + return l; +} + +export const plandbpre: Record> = { + r1_order: { + name: "采购 R1", + start: -30, + period: 30, + next: [{ + name: "收取 R1", + price: 12, + start: 30, + period: 0, + role: "r1", + }], + }, + r2_order: { + name: "采购 R2", + start: -30, + period: 30, + next: [{ + name: "收取 R2", + price: 12, + start: 30, + period: 0, + role: "r2", + }], + }, + r3_order: { + name: "采购 R3", + start: -60, + period: 60, + next: [{ + name: "收取 R3", + price: 12, + start: 60, + period: 0, + role: "r3", + }], + }, + r4_order: { + name: "采购 R4", + start: -60, + period: 60, + next: [{ + name: "收取 R4", + price: 12, + start: 60, + period: 0, + role: "r4", + }], + }, +}; + +export const storage_keys: Record = { + // factory: "工厂", + // linea: "自动线", + // optimizea: "自动线技改", + p1: "P1", + p2: "P2", + p3: "P3", + p4: "P4", + p5: "P5", + r1: "R1", + r2: "R2", + r3: "R3", + r4: "R4", + r5: "R5", +}; + +export const plandb: Record> = { + ads: { + name: "广告费", + price: 80, + }, + loan: { + name: "贷款", + price: 0, + start: 0, + role: "loan", + }, + loanback: { + name: "还贷", + price: 0, + start: 0, + role: "-loan", + }, + strategy_ads: { + name: "战略广告", + price: 20, + start: datefrom(1, 12, 30), + period: 360, + times: 5, + }, + iso9000: { + name: "ISO9000资质开发", + price: 10, + times: 2, + period: 360, + nextonce: true, + next: [{ + name: "ISO9000资质开发完成", + price: 0, + start: datefrom(3, 1, 1), + }], + }, + iso14000: { + name: "ISO14000资质开发", + price: 10, + times: 3, + period: 360, + nextonce: true, + next: [{ + name: "ISO14000资质开发完成", + price: 0, + start: datefrom(4, 1, 1), + }], + }, + market_domestic: { + name: "国内市场资质开发", + price: 10, + times: 2, + period: 360, + nextonce: true, + next: [{ + name: "国内市场资质开发完成", + price: 0, + start: datefrom(3, 1, 1), + }], + }, + market_asia: { + name: "亚洲市场资质开发", + price: 10, + times: 3, + period: 360, + nextonce: true, + next: [{ + name: "亚洲市场资质开发完成", + price: 0, + start: datefrom(4, 1, 1), + }], + }, + market_intl: { + name: "国际市场资质开发", + price: 10, + times: 4, + period: 360, + nextonce: true, + next: [{ + name: "国际市场资质开发完成", + price: 0, + start: datefrom(5, 1, 1), + }], + }, + rent_factory: { + name: "租用厂房", + price: 40, + times: 4, + period: 360, + role: "factory", + }, + p1_cert: { + name: "P1 资质开发", + price: 10, + period: 30, + nextonce: true, + next: [{ + name: "P1 资质拥有", + start: 30, + price: 0, + period: 0, + role: "p1cert", + }], + }, + p2_cert: { + name: "P2 资质开发", + price: 10, + period: 30, + times: 2, + nextonce: true, + next: [{ + name: "P2 资质拥有", + start: 60, + price: 0, + period: 0, + role: "p2cert", + }], + }, + p4_cert: { + name: "P4 资质开发", + price: 10, + period: 60, + times: 4, + nextonce: true, + next: [{ + name: "P4 资质拥有", + start: 4*60, + price: 0, + period: 0, + role: "p4cert", + }], + }, + build_line_manual: { + name: "建设手工线", + price: 50, + period: 0, + role: "linem", + }, + build_line_auto: { + name: "建设自动线", + times: 3, + price: 50, + period: 30, + role: "linea", + }, + optimize_auto: { + name: "自动线技改", + times: 1, + price: 20, + period: 20, + role: "optimizea", + }, + p1_produce: { + name: "P1 生产", + times: 30, + price: 9, + period: 0, + role: "-r1", + next: [plandbpre.r1_order, { + name: "收取 P1", + times: 1, + start: 56, + price: 0, + period: 0, + role: "p1", + }], + }, + p2_produce: { + name: "P2 生产", + times: 30, + price: 9, + period: 0, + role: "-r2,-r3", + next: [plandbpre.r2_order, plandbpre.r3_order, { + name: "收取 P2", + times: 1, + start: 56, + price: 0, + period: 0, + role: "p2", + }], + }, + p4_produce: { + name: "P4 生产", + times: 30, + price: 9, + period: 0, + role: "-r2,-r3,-r4,-r4", + next: [ + plandbpre.r2_order, + plandbpre.r3_order, + bplan(plandbpre.r4_order, { amount: 2 }), { + name: "收取 P4", + times: 1, + start: 56, + price: 0, + period: 0, + role: "p4", + }], + }, + p2_sell: { + name: "卖出 P2", + price: 0, + role: "-p2", + next: [{ + name: "收到 P2 账款", + price: -52, + start: 30, + }], + }, + p4_sell: { + name: "卖出 P4", + price: 0, + amount: 1, + role: "-p4", + next: [{ + name: "收到 P4 账款", + price: -91, + start: 30, + }], + }, +}; diff --git a/entemu/deno.json b/entemu/deno.json new file mode 100644 index 0000000..d929972 --- /dev/null +++ b/entemu/deno.json @@ -0,0 +1,44 @@ +{ + "tasks": { + "check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx", + "dev": "deno run -A --watch=static/,routes/ dev.ts", + "build": "deno run -A dev.ts build", + "start": "deno run -A main.ts", + "update": "deno run -A -r jsr:@fresh/update ." + }, + "lint": { + "rules": { + "tags": [ + "fresh", + "recommended" + ] + } + }, + "exclude": [ + "**/_fresh/*" + ], + "imports": { + "fresh": "jsr:@fresh/core@^2.0.0-alpha.25", + "@fresh/plugin-tailwind": "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7", + "preact": "npm:preact@^10.24.3", + "@preact/signals": "npm:@preact/signals@^1.3.0" + }, + "compilerOptions": { + "lib": [ + "dom", + "dom.asynciterable", + "dom.iterable", + "deno.ns" + ], + "jsx": "precompile", + "jsxImportSource": "preact", + "jsxPrecompileSkipElements": [ + "a", + "img", + "source", + "body", + "html", + "head" + ] + } +} diff --git a/entemu/deno.lock b/entemu/deno.lock new file mode 100644 index 0000000..29ed76e --- /dev/null +++ b/entemu/deno.lock @@ -0,0 +1,229 @@ +{ + "version": "4", + "specifiers": { + "jsr:@fresh/core@^2.0.0-alpha.25": "2.0.0-alpha.25", + "jsr:@luca/esbuild-deno-loader@0.11": "0.11.0", + "jsr:@std/bytes@^1.0.2": "1.0.4", + "jsr:@std/crypto@1": "1.0.3", + "jsr:@std/datetime@~0.225.2": "0.225.2", + "jsr:@std/encoding@1": "1.0.5", + "jsr:@std/encoding@^1.0.5": "1.0.5", + "jsr:@std/fmt@1": "1.0.3", + "jsr:@std/fs@1": "1.0.6", + "jsr:@std/html@1": "1.0.3", + "jsr:@std/jsonc@1": "1.0.1", + "jsr:@std/media-types@1": "1.1.0", + "jsr:@std/path@1": "1.0.8", + "jsr:@std/path@^1.0.6": "1.0.8", + "jsr:@std/path@^1.0.8": "1.0.8", + "jsr:@std/semver@1": "1.0.3", + "npm:@preact/signals@^1.2.3": "1.3.1_preact@10.25.1", + "npm:@preact/signals@^1.3.0": "1.3.1_preact@10.25.1", + "npm:esbuild-wasm@0.23.1": "0.23.1", + "npm:esbuild@0.23.1": "0.23.1", + "npm:preact-render-to-string@^6.5.11": "6.5.11_preact@10.25.1", + "npm:preact@^10.24.1": "10.25.1", + "npm:preact@^10.24.3": "10.25.1" + }, + "jsr": { + "@fresh/core@2.0.0-alpha.25": { + "integrity": "1069232989c4bc7f69ad424f6b97cdba1a7631d1307e1c2964aed748cd0cf74b", + "dependencies": [ + "jsr:@luca/esbuild-deno-loader", + "jsr:@std/crypto", + "jsr:@std/datetime", + "jsr:@std/encoding@1", + "jsr:@std/fmt", + "jsr:@std/fs", + "jsr:@std/html", + "jsr:@std/jsonc", + "jsr:@std/media-types", + "jsr:@std/path@1", + "jsr:@std/semver", + "npm:@preact/signals@^1.2.3", + "npm:esbuild", + "npm:esbuild-wasm", + "npm:preact-render-to-string", + "npm:preact@^10.24.1", + "npm:preact@^10.24.3" + ] + }, + "@luca/esbuild-deno-loader@0.11.0": { + "integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c", + "dependencies": [ + "jsr:@std/bytes", + "jsr:@std/encoding@^1.0.5", + "jsr:@std/path@^1.0.6" + ] + }, + "@std/bytes@1.0.4": { + "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" + }, + "@std/crypto@1.0.3": { + "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" + }, + "@std/datetime@0.225.2": { + "integrity": "45f0100554a912cd65f48089ef0a33aa1eb6ea21f08090840b539ab582827eaa" + }, + "@std/encoding@1.0.5": { + "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" + }, + "@std/fmt@1.0.3": { + "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" + }, + "@std/fs@1.0.6": { + "integrity": "42b56e1e41b75583a21d5a37f6a6a27de9f510bcd36c0c85791d685ca0b85fa2", + "dependencies": [ + "jsr:@std/path@^1.0.8" + ] + }, + "@std/html@1.0.3": { + "integrity": "7a0ac35e050431fb49d44e61c8b8aac1ebd55937e0dc9ec6409aa4bab39a7988" + }, + "@std/jsonc@1.0.1": { + "integrity": "6b36956e2a7cbb08ca5ad7fbec72e661e6217c202f348496ea88747636710dda" + }, + "@std/media-types@1.1.0": { + "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" + }, + "@std/path@1.0.8": { + "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" + }, + "@std/semver@1.0.3": { + "integrity": "7c139c6076a080eeaa4252c78b95ca5302818d7eafab0470d34cafd9930c13c8" + } + }, + "npm": { + "@esbuild/aix-ppc64@0.23.1": { + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==" + }, + "@esbuild/android-arm64@0.23.1": { + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==" + }, + "@esbuild/android-arm@0.23.1": { + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==" + }, + "@esbuild/android-x64@0.23.1": { + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==" + }, + "@esbuild/darwin-arm64@0.23.1": { + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==" + }, + "@esbuild/darwin-x64@0.23.1": { + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==" + }, + "@esbuild/freebsd-arm64@0.23.1": { + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==" + }, + "@esbuild/freebsd-x64@0.23.1": { + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==" + }, + "@esbuild/linux-arm64@0.23.1": { + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==" + }, + "@esbuild/linux-arm@0.23.1": { + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==" + }, + "@esbuild/linux-ia32@0.23.1": { + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==" + }, + "@esbuild/linux-loong64@0.23.1": { + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==" + }, + "@esbuild/linux-mips64el@0.23.1": { + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==" + }, + "@esbuild/linux-ppc64@0.23.1": { + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==" + }, + "@esbuild/linux-riscv64@0.23.1": { + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==" + }, + "@esbuild/linux-s390x@0.23.1": { + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==" + }, + "@esbuild/linux-x64@0.23.1": { + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==" + }, + "@esbuild/netbsd-x64@0.23.1": { + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==" + }, + "@esbuild/openbsd-arm64@0.23.1": { + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==" + }, + "@esbuild/openbsd-x64@0.23.1": { + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==" + }, + "@esbuild/sunos-x64@0.23.1": { + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==" + }, + "@esbuild/win32-arm64@0.23.1": { + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==" + }, + "@esbuild/win32-ia32@0.23.1": { + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==" + }, + "@esbuild/win32-x64@0.23.1": { + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==" + }, + "@preact/signals-core@1.8.0": { + "integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA==" + }, + "@preact/signals@1.3.1_preact@10.25.1": { + "integrity": "sha512-nNvSF2O7RDzxp1Rm7SkA5QhN1a2kN8pGE8J5o6UjgDof0F0Vlg6d6HUUVxxqZ1uJrN9xnH2DpL6rpII3Es0SsQ==", + "dependencies": [ + "@preact/signals-core", + "preact" + ] + }, + "esbuild-wasm@0.23.1": { + "integrity": "sha512-L3vn7ctvBrtScRfoB0zG1eOCiV4xYvpLYWfe6PDZuV+iDFDm4Mt3xeLIDllG8cDHQ8clUouK3XekulE+cxgkgw==" + }, + "esbuild@0.23.1": { + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dependencies": [ + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" + ] + }, + "preact-render-to-string@6.5.11_preact@10.25.1": { + "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", + "dependencies": [ + "preact" + ] + }, + "preact@10.25.1": { + "integrity": "sha512-frxeZV2vhQSohQwJ7FvlqC40ze89+8friponWUFeVEkaCfhC6Eu4V0iND5C9CXz8JLndV07QRDeXzH1+Anz5Og==" + } + }, + "workspace": { + "dependencies": [ + "jsr:@fresh/core@^2.0.0-alpha.25", + "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7", + "npm:@preact/signals@^1.3.0", + "npm:preact@^10.24.3" + ] + } +} diff --git a/entemu/dev.ts b/entemu/dev.ts new file mode 100644 index 0000000..8110b06 --- /dev/null +++ b/entemu/dev.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env -S deno run -A --watch=static/,routes/ + +import { Builder } from "fresh/dev"; +import { app } from "./main.ts"; + +const builder = new Builder(); + +if (Deno.args.includes("build")) { + await builder.build(app); +} else { + await builder.listen(app, { + hostname: Deno.env.get("junkhost") || "127.0.0.1", + port: parseInt(Deno.env.get("junkport") || "11001"), + }); +} diff --git a/entemu/islands/Calendar.tsx b/entemu/islands/Calendar.tsx new file mode 100644 index 0000000..633e7c4 --- /dev/null +++ b/entemu/islands/Calendar.tsx @@ -0,0 +1,161 @@ +import { useComputed, type Signal } from "@preact/signals"; +import { addplan, bplan, IPlan, Plan, storage_keys } from "../common/plan.ts"; +import { datefmt, datefrom } from "../common/date.ts"; + +export interface CalendarProps { + date: Signal; + plans: Signal; +} + +const casht_a = -800; +const casht_b = -950; + +const calendar_span = 30; + +const calendar_days = new Array(calendar_span).fill(0).map((_, i) => i); + +export default function Calendar({ date, plans }: CalendarProps) { + const _calendar = useComputed(() => { + // cal + let e: number[] = []; + const cal: Record = {}; + for (const p of plans.value) { + e = e.concat(addplan(cal, p)); + } + e = Array.from(new Set(e)).sort((a, b) => a - b); + let c = 600; + let current = 600; + let alert = -1; + let die = -1; + /* + const cal_ex: Record = {}; + const e_ex: number[] = []; + */ + // cash + for (const d of e) { + for (const p of cal[d]) { + c -= p.cost; + p.current = c; + if (alert === -1 && c < casht_a && d < datefrom(6, 1, 1)) + alert = d; + if (die === -1 && c < casht_b && d < datefrom(6, 1, 1)) + die = d; + /* + if (c < 0) { + e_ex.push(...(addplan(cal_ex, bplan({ + name: "借款", + start: d, + price: -p.cost, + })).concat(addplan(cal_ex, bplan({ + name: "还贷", + start: d + 360, + price: p.cost * 1.05, + }))))); + } + */ + } + if (d < date.value + 30) { + current = c; + } + } + /* + for (const k in cal_ex) { + if (cal[k]) + cal[k].push(...cal_ex[k]); + else + cal[k] = cal_ex[k]; + } + e.push(...e_ex); + */ + return { + cal: cal, + e: e, + cash: current, + alert: alert, + die: die, + }; + }); + const calendar = useComputed(() => _calendar.value.cal); + const event_days = useComputed(() => _calendar.value.e); + const cash = useComputed(() => _calendar.value.cash); + const date_alert = useComputed(() => _calendar.value.alert); + const date_die = useComputed(() => _calendar.value.die); + const storage = useComputed(() => { + const st: Record = {}; + for (const d of event_days.value) { + if (d >= date.value + 30) + break; + for (const p of calendar.value[d]) { + if (!p.plan.role) continue; + for (let role of p.plan.role.split(",")) { + let amount = p.plan.amount; + // if (!role) continue; + if (role[0] === "-") { + amount = -amount; + role = role.slice(1); + } + if (st[role] === undefined) + st[role] = 0; + st[role] += amount; + } + } + } + return st; + }); + let last_date = -1; + return
+
+
¥{cash.value}
+
+ + + + {datefmt(date.value)} + + + +
+
+ {calendar_days.map(d => {d + 1})} +
+
+ + + + + + + {Object.entries(storage.value).filter(st => storage_keys[st[0]] && st[1]).map(st => )} + +
库存数量
{storage_keys[st[0]]}{st[1]}
+ + + + + + + + + + + + + {calendar_days.map(d => d + date.value).filter(d => calendar.value[d]).map(d => calendar.value[d].map(p => { + const e = + + + + + + + + last_date = p.date; + return e; + }))} + +
日期描述数量次序收支余额
{last_date === p.date ? "" : datefmt(p.date)}{p.plan.name}{p.plan.amount}{p.plan.times === 1 ? "1" : "" + (p.order + 1) + " / " + p.plan.times}{p.cost !== 0 ? "¥" + (-p.cost) : ""}¥{p.current}
+
; +} diff --git a/entemu/islands/PlanDB.tsx b/entemu/islands/PlanDB.tsx new file mode 100644 index 0000000..cd994b2 --- /dev/null +++ b/entemu/islands/PlanDB.tsx @@ -0,0 +1,35 @@ +import { useSignal, type Signal } from "@preact/signals"; +import { bplan, dplan, Plan, plandb } from "../common/plan.ts"; + +export interface PlanDBProps { + date: Signal; + plans: Signal; + cplan: Signal; +} + +export interface PlanDisplayProps { + plan: Plan; + add?: (plan: Plan) => any; + modify?: (plan: Plan) => any; + del?: (plan: Plan) => any; +} + +export function PlanDisplay({ plan, add, modify, del }: PlanDisplayProps) { + const cplan: Signal = useSignal({ ...dplan, ...plan }); + return
+
{JSON.stringify(cplan.value)}
+
; +} + +export default function PlanDB({ date, plans, cplan }: PlanDBProps) { + return
+
所有
+
+ {Object.entries(plans.value).map(([k, v]) => )} +
+
预设
+
+ {Object.entries(plandb).map(([k, v]) => )} +
+
; +} diff --git a/entemu/islands/Planner.tsx b/entemu/islands/Planner.tsx new file mode 100644 index 0000000..bb4bebb --- /dev/null +++ b/entemu/islands/Planner.tsx @@ -0,0 +1,72 @@ +import { type Signal, useSignal } from "@preact/signals"; +import { bplan, Plan, plandb } from "../common/plan.ts"; +import Calendar from "./Calendar.tsx"; +import { datefrom } from "../common/date.ts"; + +export default function Planner() { + const date = useSignal(self.localStorage ? parseInt(localStorage.getItem("entemu.date") || "0") : 0); + date.subscribe(d => self.localStorage ? localStorage.setItem("entemu.date", d.toString()) : void 0); + const plans: Signal = useSignal([ + // base + bplan(plandb.iso9000), + bplan(plandb.iso14000), + bplan(plandb.market_domestic), + bplan(plandb.market_asia), + bplan({ name: "管理费", times: 5*12*30, period: 30, price: 5 }), + bplan({ name: "维修费", start: datefrom(2, 4, 1), times: 4, amount: 4, period: 360, price: 15 }), + + // start 1 + bplan(plandb.ads), + bplan(plandb.strategy_ads), + bplan(plandb.rent_factory, { times: 4, amount: 1 }), + // bplan(plandb.p1_cert), + bplan(plandb.p2_cert), + bplan(plandb.p4_cert), + bplan(plandb.build_line_auto, { amount: 4 }), + bplan(plandb.optimize_auto, { start: datefrom(1, 4, 1), amount: 4 }), + // bplan(plandb.loan, { price: -500, start: datefrom(1, 3, 1) }), + // bplan(plandb.loanback, { price: 525, start: datefrom(1, 11, 1) }), + // bplan(plandb.loan, { price: -30, start: datefrom(1, 6, 17) }), + // bplan(plandb.loanback, { price: 31.5, start: datefrom(2, 6, 17) }), + + // bplan(plandb.loan, { price: -10, start: datefrom(1, 7, 1) }), + // bplan(plandb.loanback, { price: 10.5, start: datefrom(2, 7, 1) }), + + // bplan(plandb.loan, { price: -40, start: datefrom(1, 8, 13) }), + // bplan(plandb.loanback, { price: 42, start: datefrom(2, 8, 13) }), + + bplan(plandb.loan, { price: -480-360, start: datefrom(1, 12, 30) }), + bplan(plandb.loanback, { price: 480*1.1 + 360*1.05, start: datefrom(3, 1, 1) }), + + bplan(plandb, { name: "贴现", price: 40-40*1.1, start: datefrom(2, 1, 1) }), + + bplan(plandb.p4_sell, { name: "贱卖 P4", start: datefrom(1, 11, 1), price: -50, role: "-p4" }), + + // prod 1 + // bplan(plandb.p1_produce, { start: 110, period: 56, amount: 30 }), + bplan(plandb.p2_produce, { start: datefrom(1, 4, 21), period: 56, times: 30, amount: 3 }), + bplan(plandb.p4_produce, { start: datefrom(1, 9, 1), period: 56, times: 30 }), + + bplan(plandb.p2_sell, { start: datefrom(1, 7, 15), amount: 3, price: 0, next: [{ name: "收到 P2 账款", price: -60, start: 30, }] }), + bplan(plandb.p2_sell, { start: datefrom(1, 12, 21), amount: 9, price: 0, next: [{ name: "收到 P2 账款", price: -54, start: 30, }] }), + + // year 2 + bplan(plandb.ad), + bplan(plandb.p2_sell, { start: datefrom(2, 3, 27), amount: 6, next: [{ name: "收到 P2 账款", price: -51, start: 30, }] }), + bplan(plandb.p2_sell, { start: datefrom(2, 7, 19), amount: 6, next: [{ name: "收到 P2 账款", price: -58, start: 30, }] }), + + bplan(plandb.p4_sell, { start: datefrom(2, 2, 19), amount: 2, next: [{ name: "收到 P4 账款", price: -92, start: 30, }] }), + bplan(plandb.p4_sell, { start: datefrom(2, 6, 11), amount: 2, next: [{ name: "收到 P4 账款", price: -91, start: 30, }] }), + + // year 3 + bplan(plandb.p2_sell, { start: datefrom(3, 11, 19), amount: 25, next: [{ name: "收到 P2 账款", price: -58, start: 30, }] }), + bplan(plandb.p4_sell, { start: datefrom(3, 11, 19), amount: 8, next: [{ name: "收到 P4 账款", price: -92, start: 30, }] }), + + // year 4 + bplan(plandb.p2_sell, { start: datefrom(4, 12, 21), amount: 19, next: [{ name: "收到 P2 账款", price: -58, start: 30, }] }), + bplan(plandb.p4_sell, { start: datefrom(4, 12, 21), amount: 8, next: [{ name: "收到 P4 账款", price: -92, start: 30, }] }), + ]); + return
+ +
; +} diff --git a/entemu/junkrc b/entemu/junkrc new file mode 100755 index 0000000..0a27c5c --- /dev/null +++ b/entemu/junkrc @@ -0,0 +1,8 @@ +#!/bin/sh +#;proxy=http://localhost:11001 +export deno=/mnt/data/app/deno +export junkhost=127.0.0.1 +export junkport=11001 + +$deno task build +exec $deno run --allow-read --allow-net --allow-env main.ts diff --git a/entemu/main.ts b/entemu/main.ts new file mode 100644 index 0000000..5b19b27 --- /dev/null +++ b/entemu/main.ts @@ -0,0 +1,33 @@ +import { App, fsRoutes, staticFiles } from "fresh"; +import { define, type State } from "./utils.ts"; + +export const app = new App(); +app.use(staticFiles()); + +// this is the same as the /api/:name route defined via a file. feel free to delete this! +app.get("/api2/:name", (ctx) => { + const name = ctx.params.name; + return new Response( + `Hello, ${name.charAt(0).toUpperCase() + name.slice(1)}!`, + ); +}); + +// this can also be defined via a file. feel free to delete this! +const exampleLoggerMiddleware = define.middleware((ctx) => { + console.log(`${ctx.req.method} ${ctx.req.url}`); + return ctx.next(); +}); +app.use(exampleLoggerMiddleware); + +await fsRoutes(app, { + dir: "./", + loadIsland: (path) => import(`./islands/${path}`), + loadRoute: (path) => import(`./routes/${path}`), +}); + +if (import.meta.main) { + await app.listen({ + hostname: Deno.env.get("junkhost") || "127.0.0.1", + port: parseInt(Deno.env.get("junkport") || "11001"), + }); +} diff --git a/entemu/routes/_app.tsx b/entemu/routes/_app.tsx new file mode 100644 index 0000000..2f149d7 --- /dev/null +++ b/entemu/routes/_app.tsx @@ -0,0 +1,17 @@ +import type { PageProps } from "fresh"; + +export default function App({ Component }: PageProps) { + return ( + + + + + entemu + + + + + + + ); +} diff --git a/entemu/routes/api/[name].tsx b/entemu/routes/api/[name].tsx new file mode 100644 index 0000000..0c97248 --- /dev/null +++ b/entemu/routes/api/[name].tsx @@ -0,0 +1,10 @@ +import { define } from "../../utils.ts"; + +export const handler = define.handlers({ + GET(ctx) { + const name = ctx.params.name; + return new Response( + `Hello, ${name.charAt(0).toUpperCase() + name.slice(1)}!`, + ); + }, +}); diff --git a/entemu/routes/index.tsx b/entemu/routes/index.tsx new file mode 100644 index 0000000..a43448f --- /dev/null +++ b/entemu/routes/index.tsx @@ -0,0 +1,6 @@ +import Planner from "../islands/Planner.tsx"; +import { define } from "../utils.ts"; + +export default define.page(function Home() { + return ; +}); diff --git a/entemu/static/favicon.ico b/entemu/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1cfaaa2193b0f210107a559f7421569f57a25388 GIT binary patch literal 22382 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 bck@HGWY%2idMme??%p8ZW3z(%VE+9-Ofn0d literal 0 HcmV?d00001 diff --git a/entemu/static/logo.svg b/entemu/static/logo.svg new file mode 100644 index 0000000..7ca6f22 --- /dev/null +++ b/entemu/static/logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/entemu/static/styles.css b/entemu/static/styles.css new file mode 100644 index 0000000..392d399 --- /dev/null +++ b/entemu/static/styles.css @@ -0,0 +1,160 @@ +:root { + --fore: #202020; + --back: #fdfdfd; + --back2: #f0f0f0; + --shade: rgba(0, 0, 0, 0.05); + --highlight: yellow; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} +* { + margin: 0; +} +button { + color: inherit; + background: none; + border: 1px solid var(--fore); + border-radius: 4px; + /* + font: inherit; + font-size: 1rem; + border: none; + padding: 4px; + background-color: var(--back); + border-radius: 1em; + width: 2.2rem; + */ +} +button, [role="button"] { + cursor: pointer; +} +code { + font-family: + ui-monospace, + SFMono-Regular, + Menlo, + Monaco, + Consolas, + "Liberation Mono", + "Courier New", + monospace; + font-size: 1em; +} +img, +svg { + display: block; +} +img, +video { + max-width: 100%; + height: auto; +} + +html { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + color: var(--fore); + background-color: var(--back); + padding-bottom: 4em; + font-family: + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + "Helvetica Neue", + Arial, + "Noto Sans", + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol", + "Noto Color Emoji"; +} + +.calendar__calendar { + width: 18em; + margin: 1em auto; + border-radius: 2em; + padding: 1em; + background-color: var(--back2); +} +.calender__cash { + margin: 4px 0.5em; +} +.calendar__head { + text-align: center; + margin: 0.5em 0; +} +.calendar__days { + display: flex; + flex-wrap: wrap; + width: 16em; + text-align: center; +} +.calendar__date { + display: inline-block; + width: 4em; + text-align: center; +} +.calendar__days > span { + width: 3.2em; + padding: 4px 0; +} +.calendar__days > span.p { + background-color: var(--highlight); +} +.calendar__events { + width: 21cm; + margin: 1em auto; +} + +.calendar__alert > * { + color: orange; + font-weight: bold; + width: 21cm; + margin: 0.5em auto; +} + +.calendar__storage { + width: 12em; + margin: 1em auto; + border-radius: 2em; + padding: 0.5em; + background-color: var(--back2); +} +.calendar__storage td { + padding: 0 8px; + width: 12em; +} +.calendar__storage td:nth-child(1) { + text-align: end; +} + +/* .calendar__events {} */ +.calendar__events thead > tr, +.calendar__events tr:nth-of-type(2n) { + background-color: var(--shade); +} +.calendar__events tr:hover { + background-color: rgba(0, 255, 0, 0.2) !important; +} + +a:any-link { + color: var(--fore); +} + +@media (prefers-color-scheme: dark) { + :root { + --fore: #a0a0a0; + --back: #303030; + --back2: #383838; + --shade: rgba(255, 255, 255, 0.05); + --highlight: rgba(255, 255, 0, 0.15); + } +} diff --git a/entemu/utils.ts b/entemu/utils.ts new file mode 100644 index 0000000..4635511 --- /dev/null +++ b/entemu/utils.ts @@ -0,0 +1,6 @@ +import { createDefine } from "fresh"; + +// deno-lint-ignore no-empty-interface +export interface State {} + +export const define = createDefine(); diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c006c00 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module junk + +go 1.21.0 diff --git a/index.html b/index.html new file mode 100644 index 0000000..cfe38df --- /dev/null +++ b/index.html @@ -0,0 +1,26 @@ + + + + + + Junk + + + +
+

Historical Interests

+
+
entemu
+
Planning for a sandbox enterprise game, made in one week. Ugly code. Change plans by changing the code.
+
+
+
entemu-legacy
+
Legacy version of entemu, made overnight. Ugly code. Have been used together with entemu.
+
+
+ + + \ No newline at end of file diff --git a/junk.css b/junk.css new file mode 100644 index 0000000..afe2008 --- /dev/null +++ b/junk.css @@ -0,0 +1,23 @@ +:root { + --fore: #202020; + --back: #fdfdfd; +} + +body { + margin: 1em auto; + width: 21cm; + line-height: 1.5; + background-color: var(--back); + color: var(--fore); +} + +a:any-link { + color: var(--fore); +} + +@media (prefers-color-scheme: dark) { + :root { + --fore: #a0a0a0; + --back: #303030; + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..5911088 --- /dev/null +++ b/main.go @@ -0,0 +1,166 @@ +package main + +import ( + "bufio" + "net/http" + "net/url" + "os" + "os/exec" + "os/signal" + "strings" + "syscall" + "time" +) + +const junkaddr = "[::]:8012" +const proxybufsize = 4 << 10 + +type JunkConf struct { + Proxy string + Cmd *exec.Cmd +} + +func main() { + status := cmain(len(os.Args), os.Args) + os.Setenv("status", status) + if status != "" { + os.Exit(1) + } +} + +func proxy(w http.ResponseWriter, r *http.Request, proxy_path string) { + var err error + r.RequestURI = "" + r.URL, err = url.Parse(proxy_path) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + resp, err := http.DefaultClient.Do(r) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + for k, v := range resp.Header { + for _, x := range v { + w.Header().Add(k, x) + } + } + w.WriteHeader(resp.StatusCode) + buf := make([]byte, proxybufsize) + for { + n, err := resp.Body.Read(buf) + w.Write(buf[:n]) + if err != nil { + break + } + } +} + +func cmain(argc int, argv []string) string { + fileserver := http.FileServer(http.Dir("")) + cmdmap := map[string]JunkConf{} + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + p1 := r.URL.Path + if p1 == "/" { + fileserver.ServeHTTP(w, r) + return + } + p := strings.Split(p1, "/")[1:] + seg := p[0] + if ref := r.Header.Get("Referer"); ref != "" { + rurl, err := url.Parse(ref) + rp := strings.Split(rurl.Path, "/")[1:] + if rurl.Path != "/" { + rseg := rp[0] + if err == nil && seg != rseg && (len(p) < 2 || p[1] != rseg) { + p1 = "/" + rseg + r.URL.Path + w.Header().Set("Location", p1) + w.WriteHeader(http.StatusFound) + return + } + } + } + if conf, ok := cmdmap[seg]; ok { + proxy_path := conf.Proxy + "/" + strings.Join(p[1:], "/") + proxy(w, r, proxy_path) + return + } + if f, err := os.Open(seg + "/junkrc"); err == nil { + // info, err := f.Stat() + s := bufio.NewScanner(f) + conf := JunkConf{} + for s.Scan() { + line := s.Text() + if len(line) > 2 && line[0:2] == "#;" { + note := line[2:] + i := strings.IndexByte(note, '=') + if i == -1 { + continue + } + key, value := note[:i], note[i+1:] + switch key { + case "proxy": + conf.Proxy = value + } + } + } + f.Close() + println("exec:", seg+"/junkrc") + cmd := exec.Command("./junkrc") + cmd.Dir = seg + // cmd.Stdout = os.Stdout + conf.Cmd = cmd + err = cmd.Start() + if err != nil { + w.WriteHeader(http.StatusServiceUnavailable) + w.Write([]byte(err.Error())) + return + } + waits := 0 + for { + _, err := http.Head(conf.Proxy) + if err != nil { + if cmd.ProcessState != nil { + cmd.Wait() + w.WriteHeader(http.StatusServiceUnavailable) + w.Write([]byte("process exited early: " + cmd.ProcessState.String())) + return + } else { + waits++ + time.Sleep(500 * time.Millisecond) + if waits > 10 { + w.WriteHeader(http.StatusServiceUnavailable) + w.Write([]byte("starting process: timeout")) + cmd.Process.Kill() + cmd.Wait() + return + } + continue + } + } + cmdmap[seg] = conf + break + } + proxy_path := conf.Proxy + "/" + strings.Join(p[1:], "/") + proxy(w, r, proxy_path) + return + } + fileserver.ServeHTTP(w, r) + }) + lock := make(chan os.Signal, 1) + signal.Notify(lock, os.Interrupt, syscall.SIGTERM) + go func() { + println("started:", "http://"+junkaddr+"/") + http.ListenAndServe(junkaddr, nil) + }() + <-lock + for k, v := range cmdmap { + println("killing:", k) + v.Cmd.Process.Kill() + v.Cmd.Wait() + } + return "" +}