mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-11 05:58:56 +08:00
refactor(editor): remove edit view of database block properties (#10748)
This commit is contained in:
@@ -75,7 +75,7 @@ export class CheckboxCell extends BaseCellRenderer<boolean> {
|
||||
override beforeEnterEditMode() {
|
||||
const checked = !this.value;
|
||||
|
||||
this.onChange(checked);
|
||||
this.valueSetImmediate(checked);
|
||||
if (checked) {
|
||||
playCheckAnimation(this._checkbox, { left: 2 }).catch(console.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const dateCellStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
border: 'none',
|
||||
fontFamily: baseTheme.fontSansFamily,
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '400',
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: 'var(--data-view-cell-text-size)',
|
||||
lineHeight: 'var(--data-view-cell-text-line-height)',
|
||||
height: 'var(--data-view-cell-text-line-height)',
|
||||
});
|
||||
|
||||
export const dateValueContainerStyle = style({
|
||||
padding: '12px',
|
||||
backgroundColor: 'var(--layer-background-primary)',
|
||||
borderRadius: '12px',
|
||||
color: 'var(--text-secondary)',
|
||||
fontSize: '17px',
|
||||
lineHeight: '22px',
|
||||
height: '46px',
|
||||
});
|
||||
|
||||
export const datePickerContainerStyle = style({
|
||||
padding: '12px',
|
||||
backgroundColor: 'var(--layer-background-primary)',
|
||||
borderRadius: '12px',
|
||||
});
|
||||
@@ -4,66 +4,23 @@ import {
|
||||
} from '@blocksuite/affine-components/context-menu';
|
||||
import { DatePicker } from '@blocksuite/affine-components/date-picker';
|
||||
import { createLitPortal } from '@blocksuite/affine-components/portal';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { flip, offset } from '@floating-ui/dom';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
import { format } from 'date-fns/format';
|
||||
import { css, html, unsafeCSS } from 'lit';
|
||||
import { html } from 'lit';
|
||||
|
||||
import { BaseCellRenderer } from '../../core/property/index.js';
|
||||
import { createFromBaseCellRenderer } from '../../core/property/renderer.js';
|
||||
import { createIcon } from '../../core/utils/uni-icon.js';
|
||||
import {
|
||||
dateCellStyle,
|
||||
datePickerContainerStyle,
|
||||
dateValueContainerStyle,
|
||||
} from './cell-renderer.css.js';
|
||||
import { datePropertyModelConfig } from './define.js';
|
||||
|
||||
export class DateCell extends BaseCellRenderer<number> {
|
||||
static override styles = css`
|
||||
affine-database-date-cell {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.affine-database-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
color: var(--affine-text-primary-color);
|
||||
font-weight: 400;
|
||||
background-color: transparent;
|
||||
font-size: var(--data-view-cell-text-size);
|
||||
line-height: var(--data-view-cell-text-line-height);
|
||||
height: var(--data-view-cell-text-line-height);
|
||||
}
|
||||
|
||||
input.affine-database-date[type='date']::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
const value = this.value ? format(this.value, 'yyyy/MM/dd') : '';
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return html` <div class="affine-database-date date">${value}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export class DateCellEditing extends BaseCellRenderer<number> {
|
||||
static override styles = css`
|
||||
affine-database-date-cell-editing {
|
||||
width: 100%;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.affine-database-date:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
private _prevPortalAbortController: AbortController | null = null;
|
||||
|
||||
private readonly openDatePicker = () => {
|
||||
@@ -96,18 +53,8 @@ export class DateCellEditing extends BaseCellRenderer<number> {
|
||||
},
|
||||
items: [
|
||||
() =>
|
||||
html`<div
|
||||
style="
|
||||
padding: 12px;
|
||||
background-color: ${unsafeCSSVarV2('layer/background/primary')};
|
||||
border-radius: 12px;
|
||||
color: ${unsafeCSSVarV2('text/secondary')};
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
height: 46px;
|
||||
"
|
||||
>
|
||||
${this.dateString}
|
||||
html` <div class="${dateValueContainerStyle}">
|
||||
${this.formattedTempValue$.value}
|
||||
</div>`,
|
||||
() => {
|
||||
const datePicker = new DatePicker();
|
||||
@@ -123,11 +70,7 @@ height: 46px;
|
||||
abortController.abort();
|
||||
};
|
||||
requestAnimationFrame(() => datePicker.focusDateCell());
|
||||
return html`<div
|
||||
style="padding: 12px;background-color: ${unsafeCSSVarV2(
|
||||
'layer/background/primary'
|
||||
)};border-radius: 12px"
|
||||
>
|
||||
return html` <div class="${datePickerContainerStyle}">
|
||||
${datePicker}
|
||||
</div>`;
|
||||
},
|
||||
@@ -179,36 +122,39 @@ height: 46px;
|
||||
return;
|
||||
}
|
||||
|
||||
this.onChange(tempValue?.getTime());
|
||||
const time = tempValue?.getTime();
|
||||
this.valueSetNextTick(time);
|
||||
this.tempValue$.value = undefined;
|
||||
};
|
||||
|
||||
tempValue$ = signal<Date>();
|
||||
tempValue$ = signal<Date | undefined>();
|
||||
|
||||
get dateString() {
|
||||
const value = this.tempValue;
|
||||
format(value?: Date) {
|
||||
return value ? format(value, 'yyyy/MM/dd') : '';
|
||||
}
|
||||
|
||||
get tempValue() {
|
||||
return this.tempValue$.value;
|
||||
}
|
||||
formattedTempValue$ = computed(() => {
|
||||
return this.format(this.tempValue$.value);
|
||||
});
|
||||
formattedValue$ = computed(() => {
|
||||
return (
|
||||
this.formattedTempValue$.value ||
|
||||
this.format(this.value ? new Date(this.value) : undefined)
|
||||
);
|
||||
});
|
||||
|
||||
override firstUpdated() {
|
||||
override afterEnterEditingMode() {
|
||||
this.openDatePicker();
|
||||
}
|
||||
|
||||
override onExitEditMode() {
|
||||
override beforeExitEditingMode() {
|
||||
this.updateValue();
|
||||
this._prevPortalAbortController?.abort();
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html` <div
|
||||
class="affine-database-date date"
|
||||
@click="${this.openDatePicker}"
|
||||
>
|
||||
${this.dateString}
|
||||
return html` <div class="${dateCellStyle} date">
|
||||
${this.formattedValue$.value}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -217,6 +163,5 @@ export const datePropertyConfig = datePropertyModelConfig.createPropertyMeta({
|
||||
icon: createIcon('DateTimeIcon'),
|
||||
cellRenderer: {
|
||||
view: createFromBaseCellRenderer(DateCell),
|
||||
edit: createFromBaseCellRenderer(DateCellEditing),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const multiSelectStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: '0',
|
||||
border: 'none',
|
||||
fontFamily: baseTheme.fontSansFamily,
|
||||
fontSize: 'var(--data-view-cell-text-size)',
|
||||
lineHeight: 'var(--data-view-cell-text-line-height)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '400',
|
||||
backgroundColor: 'transparent',
|
||||
});
|
||||
@@ -1,53 +1,33 @@
|
||||
import { popupTargetFromElement } from '@blocksuite/affine-components/context-menu';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
import { popTagSelect } from '../../core/component/tags/multi-tag-select.js';
|
||||
import type { SelectTag } from '../../core/index.js';
|
||||
import { BaseCellRenderer } from '../../core/property/index.js';
|
||||
import { createFromBaseCellRenderer } from '../../core/property/renderer.js';
|
||||
import { stopPropagation } from '../../core/utils/event.js';
|
||||
import { createIcon } from '../../core/utils/uni-icon.js';
|
||||
import type { SelectPropertyData } from '../select/define.js';
|
||||
import { multiSelectStyle } from './cell-renderer.css.js';
|
||||
import { multiSelectPropertyModelConfig } from './define.js';
|
||||
|
||||
export class MultiSelectCell extends BaseCellRenderer<
|
||||
string[],
|
||||
SelectPropertyData
|
||||
> {
|
||||
override render() {
|
||||
return html`
|
||||
<affine-multi-tag-view
|
||||
.value="${Array.isArray(this.value) ? this.value : []}"
|
||||
.options="${this.property.data$.value.options}"
|
||||
></affine-multi-tag-view>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiSelectCellEditing extends BaseCellRenderer<
|
||||
string[],
|
||||
SelectPropertyData
|
||||
> {
|
||||
closePopup?: () => void;
|
||||
private readonly popTagSelect = () => {
|
||||
const value = signal(this._value);
|
||||
this._disposables.add({
|
||||
dispose: popTagSelect(
|
||||
popupTargetFromElement(
|
||||
this.querySelector('affine-multi-tag-view') ?? this
|
||||
),
|
||||
{
|
||||
name: this.cell.property.name$.value,
|
||||
options: this.options$,
|
||||
onOptionsChange: this._onOptionsChange,
|
||||
value: value,
|
||||
onChange: v => {
|
||||
this._onChange(v);
|
||||
value.value = v;
|
||||
},
|
||||
onComplete: this._editComplete,
|
||||
minWidth: 400,
|
||||
}
|
||||
),
|
||||
this.closePopup = popTagSelect(popupTargetFromElement(this), {
|
||||
name: this.cell.property.name$.value,
|
||||
options: this.options$,
|
||||
onOptionsChange: this._onOptionsChange,
|
||||
value: this._value$,
|
||||
onChange: v => {
|
||||
this.valueSetImmediate(v);
|
||||
},
|
||||
onComplete: this._editComplete,
|
||||
minWidth: 400,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -55,10 +35,6 @@ export class MultiSelectCellEditing extends BaseCellRenderer<
|
||||
this.selectCurrentCell(false);
|
||||
};
|
||||
|
||||
_onChange = (ids: string[]) => {
|
||||
this.onChange(ids);
|
||||
};
|
||||
|
||||
_onOptionsChange = (options: SelectTag[]) => {
|
||||
this.property.dataUpdate(data => {
|
||||
return {
|
||||
@@ -71,21 +47,32 @@ export class MultiSelectCellEditing extends BaseCellRenderer<
|
||||
options$ = computed(() => {
|
||||
return this.property.data$.value.options;
|
||||
});
|
||||
|
||||
get _value() {
|
||||
_value$ = computed(() => {
|
||||
return this.value ?? [];
|
||||
});
|
||||
|
||||
override afterEnterEditingMode() {
|
||||
if (!this.closePopup) {
|
||||
this.popTagSelect();
|
||||
}
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this.popTagSelect();
|
||||
override beforeExitEditingMode() {
|
||||
this.closePopup?.();
|
||||
this.closePopup = undefined;
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<affine-multi-tag-view
|
||||
.value="${this._value}"
|
||||
.options="${this.options$.value}"
|
||||
></affine-multi-tag-view>
|
||||
<div
|
||||
class="${multiSelectStyle}"
|
||||
@pointerdown="${this.isEditing$.value ? stopPropagation : undefined}"
|
||||
>
|
||||
<affine-multi-tag-view
|
||||
.value="${this._value$.value}"
|
||||
.options="${this.options$.value}"
|
||||
></affine-multi-tag-view>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -95,6 +82,5 @@ export const multiSelectPropertyConfig =
|
||||
icon: createIcon('MultiSelectIcon'),
|
||||
cellRenderer: {
|
||||
view: createFromBaseCellRenderer(MultiSelectCell),
|
||||
edit: createFromBaseCellRenderer(MultiSelectCellEditing),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const numberStyle = style({
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
border: 'none',
|
||||
fontFamily: baseTheme.fontSansFamily,
|
||||
fontSize: 'var(--data-view-cell-text-size)',
|
||||
lineHeight: 'var(--data-view-cell-text-line-height)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '400',
|
||||
backgroundColor: 'transparent',
|
||||
wordBreak: 'break-all',
|
||||
});
|
||||
|
||||
export const numberInputStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
border: 'none',
|
||||
fontFamily: baseTheme.fontSansFamily,
|
||||
fontSize: 'var(--data-view-cell-text-size)',
|
||||
lineHeight: 'var(--data-view-cell-text-line-height)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '400',
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: 'right',
|
||||
':focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
import { IS_MAC } from '@blocksuite/global/env';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, unsafeCSS } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
|
||||
import { BaseCellRenderer } from '../../core/property/index.js';
|
||||
import { createFromBaseCellRenderer } from '../../core/property/renderer.js';
|
||||
import { stopPropagation } from '../../core/utils/event.js';
|
||||
import { createIcon } from '../../core/utils/uni-icon.js';
|
||||
import { numberInputStyle, numberStyle } from './cell-renderer.css.js';
|
||||
import { numberPropertyModelConfig } from './define.js';
|
||||
import type { NumberPropertyDataType } from './types.js';
|
||||
import {
|
||||
@@ -19,93 +19,23 @@ export class NumberCell extends BaseCellRenderer<
|
||||
number,
|
||||
NumberPropertyDataType
|
||||
> {
|
||||
static override styles = css`
|
||||
affine-database-number-cell {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
@query('input')
|
||||
private accessor _inputEle!: HTMLInputElement;
|
||||
|
||||
.affine-database-number {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
font-size: var(--data-view-cell-text-size);
|
||||
line-height: var(--data-view-cell-text-line-height);
|
||||
color: var(--affine-text-primary-color);
|
||||
font-weight: 400;
|
||||
background-color: transparent;
|
||||
word-break: break-all;
|
||||
}
|
||||
`;
|
||||
|
||||
private _getFormattedString() {
|
||||
private _getFormattedString(value: number | undefined = this.value) {
|
||||
const enableNewFormatting =
|
||||
this.view.featureFlags$.value.enable_number_formatting;
|
||||
const decimals = this.property.data$.value.decimal ?? 0;
|
||||
const formatMode = (this.property.data$.value.format ??
|
||||
'number') as NumberFormat;
|
||||
|
||||
return this.value != undefined
|
||||
return value != undefined
|
||||
? enableNewFormatting
|
||||
? formatNumber(this.value, formatMode, decimals)
|
||||
: this.value.toString()
|
||||
? formatNumber(value, formatMode, decimals)
|
||||
: value.toString()
|
||||
: '';
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html` <div class="affine-database-number number">
|
||||
${this._getFormattedString()}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export class NumberCellEditing extends BaseCellRenderer<
|
||||
number,
|
||||
NumberPropertyDataType
|
||||
> {
|
||||
static override styles = css`
|
||||
affine-database-number-cell-editing {
|
||||
display: block;
|
||||
width: 100%;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.affine-database-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
font-size: var(--data-view-cell-text-size);
|
||||
line-height: var(--data-view-cell-text-line-height);
|
||||
color: var(--affine-text-primary-color);
|
||||
font-weight: 400;
|
||||
background-color: transparent;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.affine-database-number:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _getFormattedString = (value: number) => {
|
||||
const enableNewFormatting =
|
||||
this.view.featureFlags$.value.enable_number_formatting;
|
||||
const decimals = this.property.data$.value.decimal ?? 0;
|
||||
const formatMode = (this.property.data$.value.format ??
|
||||
'number') as NumberFormat;
|
||||
return enableNewFormatting
|
||||
? formatNumber(value, formatMode, decimals)
|
||||
: value.toString();
|
||||
};
|
||||
|
||||
private readonly _keydown = (e: KeyboardEvent) => {
|
||||
const ctrlKey = IS_MAC ? e.metaKey : e.ctrlKey;
|
||||
|
||||
@@ -121,9 +51,9 @@ export class NumberCellEditing extends BaseCellRenderer<
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _setValue = (str: string = this._inputEle.value) => {
|
||||
private readonly _setValue = (str: string = this._inputEle?.value) => {
|
||||
if (!str) {
|
||||
this.onChange(undefined);
|
||||
this.valueSetNextTick(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,17 +61,23 @@ export class NumberCellEditing extends BaseCellRenderer<
|
||||
this.view.featureFlags$.value.enable_number_formatting;
|
||||
const value = enableNewFormatting ? parseNumber(str) : parseFloat(str);
|
||||
if (isNaN(value)) {
|
||||
this._inputEle.value = this.value
|
||||
? this._getFormattedString(this.value)
|
||||
: '';
|
||||
if (this._inputEle) {
|
||||
this._inputEle.value = this.value
|
||||
? this._getFormattedString(this.value)
|
||||
: '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._inputEle.value = this._getFormattedString(value);
|
||||
this.onChange(value);
|
||||
if (this._inputEle) {
|
||||
this._inputEle.value = this._getFormattedString(value);
|
||||
}
|
||||
this.valueSetNextTick(value);
|
||||
};
|
||||
|
||||
focusEnd = () => {
|
||||
if (!this._inputEle) return;
|
||||
|
||||
const end = this._inputEle.value.length;
|
||||
this._inputEle.focus();
|
||||
this._inputEle.setSelectionRange(end, end);
|
||||
@@ -152,38 +88,39 @@ export class NumberCellEditing extends BaseCellRenderer<
|
||||
}
|
||||
|
||||
_focus() {
|
||||
if (!this.isEditing) {
|
||||
if (!this.isEditing$.value) {
|
||||
this.selectCurrentCell(true);
|
||||
}
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
requestAnimationFrame(() => {
|
||||
this.focusEnd();
|
||||
});
|
||||
override afterEnterEditingMode() {
|
||||
this.focusEnd();
|
||||
}
|
||||
|
||||
override onExitEditMode() {
|
||||
override beforeExitEditingMode() {
|
||||
this._setValue();
|
||||
}
|
||||
|
||||
override render() {
|
||||
const formatted = this.value ? this._getFormattedString(this.value) : '';
|
||||
if (this.isEditing$.value) {
|
||||
const formatted = this.value ? this._getFormattedString(this.value) : '';
|
||||
|
||||
return html`<input
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
.value="${formatted}"
|
||||
@keydown="${this._keydown}"
|
||||
@blur="${this._blur}"
|
||||
@focus="${this._focus}"
|
||||
class="affine-database-number number"
|
||||
@pointerdown="${stopPropagation}"
|
||||
/>`;
|
||||
return html`<input
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
.value="${formatted}"
|
||||
@keydown="${this._keydown}"
|
||||
@blur="${this._blur}"
|
||||
@focus="${this._focus}"
|
||||
class="${numberInputStyle} number"
|
||||
@pointerdown="${stopPropagation}"
|
||||
/>`;
|
||||
} else {
|
||||
return html` <div class="${numberStyle} number">
|
||||
${this._getFormattedString()}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@query('input')
|
||||
private accessor _inputEle!: HTMLInputElement;
|
||||
}
|
||||
|
||||
export const numberPropertyConfig =
|
||||
@@ -191,6 +128,5 @@ export const numberPropertyConfig =
|
||||
icon: createIcon('NumberIcon'),
|
||||
cellRenderer: {
|
||||
view: createFromBaseCellRenderer(NumberCell),
|
||||
edit: createFromBaseCellRenderer(NumberCellEditing),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const progressCellStyle = style({
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
padding: '0 4px',
|
||||
userSelect: 'none',
|
||||
});
|
||||
|
||||
export const progressContainerStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: 'var(--data-view-cell-text-line-height)',
|
||||
gap: '4px',
|
||||
});
|
||||
|
||||
export const progressBarStyle = style({
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const progressBgStyle = style({
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: '10px',
|
||||
borderRadius: '22px',
|
||||
});
|
||||
|
||||
export const progressFgStyle = style({
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
export const progressDragHandleStyle = style({
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
left: '0',
|
||||
transform: 'translate(0px, -1px)',
|
||||
width: '6px',
|
||||
height: '12px',
|
||||
borderRadius: '2px',
|
||||
opacity: '1',
|
||||
cursor: 'ew-resize',
|
||||
background: 'var(--affine-primary-color)',
|
||||
transition: 'opacity 0.2s ease-in-out',
|
||||
});
|
||||
|
||||
export const progressNumberStyle = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '18px',
|
||||
width: '25px',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
fontSize: '14px',
|
||||
fontFamily: baseTheme.fontSansFamily,
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { css, html } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { query, state } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
@@ -6,69 +6,17 @@ import { BaseCellRenderer } from '../../core/property/index.js';
|
||||
import { createFromBaseCellRenderer } from '../../core/property/renderer.js';
|
||||
import { startDrag } from '../../core/utils/drag.js';
|
||||
import { createIcon } from '../../core/utils/uni-icon.js';
|
||||
import {
|
||||
progressBarStyle,
|
||||
progressBgStyle,
|
||||
progressCellStyle,
|
||||
progressContainerStyle,
|
||||
progressDragHandleStyle,
|
||||
progressFgStyle,
|
||||
progressNumberStyle,
|
||||
} from './cell-renderer.css.js';
|
||||
import { progressPropertyModelConfig } from './define.js';
|
||||
|
||||
const styles = css`
|
||||
affine-database-progress-cell-editing {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
affine-database-progress-cell {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.affine-database-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--data-view-cell-text-line-height);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.affine-database-progress-bar {
|
||||
position: relative;
|
||||
width: 104px;
|
||||
}
|
||||
|
||||
.affine-database-progress-bg {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
border-radius: 22px;
|
||||
}
|
||||
|
||||
.affine-database-progress-fg {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.affine-database-progress-drag-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translate(0px, -1px);
|
||||
width: 6px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
opacity: 1;
|
||||
cursor: ew-resize;
|
||||
background: var(--affine-primary-color);
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.progress-number {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 18px;
|
||||
width: 25px;
|
||||
color: var(--affine-text-secondary-color);
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const progressColors = {
|
||||
empty: 'var(--affine-black-10)',
|
||||
processing: 'var(--affine-processing-color)',
|
||||
@@ -76,38 +24,9 @@ const progressColors = {
|
||||
};
|
||||
|
||||
export class ProgressCell extends BaseCellRenderer<number> {
|
||||
static override styles = styles;
|
||||
|
||||
protected override render() {
|
||||
const progress = this.value ?? 0;
|
||||
let backgroundColor = progressColors.processing;
|
||||
if (progress === 100) {
|
||||
backgroundColor = progressColors.success;
|
||||
}
|
||||
const fgStyles = styleMap({
|
||||
width: `${progress}%`,
|
||||
backgroundColor,
|
||||
});
|
||||
const bgStyles = styleMap({
|
||||
backgroundColor:
|
||||
progress === 0 ? progressColors.empty : 'var(--affine-hover-color)',
|
||||
});
|
||||
|
||||
return html` <div class="affine-database-progress">
|
||||
<div class="affine-database-progress-bar">
|
||||
<div class="affine-database-progress-bg" style=${bgStyles}>
|
||||
<div class="affine-database-progress-fg" style=${fgStyles}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-number progress">${progress}</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export class ProgressCellEditing extends BaseCellRenderer<number> {
|
||||
static override styles = styles;
|
||||
|
||||
startDrag = (event: MouseEvent) => {
|
||||
if (!this.isEditing$.value) return;
|
||||
|
||||
const bgRect = this._progressBg.getBoundingClientRect();
|
||||
const min = bgRect.left;
|
||||
const max = bgRect.right;
|
||||
@@ -135,7 +54,9 @@ export class ProgressCellEditing extends BaseCellRenderer<number> {
|
||||
};
|
||||
|
||||
get _value() {
|
||||
return this.tempValue ?? this.value ?? 0;
|
||||
return this.isEditing$.value
|
||||
? (this.tempValue ?? this.value ?? 0)
|
||||
: (this.value ?? 0);
|
||||
}
|
||||
|
||||
_onChange(value?: number) {
|
||||
@@ -146,13 +67,17 @@ export class ProgressCellEditing extends BaseCellRenderer<number> {
|
||||
const disposables = this._disposables;
|
||||
|
||||
disposables.addFromEvent(this._progressBg, 'pointerdown', this.startDrag);
|
||||
|
||||
disposables.addFromEvent(window, 'keydown', evt => {
|
||||
if (evt.key === 'ArrowDown') {
|
||||
if (!this.isEditing$.value) {
|
||||
return;
|
||||
}
|
||||
if (evt.key === 'ArrowDown' || evt.key === 'ArrowLeft') {
|
||||
evt.preventDefault();
|
||||
this._onChange(Math.max(0, this._value - 1));
|
||||
return;
|
||||
}
|
||||
if (evt.key === 'ArrowUp') {
|
||||
if (evt.key === 'ArrowUp' || evt.key === 'ArrowRight') {
|
||||
evt.preventDefault();
|
||||
this._onChange(Math.min(100, this._value + 1));
|
||||
return;
|
||||
@@ -160,20 +85,25 @@ export class ProgressCellEditing extends BaseCellRenderer<number> {
|
||||
});
|
||||
}
|
||||
|
||||
preventDefault(e: ClipboardEvent) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
override onCopy(_e: ClipboardEvent) {
|
||||
_e.preventDefault();
|
||||
this.preventDefault(_e);
|
||||
}
|
||||
|
||||
override onCut(_e: ClipboardEvent) {
|
||||
_e.preventDefault();
|
||||
this.preventDefault(_e);
|
||||
}
|
||||
|
||||
override onExitEditMode() {
|
||||
this.onChange(this._value);
|
||||
override beforeExitEditingMode() {
|
||||
const value = this._value;
|
||||
this.valueSetNextTick(value);
|
||||
}
|
||||
|
||||
override onPaste(_e: ClipboardEvent) {
|
||||
_e.preventDefault();
|
||||
this.preventDefault(_e);
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
@@ -190,25 +120,37 @@ export class ProgressCellEditing extends BaseCellRenderer<number> {
|
||||
backgroundColor:
|
||||
progress === 0 ? progressColors.empty : 'var(--affine-hover-color)',
|
||||
});
|
||||
const handleStyles = styleMap({
|
||||
left: `calc(${progress}% - 3px)`,
|
||||
});
|
||||
|
||||
return html` <div class="affine-database-progress">
|
||||
<div class="affine-database-progress-bar">
|
||||
<div class="affine-database-progress-bg" style=${bgStyles}>
|
||||
<div class="affine-database-progress-fg" style=${fgStyles}></div>
|
||||
<div
|
||||
class="affine-database-progress-drag-handle"
|
||||
style=${handleStyles}
|
||||
></div>
|
||||
return html`
|
||||
<div class="${progressCellStyle}">
|
||||
<div class="${progressContainerStyle}">
|
||||
<div class="${progressBarStyle}">
|
||||
<div
|
||||
class="${progressBgStyle}"
|
||||
data-testid="progress-background"
|
||||
style=${bgStyles}
|
||||
>
|
||||
<div class="${progressFgStyle}" style=${fgStyles}></div>
|
||||
${this.isEditing$.value
|
||||
? html` <div
|
||||
class="${progressDragHandleStyle}"
|
||||
data-testid="progress-drag-handle"
|
||||
style=${styleMap({
|
||||
left: `calc(${progress}% - 3px)`,
|
||||
})}
|
||||
></div>`
|
||||
: ''}
|
||||
</div>
|
||||
</div>
|
||||
<span class="${progressNumberStyle}" data-testid="progress"
|
||||
>${progress}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-number progress">${progress}</div>
|
||||
</div>`;
|
||||
`;
|
||||
}
|
||||
|
||||
@query('.affine-database-progress-bg')
|
||||
@query(`.${progressBgStyle}`)
|
||||
private accessor _progressBg!: HTMLElement;
|
||||
|
||||
@state()
|
||||
@@ -220,6 +162,5 @@ export const progressPropertyConfig =
|
||||
icon: createIcon('ProgressIcon'),
|
||||
cellRenderer: {
|
||||
view: createFromBaseCellRenderer(ProgressCell),
|
||||
edit: createFromBaseCellRenderer(ProgressCellEditing),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const selectStyle = style({
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
border: 'none',
|
||||
fontFamily: baseTheme.fontSansFamily,
|
||||
fontSize: 'var(--data-view-cell-text-size)',
|
||||
lineHeight: 'var(--data-view-cell-text-line-height)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '400',
|
||||
backgroundColor: 'transparent',
|
||||
wordBreak: 'break-all',
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { popupTargetFromElement } from '@blocksuite/affine-components/context-menu';
|
||||
import { computed, signal } from '@preact/signals-core';
|
||||
import { computed } from '@preact/signals-core';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
import { popTagSelect } from '../../core/component/tags/multi-tag-select.js';
|
||||
@@ -7,48 +7,26 @@ import type { SelectTag } from '../../core/index.js';
|
||||
import { BaseCellRenderer } from '../../core/property/index.js';
|
||||
import { createFromBaseCellRenderer } from '../../core/property/renderer.js';
|
||||
import { createIcon } from '../../core/utils/uni-icon.js';
|
||||
import { selectStyle } from './cell-renderer.css.js';
|
||||
import {
|
||||
type SelectPropertyData,
|
||||
selectPropertyModelConfig,
|
||||
} from './define.js';
|
||||
|
||||
export class SelectCell extends BaseCellRenderer<string[], SelectPropertyData> {
|
||||
override render() {
|
||||
const value = this.value ? [this.value] : [];
|
||||
return html`
|
||||
<affine-multi-tag-view
|
||||
.value="${value}"
|
||||
.options="${this.property.data$.value.options}"
|
||||
></affine-multi-tag-view>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectCellEditing extends BaseCellRenderer<
|
||||
string,
|
||||
SelectPropertyData
|
||||
> {
|
||||
export class SelectCell extends BaseCellRenderer<string, SelectPropertyData> {
|
||||
closePopup?: () => void;
|
||||
private readonly popTagSelect = () => {
|
||||
const value = signal(this._value);
|
||||
this._disposables.add({
|
||||
dispose: popTagSelect(
|
||||
popupTargetFromElement(
|
||||
this.querySelector('affine-multi-tag-view') ?? this
|
||||
),
|
||||
{
|
||||
name: this.cell.property.name$.value,
|
||||
mode: 'single',
|
||||
options: this.options$,
|
||||
onOptionsChange: this._onOptionsChange,
|
||||
value: signal(this._value),
|
||||
onChange: v => {
|
||||
this._onChange(v);
|
||||
value.value = v;
|
||||
},
|
||||
onComplete: this._editComplete,
|
||||
minWidth: 400,
|
||||
}
|
||||
),
|
||||
this.closePopup = popTagSelect(popupTargetFromElement(this), {
|
||||
name: this.cell.property.name$.value,
|
||||
mode: 'single',
|
||||
options: this.options$,
|
||||
onOptionsChange: this._onOptionsChange,
|
||||
value: this._value$,
|
||||
onChange: v => {
|
||||
this.valueSetImmediate(v[0]);
|
||||
},
|
||||
onComplete: this._editComplete,
|
||||
minWidth: 400,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -56,10 +34,6 @@ export class SelectCellEditing extends BaseCellRenderer<
|
||||
this.selectCurrentCell(false);
|
||||
};
|
||||
|
||||
_onChange = ([id]: string[]) => {
|
||||
this.onChange(id);
|
||||
};
|
||||
|
||||
_onOptionsChange = (options: SelectTag[]) => {
|
||||
this.property.dataUpdate(data => {
|
||||
return {
|
||||
@@ -73,21 +47,30 @@ export class SelectCellEditing extends BaseCellRenderer<
|
||||
return this.property.data$.value.options;
|
||||
});
|
||||
|
||||
get _value() {
|
||||
_value$ = computed(() => {
|
||||
const value = this.value;
|
||||
return value ? [value] : [];
|
||||
});
|
||||
|
||||
override afterEnterEditingMode() {
|
||||
if (!this.closePopup) {
|
||||
this.popTagSelect();
|
||||
}
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this.popTagSelect();
|
||||
override beforeExitEditingMode() {
|
||||
this.closePopup?.();
|
||||
this.closePopup = undefined;
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<affine-multi-tag-view
|
||||
.value="${this._value}"
|
||||
.options="${this.options$.value}"
|
||||
></affine-multi-tag-view>
|
||||
<div class="${selectStyle}">
|
||||
<affine-multi-tag-view
|
||||
.value="${this._value$.value}"
|
||||
.options="${this.options$.value}"
|
||||
></affine-multi-tag-view>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -97,6 +80,5 @@ export const selectPropertyConfig =
|
||||
icon: createIcon('SingleSelectIcon'),
|
||||
cellRenderer: {
|
||||
view: createFromBaseCellRenderer(SelectCell),
|
||||
edit: createFromBaseCellRenderer(SelectCellEditing),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const textStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
border: 'none',
|
||||
fontFamily: baseTheme.fontSansFamily,
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
lineHeight: 'var(--affine-line-height)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '400',
|
||||
backgroundColor: 'transparent',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
export const textInputStyle = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
border: 'none',
|
||||
fontFamily: baseTheme.fontSansFamily,
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
lineHeight: 'var(--affine-line-height)',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
fontWeight: '400',
|
||||
backgroundColor: 'transparent',
|
||||
cursor: 'text',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
':focus': {
|
||||
outline: 'none',
|
||||
},
|
||||
});
|
||||
@@ -1,74 +1,15 @@
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
import { css, html, unsafeCSS } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { query } from 'lit/decorators.js';
|
||||
|
||||
import { BaseCellRenderer } from '../../core/property/index.js';
|
||||
import { createFromBaseCellRenderer } from '../../core/property/renderer.js';
|
||||
import { createIcon } from '../../core/utils/uni-icon.js';
|
||||
import { textInputStyle, textStyle } from './cell-renderer.css.js';
|
||||
import { textPropertyModelConfig } from './define.js';
|
||||
|
||||
export class TextCell extends BaseCellRenderer<string> {
|
||||
static override styles = css`
|
||||
affine-database-text-cell {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.affine-database-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
font-size: var(--affine-font-base);
|
||||
line-height: var(--affine-line-height);
|
||||
color: var(--affine-text-primary-color);
|
||||
font-weight: 400;
|
||||
background-color: transparent;
|
||||
}
|
||||
`;
|
||||
|
||||
override render() {
|
||||
return html` <div class="affine-database-text">${this.value ?? ''}</div>`;
|
||||
}
|
||||
}
|
||||
export class TextCellEditing extends BaseCellRenderer<string> {
|
||||
static override styles = css`
|
||||
affine-database-text-cell-editing {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.affine-database-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
|
||||
font-size: var(--affine-font-base);
|
||||
line-height: var(--affine-line-height);
|
||||
color: var(--affine-text-primary-color);
|
||||
font-weight: 400;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.affine-database-text:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
@query('input')
|
||||
private accessor _inputEle!: HTMLInputElement;
|
||||
|
||||
private readonly _keydown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.isComposing) {
|
||||
@@ -79,35 +20,40 @@ export class TextCellEditing extends BaseCellRenderer<string> {
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _setValue = (str: string = this._inputEle.value) => {
|
||||
this._inputEle.value = `${this.value ?? ''}`;
|
||||
this.onChange(str);
|
||||
private readonly _setValue = (str: string = this._inputEle?.value) => {
|
||||
if (this._inputEle) {
|
||||
this._inputEle.value = `${this.value ?? ''}`;
|
||||
}
|
||||
this.valueSetNextTick(str);
|
||||
};
|
||||
|
||||
focusEnd = () => {
|
||||
if (!this._inputEle) return;
|
||||
|
||||
const end = this._inputEle.value.length;
|
||||
this._inputEle.focus();
|
||||
this._inputEle.setSelectionRange(end, end);
|
||||
};
|
||||
|
||||
override firstUpdated() {
|
||||
override afterEnterEditingMode() {
|
||||
this.focusEnd();
|
||||
}
|
||||
|
||||
override onExitEditMode() {
|
||||
override beforeExitEditingMode() {
|
||||
this._setValue();
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<input
|
||||
.value="${this.value ?? ''}"
|
||||
@keydown="${this._keydown}"
|
||||
class="affine-database-text"
|
||||
/>`;
|
||||
if (this.isEditing$.value) {
|
||||
return html`<input
|
||||
.value="${this.value ?? ''}"
|
||||
@keydown="${this._keydown}"
|
||||
class="${textInputStyle}"
|
||||
/>`;
|
||||
} else {
|
||||
return html`<div class="${textStyle}">${this.value ?? ''}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@query('input')
|
||||
private accessor _inputEle!: HTMLInputElement;
|
||||
}
|
||||
|
||||
export const textPropertyConfig = textPropertyModelConfig.createPropertyMeta({
|
||||
@@ -115,6 +61,5 @@ export const textPropertyConfig = textPropertyModelConfig.createPropertyMeta({
|
||||
|
||||
cellRenderer: {
|
||||
view: createFromBaseCellRenderer(TextCell),
|
||||
edit: createFromBaseCellRenderer(TextCellEditing),
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user