mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 09:59:55 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { createUniComponentFromWebComponent } from '../../core/index.js';
|
||||
import { uniMap } from '../../core/utils/uni-component/operation.js';
|
||||
import type {
|
||||
DataViewWidget,
|
||||
DataViewWidgetProps,
|
||||
} from '../../core/widget/types.js';
|
||||
import { DataViewHeaderToolsFilter } from './presets/filter/filter.js';
|
||||
import { DataViewHeaderToolsSearch } from './presets/search/search.js';
|
||||
import { DataViewHeaderToolsSort } from './presets/sort/sort.js';
|
||||
import { DataViewHeaderToolsAddRow } from './presets/table-add-row/add-row.js';
|
||||
import { DataViewHeaderToolsViewOptions } from './presets/view-options/view-options.js';
|
||||
import { DataViewHeaderTools } from './tools-view.js';
|
||||
|
||||
export const toolsWidgetPresets = {
|
||||
sort: createUniComponentFromWebComponent(DataViewHeaderToolsSort),
|
||||
filter: createUniComponentFromWebComponent(DataViewHeaderToolsFilter),
|
||||
search: createUniComponentFromWebComponent(DataViewHeaderToolsSearch),
|
||||
viewOptions: createUniComponentFromWebComponent(
|
||||
DataViewHeaderToolsViewOptions
|
||||
),
|
||||
tableAddRow: createUniComponentFromWebComponent(DataViewHeaderToolsAddRow),
|
||||
};
|
||||
export const createWidgetTools = (
|
||||
toolsMap: Record<string, DataViewWidget[]>
|
||||
) => {
|
||||
return uniMap(
|
||||
createUniComponentFromWebComponent(DataViewHeaderTools),
|
||||
(props: DataViewWidgetProps) => ({
|
||||
...props,
|
||||
toolsMap,
|
||||
})
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
import { popupTargetFromElement } from '@blocksuite/affine-components/context-menu';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { FilterIcon } from '@blocksuite/icons/lit';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { popCreateFilter } from '../../../../core/filter/add-filter.js';
|
||||
import { filterTraitKey } from '../../../../core/filter/trait.js';
|
||||
import type { FilterGroup } from '../../../../core/filter/types.js';
|
||||
import { emptyFilterGroup } from '../../../../core/filter/utils.js';
|
||||
import { WidgetBase } from '../../../../core/widget/widget-base.js';
|
||||
import { ShowQuickSettingBarContextKey } from '../../../quick-setting-bar/context.js';
|
||||
|
||||
const styles = css`
|
||||
.affine-database-filter-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
line-height: 20px;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.affine-database-filter-button:hover,
|
||||
.affine-database-filter-button.active {
|
||||
background-color: var(--affine-hover-color);
|
||||
}
|
||||
|
||||
.affine-database-filter-button {
|
||||
}
|
||||
`;
|
||||
|
||||
export class DataViewHeaderToolsFilter extends WidgetBase {
|
||||
static override styles = styles;
|
||||
|
||||
hasFilter = computed(() => {
|
||||
return this.filterTrait?.hasFilter$.value ?? false;
|
||||
});
|
||||
|
||||
private get _filter(): FilterGroup {
|
||||
return this.filterTrait?.filter$.value ?? emptyFilterGroup;
|
||||
}
|
||||
|
||||
private set _filter(filter: FilterGroup) {
|
||||
this.filterTrait?.filterSet(filter);
|
||||
}
|
||||
|
||||
get filterTrait() {
|
||||
return this.view.traitGet(filterTraitKey);
|
||||
}
|
||||
|
||||
private get readonly() {
|
||||
return this.view.readonly$.value;
|
||||
}
|
||||
|
||||
private clickFilter(event: MouseEvent) {
|
||||
if (this.hasFilter.value) {
|
||||
this.toggleShowFilter();
|
||||
return;
|
||||
}
|
||||
popCreateFilter(
|
||||
popupTargetFromElement(event.currentTarget as HTMLElement),
|
||||
{
|
||||
vars: this.view.vars$,
|
||||
onSelect: filter => {
|
||||
this._filter = {
|
||||
...this._filter,
|
||||
conditions: [filter],
|
||||
};
|
||||
this.toggleShowFilter(true);
|
||||
},
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = IS_MOBILE ? 'none' : 'flex';
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.readonly) return nothing;
|
||||
const style = styleMap({
|
||||
color: this.hasFilter.value
|
||||
? cssVarV2('text/emphasis')
|
||||
: cssVarV2('icon/primary'),
|
||||
});
|
||||
return html` <div
|
||||
@click="${this.clickFilter}"
|
||||
style="${style}"
|
||||
class="affine-database-filter-button"
|
||||
>
|
||||
${FilterIcon()}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
toggleShowFilter(show?: boolean) {
|
||||
const map = this.view.contextGet(ShowQuickSettingBarContextKey);
|
||||
map.value = {
|
||||
...map.value,
|
||||
[this.view.id]: show ?? !map.value[this.view.id],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'data-view-header-tools-filter': DataViewHeaderToolsFilter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { CloseIcon, SearchIcon } from '@blocksuite/icons/lit';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, unsafeCSS } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { stopPropagation } from '../../../../core/utils/event.js';
|
||||
import { WidgetBase } from '../../../../core/widget/widget-base.js';
|
||||
import type {
|
||||
KanbanSingleView,
|
||||
TableSingleView,
|
||||
} from '../../../../view-presets/index.js';
|
||||
|
||||
const styles = css`
|
||||
.affine-database-search-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-container-expand {
|
||||
overflow: visible;
|
||||
width: 138px;
|
||||
background-color: var(--affine-hover-color);
|
||||
}
|
||||
|
||||
.search-input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 8px;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.affine-database-search-input-icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
font-size: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
color: ${unsafeCSSVarV2('icon/primary')};
|
||||
}
|
||||
|
||||
.affine-database-search-input-icon:hover {
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
|
||||
.search-container-expand .affine-database-search-input-icon {
|
||||
left: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.affine-database-search-input {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 0 2px 0 30px;
|
||||
border: none;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
font-size: var(--affine-font-sm);
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.affine-database-search-input::placeholder {
|
||||
color: var(--affine-placeholder-color);
|
||||
font-size: var(--affine-font-sm);
|
||||
}
|
||||
`;
|
||||
|
||||
export class DataViewHeaderToolsSearch extends WidgetBase<
|
||||
TableSingleView | KanbanSingleView
|
||||
> {
|
||||
static override styles = styles;
|
||||
|
||||
private _clearSearch = () => {
|
||||
this._searchInput.value = '';
|
||||
this.view.setSearch('');
|
||||
this.preventBlur = true;
|
||||
setTimeout(() => {
|
||||
this.preventBlur = false;
|
||||
});
|
||||
};
|
||||
|
||||
private _clickSearch = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
this.showSearch = true;
|
||||
};
|
||||
|
||||
private _onSearch = (event: InputEvent) => {
|
||||
const el = event.target as HTMLInputElement;
|
||||
const inputValue = el.value.trim();
|
||||
this.view.setSearch(inputValue);
|
||||
};
|
||||
|
||||
private _onSearchBlur = () => {
|
||||
if (this._searchInput.value || this.preventBlur) {
|
||||
return;
|
||||
}
|
||||
this.showSearch = false;
|
||||
};
|
||||
|
||||
private _onSearchKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
if (this._searchInput.value) {
|
||||
this._searchInput.value = '';
|
||||
this.view.setSearch('');
|
||||
} else {
|
||||
this.showSearch = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
preventBlur = false;
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = IS_MOBILE ? 'none' : 'flex';
|
||||
}
|
||||
|
||||
override render() {
|
||||
const searchToolClassMap = classMap({
|
||||
'affine-database-search-container': true,
|
||||
'search-container-expand': this.showSearch,
|
||||
active: this.showSearch,
|
||||
});
|
||||
return html`
|
||||
<label class="${searchToolClassMap}" @click="${this._clickSearch}">
|
||||
<div class="affine-database-search-input-icon">${SearchIcon()}</div>
|
||||
<input
|
||||
placeholder="Search..."
|
||||
class="affine-database-search-input"
|
||||
@input="${this._onSearch}"
|
||||
@click="${(event: MouseEvent) => event.stopPropagation()}"
|
||||
@keydown="${this._onSearchKeydown}"
|
||||
@pointerdown="${stopPropagation}"
|
||||
@blur="${this._onSearchBlur}"
|
||||
/>
|
||||
<div class="close-icon" @mousedown="${this._clearSearch}">
|
||||
${CloseIcon()}
|
||||
<affine-tooltip>
|
||||
<span
|
||||
style=${styleMap({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
boxSizing: 'border-box',
|
||||
padding: '2px 6px',
|
||||
borderRadius: '4px',
|
||||
background: 'var(--affine-white-10)',
|
||||
})}
|
||||
>Esc</span
|
||||
>
|
||||
to clear all
|
||||
</affine-tooltip>
|
||||
</div>
|
||||
</label>
|
||||
`;
|
||||
}
|
||||
|
||||
@query('.affine-database-search-input')
|
||||
private accessor _searchInput!: HTMLInputElement;
|
||||
|
||||
@state()
|
||||
private accessor showSearch = false;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'data-view-header-tools-search': DataViewHeaderToolsSearch;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { popupTargetFromElement } from '@blocksuite/affine-components/context-menu';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { SortIcon } from '@blocksuite/icons/lit';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { popCreateSort } from '../../../../core/sort/add-sort.js';
|
||||
import { sortTraitKey } from '../../../../core/sort/manager.js';
|
||||
import { createSortUtils } from '../../../../core/sort/utils.js';
|
||||
import { WidgetBase } from '../../../../core/widget/widget-base.js';
|
||||
import { ShowQuickSettingBarContextKey } from '../../../quick-setting-bar/context.js';
|
||||
import { popSortRoot } from '../../../quick-setting-bar/sort/root-panel.js';
|
||||
|
||||
const styles = css`
|
||||
.affine-database-sort-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
line-height: 20px;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.affine-database-sort-button:hover,
|
||||
.affine-database-sort-button.active {
|
||||
background-color: var(--affine-hover-color);
|
||||
}
|
||||
|
||||
.affine-database-sort-button {
|
||||
}
|
||||
`;
|
||||
|
||||
export class DataViewHeaderToolsSort extends WidgetBase {
|
||||
static override styles = styles;
|
||||
|
||||
sortUtils$ = computed(() => {
|
||||
const sortTrait = this.view.traitGet(sortTraitKey);
|
||||
if (sortTrait) {
|
||||
return createSortUtils(sortTrait, this.dataViewInstance.eventTrace);
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
hasSort = computed(() => {
|
||||
return (this.sortUtils$.value?.sortList$?.value?.length ?? 0) > 0;
|
||||
});
|
||||
|
||||
private get readonly() {
|
||||
return this.view.readonly$.value;
|
||||
}
|
||||
|
||||
private clickSort(event: MouseEvent) {
|
||||
const sortUtils = this.sortUtils$.value;
|
||||
if (!sortUtils) {
|
||||
return;
|
||||
}
|
||||
if (this.hasSort.value) {
|
||||
this.toggleShowQuickSettingBar();
|
||||
return;
|
||||
}
|
||||
popCreateSort(popupTargetFromElement(event.currentTarget as HTMLElement), {
|
||||
sortUtils: {
|
||||
...sortUtils,
|
||||
add: sort => {
|
||||
sortUtils.add(sort);
|
||||
this.toggleShowQuickSettingBar(true);
|
||||
requestAnimationFrame(() => {
|
||||
const ele = this.closest(
|
||||
'affine-data-view-renderer'
|
||||
)?.querySelector('.data-view-sort-button');
|
||||
if (ele) {
|
||||
popSortRoot(popupTargetFromElement(ele as HTMLElement), {
|
||||
sortUtils: sortUtils,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.style.display = IS_MOBILE ? 'none' : 'flex';
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.readonly) return nothing;
|
||||
const style = styleMap({
|
||||
color: this.hasSort.value
|
||||
? cssVarV2('text/emphasis')
|
||||
: cssVarV2('icon/primary'),
|
||||
});
|
||||
return html` <div
|
||||
@click="${this.clickSort}"
|
||||
style="${style}"
|
||||
class="affine-database-sort-button"
|
||||
>
|
||||
${SortIcon()}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
toggleShowQuickSettingBar(show?: boolean) {
|
||||
const map = this.view.contextGet(ShowQuickSettingBarContextKey);
|
||||
map.value = {
|
||||
...map.value,
|
||||
[this.view.id]: show ?? !map.value[this.view.id],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'data-view-header-tools-sort': DataViewHeaderToolsSort;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { PlusIcon } from '@blocksuite/icons/lit';
|
||||
import { css, html } from 'lit';
|
||||
|
||||
import { WidgetBase } from '../../../../core/widget/widget-base.js';
|
||||
|
||||
const styles = css`
|
||||
.new-record {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.new-record svg {
|
||||
font-size: 20px;
|
||||
color: ${unsafeCSSVarV2('icon/primary')};
|
||||
}
|
||||
`;
|
||||
|
||||
export class DataViewHeaderToolsAddRow extends WidgetBase {
|
||||
static override styles = styles;
|
||||
|
||||
private _onAddNewRecord = () => {
|
||||
if (this.readonly) return;
|
||||
this.viewMethods.addRow?.('start');
|
||||
};
|
||||
|
||||
private get readonly() {
|
||||
return this.view.readonly$.value;
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.readonly) {
|
||||
return;
|
||||
}
|
||||
return html` <data-view-component-button
|
||||
class="affine-database-toolbar-item new-record"
|
||||
.onClick="${this._onAddNewRecord}"
|
||||
.icon="${PlusIcon()}"
|
||||
.text="${IS_MOBILE
|
||||
? html`<span style="font-weight: 500">New</span>`
|
||||
: html`<span style="font-weight: 500">New Record</span>`}"
|
||||
>
|
||||
</data-view-component-button>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'data-view-header-tools-add-row': DataViewHeaderToolsAddRow;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||
import { PlusIcon } from '@blocksuite/icons/lit';
|
||||
import { html } from 'lit';
|
||||
|
||||
export class NewRecordPreview extends ShadowlessElement {
|
||||
override render() {
|
||||
return html`
|
||||
<style>
|
||||
affine-database-new-record-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border: 1px solid var(--affine-border-color);
|
||||
border-radius: 50%;
|
||||
background: var(--affine-blue-100);
|
||||
box-shadow:
|
||||
0px 0px 10px rgba(0, 0, 0, 0.05),
|
||||
0px 0px 0px 0.5px var(--affine-black-10);
|
||||
cursor: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
caret-color: transparent;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
affine-database-new-record-preview svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
affine-database-new-record-preview path {
|
||||
fill: var(--affine-brand-color);
|
||||
}
|
||||
</style>
|
||||
${PlusIcon()}
|
||||
`;
|
||||
}
|
||||
}
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
import {
|
||||
menu,
|
||||
type MenuButtonData,
|
||||
type MenuConfig,
|
||||
popMenu,
|
||||
type PopupTarget,
|
||||
popupTargetFromElement,
|
||||
} from '@blocksuite/affine-components/context-menu';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import {
|
||||
ArrowRightSmallIcon,
|
||||
DeleteIcon,
|
||||
DuplicateIcon,
|
||||
FilterIcon,
|
||||
GroupingIcon,
|
||||
InfoIcon,
|
||||
LayoutIcon,
|
||||
MoreHorizontalIcon,
|
||||
SortIcon,
|
||||
} from '@blocksuite/icons/lit';
|
||||
import { css, html } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { popPropertiesSetting } from '../../../../core/common/properties.js';
|
||||
import { filterTraitKey } from '../../../../core/filter/trait.js';
|
||||
import {
|
||||
popGroupSetting,
|
||||
popSelectGroupByProperty,
|
||||
} from '../../../../core/group-by/setting.js';
|
||||
import { groupTraitKey } from '../../../../core/group-by/trait.js';
|
||||
import {
|
||||
type DataViewInstance,
|
||||
emptyFilterGroup,
|
||||
popCreateFilter,
|
||||
renderUniLit,
|
||||
} from '../../../../core/index.js';
|
||||
import { popCreateSort } from '../../../../core/sort/add-sort.js';
|
||||
import { sortTraitKey } from '../../../../core/sort/manager.js';
|
||||
import { createSortUtils } from '../../../../core/sort/utils.js';
|
||||
import { WidgetBase } from '../../../../core/widget/widget-base.js';
|
||||
import { popFilterRoot } from '../../../quick-setting-bar/filter/root-panel-view.js';
|
||||
import { popSortRoot } from '../../../quick-setting-bar/sort/root-panel.js';
|
||||
|
||||
const styles = css`
|
||||
.affine-database-toolbar-item.more-action {
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.affine-database-toolbar-item.more-action:hover {
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
|
||||
.affine-database-toolbar-item.more-action {
|
||||
font-size: 20px;
|
||||
color: ${unsafeCSSVarV2('icon/primary')};
|
||||
}
|
||||
|
||||
.more-action.active {
|
||||
background: var(--affine-hover-color);
|
||||
}
|
||||
`;
|
||||
|
||||
export class DataViewHeaderToolsViewOptions extends WidgetBase {
|
||||
static override styles = styles;
|
||||
|
||||
clickMoreAction = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
this.openMoreAction(popupTargetFromElement(e.currentTarget as HTMLElement));
|
||||
};
|
||||
|
||||
openMoreAction = (target: PopupTarget) => {
|
||||
popViewOptions(target, this.dataViewInstance);
|
||||
};
|
||||
|
||||
override render() {
|
||||
if (this.view.readonly$.value) {
|
||||
return;
|
||||
}
|
||||
return html` <div
|
||||
class="affine-database-toolbar-item more-action"
|
||||
@click="${this.clickMoreAction}"
|
||||
>
|
||||
${MoreHorizontalIcon()}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'data-view-header-tools-view-options': DataViewHeaderToolsViewOptions;
|
||||
}
|
||||
}
|
||||
const createSettingMenus = (
|
||||
target: PopupTarget,
|
||||
dataViewInstance: DataViewInstance,
|
||||
reopen: () => void
|
||||
) => {
|
||||
const view = dataViewInstance.view;
|
||||
const settingItems: MenuConfig[] = [];
|
||||
settingItems.push(
|
||||
menu.action({
|
||||
name: 'Properties',
|
||||
prefix: InfoIcon(),
|
||||
postfix: html` <div style="font-size: 14px;">
|
||||
${view.properties$.value.length} shown
|
||||
</div>
|
||||
${ArrowRightSmallIcon()}`,
|
||||
select: () => {
|
||||
popPropertiesSetting(target, {
|
||||
view: view,
|
||||
onBack: reopen,
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
const filterTrait = view.traitGet(filterTraitKey);
|
||||
if (filterTrait) {
|
||||
const filterCount = filterTrait.filter$.value.conditions.length;
|
||||
settingItems.push(
|
||||
menu.action({
|
||||
name: 'Filter',
|
||||
prefix: FilterIcon(),
|
||||
postfix: html` <div style="font-size: 14px;">
|
||||
${filterCount === 0
|
||||
? ''
|
||||
: filterCount === 1
|
||||
? '1 filter'
|
||||
: `${filterCount} filters`}
|
||||
</div>
|
||||
${ArrowRightSmallIcon()}`,
|
||||
select: () => {
|
||||
if (!filterTrait.filter$.value.conditions.length) {
|
||||
popCreateFilter(target, {
|
||||
vars: view.vars$,
|
||||
onBack: reopen,
|
||||
onSelect: filter => {
|
||||
filterTrait.filterSet({
|
||||
...(filterTrait.filter$.value ?? emptyFilterGroup),
|
||||
conditions: [...filterTrait.filter$.value.conditions, filter],
|
||||
});
|
||||
popFilterRoot(target, {
|
||||
filterTrait: filterTrait,
|
||||
onBack: reopen,
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
popFilterRoot(target, {
|
||||
filterTrait: filterTrait,
|
||||
onBack: reopen,
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
const sortTrait = view.traitGet(sortTraitKey);
|
||||
if (sortTrait) {
|
||||
const sortCount = sortTrait.sortList$.value.length;
|
||||
settingItems.push(
|
||||
menu.action({
|
||||
name: 'Sort',
|
||||
prefix: SortIcon(),
|
||||
postfix: html` <div style="font-size: 14px;">
|
||||
${sortCount === 0
|
||||
? ''
|
||||
: sortCount === 1
|
||||
? '1 sort'
|
||||
: `${sortCount} sorts`}
|
||||
</div>
|
||||
${ArrowRightSmallIcon()}`,
|
||||
select: () => {
|
||||
const sortList = sortTrait.sortList$.value;
|
||||
const sortUtils = createSortUtils(
|
||||
sortTrait,
|
||||
dataViewInstance.eventTrace
|
||||
);
|
||||
if (!sortList.length) {
|
||||
popCreateSort(target, {
|
||||
sortUtils: sortUtils,
|
||||
onBack: reopen,
|
||||
});
|
||||
} else {
|
||||
popSortRoot(target, {
|
||||
sortUtils: sortUtils,
|
||||
title: {
|
||||
text: 'Sort',
|
||||
onBack: reopen,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
const groupTrait = view.traitGet(groupTraitKey);
|
||||
if (groupTrait) {
|
||||
settingItems.push(
|
||||
menu.action({
|
||||
name: 'Group',
|
||||
prefix: GroupingIcon(),
|
||||
postfix: html` <div style="font-size: 14px;">
|
||||
${groupTrait.property$.value?.name$.value ?? ''}
|
||||
</div>
|
||||
${ArrowRightSmallIcon()}`,
|
||||
select: () => {
|
||||
const groupBy = groupTrait.property$.value;
|
||||
if (!groupBy) {
|
||||
popSelectGroupByProperty(target, groupTrait, {
|
||||
onSelect: () => popGroupSetting(target, groupTrait, reopen),
|
||||
onBack: reopen,
|
||||
});
|
||||
} else {
|
||||
popGroupSetting(target, groupTrait, reopen);
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
return settingItems;
|
||||
};
|
||||
export const popViewOptions = (
|
||||
target: PopupTarget,
|
||||
dataViewInstance: DataViewInstance,
|
||||
onClose?: () => void
|
||||
) => {
|
||||
const view = dataViewInstance.view;
|
||||
const reopen = () => {
|
||||
popViewOptions(target, dataViewInstance);
|
||||
};
|
||||
const items: MenuConfig[] = [];
|
||||
items.push(
|
||||
menu.input({
|
||||
initialValue: view.name$.value,
|
||||
onChange: text => {
|
||||
view.nameSet(text);
|
||||
},
|
||||
})
|
||||
);
|
||||
items.push(
|
||||
menu.group({
|
||||
items: [
|
||||
menu.action({
|
||||
name: 'Layout',
|
||||
postfix: html` <div
|
||||
style="font-size: 14px;text-transform: capitalize;"
|
||||
>
|
||||
${view.type}
|
||||
</div>
|
||||
${ArrowRightSmallIcon()}`,
|
||||
select: () => {
|
||||
const viewTypes = view.manager.viewMetas.map<MenuConfig>(meta => {
|
||||
return menu => {
|
||||
if (!menu.search(meta.model.defaultName)) {
|
||||
return;
|
||||
}
|
||||
const isSelected =
|
||||
meta.type === view.manager.currentView$.value.type;
|
||||
const iconStyle = styleMap({
|
||||
fontSize: '24px',
|
||||
color: isSelected
|
||||
? 'var(--affine-text-emphasis-color)'
|
||||
: 'var(--affine-icon-secondary)',
|
||||
});
|
||||
const textStyle = styleMap({
|
||||
fontSize: '14px',
|
||||
lineHeight: '22px',
|
||||
color: isSelected
|
||||
? 'var(--affine-text-emphasis-color)'
|
||||
: 'var(--affine-text-secondary-color)',
|
||||
});
|
||||
const data: MenuButtonData = {
|
||||
content: () => html`
|
||||
<div
|
||||
style="color:var(--affine-text-emphasis-color);width:100%;display: flex;flex-direction: column;align-items: center;justify-content: center;padding: 6px 16px;"
|
||||
>
|
||||
<div style="${iconStyle}">
|
||||
${renderUniLit(meta.renderer.icon)}
|
||||
</div>
|
||||
<div style="${textStyle}">${meta.model.defaultName}</div>
|
||||
</div>
|
||||
`,
|
||||
select: () => {
|
||||
view.manager.viewChangeType(
|
||||
view.manager.currentViewId$.value,
|
||||
meta.type
|
||||
);
|
||||
dataViewInstance.clearSelection();
|
||||
},
|
||||
class: {},
|
||||
};
|
||||
const containerStyle = styleMap({
|
||||
flex: '1',
|
||||
});
|
||||
return html` <affine-menu-button
|
||||
style="${containerStyle}"
|
||||
.data="${data}"
|
||||
.menu="${menu}"
|
||||
></affine-menu-button>`;
|
||||
};
|
||||
});
|
||||
popMenu(target, {
|
||||
options: {
|
||||
title: {
|
||||
onBack: reopen,
|
||||
text: 'Layout',
|
||||
},
|
||||
items: [
|
||||
menu => {
|
||||
const result = menu.renderItems(viewTypes);
|
||||
if (result.length) {
|
||||
return html` <div style="display: flex">${result}</div>`;
|
||||
}
|
||||
return html``;
|
||||
},
|
||||
// menu.toggleSwitch({
|
||||
// name: 'Show block icon',
|
||||
// on: true,
|
||||
// onChange: value => {
|
||||
// console.log(value);
|
||||
// },
|
||||
// }),
|
||||
// menu.toggleSwitch({
|
||||
// name: 'Show Vertical lines',
|
||||
// on: true,
|
||||
// onChange: value => {
|
||||
// console.log(value);
|
||||
// },
|
||||
// }),
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
prefix: LayoutIcon(),
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
items.push(
|
||||
menu.group({
|
||||
items: createSettingMenus(target, dataViewInstance, reopen),
|
||||
})
|
||||
);
|
||||
items.push(
|
||||
menu.group({
|
||||
items: [
|
||||
menu.action({
|
||||
name: 'Duplicate',
|
||||
prefix: DuplicateIcon(),
|
||||
select: () => {
|
||||
view.duplicate();
|
||||
},
|
||||
}),
|
||||
menu.action({
|
||||
name: 'Delete',
|
||||
prefix: DeleteIcon(),
|
||||
select: () => {
|
||||
view.delete();
|
||||
},
|
||||
class: { 'delete-item': true },
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
popMenu(target, {
|
||||
options: {
|
||||
title: {
|
||||
text: 'View settings',
|
||||
},
|
||||
items,
|
||||
onClose: onClose,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { css, html } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
|
||||
import { type DataViewInstance, renderUniLit } from '../../core/index.js';
|
||||
import type { SingleView } from '../../core/view-manager/single-view.js';
|
||||
import type { ViewManager } from '../../core/view-manager/view-manager.js';
|
||||
import type { DataViewWidget } from '../../core/widget/types.js';
|
||||
import { WidgetBase } from '../../core/widget/widget-base.js';
|
||||
|
||||
const styles = css`
|
||||
.affine-database-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
opacity: 0;
|
||||
transition: opacity 150ms cubic-bezier(0.42, 0, 1, 1);
|
||||
}
|
||||
|
||||
.toolbar-hover-container:hover .affine-database-toolbar {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.toolbar-hover-container:has(.active) .affine-database-toolbar {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.show-toolbar {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.affine-database-toolbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export class DataViewHeaderTools extends WidgetBase {
|
||||
static override styles = styles;
|
||||
|
||||
override render() {
|
||||
const classList = classMap({
|
||||
'show-toolbar': IS_MOBILE,
|
||||
'affine-database-toolbar': true,
|
||||
});
|
||||
const tools = this.toolsMap[this.view.type];
|
||||
return html` <div class="${classList}">
|
||||
${repeat(tools ?? [], uni => {
|
||||
return renderUniLit(uni, {
|
||||
dataViewInstance: this.dataViewInstance,
|
||||
});
|
||||
})}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@state()
|
||||
accessor showToolBar = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor toolsMap!: Record<string, DataViewWidget[]>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'data-view-header-tools': DataViewHeaderTools;
|
||||
}
|
||||
}
|
||||
export const renderTools = (
|
||||
view: SingleView,
|
||||
viewMethods: DataViewInstance,
|
||||
viewSource: ViewManager
|
||||
) => {
|
||||
return html` <data-view-header-tools
|
||||
.viewMethods="${viewMethods}"
|
||||
.view="${view}"
|
||||
.viewSource="${viewSource}"
|
||||
></data-view-header-tools>`;
|
||||
};
|
||||
Reference in New Issue
Block a user