Skip to content

Commit

Permalink
Resizing header after data change
Browse files Browse the repository at this point in the history
  • Loading branch information
berhalak committed Nov 4, 2024
1 parent a5a466a commit c6df11e
Show file tree
Hide file tree
Showing 4 changed files with 353 additions and 194 deletions.
274 changes: 178 additions & 96 deletions timeline/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,120 +3,191 @@ import {Timeline} from 'vis-timeline';
import type * as Rx from 'rxjs';
import {buildColLabel, Command, withElementSpinner} from './lib';

export function monitorHeader() {
// Monitor left panel and adjust header left position.
const panel = document.querySelector('.vis-panel.vis-left')!;
const header = document.getElementById('groupHeader')!;
const observer = new ResizeObserver((...args: any[]) => {
export class HeaderMonitor {
private headerMonitor!: ResizeObserver;
private lastWidth = 0;
private work = () => {};


constructor() {
this.work = debounced(this.invoke.bind(this), 5);
}

public start() {
// Monitor left panel and adjust header left position.
this.headerMonitor = new ResizeObserver(this.work);
this.headerMonitor.observe(this.panel());
}

public invoke(...args: any[]) {
const [panel, header] = [this.panel(), this.header()];
const {left} = panel.querySelector('.vis-labelset')!.getBoundingClientRect();
header.style.setProperty('--left-width', `${left}px`);
const width = args[0][0].borderBoxSize[0].inlineSize;
header.style.width = `${width}px`;
});
observer.observe(panel);
if (this.lastWidth !== width) {
this.lastWidth = width;
recalculateHeader();
}
}

private panel() {
return document.querySelector('.vis-panel.vis-left')!;
}

private header() {
return document.getElementById('groupHeader')!;
}

private pauseCalls = 0;

public pause() {
this.pauseCalls++;
if (this.pauseCalls === 1) {
this.headerMonitor.disconnect();
}
}

public resume() {
this.pauseCalls--;
if (this.pauseCalls === 0) {
this.headerMonitor.observe(this.panel());
}
if (this.pauseCalls < 0) {
throw new Error('Unbalanced pause/resume calls');
}
}
}

export const headerMonitor = new HeaderMonitor();

export function rewriteHeader({mappings, timeline, cmdAddBlank}: {
mappings: Observable<any>;
timeline: Timeline;
cmdAddBlank: Command;
}) {

const headerTop = document.querySelector('#groupHeader .top')! as HTMLElement;
const headerRight = document.querySelector('#groupHeader .bottom .right')! as HTMLElement;

const columnsDiv = dom('div');
columnsDiv.classList.add('group-header-columns');

const columnElements = mappings.get().Columns.map((col: string) => {
return dom('div',
dom.text(buildColLabel(col)),
dom.cls('group-part'),
dom.style('padding', '5px')
);
});
columnsDiv.style.setProperty('grid-template-columns', 'auto');

const moreDiv = document.createElement('div');
moreDiv.style.width = '20px';

columnElements.push(moreDiv);
const collapsed = observable(false);

const iconName = computed(use => {
return use(collapsed) ? 'chevron-bar-right' : 'chevron-bar-left';
})

const resizer = headerTop.querySelector('.resizer') as HTMLElement;
const icon = headerTop.querySelector('sl-icon') as HTMLElement;
const button = headerTop.querySelector('sl-button') as HTMLElement;
const buttonLoading = observable(false);
dom.update(resizer,
dom.on('click', () => collapsed.set(!collapsed.get()))
);
dom.update(icon,
// Icon to hide or show drawer.
dom.prop('name', iconName)
);
dom.update(button,
dom.prop('loading', buttonLoading),
dom.on('click', async () => {
try {
buttonLoading.set(true);
await cmdAddBlank.invoke(null);
} finally {
buttonLoading.set(false);
}

try {
headerMonitor.pause();

const headerTop = document.querySelector('#groupHeader .top')! as HTMLElement;
const headerRight = document.querySelector('#groupHeader .bottom .right')! as HTMLElement;

const columnsDiv = dom('div');
columnsDiv.classList.add('group-header-columns');

const columnElements = mappings.get().Columns.map((col: string) => {
return dom('div',
dom.text(buildColLabel(col)),
dom.cls('group-part'),
dom.style('padding', '5px')
);
});
columnsDiv.style.setProperty('grid-template-columns', 'auto');

const moreDiv = document.createElement('div');
moreDiv.style.width = '20px';

columnElements.push(moreDiv);
const collapsed = observable(false);

const iconName = computed(use => {
return use(collapsed) ? 'chevron-bar-right' : 'chevron-bar-left';
})
);
columnsDiv.append(...columnElements);

collapsed.addListener(() => {
timeline.redraw();
});

// Now we need to update its width, we can't break lines and anything like that.
headerRight.innerHTML = '';
headerRight.append(columnsDiv);
// And set this width as minimum for the table rendered below.
const visualization = document.getElementById('visualization')!;

dom.update(visualization,
dom.cls('collapsed', collapsed)
);

// Now measure each individual line, and provide grid-template-columns variable with minimum
// width to make up for a column and header width.
// grid-template-columns: var(--grid-template-columns, repeat(12, max-content));
const widths = columnElements.map(part => part.getBoundingClientRect().width);
const templateColumns = widths
.map(w => `minmax(${w}px, max-content)`)
.join(' ');

visualization.style.setProperty('--grid-template-columns', templateColumns);
anchorHeader();

const firstLine = document.querySelector('.group-template');
if (!firstLine) {
console.error('No first line found');
return;

const resizer = headerTop.querySelector('.resizer') as HTMLElement;
const icon = headerTop.querySelector('sl-icon') as HTMLElement;
const button = headerTop.querySelector('sl-button') as HTMLElement;
const buttonLoading = observable(false);
dom.update(resizer,
dom.on('click', () => collapsed.set(!collapsed.get()))
);
dom.update(icon,
// Icon to hide or show drawer.
dom.prop('name', iconName)
);
dom.update(button,
dom.prop('loading', buttonLoading),
dom.on('click', async () => {
try {
buttonLoading.set(true);
await cmdAddBlank.invoke(null);
} finally {
buttonLoading.set(false);
}
})
);
columnsDiv.append(...columnElements);

collapsed.addListener(() => {
timeline.redraw();
});

// Now we need to update its width, we can't break lines and anything like that.
headerRight.innerHTML = '';
headerRight.append(columnsDiv);
// And set this width as minimum for the table rendered below.
const visualization = document.getElementById('visualization')!;
dom.update(visualization,
dom.cls('collapsed', collapsed)
);

recalculateHeader();

} finally {
headerMonitor.resume();
}
}

export function recalculateHeader() {

const templateColumns2 = Array.from(firstLine.children)
.map(elementWidth)
.map(pixels)
.join(' ');
try {
headerMonitor.pause();

columnsDiv.style.setProperty('grid-template-columns', templateColumns2);
const visualization = document.getElementById('visualization')!;
const columnsDiv = document.querySelector('.group-header-columns')! as HTMLElement;
const columnElements = Array.from(columnsDiv.children);

const firstPartWidth = Array.from(firstLine.children)[0].getBoundingClientRect().width;
// Now measure each individual line, and provide grid-template-columns variable with minimum
// width to make up for a column and header width.
// grid-template-columns: var(--grid-template-columns, repeat(12, max-content));

const width = Math.ceil(columnsDiv.getBoundingClientRect().width);
// Set custom property --group-header-width to the width of the groupHeader
visualization.style.setProperty('--group-header-width', `${width}px`);
visualization.style.setProperty('--group-first-width', `${firstPartWidth}px`);
// First set the auto width.
columnsDiv.style.setProperty('grid-template-columns',
'auto '.repeat(columnElements.length - 1) + '20px 1fr');

const widths = columnElements.map(part => part.getBoundingClientRect().width);
const templateColumns = widths
.map(w => `minmax(${w}px, max-content)`)
.join(' ');

visualization.style.setProperty('--grid-template-columns', templateColumns);
anchorHeader();

const firstLine = document.querySelector('.group-template');
if (!firstLine) {
console.error('No first line found');
return;
}

const firstLineColumns = Array.from(firstLine.children)
.map(elementWidth)
.map(pixels)
.join(' ');

columnsDiv.style.setProperty('grid-template-columns', firstLineColumns);

const firstPartWidth = Array.from(firstLine.children)[0].getBoundingClientRect().width;

const width = Math.ceil(columnsDiv.getBoundingClientRect().width);
// Set custom property --group-header-width to the width of the groupHeader
visualization.style.setProperty('--group-header-width', `${width}px`);
visualization.style.setProperty('--group-first-width', `${firstPartWidth}px`);


} finally {
headerMonitor.resume();
}
}

export function anchorHeader() {
Expand All @@ -142,4 +213,15 @@ export function anchorHeader() {


const elementWidth = (el: Element) => el.getBoundingClientRect().width;
const pixels = (w: number) => `${w}px`;
const pixels = (w: number) => `${w}px`;
function debounced(func: (...args: any[]) => void, wait: number = 100): () => void {
let timeout: NodeJS.Timeout | null = null;
return function(...args: any[]) {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
func(...args);
}, wait);
};
}
27 changes: 16 additions & 11 deletions timeline/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {buildCursor} from './cursor';
import {monitorHeader, rewriteHeader} from './header';
import {headerMonitor, rewriteHeader} from './header';
import {
Command,
fetchSchema,
Expand Down Expand Up @@ -377,17 +377,22 @@ grist.onRecords(async (recs: any[], maps: any) => {
schema = await fetchSchema();
}

mappings.set(maps);
records.set(grist.mapColumnNames(recs));
order.clear();
recs.forEach((r, i) => order.set(r.id, i));
renderAllItems();
try {
headerMonitor.pause();
mappings.set(maps);
records.set(grist.mapColumnNames(recs));
order.clear();
recs.forEach((r, i) => order.set(r.id, i));
renderAllItems();

if (areMappingsNew) {
rewriteHeader({mappings, timeline, cmdAddBlank});
}
if (areMappingsNew) {
rewriteHeader({mappings, timeline, cmdAddBlank});
}

document.body.classList.remove('loading');
document.body.classList.remove('loading');
} finally {
headerMonitor.resume();
}
});


Expand Down Expand Up @@ -723,7 +728,7 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('fitButton')!.addEventListener('click', () => autoFit(true));


monitorHeader();
headerMonitor.start();


// same manu for .vis-item.vis-range
Expand Down
Loading

0 comments on commit c6df11e

Please sign in to comment.