mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
Added bidirectional linking (BIG DIFF)
This commit is contained in:
parent
1b1970c075
commit
d3126bec62
@ -131,8 +131,8 @@ function BaseView(gristDoc, viewSectionModel, options) {
|
||||
|
||||
// Update the cursor whenever linkedRowId() changes (but only if we have any linking).
|
||||
this.autoDispose(this.linkedRowId.subscribe(rowId => {
|
||||
if (this.viewSection.linkingState.peek()) {
|
||||
this.setCursorPos({rowId: rowId || 'new'});
|
||||
if (this.viewSection.linkingState.peek() && rowId != null) { //TODO JV: used to be that null meant "new", now it means "no cursor linking"
|
||||
this.setCursorPos({rowId: rowId || 'new'}, true); //true b/c not a user-edit (caused by linking)
|
||||
}
|
||||
}));
|
||||
|
||||
@ -282,14 +282,14 @@ BaseView.prototype.deleteRecords = function(source) {
|
||||
|
||||
/**
|
||||
* Sets the cursor to the given position, deferring if necessary until the current query finishes
|
||||
* loading.
|
||||
* loading. silentUpdate will be set if updating as a result of cursor linking(see Cursor.setCursorPos for info)
|
||||
*/
|
||||
BaseView.prototype.setCursorPos = function(cursorPos) {
|
||||
BaseView.prototype.setCursorPos = function(cursorPos, silentUpdate = false) {
|
||||
if (this.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
if (!this._isLoading.peek()) {
|
||||
this.cursor.setCursorPos(cursorPos);
|
||||
this.cursor.setCursorPos(cursorPos, silentUpdate);
|
||||
} else {
|
||||
// This is the first step; the second happens in onTableLoaded.
|
||||
this._pendingCursorPos = cursorPos;
|
||||
|
@ -16,6 +16,21 @@ function nullAsUndefined<T>(value: T|null|undefined): T|undefined {
|
||||
return value == null ? undefined : value;
|
||||
}
|
||||
|
||||
// ================ SequenceNum: used to keep track of cursor edits (lastEditedAt)
|
||||
// basically just a global auto-incrementing counter, with some types to make intent a bit more clear
|
||||
// Each cursor starts at SequenceNEVER (0), and after that all cursors using NextSequenceNum() will have
|
||||
// a unique, monotonically increasing number for their lastEditedAt()
|
||||
export type SequenceNum = number;
|
||||
export const SequenceNEVER: SequenceNum = 0;
|
||||
let latestGlobalSequenceNum = SequenceNEVER;
|
||||
const nextSequenceNum = () => { return latestGlobalSequenceNum++; };
|
||||
|
||||
// Note: we don't make any provisions for handling overflow. It's fine because:
|
||||
// - Number.MAX_SAFE_INTEGER is 9,007,199,254,740,991 (9 * 10^15)
|
||||
// - even at 1000 cursor-edits per second, it would take ~300,000 yrs to overflow
|
||||
// - Plus it's client-side, so that's a single continuous 300-millenia-long session, which would be impressive uptime
|
||||
|
||||
|
||||
/**
|
||||
* Cursor represents the location of the cursor in the viewsection. It is maintained by BaseView,
|
||||
* and implements the shared functionality related to the cursor cell.
|
||||
@ -62,6 +77,12 @@ export class Cursor extends Disposable {
|
||||
private _sectionId: ko.Computed<number>;
|
||||
|
||||
private _properRowId: ko.Computed<UIRowId|null>;
|
||||
private _lastEditedAt: ko.Observable<SequenceNum>;
|
||||
|
||||
private _silentUpdatesFlag: boolean = false;
|
||||
// lastEditedAt is updated on rowIndex or fieldIndex update (including through setCursorPos)
|
||||
// _silentUpdatesFlag disables this, set when setCursorPos called from cursor link to prevent infinite loops
|
||||
// WARNING: the flag approach will only work if ko observables work synchronously, which they appear to do.
|
||||
|
||||
constructor(baseView: BaseView, optCursorPos?: CursorPos) {
|
||||
super();
|
||||
@ -79,10 +100,13 @@ export class Cursor extends Disposable {
|
||||
write: (index) => {
|
||||
const rowIndex = index === null ? null : this.viewData.clampIndex(index);
|
||||
this._rowId(rowIndex == null ? null : this.viewData.getRowId(rowIndex));
|
||||
this.cursorEdited();
|
||||
},
|
||||
}));
|
||||
|
||||
this.fieldIndex = baseView.viewSection.viewFields().makeLiveIndex(optCursorPos.fieldIndex || 0);
|
||||
this.fieldIndex.subscribe(() => { this.cursorEdited(); });
|
||||
|
||||
this.autoDispose(commands.createGroup(Cursor.editorCommands, this, baseView.viewSection.hasFocus));
|
||||
|
||||
// RowId might diverge from the one stored in _rowId when the data changes (it is filtered out). So here
|
||||
@ -93,8 +117,11 @@ export class Cursor extends Disposable {
|
||||
return rowId;
|
||||
}));
|
||||
|
||||
// Update the section's activeRowId when the cursor's rowIndex is changed.
|
||||
this._lastEditedAt = ko.observable(SequenceNEVER);
|
||||
|
||||
// update the section's activeRowId and lastCursorEdit when needed
|
||||
this.autoDispose(this._properRowId.subscribe((rowId) => baseView.viewSection.activeRowId(rowId)));
|
||||
this.autoDispose(this._lastEditedAt.subscribe((seqNum) => baseView.viewSection.lastCursorEdit(seqNum)));
|
||||
|
||||
// On dispose, save the current cursor position to the section model.
|
||||
this.onDispose(() => { baseView.viewSection.lastCursorPos = this.getCursorPos(); });
|
||||
@ -116,9 +143,16 @@ export class Cursor extends Disposable {
|
||||
/**
|
||||
* Moves the cursor to the given position. Only moves the row if rowId or rowIndex is valid,
|
||||
* preferring rowId.
|
||||
*
|
||||
* silentUpdate prevents lastEditedAt from being updated, so linking doesn't cause an infinite loop of updates
|
||||
* @param cursorPos: Position as { rowId?, rowIndex?, fieldIndex? }, as from getCursorPos().
|
||||
* @param silentUpdate: should only be set if this is a cascading update from cursor-linking
|
||||
*/
|
||||
public setCursorPos(cursorPos: CursorPos): void {
|
||||
public setCursorPos(cursorPos: CursorPos, silentUpdate: boolean = false): void {
|
||||
//If updating as a result of links, we want to NOT update lastEditedAt
|
||||
if(silentUpdate) { this._silentUpdatesFlag = true; }
|
||||
//console.log(`CURSOR: ${silentUpdate}, silentUpdate=${this._silentUpdatesFlag}, lastUpdated = ${this.lastUpdated.peek()}`) //TODO JV DEBUG TEMP
|
||||
|
||||
if (cursorPos.rowId !== undefined && this.viewData.getRowIndex(cursorPos.rowId) >= 0) {
|
||||
this.rowIndex(this.viewData.getRowIndex(cursorPos.rowId) );
|
||||
} else if (cursorPos.rowIndex !== undefined && cursorPos.rowIndex >= 0) {
|
||||
@ -130,9 +164,25 @@ export class Cursor extends Disposable {
|
||||
if (cursorPos.fieldIndex !== undefined) {
|
||||
this.fieldIndex(cursorPos.fieldIndex);
|
||||
}
|
||||
|
||||
//console.log(`CURSOR-END: silentUpdate=${this._silentUpdatesFlag}, lastEditedAt = ${this._lastEditedAt.peek()} `); //TODO JV DEBUG TEMP
|
||||
this._silentUpdatesFlag = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public setLive(isLive: boolean): void {
|
||||
this._isLive(isLive);
|
||||
}
|
||||
|
||||
//Should be called whenever the cursor is updated
|
||||
//EXCEPT FOR: when cursor is set by linking
|
||||
//this is used to determine which widget/cursor has most recently been touched,
|
||||
//and therefore which one should be used to drive linking if there's a conflict
|
||||
public cursorEdited(): void {
|
||||
//If updating as a result of links, we want to NOT update lastEdited
|
||||
if(!this._silentUpdatesFlag)
|
||||
{ this._lastEditedAt(nextSequenceNum()); }
|
||||
}
|
||||
}
|
||||
|
@ -1120,7 +1120,8 @@ export class GristDoc extends DisposableWithEvents {
|
||||
public async recursiveMoveToCursorPos(
|
||||
cursorPos: CursorPos,
|
||||
setAsActiveSection: boolean,
|
||||
silent: boolean = false): Promise<boolean> {
|
||||
silent: boolean = false,
|
||||
visitedSections: number[] = []): Promise<boolean> {
|
||||
try {
|
||||
if (!cursorPos.sectionId) {
|
||||
throw new Error('sectionId required');
|
||||
@ -1132,6 +1133,12 @@ export class GristDoc extends DisposableWithEvents {
|
||||
if (!section.id.peek()) {
|
||||
throw new Error(`Section ${cursorPos.sectionId} does not exist`);
|
||||
}
|
||||
|
||||
if (visitedSections.includes(section.id.peek())) {
|
||||
//We've already been here (we hit a cycle), just return immediately
|
||||
return true;
|
||||
}
|
||||
|
||||
const srcSection = section.linkSrcSection.peek();
|
||||
if (srcSection.id.peek()) {
|
||||
// We're in a linked section, so we need to recurse to make sure the row we want
|
||||
@ -1178,10 +1185,12 @@ export class GristDoc extends DisposableWithEvents {
|
||||
if (!srcRowId || typeof srcRowId !== 'number') {
|
||||
throw new Error('cannot trace rowId');
|
||||
}
|
||||
|
||||
await this.recursiveMoveToCursorPos({
|
||||
rowId: srcRowId,
|
||||
sectionId: srcSection.id.peek(),
|
||||
}, false, silent);
|
||||
}, false, silent, visitedSections.concat([section.id.peek()]));
|
||||
|
||||
}
|
||||
const view: ViewRec = section.view.peek();
|
||||
const docPage: ViewDocPage = section.isRaw.peek() ? "data" : view.getRowId();
|
||||
|
@ -17,6 +17,7 @@ import merge = require('lodash/merge');
|
||||
import mapValues = require('lodash/mapValues');
|
||||
import pick = require('lodash/pick');
|
||||
import pickBy = require('lodash/pickBy');
|
||||
import {SequenceNEVER, SequenceNum} from "./Cursor";
|
||||
|
||||
|
||||
// Descriptive string enum for each case of linking
|
||||
@ -51,7 +52,13 @@ export const EmptyFilterColValues: FilterColValues = FilterStateToColValues(Empt
|
||||
export class LinkingState extends Disposable {
|
||||
// If linking affects target section's cursor, this will be a computed for the cursor rowId.
|
||||
// Is undefined if not cursor-linked
|
||||
public readonly cursorPos?: ko.Computed<UIRowId>;
|
||||
public readonly cursorPos?: ko.Computed<UIRowId|null>;
|
||||
|
||||
//TODO: JV bidirectional linking stuff
|
||||
//It's a pair of [position, version]
|
||||
//NOTE: observables don't do deep-equality check, so MUST NEVER change the value of the components individually,
|
||||
//have to update the whole array so that `==` can catch the change
|
||||
public readonly incomingCursorPos: ko.Computed<[UIRowId|null, SequenceNum]>;
|
||||
|
||||
// If linking affects filtering, this is a computed for the current filtering state, including user-facing
|
||||
// labels for filter values and types of the filtered columns
|
||||
@ -71,6 +78,7 @@ export class LinkingState extends Disposable {
|
||||
|
||||
private _docModel: DocModel;
|
||||
private _srcSection: ViewSectionRec;
|
||||
private _tgtSection: ViewSectionRec;
|
||||
private _srcTableModel: DataTableModel;
|
||||
private _srcColId: string | undefined;
|
||||
|
||||
@ -79,10 +87,14 @@ export class LinkingState extends Disposable {
|
||||
const {srcSection, srcCol, srcColId, tgtSection, tgtCol, tgtColId} = linkConfig;
|
||||
this._docModel = docModel;
|
||||
this._srcSection = srcSection;
|
||||
this._tgtSection = tgtSection;
|
||||
this._srcColId = srcColId;
|
||||
this._srcTableModel = docModel.dataTables[srcSection.table().tableId()];
|
||||
const srcTableData = this._srcTableModel.tableData;
|
||||
|
||||
console.log(`============ LinkingState: Re-running constructor; tgtSec:${tgtSection.id()}: ${tgtSection.titleDef()}`); //TODO JV TEMP;
|
||||
|
||||
|
||||
// === IMPORTANT NOTE! (this applies throughout this file)
|
||||
// srcCol and tgtCol can be the "empty column"
|
||||
// - emptyCol.getRowId() === 0
|
||||
@ -194,13 +206,70 @@ export class LinkingState extends Disposable {
|
||||
// either same-table cursor-link (!srcCol && !tgtCol, so do activeRowId -> cursorPos)
|
||||
// or cursor-link by reference ( srcCol && !tgtCol, so do srcCol -> cursorPos)
|
||||
|
||||
//colVal, or rowId if no srcCol
|
||||
// gets the relevant col value for the passed-in rowId, or return rowId unchanged if same-table link
|
||||
const srcValueFunc = this._makeValGetter(this._srcSection.table(), this._srcColId);
|
||||
|
||||
if (srcValueFunc) { // if makeValGetter succeeded, set up cursorPos
|
||||
this.cursorPos = this.autoDispose(ko.computed(() =>
|
||||
srcValueFunc(srcSection.activeRowId()) as UIRowId
|
||||
));
|
||||
if (srcValueFunc) {
|
||||
//Incoming-cursor-pos determines what the linked cursor position should be only considering the previous
|
||||
//linked section (srcSection) and all upstream sections (through srcSection.linkingState)
|
||||
//does NOT take into account tgtSection, so will be out of date if tgtSection has been updated more recently
|
||||
this.incomingCursorPos = this.autoDispose((ko.computed(() => {
|
||||
// Note: prevLink is the link info for 2 hops behind the current (tgt) section. 1 hop back is this.srcSec;
|
||||
// this.srcSec.linkingState is 2 hops back, (i.e. it reads from from this.srcSec.linkSrcSec)
|
||||
const prevLink = this._srcSection.linkingState?.();
|
||||
const prevLinkHasCursor = prevLink &&
|
||||
(prevLink.linkTypeDescription() == "Cursor:Same-Table" ||
|
||||
prevLink.linkTypeDescription() == "Cursor:Reference");
|
||||
|
||||
const [prevLinkedPos, prevLinkedVersion] = prevLinkHasCursor ? prevLink.incomingCursorPos(): [null, SequenceNEVER];
|
||||
|
||||
const srcSecPos = this._srcSection.activeRowId.peek(); //we don't depend on this, only on its cursor version
|
||||
const srcSecVersion = this._srcSection.lastCursorEdit();
|
||||
|
||||
// is NEVER if viewSection's cursor hasn't yet initialized (shouldn't happen?)?
|
||||
//TODO JV when will this happen? do some checks. Should get set on page load / setCursorPos to saved pos
|
||||
// maybe more correct to just interpret NEVER as "never updated", which is already handled correctly
|
||||
if(srcSecVersion == SequenceNEVER) {
|
||||
console.log("=== linkingState: cursor-linking, srcSecVersion = NEVER");
|
||||
return [null, SequenceNEVER] as [UIRowId|null, SequenceNum];
|
||||
}
|
||||
|
||||
// ==== Determine whose info to use:
|
||||
// If prevLinkedVersion < srcSecVersion, then the prev linked data is stale, don't use it
|
||||
// If prevLinkedVersion == srcSecVersion, then srcSec is the driver for this link cycle (i.e. we're its first
|
||||
// outgoing link), AND the link cycle has come all the way around
|
||||
const useLinked = prevLinkHasCursor && prevLinkedVersion > srcSecVersion;
|
||||
|
||||
// srcSec/prevLinkedPos is rowId from srcSec. However if "Cursor:Reference", need to follow the ref in srcCol
|
||||
// srcValueFunc will get the appropriate value based on this._srcColId
|
||||
const tgtCursorPos = (srcValueFunc(useLinked ? prevLinkedPos : srcSecPos) || "new") as UIRowId;
|
||||
// NOTE: srcValueFunc returns 'null' if rowId is the add-row, so we coerce that back into "new"
|
||||
// NOTE: cursor linking is only ever done by the id column (for same-table) or by single Ref col (cursor:ref),
|
||||
// so we'll never have to worry about `null` showing up as an actual cell-value, since null refs are `0`
|
||||
|
||||
return [
|
||||
tgtCursorPos,
|
||||
useLinked ? prevLinkedVersion : srcSecVersion, //propagate which version our cursorPos is from
|
||||
] as [UIRowId|null, SequenceNum];
|
||||
})));
|
||||
//public readonly incomingCursorPos: ko.Computed<UIRowId>;
|
||||
|
||||
|
||||
//This is the cursorPos that's directly applied to tgtSection, should be null if incoming link is outdated
|
||||
//where null means "cursorPos does not apply to tgtSection and should be ignored"
|
||||
this.cursorPos = this.autoDispose(ko.computed(() => {
|
||||
const [incomingPos, incomingVersion]: [UIRowId|null, SequenceNum] = this.incomingCursorPos();
|
||||
const tgtSecVersion = this._tgtSection.lastCursorEdit();
|
||||
|
||||
//if(!tgtSecVersion) { return null; }
|
||||
|
||||
if(incomingVersion > tgtSecVersion) { // if linked cursor newer that current sec, use it
|
||||
return incomingPos;
|
||||
} else { // else, there's no linked cursor, since current section is driving the linking
|
||||
return null;
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
if (!srcColId) { // If same-table cursor-link, copy getDefaultColValues from the source if possible
|
||||
|
@ -1,4 +1,5 @@
|
||||
import BaseView from 'app/client/components/BaseView';
|
||||
import {SequenceNEVER, SequenceNum} from 'app/client/components/Cursor';
|
||||
import {EmptyFilterColValues, LinkingState} from 'app/client/components/LinkingState';
|
||||
import {KoArray} from 'app/client/lib/koArray';
|
||||
import {ColumnToMapImpl} from 'app/client/models/ColumnToMap';
|
||||
@ -155,6 +156,8 @@ export interface ViewSectionRec extends IRowModel<"_grist_Views_section">, RuleO
|
||||
|
||||
activeRowId: ko.Observable<UIRowId | null>; // May be null when there are no rows.
|
||||
|
||||
lastCursorEdit: ko.Observable<SequenceNum>;
|
||||
|
||||
// If the view instance for section is instantiated, it will be accessible here.
|
||||
viewInstance: ko.Observable<BaseView | null>;
|
||||
|
||||
@ -618,6 +621,7 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
|
||||
this.linkTargetCol = refRecord(docModel.columns, this.linkTargetColRef);
|
||||
|
||||
this.activeRowId = ko.observable<UIRowId|null>(null);
|
||||
this.lastCursorEdit = ko.observable<SequenceNum>(SequenceNEVER);
|
||||
|
||||
this._linkingState = Holder.create(this);
|
||||
this.linkingState = this.autoDispose(ko.pureComputed(() => {
|
||||
|
@ -644,7 +644,14 @@ export class RightPanel extends Disposable {
|
||||
const lstate = use(tgtSec.linkingState);
|
||||
const lfilter = lstate?.filterState ? use(lstate.filterState) : undefined;
|
||||
|
||||
const cursorPosStr = lstate?.cursorPos ? `${tgtSec.tableId()}[${use(lstate.cursorPos)}]` : "N/A";
|
||||
//TODO JV TEMP DEBUG: srcLastUpdated is temporary
|
||||
const cursorPosStr = (lstate?.cursorPos ? `${tgtSec.tableId()}[${use(lstate.cursorPos)}]` : "N/A") +
|
||||
`\n srclastEdited: ${use(srcSec.lastCursorEdit)} \n tgtLastEdited: ${use(tgtSec.lastCursorEdit)}` +
|
||||
`\n incomingCursorPos: ${lstate?.incomingCursorPos ? `${lstate.incomingCursorPos()}` : "N/A"}` +
|
||||
(() => {
|
||||
//const;
|
||||
return '';
|
||||
})();
|
||||
|
||||
//Main link info as a big string, will be in a <pre></pre> block
|
||||
let preString = "No Incoming Link";
|
||||
|
@ -49,8 +49,12 @@ interface LinkNode {
|
||||
groupbyColumns?: Set<number>;
|
||||
|
||||
// list of ids of the sections that are ancestors to this section according to the linked section
|
||||
// relationship
|
||||
ancestors: Set<number>;
|
||||
// relationship. ancestors[0] is this.section, ancestors[last] is oldest ancestor
|
||||
ancestors: number[];
|
||||
|
||||
//corresponds to ancestors array, but is 1 shorter.
|
||||
// if isAncCursLink[0] == true, that means the link from ancestors[0] to ancestors[1] is a same-table cursor-link
|
||||
isAncestorCursorLink: boolean[];
|
||||
|
||||
// the section record. Must be the empty record sections that are to be created.
|
||||
section: ViewSectionRec;
|
||||
@ -141,9 +145,39 @@ function isValidLink(source: LinkNode, target: LinkNode) {
|
||||
}
|
||||
}
|
||||
|
||||
// The link must not create a cycle
|
||||
if (source.ancestors.has(target.section.getRowId())) {
|
||||
return false;
|
||||
//The link must not create a cycle, unless it's only same-table cursor-links all the way to target
|
||||
if (source.ancestors.includes(target.section.getRowId())) {
|
||||
|
||||
//cycles only allowed for cursor links
|
||||
if(source.column || target.column || source.isSummary) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Walk backwards along the chain of ancestors
|
||||
// - if we hit a non-cursor link before reaching target, then that would be an illegal cycle
|
||||
// - when we hit target, we've verified that this is a legal cycle, so break
|
||||
// (ancestors further up the hierarchy past target don't matter, since once we set target.linkSrcSec = src.sec,
|
||||
// they would stop being ancestors of src)
|
||||
// NOTE: we're guaranteed to hit target before the end of the array (because of the `if` above)
|
||||
// but I'm paranoid so let's check and throw if it happens
|
||||
// ALSO NOTE: isAncestorCursorLink may be 1 shorter than ancestors, but it's accounted for by the above
|
||||
|
||||
for(let i = 0; ; i++) {
|
||||
//We made it! All is well
|
||||
if(source.ancestors[i] == target.section.getRowId()) {
|
||||
break;
|
||||
}
|
||||
|
||||
//If we've hit the last ancestor and haven't found target, error out (shouldn't happen)
|
||||
if(i == source.ancestors.length-1) { throw Error("Error: Array doesn't include targetSection"); }
|
||||
|
||||
//Need to keep following links back, make sure this one is cursorLink
|
||||
if(!source.isAncestorCursorLink[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
console.log("===== selectBy found valid cycle", JSON.stringify(source)); //TODO JV TEMP DEBUG
|
||||
//Yay, this is a valid cycle of same-table cursor-links
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -224,15 +258,29 @@ function fromViewSectionRec(section: ViewSectionRec): LinkNode[] {
|
||||
return [];
|
||||
}
|
||||
const table = section.table.peek();
|
||||
const ancestors = new Set<number>();
|
||||
const ancestors: number[] = [];
|
||||
|
||||
const isAncestorCursorLink: boolean[] = [];
|
||||
|
||||
for (let sec = section; sec.getRowId(); sec = sec.linkSrcSection.peek()) {
|
||||
if (ancestors.has(sec.getRowId())) {
|
||||
if (ancestors.includes(sec.getRowId())) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.warn(`Links should not create a cycle - section ids: ${Array.from(ancestors)}`);
|
||||
console.warn(`Links should not create a cycle - section ids: ${ancestors}`);
|
||||
//TODO JV: change this to only warn if cycles aren't all Cursor:Same-Table
|
||||
break;
|
||||
}
|
||||
ancestors.add(sec.getRowId());
|
||||
ancestors.push(sec.getRowId());
|
||||
|
||||
//isAncestorCursorLink may be 1 shorter than ancestors, since last ancestor has no incoming link
|
||||
// however if we have a cycle (of cursor-links), then they'll be the same length
|
||||
if(sec.linkSrcSection.peek().getRowId()) {
|
||||
//TODO JV TEMP: Dear god determining if something is a cursor link or not is a nightmare
|
||||
const srcCol = sec.linkSrcCol.peek().getRowId();
|
||||
const tgtCol = sec.linkTargetCol.peek().getRowId();
|
||||
const srcTable = sec.linkSrcSection.peek().table.peek();
|
||||
const srcIsSummary = srcTable.primaryTableId.peek() !== srcTable.tableId.peek();
|
||||
isAncestorCursorLink.push(srcCol == 0 && tgtCol == 0 && !srcIsSummary);
|
||||
}
|
||||
}
|
||||
|
||||
const isSummary = table.primaryTableId.peek() !== table.tableId.peek();
|
||||
@ -243,6 +291,7 @@ function fromViewSectionRec(section: ViewSectionRec): LinkNode[] {
|
||||
groupbyColumns: isSummary ? table.summarySourceColRefs.peek() : undefined,
|
||||
widgetType: section.parentKey.peek(),
|
||||
ancestors,
|
||||
isAncestorCursorLink,
|
||||
section,
|
||||
};
|
||||
|
||||
@ -280,7 +329,8 @@ function fromPageWidget(docModel: DocModel, pageWidget: IPageWidget): LinkNode[]
|
||||
// (e.g.: link from summary table with Attachments in group-by) but it seems to work fine as is
|
||||
groupbyColumns,
|
||||
widgetType: pageWidget.type,
|
||||
ancestors: new Set(),
|
||||
ancestors: [],
|
||||
isAncestorCursorLink: [],
|
||||
section: docModel.viewSections.getRowModel(pageWidget.section),
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user