diff --git a/app/common/TableData.ts b/app/common/TableData.ts index b3c8f76a..55b39d7f 100644 --- a/app/common/TableData.ts +++ b/app/common/TableData.ts @@ -251,21 +251,16 @@ export class TableData extends ActionDispatcher { return records; } + public filterRowIds(properties: {[key: string]: any}): number[] { + return this._filterRowIndices(properties).map(i => this._rowIdCol[i]); + } + /** * Builds and returns the list of records in this table that match the given properties object. * Properties may include 'id' and any table columns. Returned records are not sorted. */ public filterRecords(properties: {[key: string]: any}): RowRecord[] { - const rowIndices: number[] = []; - // Pairs of [valueToMatch, arrayOfColValues] - const props = Object.keys(properties).map(p => [properties[p], this._columns.get(p)]); - this._rowIdCol.forEach((id, i) => { - for (const p of props) { - if (p[1].values[i] !== p[0]) { return; } - } - // Collect the indices of the matching rows. - rowIndices.push(i); - }); + const rowIndices: number[] = this._filterRowIndices(properties); // Convert the array of indices to an array of RowRecords. const records: RowRecord[] = rowIndices.map(i => ({id: this._rowIdCol[i]})); @@ -289,6 +284,20 @@ export class TableData extends ActionDispatcher { return index < 0 ? 0 : this._rowIdCol[index]; } + /** + * Returns the first rowId matching the given filters, or 0 if no match. If there are multiple + * matches, it is unspecified which will be returned. + */ + public findMatchingRowId(properties: {[key: string]: CellValue}): number { + const props = Object.keys(properties).map(p => ({col: this._columns.get(p)!, value: properties[p]})); + if (!props.every((p) => p.col)) { + return 0; + } + return this._rowIdCol.find((id, i) => + props.every((p) => (p.col.values[i] === p.value)) + ) || 0; + } + /** * Applies a DocAction received from the server; returns true, or false if it was skipped. */ @@ -419,6 +428,19 @@ export class TableData extends ActionDispatcher { // Stop dispatching actions if we've been deleted. We might also want to clean up in the future. this._isLoaded = false; } + + private _filterRowIndices(properties: {[key: string]: any}): number[] { + const rowIndices: number[] = []; + // Array of {col: arrayOfColValues, value: valueToMatch} + const props = Object.keys(properties).map(p => ({col: this._columns.get(p)!, value: properties[p]})); + this._rowIdCol.forEach((id, i) => { + // Collect the indices of the matching rows. + if (props.every((p) => (p.col.values[i] === p.value))) { + rowIndices.push(i); + } + }); + return rowIndices; + } } function reassignArray(targetArray: T[], sourceArray: T[]): void { diff --git a/app/common/gristUrls.ts b/app/common/gristUrls.ts index eb69d441..4e84ac0f 100644 --- a/app/common/gristUrls.ts +++ b/app/common/gristUrls.ts @@ -66,6 +66,7 @@ export interface IGristUrlState { embed?: boolean; style?: InterfaceStyle; compare?: string; + aclUI?: boolean; }; hash?: HashLink; // if present, this specifies an individual row within a section of a page. } @@ -260,6 +261,9 @@ export function decodeUrl(gristConfig: Partial, location: Locat if (sp.has('compare')) { state.params!.compare = sp.get('compare')!; } + if (sp.has('aclUI')) { + state.params!.aclUI = isAffirmative(sp.get('aclUI')); + } if (location.hash) { const hash = location.hash; const hashParts = hash.split('.'); diff --git a/app/server/lib/GranularAccess.ts b/app/server/lib/GranularAccess.ts index 80319599..fa7b85fa 100644 --- a/app/server/lib/GranularAccess.ts +++ b/app/server/lib/GranularAccess.ts @@ -75,6 +75,7 @@ export class GranularAccess { public update() { this._resources = this._docData.getTable('_grist_ACLResources')!; this._ownerOnlyTableIds.clear(); + this._onlyOwnersCanModifyStructure = false; for (const res of this._resources.getRecords()) { const code = String(res.colIds); if (res.tableId && code === '~o') {