resize
This commit is contained in:
@@ -1,46 +1,338 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
The path provided below has to start and end with a slash "/" in order for
|
||||
it to work correctly.
|
||||
|
||||
For more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="A new Flutter project.">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="description" content="Zendegi Timeline - Dev Mode">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="z_flutter">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<title>z_flutter</title>
|
||||
<title>Zendegi Timeline (Dev)</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #e0e0e0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
#toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #16213e;
|
||||
border-bottom: 1px solid #0f3460;
|
||||
font-size: 13px;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#toolbar .group { display: flex; align-items: center; gap: 4px; }
|
||||
#toolbar label { color: #8899aa; }
|
||||
#toolbar button {
|
||||
padding: 4px 10px;
|
||||
border: 1px solid #0f3460;
|
||||
border-radius: 4px;
|
||||
background: #1a1a2e;
|
||||
color: #e0e0e0;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
#toolbar button:hover { background: #0f3460; }
|
||||
#toolbar .sep { width: 1px; height: 20px; background: #0f3460; }
|
||||
#event-log {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
background: #0d1117;
|
||||
border-top: 1px solid #0f3460;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
padding: 6px 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
#event-log .entry { padding: 2px 0; border-bottom: 1px solid #161b22; }
|
||||
#event-log .type { color: #58a6ff; }
|
||||
#event-log .time { color: #6e7681; margin-right: 8px; }
|
||||
#event-log .payload { color: #8b949e; }
|
||||
#flutter-container { flex: 1; min-height: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
You can customize the "flutter_bootstrap.js" script.
|
||||
This is useful to provide a custom configuration to the Flutter loader
|
||||
or to give the user feedback during the initialization process.
|
||||
<div id="toolbar">
|
||||
<div class="group">
|
||||
<label>Theme:</label>
|
||||
<button id="btn-light">Light</button>
|
||||
<button id="btn-dark">Dark</button>
|
||||
</div>
|
||||
<div class="sep"></div>
|
||||
<div class="group">
|
||||
<label>Data:</label>
|
||||
<button id="btn-few">Few items</button>
|
||||
<button id="btn-many">Many items</button>
|
||||
<button id="btn-empty">Empty</button>
|
||||
</div>
|
||||
<div class="sep"></div>
|
||||
<div class="group">
|
||||
<button id="btn-push">Push state</button>
|
||||
<button id="btn-clear-log">Clear log</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="flutter-container"></div>
|
||||
<div id="event-log"></div>
|
||||
|
||||
<script>
|
||||
// -----------------------------------------------------------------------
|
||||
// Test data generators
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
function makeId() {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
function iso(year, month, day) {
|
||||
return new Date(year, month - 1, day).toISOString();
|
||||
}
|
||||
|
||||
function buildFewItems() {
|
||||
const g1 = makeId();
|
||||
const g2 = makeId();
|
||||
const g3 = makeId();
|
||||
|
||||
const items = {};
|
||||
const addItem = (groupId, title, start, end, lane, desc) => {
|
||||
const id = makeId();
|
||||
items[id] = {
|
||||
id, groupId, title, description: desc ?? null,
|
||||
start, end: end ?? null, lane,
|
||||
};
|
||||
};
|
||||
|
||||
// Work group
|
||||
addItem(g1, "Project Alpha", iso(2026, 1, 5), iso(2026, 3, 15), 1, "Main project");
|
||||
addItem(g1, "Project Beta", iso(2026, 2, 10), iso(2026, 5, 20), 2, "Secondary project");
|
||||
addItem(g1, "Code Review", iso(2026, 3, 1), iso(2026, 3, 5), 1);
|
||||
addItem(g1, "Sprint Planning", iso(2026, 1, 15), null, 3); // point event
|
||||
|
||||
// Personal group
|
||||
addItem(g2, "Vacation", iso(2026, 4, 1), iso(2026, 4, 14), 1, "Spring break");
|
||||
addItem(g2, "Birthday", iso(2026, 6, 12), null, 1); // point event
|
||||
addItem(g2, "Move apartments", iso(2026, 3, 20), iso(2026, 3, 25), 2);
|
||||
|
||||
// Learning group
|
||||
addItem(g3, "Flutter course", iso(2026, 1, 1), iso(2026, 2, 28), 1);
|
||||
addItem(g3, "Rust book", iso(2026, 2, 15), iso(2026, 4, 30), 2);
|
||||
addItem(g3, "Conference talk", iso(2026, 5, 10), null, 1); // point event
|
||||
|
||||
return {
|
||||
timeline: { id: makeId(), title: "My Timeline" },
|
||||
groups: {
|
||||
[g1]: { id: g1, title: "Work", sortOrder: 0 },
|
||||
[g2]: { id: g2, title: "Personal", sortOrder: 1 },
|
||||
[g3]: { id: g3, title: "Learning", sortOrder: 2 },
|
||||
},
|
||||
items,
|
||||
groupOrder: [g1, g2, g3],
|
||||
selectedItemId: null,
|
||||
darkMode: true,
|
||||
};
|
||||
}
|
||||
|
||||
function buildManyItems() {
|
||||
const groupCount = 5;
|
||||
const groupIds = Array.from({ length: groupCount }, makeId);
|
||||
const groupNames = ["Engineering", "Design", "Marketing", "Operations", "Research"];
|
||||
const groups = {};
|
||||
for (let i = 0; i < groupCount; i++) {
|
||||
groups[groupIds[i]] = { id: groupIds[i], title: groupNames[i], sortOrder: i };
|
||||
}
|
||||
|
||||
const items = {};
|
||||
const baseDate = new Date(2026, 0, 1);
|
||||
let itemIndex = 0;
|
||||
for (const gId of groupIds) {
|
||||
for (let lane = 1; lane <= 3; lane++) {
|
||||
for (let j = 0; j < 4; j++) {
|
||||
const id = makeId();
|
||||
const startOffset = j * 45 + lane * 5 + Math.floor(Math.random() * 10);
|
||||
const duration = 14 + Math.floor(Math.random() * 30);
|
||||
const start = new Date(baseDate);
|
||||
start.setDate(start.getDate() + startOffset);
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + duration);
|
||||
const isPoint = Math.random() < 0.15;
|
||||
items[id] = {
|
||||
id, groupId: gId,
|
||||
title: `Task ${++itemIndex}`,
|
||||
description: null,
|
||||
start: start.toISOString(),
|
||||
end: isPoint ? null : end.toISOString(),
|
||||
lane,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
timeline: { id: makeId(), title: "Large Timeline" },
|
||||
groups, items,
|
||||
groupOrder: groupIds,
|
||||
selectedItemId: null,
|
||||
darkMode: true,
|
||||
};
|
||||
}
|
||||
|
||||
function buildEmpty() {
|
||||
const g1 = makeId();
|
||||
return {
|
||||
timeline: { id: makeId(), title: "Empty Timeline" },
|
||||
groups: { [g1]: { id: g1, title: "Untitled Group", sortOrder: 0 } },
|
||||
items: {},
|
||||
groupOrder: [g1],
|
||||
selectedItemId: null,
|
||||
darkMode: true,
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Bridge state
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
let currentState = buildFewItems();
|
||||
let updateStateCallback = null;
|
||||
|
||||
window.__zendegi__ = {
|
||||
getState: () => JSON.stringify(currentState),
|
||||
onEvent: (json) => {
|
||||
const event = JSON.parse(json);
|
||||
logEvent(event);
|
||||
handleEvent(event);
|
||||
},
|
||||
set updateState(cb) { updateStateCallback = cb; },
|
||||
get updateState() { return updateStateCallback; },
|
||||
};
|
||||
|
||||
function pushState(state) {
|
||||
currentState = state;
|
||||
if (updateStateCallback) {
|
||||
updateStateCallback(JSON.stringify(state));
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Event handling
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
function handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "content_height": {
|
||||
const container = document.getElementById("flutter-container");
|
||||
if (container) container.style.height = event.payload.height + "px";
|
||||
break;
|
||||
}
|
||||
case "entry_moved": {
|
||||
const { entryId, newStart, newEnd, newGroupId, newLane } = event.payload;
|
||||
const item = currentState.items[entryId];
|
||||
if (item) {
|
||||
currentState.items[entryId] = {
|
||||
...item,
|
||||
start: newStart,
|
||||
end: newEnd,
|
||||
groupId: newGroupId,
|
||||
lane: newLane,
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "entry_resized": {
|
||||
const { entryId, newStart, newEnd, groupId, lane } = event.payload;
|
||||
const item = currentState.items[entryId];
|
||||
if (item) {
|
||||
currentState.items[entryId] = {
|
||||
...item,
|
||||
start: newStart,
|
||||
end: newEnd,
|
||||
groupId: groupId,
|
||||
lane: lane,
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "item_selected":
|
||||
currentState.selectedItemId = event.payload.itemId;
|
||||
break;
|
||||
case "item_deselected":
|
||||
currentState.selectedItemId = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Event log
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
function logEvent(event) {
|
||||
const log = document.getElementById("event-log");
|
||||
const entry = document.createElement("div");
|
||||
entry.className = "entry";
|
||||
const now = new Date().toLocaleTimeString("en-GB", { hour12: false });
|
||||
const payloadStr = event.payload ? " " + JSON.stringify(event.payload) : "";
|
||||
entry.innerHTML =
|
||||
`<span class="time">${now}</span>` +
|
||||
`<span class="type">${event.type}</span>` +
|
||||
`<span class="payload">${payloadStr}</span>`;
|
||||
log.appendChild(entry);
|
||||
log.scrollTop = log.scrollHeight;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Toolbar actions
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
document.getElementById("btn-dark").addEventListener("click", () => {
|
||||
currentState.darkMode = true;
|
||||
document.body.style.background = "#1a1a2e";
|
||||
pushState(currentState);
|
||||
});
|
||||
|
||||
document.getElementById("btn-light").addEventListener("click", () => {
|
||||
currentState.darkMode = false;
|
||||
document.body.style.background = "#f0f0f0";
|
||||
document.body.style.color = "#333";
|
||||
pushState(currentState);
|
||||
});
|
||||
|
||||
document.getElementById("btn-few").addEventListener("click", () => {
|
||||
pushState(buildFewItems());
|
||||
});
|
||||
|
||||
document.getElementById("btn-many").addEventListener("click", () => {
|
||||
pushState(buildManyItems());
|
||||
});
|
||||
|
||||
document.getElementById("btn-empty").addEventListener("click", () => {
|
||||
pushState(buildEmpty());
|
||||
});
|
||||
|
||||
document.getElementById("btn-push").addEventListener("click", () => {
|
||||
pushState(currentState);
|
||||
});
|
||||
|
||||
document.getElementById("btn-clear-log").addEventListener("click", () => {
|
||||
document.getElementById("event-log").innerHTML = "";
|
||||
});
|
||||
</script>
|
||||
|
||||
For more details:
|
||||
* https://docs.flutter.dev/platform-integration/web/initialization
|
||||
-->
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user