Initial Lime window manager
This commit is contained in:
104
work/activity-workflow-proposal.html
Normal file
104
work/activity-workflow-proposal.html
Normal file
@@ -0,0 +1,104 @@
|
||||
<div id="lime-activity-workflow-proposal">
|
||||
<style>
|
||||
#lime-activity-workflow-proposal .monitor-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
|
||||
gap: 16px;
|
||||
margin: 16px 0 8px;
|
||||
}
|
||||
#lime-activity-workflow-proposal .monitor { min-width: 0; }
|
||||
#lime-activity-workflow-proposal svg { display: block; width: 100%; }
|
||||
#lime-activity-workflow-proposal svg text { fill: var(--foreground); }
|
||||
#lime-activity-workflow-proposal .monitor-outline {
|
||||
fill: none;
|
||||
stroke: var(--border);
|
||||
}
|
||||
#lime-activity-workflow-proposal .window {
|
||||
fill: color-mix(in srgb, var(--viz-series-1) 12%, transparent);
|
||||
stroke: var(--border);
|
||||
}
|
||||
</style>
|
||||
<div class="viz-controls">
|
||||
<label class="form-label">Proposed behavior · scenario
|
||||
<select class="form-select" data-scenario>
|
||||
<optgroup label="Everyday workflow">
|
||||
<option value="0">1 · Start: Lime left, Chill right</option>
|
||||
<option value="1">2 · Open Lime on the right too</option>
|
||||
<option value="2">3 · Open Docs on the right</option>
|
||||
<option value="3">4 · Switch right to Chill</option>
|
||||
<option value="4">5 · Switch left to Work too</option>
|
||||
<option value="5">6 · Return to Lime on the right</option>
|
||||
<option value="6">7 · Return to Lime on the left too</option>
|
||||
<option value="7">8 · Move Docs to the left</option>
|
||||
<option value="8">9 · Right: away, then back to Lime</option>
|
||||
</optgroup>
|
||||
<optgroup label="A separate edge case to decide">
|
||||
<option value="9">First visit on right; Lime inactive</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="monitor-grid">
|
||||
<div class="monitor" data-head="left"></div>
|
||||
<div class="monitor" data-head="right"></div>
|
||||
</div>
|
||||
<div class="text-small" data-detail role="status" aria-live="polite"></div>
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
const root = document.getElementById('lime-activity-workflow-proposal');
|
||||
const choose = root.querySelector('[data-scenario]');
|
||||
const detail = root.querySelector('[data-detail]');
|
||||
const panels = ['left', 'right'].map(head => root.querySelector(`[data-head="${head}"]`));
|
||||
const steps = [
|
||||
{left:['Lime',['Editor']], right:['Chill',['Chat']], detail:'Lime has only been used on the left so far.'},
|
||||
{left:['Lime',['Editor']], right:['Lime',[]], detail:'Same activity, additional space. Editor stays on the left.'},
|
||||
{left:['Lime',['Editor']], right:['Lime',['Docs']], detail:'Both windows belong to Lime; each has one current location.'},
|
||||
{left:['Lime',['Editor']], right:['Chill',['Chat']], detail:'Docs stays open but is not displayed; its right-hand placement is remembered.'},
|
||||
{left:['Work',['Work browser']], right:['Chill',['Chat']], detail:'Lime is not displayed. Editor and Docs stay open with their positions remembered.'},
|
||||
{left:['Work',['Work browser']], right:['Lime',['Docs']], detail:'Docs returns on the right. Editor remains out of sight on the left.'},
|
||||
{left:['Lime',['Editor']], right:['Lime',['Docs']], detail:'Both halves of the live arrangement are back. No saved View was needed.'},
|
||||
{left:['Lime',['Editor','Docs']], right:['Lime',[]], detail:'The move updates Lime’s live arrangement: Docs no longer belongs in the right-hand layout.'},
|
||||
{left:['Lime',['Editor','Docs']], right:['Lime',[]], detail:'No reclaim attempt or conflict dialog. The right-hand layout is still empty.'},
|
||||
{left:['Work',['Work browser']], right:['Lime',[]], detail:'Edge case: Lime’s Editor is remembered on the left, not automatically brought to the right.'}
|
||||
];
|
||||
const ns = 'http://www.w3.org/2000/svg';
|
||||
function element(tag, attributes, text) {
|
||||
const node = document.createElementNS(ns, tag);
|
||||
for (const [key,value] of Object.entries(attributes || {})) node.setAttribute(key, value);
|
||||
if (text !== undefined) node.textContent = text;
|
||||
return node;
|
||||
}
|
||||
function drawMonitor(panel, side, state) {
|
||||
const width = Math.max(1, panel.getBoundingClientRect().width);
|
||||
const height = 204;
|
||||
const svg = element('svg', {viewBox:`0 0 ${width} ${height}`, height,
|
||||
role:'img', 'aria-label':`${side} monitor showing ${state[0]}: ${state[1].join(' and ') || 'empty'}`});
|
||||
svg.append(element('rect',{x:1,y:1,width:width-2,height:height-2,rx:4,class:'monitor-outline'}));
|
||||
svg.append(element('text',{x:12,y:26,'font-weight':500},`${side} · ${state[0]}`));
|
||||
svg.append(element('line',{x1:1,y1:39,x2:width-1,y2:39,stroke:'var(--border)'}));
|
||||
const windows = state[1];
|
||||
if (!windows.length) {
|
||||
svg.append(element('text',{x:width/2,y:122,'text-anchor':'middle'},'Empty live arrangement'));
|
||||
} else {
|
||||
const gap = 8;
|
||||
const tileWidth = (width-24-gap*(windows.length-1))/windows.length;
|
||||
windows.forEach((name,index) => {
|
||||
const x = 12+index*(tileWidth+gap);
|
||||
svg.append(element('rect',{x,y:51,width:tileWidth,height:140,rx:2,class:'window'}));
|
||||
svg.append(element('text',{x:x+tileWidth/2,y:126,'text-anchor':'middle'},name));
|
||||
});
|
||||
}
|
||||
panel.replaceChildren(svg);
|
||||
}
|
||||
function draw() {
|
||||
const step = steps[Number(choose.value)];
|
||||
drawMonitor(panels[0],'Left',step.left);
|
||||
drawMonitor(panels[1],'Right',step.right);
|
||||
detail.textContent = step.detail;
|
||||
}
|
||||
choose.addEventListener('change', draw);
|
||||
new ResizeObserver(draw).observe(root);
|
||||
draw();
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user