Compare commits

..

15 Commits

Author SHA1 Message Date
ae89ac642e due date working 2025-11-07 09:00:58 -05:00
dfffa59094 estimation started 2025-11-06 22:27:09 -05:00
ec85ab6a34 misc tweaks 2025-11-06 21:58:13 -05:00
76d4c4be73 progress... 2025-11-05 14:15:40 -05:00
ef1fb55057 add typings/parsing 2025-11-05 08:18:24 -05:00
697f8a2d7d complex editor 2025-11-04 11:29:19 -05:00
11bc2e1d99 style tweaks 2025-11-03 15:58:48 -05:00
66f0d22970 need colors finished 2025-11-03 15:32:46 -05:00
2ca31bb21d fixed make 2025-11-03 15:22:18 -05:00
cbcf559bac better truth table 2025-11-03 14:02:40 -05:00
a573835219 colors improved, still not quite right 2025-11-03 13:24:16 -05:00
e682b8a619 desk status started 2025-11-02 21:21:40 -05:00
f47ab32d68 input controls 2025-11-02 13:47:12 -05:00
396ad5b3c9 table layout started 2025-11-01 15:55:24 -04:00
1d527de26f ui tweaks 2025-11-01 15:03:57 -04:00
20 changed files with 689 additions and 281 deletions

461
app.js
View File

@ -1,32 +1,8 @@
/** @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";
const {Div, DOM} = Gale({ import Styles from "./styles.js";
Title:{ const {DOM, Div, Tag} = Styles;
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()
{ {
@ -44,11 +20,21 @@ 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.") console.log("the handle exists, but the request failed. the service work must not be ready yet.", e)
rooms.val = {}; rooms.val = {};
} }
} }
@ -59,45 +45,281 @@ 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)
{ {
@ -109,10 +331,6 @@ 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),
()=>{ ()=>{
@ -124,132 +342,21 @@ function Room(room_id, graphParts)
) )
}, },
) )
}
}) })
), ),
Div.Plain("Passes:"),
Div.PartGroup(
Object.entries(graphParts.Pass).map(([pass_id, pass])=>{
return ()=>{
console.log("rerendering...", rerender.rawVal);
rerender.val;
return Div.Part(
DOM.div.Plain(pass.name),
()=>{ ()=>{
return DOM.button.Plain( return DOM.button({onclick(){
{async onclick(){ showDesks.val = !showDesks.val;
await pass[pass.live ? "dump" : "load"](); }}, showDesks.val ? "Show Parts" : "Show Desks")
rerender.val++
}},
pass.live ? "Dump" : "Load"
)
}, },
)
}
})
),
Div.Plain("Parts"),
Div.PartGroup(
Object.entries(graphParts.Part).map(([part_id, part])=>{
return ()=>{
()=>{
rerender.val; rerender.val;
return showDesks.val ? Desks(graphParts.Desk) : Parts(graphParts.Part, graphParts.Pass);
},
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

@ -0,0 +1,43 @@
//@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,4 +1,7 @@
/** @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})
@ -31,8 +34,9 @@ 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 = part[partId]; const [name, loop] = 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
@ -41,7 +45,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, mode, needObj, ...makePartIDs] = desk[deskId]; const [name, roleIDs, needObj, ...makePartIDs] = desk[deskId];
/** @type {TYPES.Part[]}*/ const need =[]; /** @type {TYPES.Part[]}*/ const need =[];
/** @type {number[]}*/ const time =[]; /** @type {number[]}*/ const time =[];
@ -49,7 +53,6 @@ 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:[],
@ -102,12 +105,21 @@ export function Room({user, role, part, desk, pass})
{ {
this.time = Date.now(); this.time = Date.now();
const body = JSON.stringify({ const handle = await FSAccess.getDirectoryHandle();
[partObj.id]:[this.time, data] const path = ["store", context.Path, passID, user.id+".json"];
}) let text = await FSAccess.Read(handle, path) || "{}";
/** @type {TYPES.UserPassFile} */
const json = JSON.parse(text);
const http = await fetch(`./store/${context.Path}/${passID}/${user.id}.json`, {method:"PUT", body}); let field = json[partObj.id];
console.log(http); if(!field)
{
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)});
@ -127,9 +139,10 @@ export function Room({user, role, part, desk, pass})
const [userID, userObject] = userData[i]; const [userID, userObject] = userData[i];
try try
{ {
const http = await fetch(`./store/${context.Path}/${passID}/${userID}.json`); const handle = await FSAccess.getDirectoryHandle();
const text = await FSAccess.Read(handle, ["store", context.Path, passID, userID+".json"]);
/** @type {TYPES.UserPassFile} */ /** @type {TYPES.UserPassFile} */
const json = await http.json(); const json = JSON.parse(text);
Object.entries(json).forEach(([partID, payload])=>{ Object.entries(json).forEach(([partID, payload])=>{
@ -149,6 +162,7 @@ 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);
} }
@ -208,39 +222,93 @@ 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 part = desk.need[i]; const [time, value, part] = lookup(desk.need[i]);
const partPassTime = part.pass.get(pass)?.time || 0;
if(partPassTime > needMax) needMax = partPassTime; if(part.loop)
{
if(!value || value == noop)
{
continue;
}
} }
// update makeMin AND dirty check makes if(time > needMax) needMax = time;
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 part = desk.make[i]; const [time] = lookup(desk.make[i]);
const partPassTime = part.pass.get(pass)?.time || 0;
if(partPassTime < makeMin) makeMin = partPassTime; if(time < makeMin) makeMin = time;
if(partPassTime < needMax) if(time < needMax) dirtyMake.push(i);
{ if(!time) emptyMake.push(i)
dirtyMake.push(i);
}
} }
// dirty check needs // dirtyNeeds
for(let i=0; i<desk.need.length; i++) for(let i=0; i<desk.need.length; i++)
{ {
const part = desk.need[i]; const [time, value, part] = lookup(desk.need[i]);
const partPassTime = part.pass.get(pass)?.time || 0;
if(partPassTime > makeMin) if(part.loop)
{ {
dirtyNeed.push(i); if(!value || value == noop)
{
continue;
} }
} }
desk.pass.set(pass, {need:dirtyNeed, make:dirtyMake}) if(time > makeMin)
{
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;
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, Flag>, mode:string, role:Role[]}; export type Desk = {name:string, id:string, need:Part[], time:number[], make:Part[], pass:Map<Pass, Scan>, 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[]}; 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 Work = [time:number, data:string, user:User]; export type Work = [time:number, data:string, user:User];
export type Flag = {need:number[], make:number[]} export type Scan = {need_dirty:number[], make_dirty:number[], need_empty:number[], make_empty:number[], due_date?:Date}
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, string>, Parts extends Record<string, [ name:string, loop?:"loop"] >,
Desks extends Record<string, [ name:string, roles:Array<keyof Roles>, mode:"all"|"one", need:Partial<Record<keyof Parts, number>>, ...make:Array<keyof Parts>]>, Desks extends Record<string, [ name:string, roles:Array<keyof Roles>, need:Partial<Record<keyof Parts, number>>, ...make:Array<keyof Parts>]>,
> >
( (
params:{ params:{

View File

@ -3,6 +3,46 @@
<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,25 +1,43 @@
//@ts-check //@ts-check
import Rooms, {Room} from "../../graph/graph.js"; import CreateAllRooms, {Room} from "../../graph/graph.js";
import User from "./user.js";
export default Rooms({ 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({ 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", ["dev"], "all", {}, "p1", "p2"], d1:["Write page metas", ["admin", "write"], { }, "p1", "p2"],
d2:["Build Page preview", ["dev"], "all", {p1:1, p2:1}, "p3"] d2:["Build Page preview", ["admin", "dev" ], {p1:1, p2:1, p5:1}, "p3", "p4"],
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,47 +21,21 @@ 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" || parts[0] == "store") if(parts[0] == "graph")
{ {
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);
}
} }

64
styles.js Normal file
View File

@ -0,0 +1,64 @@
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",
}
});