mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
Added in all link bubbles code
This commit is contained in:
parent
97a84ce6ee
commit
48a6edae29
@ -1,4 +1,5 @@
|
|||||||
import BaseView from 'app/client/components/BaseView';
|
import BaseView from 'app/client/components/BaseView';
|
||||||
|
import {allCommands} from "app/client/components/commands";
|
||||||
import {GristDoc} from 'app/client/components/GristDoc';
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
import {makeT} from 'app/client/lib/localization';
|
import {makeT} from 'app/client/lib/localization';
|
||||||
import {ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
|
import {ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
@ -13,6 +14,7 @@ import {menu} from 'app/client/ui2018/menus';
|
|||||||
import {getWidgetTypes} from "app/client/ui/widgetTypesMap";
|
import {getWidgetTypes} from "app/client/ui/widgetTypesMap";
|
||||||
import {Computed, dom, DomElementArg, Observable, styled} from 'grainjs';
|
import {Computed, dom, DomElementArg, Observable, styled} from 'grainjs';
|
||||||
import {defaultMenuOptions} from 'popweasel';
|
import {defaultMenuOptions} from 'popweasel';
|
||||||
|
import {EmptyFilterState} from "./LinkingState";
|
||||||
|
|
||||||
const t = makeT('ViewSection');
|
const t = makeT('ViewSection');
|
||||||
|
|
||||||
@ -54,6 +56,118 @@ export function buildCollapsedSectionDom(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function buildLinkStateIndicatorDom(options: {
|
||||||
|
gristDoc: GristDoc,
|
||||||
|
sectionRowId: number,
|
||||||
|
}, ...domArgs: DomElementArg[]) {
|
||||||
|
const {gristDoc, sectionRowId} = options;
|
||||||
|
const tgtSec = gristDoc.docModel.viewSections.getRowModel(sectionRowId);
|
||||||
|
|
||||||
|
return dom.domComputed((use) => {
|
||||||
|
//makes an observable for the passed-in section
|
||||||
|
const lstate = use(tgtSec.linkingState);
|
||||||
|
if(lstate == null) { return null; }
|
||||||
|
|
||||||
|
// Default to empty for ease of coding, will be set in cases where it's relevant
|
||||||
|
const lfilter = lstate.filterState ? use(lstate.filterState): EmptyFilterState;
|
||||||
|
|
||||||
|
// crunch filterVals into compact string for display in bubble (not tooltip),
|
||||||
|
// eg "USA", "USA;2022", "(USA +3 others)"
|
||||||
|
// only for filter linking and summary-filter-linking
|
||||||
|
|
||||||
|
//filters is a map {column: [vals...]}
|
||||||
|
// if multiple filters, join each with ";"
|
||||||
|
// each filter can have multiple vals (if reflist), show as "(SomeValue +3 others)",
|
||||||
|
|
||||||
|
const filterValsShortLabel = Object.keys(lfilter.filterLabels).map(colId => {
|
||||||
|
const vals = lfilter.filterLabels[colId];
|
||||||
|
//selector can be an empty reflist (filterLabels[colId] = [])
|
||||||
|
if(vals.length == 0)
|
||||||
|
{ return '- blank -'; }
|
||||||
|
|
||||||
|
// Even if vals != [], selector might be a null/empty cell value.
|
||||||
|
// - if a null reference: filter[colId] = [0], but filterLabels would be ['']
|
||||||
|
// - if an empty string/choice filter = [''], label = ['']
|
||||||
|
// - if an empty number/date/etc: filter[colId] = [null], but filterLabel will be ['']
|
||||||
|
//Note: numeric 0 won't become blank, since filterLabel will be "0", which is truthy
|
||||||
|
const dispVal = vals[0] || '- blank -';
|
||||||
|
|
||||||
|
//If 2 or more vals, abbreviate it
|
||||||
|
return vals.length <= 1 ? dispVal: `(${dispVal} +${vals.length - 1} others)`;
|
||||||
|
//TODO: could show multiple vals if short, and/or let css overflow ellipsis handle it?
|
||||||
|
}).join("; ");
|
||||||
|
|
||||||
|
let bubbleContent: DomElementArg[];
|
||||||
|
switch (use(lstate.linkTypeDescription)) {
|
||||||
|
case "Filter:Summary-Group":
|
||||||
|
case "Filter:Col->Col":
|
||||||
|
case "Filter:Row->Col":
|
||||||
|
case "Summary":
|
||||||
|
bubbleContent = [
|
||||||
|
dom("div", dom.style('width', '2px'), dom.style('display', 'inline-block')), //spacer for text
|
||||||
|
filterValsShortLabel,
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case "Show-Referenced-Records":
|
||||||
|
bubbleContent = [];
|
||||||
|
break;
|
||||||
|
case "Cursor:Same-Table":
|
||||||
|
case "Cursor:Reference":
|
||||||
|
bubbleContent = [];
|
||||||
|
break;
|
||||||
|
case "Error:Invalid":
|
||||||
|
default:
|
||||||
|
bubbleContent = ["Error"];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return linkStateBubble(
|
||||||
|
customIcon(dom.style("background-color", theme.filterBarButtonSavedFg + "")),
|
||||||
|
bubbleContent,
|
||||||
|
dom.on("click", () => allCommands.dataSelectionTabOpen.run()),
|
||||||
|
...domArgs,
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const tempIconSVGString= `url('data:image/svg+xml;utf8,<svg width="16px" height="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> <ellipse style="stroke: rgb(0, 0, 0);" cx="2.426" cy="13.913" rx="1.361" ry="1.361"/> <ellipse style="stroke: rgb(0, 0, 0);" cx="13.827" cy="3.039" rx="1.222" ry="1.222"/> <path style="stroke: rgb(0, 0, 0); fill: none;" d="M 2.396 12.802 C 2.363 7.985 6.014 2.893 11.895 3.027"/> <path style="stroke: rgb(0, 0, 0); fill: none;" d="M 8.49 1.047 L 12.265 2.871 L 8.986 5.874"/> </svg>')`;
|
||||||
|
|
||||||
|
//TODO JV TEMP: Shamelessly copied from icon.ts
|
||||||
|
const customIcon = styled('div', `
|
||||||
|
-webkit-mask-image: ${tempIconSVGString};
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
-webkit-mask-position: center;
|
||||||
|
-webkit-mask-size: contain;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: var(--icon-color, var(--grist-theme-text, black));
|
||||||
|
|
||||||
|
`);
|
||||||
|
|
||||||
|
const linkStateBubble = styled('div', `
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 3px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
align-self: start;
|
||||||
|
height: 21px;
|
||||||
|
margin-top: -4px;
|
||||||
|
margin-left: 4px;
|
||||||
|
color: ${theme.filterBarButtonSavedFg};
|
||||||
|
background-color: ${theme.filterBarButtonSavedBg};
|
||||||
|
&:hover {
|
||||||
|
background-color: ${theme.filterBarButtonSavedHoverBg};
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
export function buildViewSectionDom(options: {
|
export function buildViewSectionDom(options: {
|
||||||
gristDoc: GristDoc,
|
gristDoc: GristDoc,
|
||||||
sectionRowId: number,
|
sectionRowId: number,
|
||||||
@ -93,6 +207,9 @@ export function buildViewSectionDom(options: {
|
|||||||
dom.maybe((use) => use(use(viewInstance.viewSection.table).summarySourceTable), () =>
|
dom.maybe((use) => use(use(viewInstance.viewSection.table).summarySourceTable), () =>
|
||||||
cssSigmaIcon('Pivot', testId('sigma'))),
|
cssSigmaIcon('Pivot', testId('sigma'))),
|
||||||
buildWidgetTitle(vs, options, testId('viewsection-title'), cssTestClick(testId("viewsection-blank"))),
|
buildWidgetTitle(vs, options, testId('viewsection-title'), cssTestClick(testId("viewsection-blank"))),
|
||||||
|
dom.maybe((use) => use(vs.linkSrcSectionRef) != 0, () =>
|
||||||
|
buildLinkStateIndicatorDom({gristDoc, sectionRowId}, testId("viewsection-linkstate"))),
|
||||||
|
dom("div", dom.style("flex", "1 0 0px")), //spacer, 0 size by default, grows to take up remaining space
|
||||||
viewInstance.buildTitleControls(),
|
viewInstance.buildTitleControls(),
|
||||||
dom('div.viewsection_buttons',
|
dom('div.viewsection_buttons',
|
||||||
dom.create(viewSectionMenu, gristDoc, vs)
|
dom.create(viewSectionMenu, gristDoc, vs)
|
||||||
|
@ -269,7 +269,7 @@ const updateOnKey = {onInput: true};
|
|||||||
|
|
||||||
// Leave class for tests.
|
// Leave class for tests.
|
||||||
const cssTitleContainer = styled('div', `
|
const cssTitleContainer = styled('div', `
|
||||||
flex: 1 1 0px;
|
flex: 0 1 auto; /* won't grow, starts at size of content, will shrink if needed */
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
.info_toggle_icon {
|
.info_toggle_icon {
|
||||||
|
@ -36,7 +36,7 @@ describe('Views.ntest', function() {
|
|||||||
// Check that viewsection titles are correct and editable
|
// Check that viewsection titles are correct and editable
|
||||||
var recordTitle = recordSection.find('.test-viewsection-title');
|
var recordTitle = recordSection.find('.test-viewsection-title');
|
||||||
assert.equal(await recordTitle.text(), 'TABLE1');
|
assert.equal(await recordTitle.text(), 'TABLE1');
|
||||||
await recordTitle.click();
|
await recordSection.find('.test-viewsection-blank').click(); //switch to recordSection without opening title widget
|
||||||
await gu.renameActiveSection('foo');
|
await gu.renameActiveSection('foo');
|
||||||
assert.equal(await recordTitle.text(), 'foo');
|
assert.equal(await recordTitle.text(), 'foo');
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user