mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
Merge 9b27e3fd2b
into c27f832851
This commit is contained in:
commit
1f30d87b7b
@ -701,7 +701,7 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
|
|
||||||
const {recordCard, rowId} = popupOptions.hash;
|
const {recordCard, rowId} = popupOptions.hash;
|
||||||
if (recordCard) {
|
if (recordCard) {
|
||||||
if (!rowId || rowId === 'new') {
|
if (!rowId) {
|
||||||
// Should be unreachable, but just to be sure (and to satisfy type checking)...
|
// Should be unreachable, but just to be sure (and to satisfy type checking)...
|
||||||
throw new Error('Unable to open Record Card: undefined row id');
|
throw new Error('Unable to open Record Card: undefined row id');
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,14 @@ import {ViewSectionRec} from 'app/client/models/DocModel';
|
|||||||
import {ChangeType, RowList} from 'app/client/models/rowset';
|
import {ChangeType, RowList} from 'app/client/models/rowset';
|
||||||
import {theme} from 'app/client/ui2018/cssVars';
|
import {theme} from 'app/client/ui2018/cssVars';
|
||||||
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
|
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
|
||||||
|
import {UIRowId} from 'app/plugin/GristAPI';
|
||||||
import {dom, makeTestId, styled} from 'grainjs';
|
import {dom, makeTestId, styled} from 'grainjs';
|
||||||
|
|
||||||
const testId = makeTestId('test-record-card-popup-');
|
const testId = makeTestId('test-record-card-popup-');
|
||||||
|
|
||||||
interface RecordCardPopupOptions {
|
interface RecordCardPopupOptions {
|
||||||
gristDoc: GristDoc;
|
gristDoc: GristDoc;
|
||||||
rowId: number;
|
rowId: UIRowId;
|
||||||
viewSection: ViewSectionRec;
|
viewSection: ViewSectionRec;
|
||||||
onClose(): void;
|
onClose(): void;
|
||||||
}
|
}
|
||||||
|
@ -230,6 +230,24 @@ export function formatterForRec(
|
|||||||
return func(args);
|
return func(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ColumnInfo = {label: string}|{label: ko.Observable<string>};
|
||||||
|
function peekLabel(info: ColumnInfo): string {
|
||||||
|
return typeof info.label === 'string' ? info.label : info.label.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to sort columns based on the label. Puts # columns at the end as this is
|
||||||
|
* treated as private columns.
|
||||||
|
*/
|
||||||
|
export function columnsOrder(a: ColumnInfo, b: ColumnInfo): number {
|
||||||
|
const left = peekLabel(a)?.toLowerCase() || '';
|
||||||
|
const right = peekLabel(b)?.toLowerCase() || '';
|
||||||
|
if (left[0] === '#' && right[0] !== '#') { return 1; }
|
||||||
|
if (left[0] !== '#' && right[0] === '#') { return -1; }
|
||||||
|
return left.localeCompare(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A chat message. Either send by the user or by the AI.
|
* A chat message. Either send by the user or by the AI.
|
||||||
*/
|
*/
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
ViewFieldRec,
|
ViewFieldRec,
|
||||||
ViewRec
|
ViewRec
|
||||||
} from 'app/client/models/DocModel';
|
} from 'app/client/models/DocModel';
|
||||||
import {BEHAVIOR} from 'app/client/models/entities/ColumnRec';
|
import {BEHAVIOR, columnsOrder} from 'app/client/models/entities/ColumnRec';
|
||||||
import * as modelUtil from 'app/client/models/modelUtil';
|
import * as modelUtil from 'app/client/models/modelUtil';
|
||||||
import {removeRule, RuleOwner} from 'app/client/models/RuleOwner';
|
import {removeRule, RuleOwner} from 'app/client/models/RuleOwner';
|
||||||
import {LinkConfig} from 'app/client/ui/selectBy';
|
import {LinkConfig} from 'app/client/ui/selectBy';
|
||||||
@ -521,7 +521,7 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
|
|||||||
const savedFiltersByColRef = new Map(this._savedFilters().all().map(f => [f.colRef(), f]));
|
const savedFiltersByColRef = new Map(this._savedFilters().all().map(f => [f.colRef(), f]));
|
||||||
const viewFieldsByColRef = new Map(this.viewFields().all().map(f => [f.origCol().getRowId(), f]));
|
const viewFieldsByColRef = new Map(this.viewFields().all().map(f => [f.origCol().getRowId(), f]));
|
||||||
|
|
||||||
return this.columns().map(column => {
|
return [...this.columns()].sort(columnsOrder).map(column => {
|
||||||
const savedFilter = savedFiltersByColRef.get(column.origColRef());
|
const savedFilter = savedFiltersByColRef.get(column.origColRef());
|
||||||
// Initialize with a saved filter, if one exists. Otherwise, use a blank filter.
|
// Initialize with a saved filter, if one exists. Otherwise, use a blank filter.
|
||||||
const filter = modelUtil.customComputed({
|
const filter = modelUtil.customComputed({
|
||||||
@ -698,7 +698,9 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
|
|||||||
// Evaluates to an array of column models, which are not referenced by anything in viewFields.
|
// Evaluates to an array of column models, which are not referenced by anything in viewFields.
|
||||||
this.hiddenColumns = this.autoDispose(ko.pureComputed(() => {
|
this.hiddenColumns = this.autoDispose(ko.pureComputed(() => {
|
||||||
const included = new Set(this.viewFields().all().map((f) => f.column().origColRef()));
|
const included = new Set(this.viewFields().all().map((f) => f.column().origColRef()));
|
||||||
return this.columns().filter(c => !included.has(c.getRowId()));
|
return this.columns()
|
||||||
|
.filter(c => !included.has(c.getRowId()))
|
||||||
|
.sort(columnsOrder);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.hasFocus = ko.pureComputed({
|
this.hasFocus = ko.pureComputed({
|
||||||
|
@ -7,6 +7,7 @@ import {makeT} from 'app/client/lib/localization';
|
|||||||
import {localStorageBoolObs} from 'app/client/lib/localStorageObs';
|
import {localStorageBoolObs} from 'app/client/lib/localStorageObs';
|
||||||
import {ColumnToMapImpl} from 'app/client/models/ColumnToMap';
|
import {ColumnToMapImpl} from 'app/client/models/ColumnToMap';
|
||||||
import {ColumnRec, ViewSectionRec} from 'app/client/models/DocModel';
|
import {ColumnRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
|
import {columnsOrder} from 'app/client/models/entities/ColumnRec';
|
||||||
import {
|
import {
|
||||||
cssDeveloperLink,
|
cssDeveloperLink,
|
||||||
cssWidgetMetadata,
|
cssWidgetMetadata,
|
||||||
@ -75,11 +76,13 @@ class ColumnPicker extends Disposable {
|
|||||||
void use(refreshTrigger);
|
void use(refreshTrigger);
|
||||||
|
|
||||||
const columnsAsOptions: IOption<number|null>[] = use(canBeMapped)
|
const columnsAsOptions: IOption<number|null>[] = use(canBeMapped)
|
||||||
.map((col) => ({
|
.map((col) => ({
|
||||||
value: col.getRowId(),
|
value: col.getRowId(),
|
||||||
label: col.label.peek(),
|
label: col.label.peek() || '',
|
||||||
icon: 'FieldColumn',
|
icon: 'FieldColumn' as const,
|
||||||
}));
|
}))
|
||||||
|
.sort(columnsOrder);
|
||||||
|
|
||||||
|
|
||||||
// For optional mappings, add 'Blank' option but only if the value is set.
|
// For optional mappings, add 'Blank' option but only if the value is set.
|
||||||
// This option will allow to clear the selection.
|
// This option will allow to clear the selection.
|
||||||
@ -202,7 +205,8 @@ class ColumnListPicker extends Disposable {
|
|||||||
menu(() => {
|
menu(() => {
|
||||||
const wrongTypeCount = notMapped.get().length - typedColumns.get().length;
|
const wrongTypeCount = notMapped.get().length - typedColumns.get().length;
|
||||||
return [
|
return [
|
||||||
...typedColumns.get()
|
...typedColumns.get() // returns a temp table.
|
||||||
|
.sort(columnsOrder)
|
||||||
.map((col) => menuItem(
|
.map((col) => menuItem(
|
||||||
() => this._addColumn(col),
|
() => this._addColumn(col),
|
||||||
col.label.peek(),
|
col.label.peek(),
|
||||||
|
@ -2,7 +2,7 @@ import {allCommands} from 'app/client/components/commands';
|
|||||||
import {GristDoc} from 'app/client/components/GristDoc';
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
import GridView from 'app/client/components/GridView';
|
import GridView from 'app/client/components/GridView';
|
||||||
import {makeT} from 'app/client/lib/localization';
|
import {makeT} from 'app/client/lib/localization';
|
||||||
import {ColumnRec} from "app/client/models/entities/ColumnRec";
|
import {ColumnRec, columnsOrder} from "app/client/models/entities/ColumnRec";
|
||||||
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
|
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
|
||||||
import {withInfoTooltip} from 'app/client/ui/tooltips';
|
import {withInfoTooltip} from 'app/client/ui/tooltips';
|
||||||
import {isNarrowScreen, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
import {isNarrowScreen, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||||
@ -293,7 +293,7 @@ function buildDetectDuplicatesMenuItems(gridView: GridView, index?: number) {
|
|||||||
const {viewSection} = gridView;
|
const {viewSection} = gridView;
|
||||||
return menuItemSubmenu(
|
return menuItemSubmenu(
|
||||||
() => searchableMenu(
|
() => searchableMenu(
|
||||||
viewSection.columns().map((col) => {
|
[...viewSection.columns()].sort(columnsOrder).map((col) => {
|
||||||
function buildFormula() {
|
function buildFormula() {
|
||||||
if (isListType(col.type())) {
|
if (isListType(col.type())) {
|
||||||
return `any([len(${col.table().tableId()}.lookupRecords(${col.colId()}` +
|
return `any([len(${col.table().tableId()}.lookupRecords(${col.colId()}` +
|
||||||
@ -568,7 +568,9 @@ function buildLookupSection(gridView: GridView, index?: number){
|
|||||||
|
|
||||||
return references.map((ref) => menuItemSubmenu(
|
return references.map((ref) => menuItemSubmenu(
|
||||||
() => searchableMenu(
|
() => searchableMenu(
|
||||||
ref.refTable()?.visibleColumns().map(buildRefColMenu.bind(null, ref)) ?? [],
|
(ref.refTable()?.visibleColumns() ?? [])
|
||||||
|
.sort(columnsOrder)
|
||||||
|
.map(buildRefColMenu.bind(null, ref)),
|
||||||
{
|
{
|
||||||
searchInputPlaceholder: t('Search columns')
|
searchInputPlaceholder: t('Search columns')
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import {FocusLayer} from 'app/client/lib/FocusLayer';
|
|||||||
import {makeT} from 'app/client/lib/localization';
|
import {makeT} from 'app/client/lib/localization';
|
||||||
import {reportError} from 'app/client/models/AppModel';
|
import {reportError} from 'app/client/models/AppModel';
|
||||||
import {ColumnRec, TableRec, ViewSectionRec} from 'app/client/models/DocModel';
|
import {ColumnRec, TableRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
|
import {columnsOrder} from 'app/client/models/entities/ColumnRec';
|
||||||
import {PERMITTED_CUSTOM_WIDGETS} from "app/client/models/features";
|
import {PERMITTED_CUSTOM_WIDGETS} from "app/client/models/features";
|
||||||
import {linkId, NoLink} from 'app/client/ui/selectBy';
|
import {linkId, NoLink} from 'app/client/ui/selectBy';
|
||||||
import {overflowTooltip, withInfoTooltip} from 'app/client/ui/tooltips';
|
import {overflowTooltip, withInfoTooltip} from 'app/client/ui/tooltips';
|
||||||
@ -407,7 +408,7 @@ export class PageWidgetSelect extends Disposable {
|
|||||||
(use) => use(this._columns)
|
(use) => use(this._columns)
|
||||||
.filter((col) => !col.isHiddenCol() && col.parentId() === use(this._value.table)),
|
.filter((col) => !col.isHiddenCol() && col.parentId() === use(this._value.table)),
|
||||||
(cols) => cols ?
|
(cols) => cols ?
|
||||||
dom.forEach(cols, (col) =>
|
dom.forEach([...cols].sort(columnsOrder), (col) =>
|
||||||
cssEntry(cssIcon('FieldColumn'), cssFieldLabel(dom.text(col.label)),
|
cssEntry(cssIcon('FieldColumn'), cssFieldLabel(dom.text(col.label)),
|
||||||
dom.on('click', () => this._toggleColumnId(col.id())),
|
dom.on('click', () => this._toggleColumnId(col.id())),
|
||||||
cssEntry.cls('-selected', (use) => use(this._value.columns).includes(col.id())),
|
cssEntry.cls('-selected', (use) => use(this._value.columns).includes(col.id())),
|
||||||
|
@ -4,6 +4,7 @@ import * as kf from 'app/client/lib/koForm';
|
|||||||
import {makeT} from 'app/client/lib/localization';
|
import {makeT} from 'app/client/lib/localization';
|
||||||
import {addToSort, updatePositions} from 'app/client/lib/sortUtil';
|
import {addToSort, updatePositions} from 'app/client/lib/sortUtil';
|
||||||
import {ViewSectionRec} from 'app/client/models/DocModel';
|
import {ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
|
import {columnsOrder} from 'app/client/models/entities/ColumnRec';
|
||||||
import {ObjObservable} from 'app/client/models/modelUtil';
|
import {ObjObservable} from 'app/client/models/modelUtil';
|
||||||
import {dropdownWithSearch} from 'app/client/ui/searchDropdown';
|
import {dropdownWithSearch} from 'app/client/ui/searchDropdown';
|
||||||
import {cssIcon, cssRow, cssSortFilterColumn} from 'app/client/ui/RightPanelStyles';
|
import {cssIcon, cssRow, cssSortFilterColumn} from 'app/client/ui/RightPanelStyles';
|
||||||
@ -215,7 +216,7 @@ export class SortConfig extends Disposable {
|
|||||||
const currentSection = this._section;
|
const currentSection = this._section;
|
||||||
const currentSortSpec = use(currentSection.activeSortSpec);
|
const currentSortSpec = use(currentSection.activeSortSpec);
|
||||||
const specRowIds = new Set(currentSortSpec.map(_sortRef => Sort.getColRef(_sortRef)));
|
const specRowIds = new Set(currentSortSpec.map(_sortRef => Sort.getColRef(_sortRef)));
|
||||||
return use(columns).filter(_col => !specRowIds.has(_col.value));
|
return use(columns).filter(_col => !specRowIds.has(_col.value)).sort(columnsOrder);
|
||||||
});
|
});
|
||||||
const {menuOptions} = this._options;
|
const {menuOptions} = this._options;
|
||||||
return cssButtonRow(
|
return cssButtonRow(
|
||||||
|
Loading…
Reference in New Issue
Block a user