(core) Minimazing widgets

Summary:
A feature that allows minimizing widgets on the ViewLayout.
- Code in ViewLayout and Layout hasn't been changed. Only some methods or variables were made public, and some events are now triggered when a section is dragged.
- Widgets can be collapsed or expanded (added back to the main area)
- Collapsed widgets can be expanded and shown as a popup
- Collapsed widgets support drugging, reordering, and transferring between the main and collapsed areas.

Test Plan: New test

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3779
This commit is contained in:
Jarosław Sadziński
2023-02-24 12:12:55 +01:00
parent e9efac05f7
commit 59cf654190
19 changed files with 1949 additions and 259 deletions

125
test/client/lib/Signal.ts Normal file
View File

@@ -0,0 +1,125 @@
import { assert } from 'chai';
import { Signal } from 'app/client/lib/Signal';
describe('Signal', function() {
it('computes new signal from other events', function() {
const started = Signal.create(null, false);
const hovered = Signal.create(null, false);
const hoverAndStarted = Signal.compute(null, on => on(started) && on(hovered));
let flag: any = 'not-called';
hoverAndStarted.listen(val => flag = val);
function start(emit: boolean, expected: boolean) {
started.emit(emit);
assert.equal(flag, expected);
flag = 'not-called';
}
function hover(emit: boolean, expected: boolean) {
hovered.emit(emit);
assert.equal(flag, expected);
flag = 'not-called';
}
start(true, false);
start(false, false);
start(true, false);
hover(true, true);
hover(false, false);
hover(true, true);
start(false, false);
});
it('works as flag', function() {
const started = Signal.create(null, false);
const hovered = Signal.create(null, false);
const andEvent = Signal.compute(null, on => on(started) && on(hovered)).distinct();
const notCalled = {};
let andCalled = notCalled;
andEvent.listen(val => andCalled = val);
function start(emit: boolean, expected: any) {
started.emit(emit);
assert.equal(andCalled, expected);
andCalled = notCalled;
}
start(true, notCalled);
start(false, notCalled);
start(true, notCalled);
function hover(emit: boolean, expected: any) {
hovered.emit(emit);
assert.equal(andCalled, expected);
andCalled = notCalled;
}
hover(true, true);
hover(false, false);
hover(true, true);
start(false, false);
});
it('supports basic compositions', function() {
const numbers = Signal.create(null, 0);
const even = numbers.filter(n => n % 2 === 0);
const odd = numbers.filter(n => n % 2 === 1);
let evenCount = 0;
let oddCount = 0;
even.listen(() => evenCount++);
odd.listen(() => oddCount++);
assert.equal(evenCount, 0);
assert.equal(oddCount, 0);
numbers.emit(2);
assert.equal(evenCount, 1);
assert.equal(oddCount, 0);
numbers.emit(3);
assert.equal(evenCount, 1);
assert.equal(oddCount, 1);
const distinct = numbers.distinct();
let distinctCount = 0;
distinct.listen(() => distinctCount++);
assert.equal(distinctCount, 0);
numbers.emit(3);
assert.equal(distinctCount, 0);
numbers.emit(3);
assert.equal(distinctCount, 0);
numbers.emit(4);
assert.equal(distinctCount, 1);
numbers.emit(4);
assert.equal(distinctCount, 1);
numbers.emit(5);
assert.equal(distinctCount, 2);
const trafficLight = Signal.create(null, false);
const onRoad = numbers.filter(n => !!trafficLight.state.get());
let onRoadCount = 0;
onRoad.listen(() => onRoadCount++);
assert.equal(onRoadCount, 0);
numbers.emit(5);
assert.equal(onRoadCount, 0);
trafficLight.emit(true);
assert.equal(onRoadCount, 0);
numbers.emit(6);
assert.equal(onRoadCount, 1);
});
it('detects cycles', function() {
const first = Signal.create(null, 0);
const second = Signal.create(null, 0);
first.listen(n => second.emit(n + 1));
second.listen(n => first.emit(n + 1));
assert.throws(() => first.emit(1));
});
});

View File

@@ -1060,8 +1060,12 @@ export async function addNewPage(
await driver.wait(async () => (await driver.getCurrentUrl()) !== url, 2000);
}
type SectionTypes = 'Table'|'Card'|'Card List'|'Chart'|'Custom';
// Add a new widget to the current page using the 'Add New' menu.
export async function addNewSection(typeRe: RegExp|string, tableRe: RegExp|string, options?: PageWidgetPickerOptions) {
export async function addNewSection(
typeRe: RegExp|SectionTypes, tableRe: RegExp|string, options?: PageWidgetPickerOptions
) {
// Click the 'Add widget to page' entry in the 'Add New' menu
await driver.findWait('.test-dp-add-new', 2000).doClick();
await driver.findWait('.test-dp-add-widget-to-page', 500).doClick();
@@ -1515,7 +1519,6 @@ const ColumnMenuOption: { [id: string]: string; } = {
Filter: '.test-filter-menu-wrapper'
};
async function openColumnMenuHelper(col: IColHeader|string, option?: string): Promise<WebElement> {
await getColumnHeader(typeof col === 'string' ? {col} : col).mouseMove().find('.g-column-main-menu').click();
const menu = await driver.findWait('.grist-floating-menu', 100);