Compare commits

..

1 Commits

Author SHA1 Message Date
b6c80af059 http api actually works 2025-11-01 09:58:06 -04:00
20 changed files with 281 additions and 689 deletions

465
app.js
View File

@ -1,8 +1,32 @@
/** @import * as TYPES from "./graph/types.ts" */ /** @import * as TYPES from "./graph/types.ts" */
import * as FSHandle from "./store-directory-handle.js"; import * as FSHandle from "./store-directory-handle.js";
import Styles from "./styles.js"; const {Div, DOM} = Gale({
const {DOM, Div, Tag} = Styles; Title:{
padding:"2rem",
background: "blue",
color:"white"
},
Plain:{},
PartGroup:{
display: `flex`,
flexWrap: `wrap`
},
Part:{
border: `1px solid black`,
borderRadius: `5px`,
padding: `1rem`
},
BlockScreen:{
position: "fixed",
zIndex: "9999",
top: "0",
left: "0",
width: "100%",
height: "100%",
background: "rgba(128, 128, 128, 0.5)"
}
});
async function PickHandle() async function PickHandle()
{ {
@ -20,21 +44,11 @@ async function LoadHandleFiles()
const module = await import("./graph/room.js"+"?bust="+Math.random()); const module = await import("./graph/room.js"+"?bust="+Math.random());
/** @type {Record<string, TYPES.GraphParts>} */ /** @type {Record<string, TYPES.GraphParts>} */
const read = module.default(); const read = module.default();
for(const roomKey in read)
{
const room = read[roomKey]
for(const pass in room.Pass)
{
await room.Pass[pass].load();
}
}
rooms.val = read; rooms.val = read;
} }
catch(e) catch(_e)
{ {
console.log("the handle exists, but the request failed. the service work must not be ready yet.", e) console.log("the handle exists, but the request failed. the service work must not be ready yet.")
rooms.val = {}; rooms.val = {};
} }
} }
@ -45,281 +59,45 @@ async function LoadHandleFiles()
} }
} }
/** @type {Van.State<Record<string, TYPES.GraphParts>>} */ /** @type {Van.State<Record<string, TYPES.GraphParts>>} */
const rooms = van.state({}); const rooms = van.state({});
let handle = await FSHandle.getDirectoryHandle(); let handle = await FSHandle.getDirectoryHandle();
await LoadHandleFiles(); await LoadHandleFiles();
/** @type {(path:string[], part:string, time:number, data:string)=>void} */
async function WRITE(path, part, time, data)
{
const fileHandle = await FSHandle.Dig(handle, path, true);
if(fileHandle)
{
const file = await fileHandle.getFile();
const text = await file.text();
let json = {};
if(text)
{
json = JSON.parse(text);
}
let partProp = json[part];
if(!partProp)
{
partProp = [];
json[part] = partProp;
}
partProp.push([time, data]);
const writeable = await fileHandle.createWritable();
await writeable.write(JSON.stringify(json, null, 2));
await writeable.close();
}
}
/** @type {Van.State<TYPES.User|false>} */ /** @type {Van.State<TYPES.User|false>} */
const loggedIn = van.state(false); const loggedIn = van.state(false);
const blocking = van.state(false); const blocking = van.state(false);
const showDesks = van.state(true, "desks");
/** @type {(inParts:Record<string, TYPES.Part>, inPasses:Record<string, TYPES.Pass>)=>HTMLElement} */
function Parts(inParts, inPasses)
{
const rows = [];
const row = [DOM.th()]
for(const pass in inPasses)
{
row.push(DOM.th(inPasses[pass].name));
}
rows.push(DOM.thead(row));
Object.entries(inParts).map(([part_id, part])=>{
const row = [DOM.th(part.name)];
for(const [pass, data] of part.pass)
{
row.push(DOM.td.Part(data.work.map(w=>Div.Plain(w[1]) )))
}
rows.push(DOM.tr(row))
});
return DOM.table.GapVertical(rows);
}
//const deskRender = van.state(0);
/** @type {(part:TYPES.Part, pass:TYPES.Pass, closeHandler:()=>void)=>HTMLElement} */
function PartEditor(part, pass, closeHandler)
{
const partPass = part.pass?.get(pass);
const hist = van.state(false);
const edit = van.state(false);
const upper = ()=>{
if(partPass?.work.length)
{
return DOM.div(
DOM.button(
{
onclick()
{
hist.val=!hist.val;
}
}, ()=>(hist.val ? "hide" : "show")+" changes"
),
hist.val ? partPass.work.map(w=>{
const date = new Date(w[0]);
return DOM.div(`${date.getMonth()}/${date.getDate()}`, DOM.strong(w[1]), w[2].name);
}) : ""
);
}
else
{
return "";
}
}
const lower = ()=>
{
return DOM.div(
()=>{
return loggedIn.rawVal ? DOM.button(
{onclick(){edit.val = !edit.val;}},
()=>(edit.val ? "cancel" : "make") + " changes"
) : DOM.p("log in to make changes")
},
()=>{
const textarea = DOM.textarea()
return edit.val ? DOM.div(
textarea,
DOM.button({
onclick(){
if(loggedIn.rawVal && partPass)
{
blocking.val = true;
partPass.make(loggedIn.rawVal, textarea.value).then(()=>{
blocking.val = false;
//deskRender.val++;
})
}
else
{
return false;
}
}
}, "save changes")
) : ""
}
)
}
const self = DOM.div(
upper,
()=>DOM.button({onclick(e){e.stopPropagation(); closeHandler(this.parentElement)}}, "close"),
DOM.div(
()=>{
if(partPass)
{
return partPass.work.find((w)=>w[0] == partPass.time)?.[1] || ""
}
return "(no data yet)";
}
),
lower,
);
return self;
}
/** @type {(inDesks:Record<string, TYPES.Desk>)=>HTMLElement} */
function Desks(inDesks)
{
return Div.PartGroup(
Object.entries(inDesks).map(([desk_id, desk])=>{
//loggedIn.val;
//deskRender.val;
//console.log("reredering desk", desk.name);
if (loggedIn.val)
{
let userInRole = false;
for(const role of desk.role)
{
if(role.user.includes(loggedIn.val))
{
userInRole = true;
}
}
if(!userInRole)
{
return null;
}
}
const work = [];
for(const [pass, scan] of desk.pass)
{
// at least one but not all need fields are empty
const caution = scan.need_empty.length>0 && scan.need_empty.length<desk.need.length;
work.push(DOM.tr(
DOM.td(pass.name),
desk.need.map((part, index, array)=>
{
const partPass = part.pass.get(pass);
if(!partPass){ return null }
const latest = partPass.work.find(t=>t[0] == partPass.time)?.[1];
const attributes = {};
if(latest)
{
attributes.class = Tag("PartGood")
}
else
{
attributes.class = Tag("PartEmpty")
}
if(scan.need_dirty.includes(index))
{
attributes.class = Tag("PartDirty")
}
return DOM.td(
Div.Part(
attributes,
latest
)
);
}),
DOM.td(
Div.Icon("⇉"),
scan.due_date ? Div.Part(
scan.due_date.toLocaleDateString(),
scan.due_date.toLocaleTimeString()
) : " "
),
desk.make.map((part, index, array)=>
{
const partPass = part.pass.get(pass);
if(!partPass){ return null }
const latest = partPass.work.find(t=>t[0] == partPass.time)?.[1];
const attr = "data-editing"
function close(editor){
editor.remove();
this.setAttribute(attr, "false");
}
const attributes = {
onclick(){
const check = this.getAttribute(attr);
if(check !== "true")
{
this.setAttribute(attr, "true");
this.appendChild(PartEditor(part, pass, close.bind(this)));
}
}
};
if(latest)
{
attributes.class = Tag("PartGood")
}
else
{
attributes.class = Tag("PartEmpty")
}
if( (desk.need.length==0 && !latest) || scan.make_dirty.includes(index))
{
if(!latest && caution)
{
attributes.class = Tag("PartCaution")
}
else
{
attributes.class = Tag("PartDirty");
}
}
return DOM.td(
Div.Part(
attributes,
latest
)
);
}),
))
}
return Div.DeskContainer(
DOM.h3(desk.name),
DOM.table.GapHorizontal(
DOM.thead(
DOM.tr(
DOM.th(),
desk.need.map((part, index)=>DOM.th(part.name)),
DOM.th("→"),
desk.make.map((part, index)=>DOM.th(part.name))
)
),
work
)
)
}),
)
}
/** @type {(room_id:string, graphParts:TYPES.GraphParts)=>HTMLElement} */ /** @type {(room_id:string, graphParts:TYPES.GraphParts)=>HTMLElement} */
function Room(room_id, graphParts) function Room(room_id, graphParts)
{ {
@ -331,6 +109,10 @@ function Room(room_id, graphParts)
Div.Plain("Users:"), Div.Plain("Users:"),
Div.PartGroup( Div.PartGroup(
Object.entries(graphParts.User).map(([user_id, user])=>{ Object.entries(graphParts.User).map(([user_id, user])=>{
return ()=>{
//rerender.val;
return Div.Part( return Div.Part(
DOM.div.Plain(user.name), DOM.div.Plain(user.name),
()=>{ ()=>{
@ -342,21 +124,132 @@ function Room(room_id, graphParts)
) )
}, },
) )
}
}) })
), ),
()=>{ Div.Plain("Passes:"),
return DOM.button({onclick(){ Div.PartGroup(
showDesks.val = !showDesks.val; Object.entries(graphParts.Pass).map(([pass_id, pass])=>{
}}, showDesks.val ? "Show Parts" : "Show Desks") return ()=>{
}, console.log("rerendering...", rerender.rawVal);
()=>{
rerender.val; rerender.val;
return showDesks.val ? Desks(graphParts.Desk) : Parts(graphParts.Part, graphParts.Pass);
},
return Div.Part(
DOM.div.Plain(pass.name),
()=>{
return DOM.button.Plain(
{async onclick(){
await pass[pass.live ? "dump" : "load"]();
rerender.val++
}},
pass.live ? "Dump" : "Load"
)
},
)
}
})
),
Div.Plain("Parts"),
Div.PartGroup(
Object.entries(graphParts.Part).map(([part_id, part])=>{
return ()=>{
rerender.val;
const work = []
for(const [pass, data] of part.pass)
{
work.push(Div.Part(
DOM.h5(pass.name),
DOM.p(data.time),
data.work.map(([time, data, user])=>Div.Plain(
DOM.span(time),
" ",
DOM.strong(data),
)),
Div.Plain(
loggedIn.val ? DOM.button({onclick(){
if(loggedIn.val)
{
blocking.val = true;
data.make(loggedIn.val, "NEW").then(()=>{
blocking.val = false;
rerender.val++;
});
}
}}, "Add work!")
: null
)
))
}
return Div.Part(
DOM.h3(part.name),
...work,
);
}
}),
),
Div.Plain("Desks"),
Div.PartGroup(
Object.entries(graphParts.Desk).map(([desk_id, desk])=>{
return ()=>{
rerender.val;
/** @type {(part:TYPES.Part, index:number, pass:TYPES.Pass, dirty:number[])=>HTMLElement|null} */
const Iterator = (part, index, pass, dirty)=>{
const partPass = part.pass.get(pass);
if(partPass)
{
const time = partPass.time;
const latest = partPass.work.find(t=>t[0] == time);
return Div.Plain(
part.name,
DOM.p( dirty.includes(index) ? "Dirty" : "Good!" ),
Div.Part(
time, " ", latest?.[1] || ""
)
);
}
return null;
}
const work = [];
for(const [pass, dirty] of desk.pass)
{
work.push(Div.Part(
DOM.h5(pass.name),
Div.Part(
DOM.h4("Need:"),
desk.need.map((part, index)=>Iterator(part, index, pass, dirty.need))
),
Div.Part(
DOM.h4("Make:"),
desk.make.map((part, index)=>Iterator(part, index, pass, dirty.make))
)
))
}
return Div.Part(
DOM.h3(desk.name),
work
)
}
}),
),
()=>{ ()=>{
return blocking.val ? Div.BlockScreen() : null return blocking.val ? Div.BlockScreen() : null

View File

@ -1,43 +0,0 @@
//@ts-check
import CreateAllRooms, {Room} from "../../graph/graph.js";
const user = {
u1:"Seth T",
u4:"Sarah S",
u5:"Adam M",
u6:"Matt Y",
u7:"Seth F",
u8:"Brittany F"
};
export default CreateAllRooms({
room_01:Room({
user,
role:{
dev:["Development", "u1"],
write:["Writing", "u5"],
admin:["Admin", "u4"]
},
part:{
p1:["Page title"],
p2:["Page slug"],
p3:["Page preview"],
p4:["Page Project"],
p5:["Page Corrections", "loop"],
},
desk:{
d1:["Write page metas", ["admin", "write"], { }, "p1", "p2"],
d2:["Build Page preview", ["admin", "dev" ], {p1:1, p2:1, p5:4}, "p3", "p4"],
d3:["Proof Page", ["admin", "write"], {p3:1, }, "p5" ]
},
pass:{
pass_01:["January"],
//pass_02:["February"],
//pass_03:["March"],
//pass_04:["April"],
//pass_05:["May"],
//pass_06:["June"],
//pass_07:["July"],
}
})
});

View File

@ -1,7 +1,4 @@
/** @import * as TYPES from "./types.ts" */ /** @import * as TYPES from "./types.ts" */
import * as FSAccess from "../store-directory-handle.js";
export const noop = "no-op";
/** @type {TYPES.GraphBuilder} */ /** @type {TYPES.GraphBuilder} */
export function Room({user, role, part, desk, pass}) export function Room({user, role, part, desk, pass})
@ -34,9 +31,8 @@ export function Room({user, role, part, desk, pass})
const PartList = part; const PartList = part;
for(let partId in part) for(let partId in part)
{ {
const [name, loop] = part[partId]; const name = part[partId];
PartList[partId] = /** @type {TYPES.Part} */({name, id:partId, need:[], make:[], pass:new Map()});
PartList[partId] = /** @type {TYPES.Part} */({name, id:partId, need:[], make:[], pass:new Map(), loop:loop});
} }
// mutate desks // mutate desks
@ -45,7 +41,7 @@ export function Room({user, role, part, desk, pass})
const DeskList = desk; const DeskList = desk;
for(let deskId in desk) for(let deskId in desk)
{ {
const [name, roleIDs, needObj, ...makePartIDs] = desk[deskId]; const [name, roleIDs, mode, needObj, ...makePartIDs] = desk[deskId];
/** @type {TYPES.Part[]}*/ const need =[]; /** @type {TYPES.Part[]}*/ const need =[];
/** @type {number[]}*/ const time =[]; /** @type {number[]}*/ const time =[];
@ -53,6 +49,7 @@ export function Room({user, role, part, desk, pass})
const deskObj = { const deskObj = {
name, name,
id:deskId, id:deskId,
mode,
need, need,
time, time,
make:[], make:[],
@ -105,21 +102,12 @@ export function Room({user, role, part, desk, pass})
{ {
this.time = Date.now(); this.time = Date.now();
const handle = await FSAccess.getDirectoryHandle(); const body = JSON.stringify({
const path = ["store", context.Path, passID, user.id+".json"]; [partObj.id]:[this.time, data]
let text = await FSAccess.Read(handle, path) || "{}"; })
/** @type {TYPES.UserPassFile} */
const json = JSON.parse(text);
let field = json[partObj.id]; const http = await fetch(`./store/${context.Path}/${passID}/${user.id}.json`, {method:"PUT", body});
if(!field) console.log(http);
{
field = [];
json[partObj.id] = field;
}
field.push([this.time, data]);
text = JSON.stringify(json, null, 2);
await FSAccess.Write(handle, path, text);
this.work.push(/** @type {TYPES.Work}*/([this.time, data, user])); this.work.push(/** @type {TYPES.Work}*/([this.time, data, user]));
partObj.make.forEach((arg)=>{Scan(arg, passObj)}); partObj.make.forEach((arg)=>{Scan(arg, passObj)});
@ -139,10 +127,9 @@ export function Room({user, role, part, desk, pass})
const [userID, userObject] = userData[i]; const [userID, userObject] = userData[i];
try try
{ {
const handle = await FSAccess.getDirectoryHandle(); const http = await fetch(`./store/${context.Path}/${passID}/${userID}.json`);
const text = await FSAccess.Read(handle, ["store", context.Path, passID, userID+".json"]);
/** @type {TYPES.UserPassFile} */ /** @type {TYPES.UserPassFile} */
const json = JSON.parse(text); const json = await http.json();
Object.entries(json).forEach(([partID, payload])=>{ Object.entries(json).forEach(([partID, payload])=>{
@ -162,7 +149,6 @@ export function Room({user, role, part, desk, pass})
{ {
passCheck.time = latest; passCheck.time = latest;
} }
//payload.sort()
passCheck.work = /** @type {TYPES.Work[]}*/(payload); passCheck.work = /** @type {TYPES.Work[]}*/(payload);
} }
@ -222,93 +208,39 @@ const Scan =(desk, pass)=>
const dirtyNeed = []; const dirtyNeed = [];
const dirtyMake = []; const dirtyMake = [];
const emptyNeed = [];
const emptyMake = [];
let makeMin = Infinity; let makeMin = Infinity;
let needMax = -Infinity; let needMax = -Infinity;
// added for estimation
let estMin = Infinity;
let estMax = -Infinity;
let estSum = 0;
/*
Loop parts:
- always considered clean when the leading value is a no-op
- as a need, considered clean when empty
*/
/** @type {(part:TYPES.Part)=>[time:number, value:string|undefined, part:TYPES.Part]} */
const lookup =(part)=>
{
const partPass = part.pass.get(pass);
const partPassTime = partPass?.time || 0;
const partPassValue = partPass?.work.find(t=>t[0] == partPassTime)?.[1];
return [partPassTime, partPassValue, part];
}
// update needMax // update needMax
for(let i=0; i<desk.need.length; i++) for(let i=0; i<desk.need.length; i++)
{ {
const [time, value, part] = lookup(desk.need[i]); const part = desk.need[i];
const partPassTime = part.pass.get(pass)?.time || 0;
if(part.loop) if(partPassTime > needMax) needMax = partPassTime;
{
if(!value || value == noop)
{
continue;
}
} }
if(time > needMax) needMax = time; // update makeMin AND dirty check makes
if(!time) emptyNeed.push(i);
}
// update makeMin AND dirtyMakes
for(let i=0; i<desk.make.length; i++) for(let i=0; i<desk.make.length; i++)
{ {
const [time] = lookup(desk.make[i]); const part = desk.make[i];
const partPassTime = part.pass.get(pass)?.time || 0;
if(time < makeMin) makeMin = time; if(partPassTime < makeMin) makeMin = partPassTime;
if(time < needMax) dirtyMake.push(i); if(partPassTime < needMax)
if(!time) emptyMake.push(i) {
dirtyMake.push(i);
}
} }
// dirtyNeeds // dirty check needs
for(let i=0; i<desk.need.length; i++) for(let i=0; i<desk.need.length; i++)
{ {
const [time, value, part] = lookup(desk.need[i]); const part = desk.need[i];
const partPassTime = part.pass.get(pass)?.time || 0;
if(part.loop) if(partPassTime > makeMin)
{
if(!value || value == noop)
{
continue;
}
}
if(time > makeMin)
{ {
dirtyNeed.push(i); dirtyNeed.push(i);
// estimation
if(time < estMin) estMin = time;
const allottedTime = desk.time[i] * 1000 * 60 * 60;
const projectedTime = time + allottedTime;
estSum += allottedTime;
if(projectedTime > estMax) estMax = projectedTime;
} }
} }
estSum += estMin; desk.pass.set(pass, {need:dirtyNeed, make:dirtyMake})
let stamp = estSum;
if(estMax > estSum)
{
stamp = estMax;
}
desk.pass.set(pass, {need_dirty:dirtyNeed, make_dirty:dirtyMake, need_empty:emptyNeed, make_empty:emptyMake, due_date:isFinite(stamp) ? new Date(stamp) : undefined})
}; };

View File

@ -1,17 +1,17 @@
export type User = {name:string, id:string, desk:Set<Desk>}; export type User = {name:string, id:string, desk:Set<Desk>};
export type Role = {name:string, id:string, user:User[]}; export type Role = {name:string, id:string, user:User[]};
export type Desk = {name:string, id:string, need:Part[], time:number[], make:Part[], pass:Map<Pass, Scan>, role:Role[]}; export type Desk = {name:string, id:string, need:Part[], time:number[], make:Part[], pass:Map<Pass, Flag>, mode:string, role:Role[]};
export type Pass = {name:string, id:string, path:string, live:boolean, load:()=>Promise<void>, dump:()=>void}; export type Pass = {name:string, id:string, path:string, live:boolean, load:()=>Promise<void>, dump:()=>void};
export type Part = {name:string, id:string, pass:Map<Pass, {time:number, work:Work[], make:(user:User, data:string)=>Promise<void>}>, need:Desk[], make:Desk[], loop?:boolean}; export type Part = {name:string, id:string, pass:Map<Pass, {time:number, work:Work[], make:(user:User, data:string)=>Promise<void>}>, need:Desk[], make:Desk[]};
export type Work = [time:number, data:string, user:User]; export type Work = [time:number, data:string, user:User];
export type Scan = {need_dirty:number[], make_dirty:number[], need_empty:number[], make_empty:number[], due_date?:Date} export type Flag = {need:number[], make:number[]}
export type GraphBuilder= export type GraphBuilder=
< <
Users extends Record<string, string>, Users extends Record<string, string>,
Roles extends Record<string, [ name:string, ...users:Array<keyof Users>]>, Roles extends Record<string, [ name:string, ...users:Array<keyof Users>]>,
Parts extends Record<string, [ name:string, loop?:"loop"] >, Parts extends Record<string, string>,
Desks extends Record<string, [ name:string, roles:Array<keyof Roles>, need:Partial<Record<keyof Parts, number>>, ...make:Array<keyof Parts>]>, Desks extends Record<string, [ name:string, roles:Array<keyof Roles>, mode:"all"|"one", need:Partial<Record<keyof Parts, number>>, ...make:Array<keyof Parts>]>,
> >
( (
params:{ params:{

View File

@ -3,46 +3,6 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title> <title>Document</title>
<style>
/* Basic CSS Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
font-family: sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}
a {
text-decoration: none;
color: inherit;
}
ul, ol {
list-style: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
</style>
</head> </head>
<body> <body>
<script> <script>

View File

@ -1,43 +1,25 @@
//@ts-check //@ts-check
import CreateAllRooms, {Room} from "../../graph/graph.js"; import Rooms, {Room} from "../../graph/graph.js";
import User from "./user.js";
const user = { export default Rooms({
u1:"Seth T",
u4:"Sarah S",
u5:"Adam M",
u6:"Matt Y",
u7:"Seth F",
u8:"Brittany F"
};
export default CreateAllRooms({
room_01:Room({ room_01:Room({
user, user:User,
role:{ role:{
dev:["Development", "u1"], dev:["Development", "u1"]
write:["Writing", "u5"],
admin:["Admin", "u4"]
}, },
part:{ part:{
p1:["Page title"], p1:"Page title",
p2:["Page slug"], p2:"Page slug",
p3:["Page preview"], p3:"Page preview",
p4:["Page Project"],
p5:["Page Corrections", "loop"],
}, },
desk:{ desk:{
d1:["Write page metas", ["admin", "write"], { }, "p1", "p2"], d1:["Write page metas", ["dev"], "all", {}, "p1", "p2"],
d2:["Build Page preview", ["admin", "dev" ], {p1:1, p2:1, p5:1}, "p3", "p4"], d2:["Build Page preview", ["dev"], "all", {p1:1, p2:1}, "p3"]
d3:["Proof Page", ["admin", "write"], {p3:1, }, "p5" ]
}, },
pass:{ pass:{
pass_01:["January"], pass_01:["January"],
//pass_02:["February"], pass_02:["February"]
//pass_03:["March"],
//pass_04:["April"],
//pass_05:["May"],
//pass_06:["June"],
//pass_07:["July"],
} }
}) })
}); });

View File

@ -0,0 +1,5 @@
export default {
u1:"seth",
u2:"seth2",
u3:"seth3"
}

View File

@ -0,0 +1,9 @@
{
"p1":
[
[123, "data"],
[456, "more data"],
[789, "even more data"],
[101112, "even more data"]
]
}

View File

@ -0,0 +1,8 @@
{
"p2":
[
[123, "data"],
[456, "more data"],
[789, "even more data"]
]
}

View File

@ -1,14 +0,0 @@
{
"p1": [
[
1762196165935,
"normal title"
]
],
"p2": [
[
1762196173135,
"normal slug"
]
]
}

View File

@ -1,8 +0,0 @@
{
"p4": [
[
1762193485093,
"Make made Late"
]
]
}

View File

@ -1,8 +0,0 @@
{
"p1": [
[
1762186057868,
"Need made Early"
]
]
}

View File

@ -1,14 +0,0 @@
{
"p4": [
[
1762196034794,
"Make made Early"
]
],
"p1": [
[
1762196047017,
"Need made Late"
]
]
}

View File

@ -1,14 +0,0 @@
{
"p3": [
[
1762196236384,
"complete 3"
]
],
"p4": [
[
1762196245327,
"complete 4"
]
]
}

View File

@ -1,14 +0,0 @@
{
"p1": [
[
1762196209704,
"complete 1"
]
],
"p2": [
[
1762196217319,
"complete 2"
]
]
}

View File

@ -1,18 +0,0 @@
{
"p3": [
[
1762196341950,
"complete 3"
]
],
"p4": [
[
1762196348950,
"complete 4"
],
[
1762196393702,
"complete Later"
]
]
}

View File

@ -1,18 +0,0 @@
{
"p1": [
[
1762196328127,
"complete 1"
]
],
"p2": [
[
1762196335342,
"complete 2"
],
[
1762196363079,
"complete Late"
]
]
}

View File

@ -1,8 +0,0 @@
{
"p1": [
[
1762201681431,
"underway"
]
]
}

View File

@ -21,21 +21,47 @@ async function Interceptor(event)
const pathname = url.pathname.substring(1); const pathname = url.pathname.substring(1);
let parts = pathname.split("/"); let parts = pathname.split("/");
if(parts[0] == "graph") if(parts[0] == "graph" || parts[0] == "store")
{ {
console.log("intercept:", pathname); console.log("intercept:", pathname);
const handle = await FSAccess.getDirectoryHandle(); const handle = await FSAccess.getDirectoryHandle();
if(event.request.method == "PUT")
{
console.log("put operation");
const payloadJSON = await event.request.clone().json();
let text = await FSAccess.Read(handle, parts) || "{}";
const fileJSON = JSON.parse(text);
for(const key in payloadJSON)
{
const value = payloadJSON[key];
let field = fileJSON[key];
if(!field)
{
field = [];
fileJSON[key] = field;
}
field.push(value);
}
console.log(`incoming:`, payloadJSON);
console.log(`outgoing:`, fileJSON);
text = JSON.stringify(fileJSON, null, 2);
const result = await FSAccess.Write(handle, parts, text);
return new Response(result+"", options);
}
const text = await FSAccess.Read(handle, parts); const text = await FSAccess.Read(handle, parts);
if(text) if(text)
{ {
console.log("successful intercept:", pathname);
return new Response(text, options); return new Response(text, options);
} }
else
{
console.log("failed intercept:", pathname);
}
} }

View File

@ -1,64 +0,0 @@
export default Gale({
Title:{
padding:"2rem",
background: "blue",
color:"white"
},
Plain:{},
PartGroup:{
display: `flex`,
flexWrap: `wrap`
},
Icon:{
background:"black",
color:"white",
borderRadius:"2rem",
fontWeight:"bolder",
padding:"0 0.8rem",
margin:"0 1rem"
},
Part:{
border: `1px solid black`,
borderRadius: `5px`,
padding: `0.5rem 1rem`,
minHeight: "2rem",
},
PartGood:{
background:"#009b2e",
color:"white",
},
PartEmpty:{
background:"#ddd"
},
PartDirty:{
background:"red",
color:"white",
fontWeight:"bold"
},
PartCaution:{
background:"yellow",
color:"black",
},
BlockScreen:{
position: "fixed",
zIndex: "9999",
top: "0",
left: "0",
width: "100%",
height: "100%",
background: "rgba(128, 128, 128, 0.5)"
},
DeskContainer:{
border: `1px solid black`,
borderRadius: `5px`,
padding: `1rem`
},
GapHorizontal:{
borderCollapse:"separate",
borderSpacing:"0.3rem 2rem",
},
GapVertical:{
borderCollapse:"separate",
borderSpacing:"2rem 0.2rem",
}
});