diff --git a/src/templates/index.html b/src/templates/index.html
index 822f518..b8b28ce 100644
--- a/src/templates/index.html
+++ b/src/templates/index.html
@@ -86,7 +86,20 @@
-
+
diff --git a/static/js/ui-controls.js b/static/js/ui-controls.js
index c1f2fdb..e933069 100644
--- a/static/js/ui-controls.js
+++ b/static/js/ui-controls.js
@@ -40,6 +40,10 @@
return Math.min(max, Math.max(min, value));
}
+ function hasOwn(obj, key) {
+ return Object.prototype.hasOwnProperty.call(obj, key);
+ }
+
function parseJSON(value, fallback) {
if (!value) {
return fallback;
@@ -153,7 +157,58 @@
saveWindowLayouts();
}
- function ensureWindowLayout(def) {
+ function measureLayoutFromContent(def) {
+ const windowEl = def.el;
+ const fallback = def.defaultLayout;
+ const wasHidden = windowEl.classList.contains('hidden');
+ const previousStyles = {
+ visibility: windowEl.style.visibility,
+ left: windowEl.style.left,
+ top: windowEl.style.top,
+ width: windowEl.style.width,
+ height: windowEl.style.height,
+ right: windowEl.style.right,
+ bottom: windowEl.style.bottom,
+ transform: windowEl.style.transform,
+ };
+
+ if (wasHidden) {
+ windowEl.classList.remove('hidden');
+ }
+
+ windowEl.style.visibility = 'hidden';
+ windowEl.style.left = '-10000px';
+ windowEl.style.top = '-10000px';
+ windowEl.style.width = 'max-content';
+ windowEl.style.height = 'auto';
+ windowEl.style.right = 'auto';
+ windowEl.style.bottom = 'auto';
+ windowEl.style.transform = 'none';
+
+ const rect = windowEl.getBoundingClientRect();
+
+ windowEl.style.visibility = previousStyles.visibility;
+ windowEl.style.left = previousStyles.left;
+ windowEl.style.top = previousStyles.top;
+ windowEl.style.width = previousStyles.width;
+ windowEl.style.height = previousStyles.height;
+ windowEl.style.right = previousStyles.right;
+ windowEl.style.bottom = previousStyles.bottom;
+ windowEl.style.transform = previousStyles.transform;
+
+ if (wasHidden) {
+ windowEl.classList.add('hidden');
+ }
+
+ return normalizeLayout({
+ left: fallback.left,
+ top: fallback.top,
+ width: Number.isFinite(rect.width) && rect.width > 0 ? Math.ceil(rect.width) : fallback.width,
+ height: Number.isFinite(rect.height) && rect.height > 0 ? Math.ceil(rect.height) : fallback.height,
+ }, fallback);
+ }
+
+ function ensureWindowLayout(def, options = {}) {
if (isMobileViewport()) {
def.el.style.right = 'auto';
def.el.style.bottom = 'auto';
@@ -161,7 +216,12 @@
return;
}
- const normalized = normalizeLayout(windowLayouts[def.id], def.defaultLayout);
+ const hasSavedLayout = hasOwn(options, 'hasSavedLayout')
+ ? options.hasSavedLayout
+ : hasOwn(windowLayouts, def.id);
+ const normalized = hasSavedLayout
+ ? normalizeLayout(windowLayouts[def.id], def.defaultLayout)
+ : measureLayoutFromContent(def);
const clamped = clampLayoutToViewport(normalized);
applyLayout(def.el, clamped);
windowLayouts[def.id] = clamped;
@@ -284,11 +344,20 @@
function initWindowLayouts() {
windowLayouts = loadWindowLayouts();
+ let shouldPersistSeededLayouts = false;
windowDefs.forEach((def) => {
- ensureWindowLayout(def);
+ const hasSavedLayout = hasOwn(windowLayouts, def.id);
+ ensureWindowLayout(def, { hasSavedLayout });
+ if (!hasSavedLayout) {
+ shouldPersistSeededLayouts = true;
+ }
});
+ if (shouldPersistSeededLayouts) {
+ saveWindowLayouts();
+ }
+
window.addEventListener('resize', () => {
if (isMobileViewport()) {
return;