1
0
Fork 0
mirror of https://github.com/anyproto/anytype-ts.git synced 2025-06-11 02:13:48 +09:00

Merge pull request #850 from anyproto/feature/JS-4800-sorts-and-filters-logic-update

Feature/JS-4800: Sorts and filters empty logic
This commit is contained in:
Razor 2024-07-25 11:58:44 +02:00 committed by GitHub
commit 6476427f02
Signed by: github
GPG key ID: B5690EEEBB952194
13 changed files with 326 additions and 62 deletions

View file

@ -31,12 +31,18 @@
.iconObject { margin-right: 2px; }
}
.txt { width: calc(100% - 70px); line-height: 20px; height: 40px; display: flex; flex-direction: column; }
.txt { width: calc(100% - 70px); line-height: 20px; height: 40px; display: flex; flex-direction: column; align-items: flex-start; }
.txt {
.name { @include text-overflow-nw; width: 100%; }
.name { @include text-overflow-nw; }
.flex { line-height: 20px; }
.condition { color: var(--color-text-secondary); margin-right: 4px; }
.name,
.flex { display: flex; align-items: center; }
.name::after,
.flex::after { content: ''; width: 20px; height: 20px; background-image: url('~img/arrow/select/dark.svg'); }
.flex::after { background-image: url('~img/arrow/select/light.svg'); }
.condition { color: var(--color-text-secondary); }
.value { color: var(--color-text-secondary); white-space: nowrap; overflow: hidden; }
}
}
@ -106,4 +112,4 @@
.input { border: 0px; padding: 0px; height: 20px; }
}
}
}

View file

@ -11,12 +11,11 @@
.item {
.iconObject { margin-right: 10px; }
.select { border: 0px; padding: 0px; display: block; }
.select { border: 0px; padding: 0px; display: block; padding-right: 20px; }
.select:hover, .select.isFocused { background: none; }
.select.grey { color: var(--color-control-active); }
.select {
.icon.relation { display: none; }
.icon.arrow { display: none; }
}
.buttons { line-height: 44px; opacity: 0; transition: $transitionAllCommon; }
@ -24,7 +23,11 @@
.icon { vertical-align: middle; opacity: 1; }
}
.txt { width: calc(100% - 74px); line-height: 20px; height: 40px; display: flex; flex-direction: column; }
.txt { width: calc(100% - 74px); line-height: 20px; height: 40px; display: flex; flex-direction: column; align-items: flex-start; }
.txt {
.label { @include text-common; display: flex; align-items: center; font-weight: 400; line-height: 20px; padding: 0px; margin: 0px; color: var(--color-text-primary); }
.label::after { content: ''; width: 20px; height: 20px; background-image: url('~img/arrow/select/dark.svg'); }
}
}
.item.empty { margin: 8px 0px; padding: 14px 16px; }
.item.isReadonly {
@ -43,4 +46,4 @@
.line { margin-top: 0px; }
}
}
}
}

View file

@ -20,7 +20,11 @@
.menu.menuSelect.withFilter {
.content { padding: 8px 0px 0px 0px; }
.items { height: calc(100% - 38px); }
.items { height: calc(100% - 30px); }
}
.menu.menuSelect.withAdd {
.content { max-height: 378px; }
}
.menu.menuSelect.skip { width: var(--menu-width-value); }

View file

@ -241,6 +241,26 @@
}
}
/* DataviewFilterList */
.menu.menuDataviewFilterList {
.item {
.txt {
.name::after { background-image: url('#{$themePath}/arrow/select/dark.svg'); }
}
}
}
/* DataviewSort */
.menu.menuDataviewSort {
.item {
.txt {
.label::after { background-image: url('#{$themePath}/arrow/select/dark.svg'); }
}
}
}
/* QuickCapture */
.menuQuickCapture { background: none; box-shadow: none; }

View file

@ -78,6 +78,8 @@ const BlockDataview = observer(class BlockDataview extends React.Component<Props
this.onSelectEnd = this.onSelectEnd.bind(this);
this.onSelectToggle = this.onSelectToggle.bind(this);
this.onFilterChange = this.onFilterChange.bind(this);
this.onSortAdd = this.onSortAdd.bind(this);
this.onFilterAdd = this.onFilterAdd.bind(this);
this.getSearchIds = this.getSearchIds.bind(this);
this.objectOrderUpdate = this.objectOrderUpdate.bind(this);
@ -167,6 +169,8 @@ const BlockDataview = observer(class BlockDataview extends React.Component<Props
onRecordAdd: this.onRecordAdd,
onTemplateMenu: this.onTemplateMenu,
onTemplateAdd: this.onTemplateAdd,
onSortAdd: this.onSortAdd,
onFilterAdd: this.onFilterAdd,
isAllowedObject: this.isAllowedObject,
isAllowedDefaultType: this.isAllowedDefaultType,
onSourceSelect: this.onSourceSelect,
@ -1105,6 +1109,41 @@ const BlockDataview = observer(class BlockDataview extends React.Component<Props
this.objectOrderUpdate([ { viewId: view.id, groupId: '', objectIds: records } ], records);
};
onSortAdd (item: any, callBack?: () => void) {
const { rootId, block, isInline } = this.props;
const view = this.getView();
const object = this.getTarget();
C.BlockDataviewSortAdd(rootId, block.id, view.id, item, () => {
if (callBack) {
callBack();
};
analytics.event('AddSort', {
objectType: object.type,
embedType: analytics.embedType(isInline)
});
});
};
onFilterAdd (item: any, callBack?: () => void) {
const { rootId, block, isInline } = this.props;
const view = this.getView();
const object = this.getTarget();
C.BlockDataviewFilterAdd(rootId, block.id, view.id, item, () => {
if (callBack) {
callBack();
};
analytics.event('AddFilter', {
condition: item.condition,
objectType: object.type,
embedType: analytics.embedType(isInline)
});
});
};
getIdPrefix () {
return [ 'dataviewCell', this.props.block.id ].join('-');
};

View file

@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
import { observable } from 'mobx';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
import { Icon, Button, Filter } from 'Component';
import { C, I, S, U, analytics, Relation, keyboard, translate, Dataview, sidebar } from 'Lib';
import { C, I, S, U, analytics, Relation, keyboard, translate, Dataview, sidebar, J } from 'Lib';
import Head from './head';
interface Props extends I.ViewComponent {
@ -23,6 +23,8 @@ const Controls = observer(class Controls extends React.Component<Props> {
super(props);
this.onButton = this.onButton.bind(this);
this.sortOrFilterRelationSelect = this.sortOrFilterRelationSelect.bind(this);
this.onSortOrFilterAdd = this.onSortOrFilterAdd.bind(this);
this.onSortStart = this.onSortStart.bind(this);
this.onSortEnd = this.onSortEnd.bind(this);
this.onViewAdd = this.onViewAdd.bind(this);
@ -259,11 +261,18 @@ const Controls = observer(class Controls extends React.Component<Props> {
const {
rootId, block, readonly, loadData, getView, getSources, getVisibleRelations, getTarget, isInline, isCollection,
getTypeId, getTemplateId, isAllowedDefaultType, onTemplateAdd,
getTypeId, getTemplateId, isAllowedDefaultType, onTemplateAdd, onSortAdd, onFilterAdd,
} = this.props;
const view = getView();
const obj = $(element);
if (((component == 'dataviewSort') && !view.sorts.length) || ((component == 'dataviewFilterList') && !view.filters.length)) {
this.sortOrFilterRelationSelect(component,{ element }, () => {
this.onButton(element, component);
});
return;
};
const param: any = {
element,
horizontal: I.MenuDirection.Center,
@ -296,10 +305,19 @@ const Controls = observer(class Controls extends React.Component<Props> {
isCollection,
isAllowedDefaultType,
onTemplateAdd,
onSortAdd,
onFilterAdd,
onViewSwitch: this.onViewSwitch,
onViewCopy: this.onViewCopy,
onViewRemove: this.onViewRemove,
view: observable.box(view)
view: observable.box(view),
onAdd: (menuId, menuWidth) => {
this.sortOrFilterRelationSelect(component,{
element: `#${menuId} #item-add`,
offsetX: menuWidth,
horizontal: I.MenuDirection.Right
});
},
},
};
@ -307,9 +325,58 @@ const Controls = observer(class Controls extends React.Component<Props> {
param.title = translate('menuDataviewViewSettings');
};
if (S.Menu.isOpen('select')) {
S.Menu.close('select');
};
S.Menu.open(component, param);
};
sortOrFilterRelationSelect (component: string, param: any, callBack?: () => void) {
const { rootId, block, getView } = this.props;
U.Menu.sortOrFilterRelationSelect({
menuParam: param,
rootId,
blockId: block.id,
getView,
onSelect: (item) => {
this.onSortOrFilterAdd(item, component, () => {
if (callBack) {
callBack();
};
});
}
});
};
onSortOrFilterAdd (item: any, component: string, callBack: () => void) {
const { onSortAdd, onFilterAdd } = this.props;
let newItem = {
relationKey: item.relationKey ? item.relationKey : item.id
};
if (component == 'dataviewSort') {
newItem = Object.assign(newItem, {
type: I.SortType.Asc,
});
onSortAdd(newItem, callBack);
} else
if (component == 'dataviewFilterList') {
const conditions = Relation.filterConditionsByType(item.format);
const condition = conditions.length ? conditions[0].id : I.FilterCondition.None;
newItem = Object.assign(newItem, {
operator: I.FilterOperator.And,
condition: condition as I.FilterCondition,
value: Relation.formatValue(item, null, false),
});
onFilterAdd(newItem, callBack);
};
};
onViewAdd (e: any) {
e.persist();

View file

@ -192,17 +192,20 @@ const MenuFilterList = observer(class MenuFilterList extends React.Component<I.M
};
onAdd (e: any) {
const { param, getId } = this.props;
const { param, getId, getSize } = this.props;
const { data } = param;
const { rootId, blockId, getView, isInline, getTarget } = data;
const view = getView();
const { onFilterAdd, onAdd } = data;
const relationOptions = this.getRelationOptions();
const object = getTarget();
if (!relationOptions.length) {
return;
};
if (onAdd) {
onAdd(getId(), getSize().width);
return;
};
const obj = $(`#${getId()} .content`);
const first = relationOptions[0];
const conditions = Relation.filterConditionsByType(first.format);
@ -214,14 +217,8 @@ const MenuFilterList = observer(class MenuFilterList extends React.Component<I.M
value: Relation.formatValue(first, null, false),
};
C.BlockDataviewFilterAdd(rootId, blockId, view.id, newItem);
obj.animate({ scrollTop: obj.get(0).scrollHeight }, 50);
analytics.event('AddFilter', {
condition: newItem.condition,
objectType: object.type,
embedType: analytics.embedType(isInline)
onFilterAdd(newItem, () => {
obj.animate({ scrollTop: obj.get(0).scrollHeight }, 50);
});
};
@ -348,4 +345,4 @@ const MenuFilterList = observer(class MenuFilterList extends React.Component<I.M
});
export default MenuFilterList;
export default MenuFilterList;

View file

@ -414,7 +414,7 @@ const MenuDataviewFilterValues = observer(class MenuDataviewFilterValues extends
onOver (e: any, item: any) {
const { getId, getSize, setActive, param } = this.props;
const { data } = param;
const { getView, itemId } = data;
const { rootId, blockId, getView, itemId } = data;
const view = getView();
const filter = view.getFilter(itemId);
const isReadonly = this.isReadonly();
@ -430,13 +430,29 @@ const MenuDataviewFilterValues = observer(class MenuDataviewFilterValues extends
let options = [];
let key = item.id;
switch (item.id) {
case 'relation': {
options = this.getRelationOptions();
key = 'relationKey';
break;
if (item.id == 'relation') {
const menuParam = {
element: `#${getId()} #item-${item.id}`,
offsetX: getSize().width,
horizontal: I.MenuDirection.Right,
vertical: I.MenuDirection.Center,
passThrough: true,
};
U.Menu.sortOrFilterRelationSelect({
menuParam,
rootId,
blockId,
getView,
onSelect: (item) => {
this.onChange('relationKey', item.relationKey ? item.relationKey : item.id);
}
});
return;
};
switch (item.id) {
case 'condition': {
if (Relation.isDictionary(filter.relationKey)) {
options = Relation.filterConditionsDictionary();
@ -758,4 +774,4 @@ const MenuDataviewFilterValues = observer(class MenuDataviewFilterValues extends
});
export default MenuDataviewFilterValues;
export default MenuDataviewFilterValues;

View file

@ -4,7 +4,7 @@ import $ from 'jquery';
import { observer } from 'mobx-react';
import { AutoSizer, CellMeasurer, InfiniteLoader, List as VList, CellMeasurerCache } from 'react-virtualized';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import { Icon, IconObject, Select } from 'Component';
import { Icon, IconObject, Label, Select } from 'Component';
import { I, C, S, U, J, Relation, keyboard, analytics, translate } from 'Lib';
const HEIGHT = 48;
@ -26,6 +26,7 @@ const MenuSort = observer(class MenuSort extends React.Component<I.Menu> {
this.onSortStart = this.onSortStart.bind(this);
this.onSortEnd = this.onSortEnd.bind(this);
this.onScroll = this.onScroll.bind(this);
this.onSortNameClick = this.onSortNameClick.bind(this);
};
render () {
@ -47,8 +48,6 @@ const MenuSort = observer(class MenuSort extends React.Component<I.Menu> {
{ id: String(I.SortType.Asc), name: translate('commonAscending') },
{ id: String(I.SortType.Desc), name: translate('commonDescending') },
];
const relationOptions = this.getRelationOptions();
const Handle = SortableHandle(() => (
<Icon className="dnd" />
@ -56,6 +55,7 @@ const MenuSort = observer(class MenuSort extends React.Component<I.Menu> {
const Item = SortableElement((item: any) => {
const relation: any = S.Record.getRelationByKey(item.relationKey) || {};
return (
<div
id={'item-' + item.id}
@ -66,18 +66,13 @@ const MenuSort = observer(class MenuSort extends React.Component<I.Menu> {
{!isReadonly ? <Handle /> : ''}
<IconObject size={40} object={{ relationFormat: relation.format, layout: I.ObjectLayout.Relation }} />
<div className="txt">
<Select
id={[ 'filter', 'relation', item.id ].join('-')}
options={relationOptions}
value={item.relationKey}
onChange={v => this.onChange(item.id, 'relationKey', v)}
readonly={isReadonly}
/>
<Label id={[ 'filter', 'relation', item.id ].join('-')} text={relation.name} onClick={e => this.onSortNameClick(e, item)} />
<Select
id={[ 'filter', 'type', item.id ].join('-')}
className="grey"
options={typeOptions}
options={typeOptions}
arrowClassName={'light'}
value={item.type}
onChange={v => this.onChange(item.id, 'type', v)}
readonly={isReadonly}
@ -290,19 +285,48 @@ const MenuSort = observer(class MenuSort extends React.Component<I.Menu> {
});
};
onSortNameClick (e: React.MouseEvent, item: any) {
if (this.isReadonly()) {
return;
};
const { param, getId, getSize } = this.props;
const { data } = param;
const { rootId, blockId, getView } = data;
const menuParam = {
element: `#${getId()} #item-${item.id}`,
offsetX: getSize().width,
horizontal: I.MenuDirection.Right,
vertical: I.MenuDirection.Center
};
U.Menu.sortOrFilterRelationSelect({
menuParam,
rootId,
blockId,
getView,
onSelect: (v) => {
this.onChange(item.id, 'relationKey', v.relationKey ? v.relationKey : v.id);
}
});
};
onAdd () {
const { param, getId } = this.props;
const { param, getId, getSize } = this.props;
const { data } = param;
const { rootId, getView, getTarget, blockId, isInline } = data;
const view = getView();
const object = getTarget();
const { onSortAdd, onAdd } = data;
const relationOptions = this.getRelationOptions();
if (!relationOptions.length) {
return;
};
if (onAdd) {
onAdd(getId(), getSize().width);
return;
};
const obj = $(`#${getId()}`);
const content = obj.find('.content');
const newItem = {
@ -310,13 +334,8 @@ const MenuSort = observer(class MenuSort extends React.Component<I.Menu> {
type: I.SortType.Asc,
};
C.BlockDataviewSortAdd(rootId, blockId, view.id, newItem, () => {
onSortAdd(newItem, () => {
content.animate({ scrollTop: content.get(0).scrollHeight }, 50);
analytics.event('AddSort', {
objectType: object.type,
embedType: analytics.embedType(isInline)
});
});
};
@ -408,4 +427,4 @@ const MenuSort = observer(class MenuSort extends React.Component<I.Menu> {
});
export default MenuSort;
export default MenuSort;

View file

@ -2,7 +2,7 @@ import * as React from 'react';
import $ from 'jquery';
import { observer } from 'mobx-react';
import { AutoSizer, CellMeasurer, InfiniteLoader, List, CellMeasurerCache } from 'react-virtualized';
import { Filter, MenuItemVertical, Label } from 'Component';
import { Filter, MenuItemVertical, Label, Icon } from 'Component';
import { I, U, Relation, keyboard, translate } from 'Lib';
const HEIGHT_ITEM = 28;
@ -61,6 +61,20 @@ const MenuSelect = observer(class MenuSelect extends React.Component<I.Menu> {
<div className="inner" />
</div>
);
} else
if (item.id == 'add') {
content = (
<div
id="item-add"
className="item add"
onMouseEnter={e => this.onMouseEnter(e, item)}
onClick={e => this.onClick(e, item)}
style={item.style}
>
<Icon className="plus" />
<div className="name">{item.name}</div>
</div>
);
} else {
if (item.isInitial) {
cn.push('isInitial');
@ -144,7 +158,8 @@ const MenuSelect = observer(class MenuSelect extends React.Component<I.Menu> {
<React.Fragment>
{withFilter ? (
<Filter
ref={ref => this.refFilter = ref}
ref={ref => this.refFilter = ref}
className="outlined"
value={filter}
placeholder={placeholder}
onChange={this.onFilterChange}
@ -268,7 +283,7 @@ const MenuSelect = observer(class MenuSelect extends React.Component<I.Menu> {
getItems (withSections: boolean) {
const { param } = this.props;
const { data } = param;
const { preventFilter } = data;
const { preventFilter, onAdd } = data;
const sections = this.getSections();
let items: any[] = [];
@ -289,6 +304,14 @@ const MenuSelect = observer(class MenuSelect extends React.Component<I.Menu> {
items = items.filter(it => String(it.name || '').match(filter));
};
if (onAdd) {
items = items.concat([
{ isDiv: true },
{ id: 'add', name: translate('commonAddRelation') }
]);
};
return items || [];
};
@ -314,14 +337,19 @@ const MenuSelect = observer(class MenuSelect extends React.Component<I.Menu> {
};
onClick (e: any, item: any) {
const { param, close } = this.props;
const { param, close, getId, getSize } = this.props;
const { data } = param;
const { onSelect, canSelectInitial, noClose, disabled } = data;
const { onSelect, canSelectInitial, noClose, disabled, onAdd } = data;
if (item.isInitial && !canSelectInitial) {
return;
};
if ((item.id == 'add') && onAdd) {
onAdd(getId(), getSize().width);
return;
};
if (!noClose) {
close();
};
@ -397,7 +425,7 @@ const MenuSelect = observer(class MenuSelect extends React.Component<I.Menu> {
resize () {
const { position, getId, param } = this.props;
const { data } = param;
const { noScroll, maxHeight, noVirtualisation } = data;
const { noScroll, maxHeight, noVirtualisation, onAdd } = data;
const items = this.getItems(true);
const obj = $(`#${getId()}`);
const content = obj.find('.content');
@ -425,6 +453,7 @@ const MenuSelect = observer(class MenuSelect extends React.Component<I.Menu> {
};
withFilter ? obj.addClass('withFilter') : obj.removeClass('withFilter');
onAdd ? obj.addClass('withAdd') : obj.removeClass('withAdd');
noScroll ? obj.addClass('noScroll') : obj.removeClass('noScroll');
noVirtualisation ? obj.addClass('noVirtualisation') : obj.removeClass('noVirtualisation');

View file

@ -412,4 +412,4 @@ const MenuSyncStatus = observer(class MenuSyncStatus extends React.Component<I.M
});
export default MenuSyncStatus;
export default MenuSyncStatus;

View file

@ -136,6 +136,8 @@ export interface ViewComponent {
getEmpty?(type: string): any;
onRecordAdd?: (e: any, dir: number, groupId?: string) => void;
onTemplateAdd?: () => void;
onSortAdd?: (item: any, callBack?: () => void) => void;
onFilterAdd?: (item: any, callBack?: () => void) => void;
onTemplateMenu?: (e: any, dur: number) => void;
onCellClick?(e: any, key: string, id?: string, record?: any): void;
onContext?(e: any, id: string): void;

View file

@ -832,6 +832,68 @@ class UtilMenu {
];
};
sortOrFilterRelationSelect (param: any) {
const { rootId, blockId, getView, onSelect, menuParam } = param;
const options = Relation.getFilterOptions(rootId, blockId, getView());
const callBack = (item: any) => {
onSelect(item);
S.Menu.close('select');
};
const menu = Object.assign({
horizontal: I.MenuDirection.Center,
offsetY: 10,
noFlipY: true,
}, menuParam);
if (S.Menu.isOpen('select')) {
S.Menu.close('select');
};
S.Menu.open('select', {
...menu,
data: {
options,
withFilter: true,
maxHeight: 378,
onAdd: (menuId, menuWidth) => {
this.sortOrFilterRelationAdd({ menuId, menuWidth }, param, (relation) => callBack(relation));
},
onSelect: (e: any, item: any) => callBack(item)
}
});
};
sortOrFilterRelationAdd (menuParam: any, param: any, callBack: (relation: any) => void) {
const { rootId, blockId, getView } = param;
const { menuId, menuWidth } = menuParam;
const relations = Relation.getFilterOptions(rootId, blockId, getView());
const element = `#${menuId} #item-add`;
S.Menu.open('relationSuggest', {
element,
offsetX: menuWidth,
horizontal: I.MenuDirection.Right,
vertical: I.MenuDirection.Center,
onOpen: () => $(element).addClass('active'),
onClose: () => $(element).removeClass('active'),
data: {
rootId,
blockId,
skipKeys: relations.map(it => it.id),
ref: 'dataview',
menuIdEdit: 'blockRelationEdit',
addCommand: (rootId: string, blockId: string, relation: any, onChange: (message: any) => void) => {
Dataview.relationAdd(rootId, blockId, relation.relationKey, relations.length, getView(), (message: any) => {
callBack(relation);
S.Menu.close('relationSuggest');
});
}
}
});
};
};
export default new UtilMenu();
export default new UtilMenu();