Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database table view supports pin/unpin column #9617

Merged
merged 5 commits into from
Nov 10, 2023

Conversation

Zuoqiu-Yingyi
Copy link
Contributor

  • Please commit to the dev branch
  • For contributing new features, please supplement and improve the corresponding user guide documents
  • For bug fixes, please describe the problem and solution via code comments
  • For text improvements (such as typos and wording adjustments), please submit directly
  • 属性视图支持冻结首行/尾行/首列
    The attribute view supports freezing the first row / last row / first column

REF: #9280 (comment)

TESTED

@88250 88250 requested a review from Vanessa219 November 10, 2023 00:33
@Vanessa219 Vanessa219 merged commit 433f4d5 into siyuan-note:dev Nov 10, 2023
@88250 88250 added this to the 2.10.15 milestone Nov 10, 2023
88250 added a commit that referenced this pull request Nov 10, 2023
@Vanessa219 Vanessa219 changed the title Attribute view support sticky layout Database table view supports pin/unpin column Nov 10, 2023
Vanessa219 added a commit that referenced this pull request Nov 10, 2023
@Vanessa219
Copy link
Member

添加对每列进行固定的功能。

@Zuoqiu-Yingyi
Copy link
Contributor Author

这里怎么又回滚了👀

@Vanessa219
Copy link
Member

后面和 D 讨论了下,要做成每一列都可以 pin 住的那种。非常抱歉。目前后端已经提供了 setAttrViewColPin 接口。

@Zuoqiu-Yingyi
Copy link
Contributor Author

后面和 D 讨论了下,要做成每一列都可以 pin 住的那种。非常抱歉。目前后端已经提供了 setAttrViewColPin 接口。

首列是指选择框那一列,是功能性的,与pin住其他列不冲突

同时本次PR还用8小时还将首行与尾行冻结与面包屑高度解耦,不能移除啊

@Vanessa219
Copy link
Member

代码无法兼容,比如 pin 住第三列,需要把前三列和选择列放到一个 div 中。

首行与尾行固定已经实现,和面包屑的关系是?

@Zuoqiu-Yingyi
Copy link
Contributor Author

代码无法兼容,比如 pin 住第三列,需要把前三列和选择列放到一个 div 中。

为啥不用粘性布局实现。。。

首行与尾行固定已经实现,和面包屑的关系是?

位置计算时面包屑高度是写死的,无法自适应其他主题,且不能在其他容器块内使用

@Zuoqiu-Yingyi
Copy link
Contributor Author

我封装了一个使用 js 实现类粘性布局的工具

@Zuoqiu-Yingyi
Copy link
Contributor Author

Zuoqiu-Yingyi commented Nov 10, 2023

代码无法兼容,比如 pin 住第三列,需要把前三列和选择列放到一个 div 中。

我看最近的提交没有相关的内容啊

要是还没开始实现的话先不要实现, 我想讨论一下目标效果, 我瞅瞅有无更好的实现方案

@Vanessa219
Copy link
Member

  1. 这部分还没写,也是计划用的 sticky,这个应该是 notion 新加的功能,可以参考下。感觉他这样虽然只 pin 了一列,但是可以有很多列一起 pin,也不需要去计算过多的宽度,应该还不错,只是拖拽列后需要同步修改一下。
    PS:这个 PR 样式改的有点多,导致和原有需求冲突了。后续比较大的需求可以讨论清楚再进行修改,尽量不要改动到原有的交互,抱歉之前没有说清楚。
  2. 大部分地方偷懒了,我先去改一下 scroll 事件中的,其他地方后面再进行修改。

@Zuoqiu-Yingyi
Copy link
Contributor Author

Zuoqiu-Yingyi commented Nov 10, 2023

  1. 这部分还没写,也是计划用的 sticky,这个应该是 notion 新加的功能,可以参考下。感觉他这样虽然只 pin 了一列,但是可以有很多列一起 pin,也不需要去计算过多的宽度,应该还不错,只是拖拽列后需要同步修改一下。

没看明白这与本 PR 中首列冻结在哪里存在冲突

PS:这个 PR 样式改的有点多,导致和原有需求冲突了。后续比较大的需求可以讨论清楚再进行修改,尽量不要改动到原有的交互,抱歉之前没有说清楚。

这里样式大都与首列冻结相关, 而且经过了测试, 且没有改动原有交互, 也没有和原有需求冲突

  1. 大部分地方偷懒了,我先去改一下 scroll 事件中的,其他地方后面再进行修改。

这里同时涉及了不少样式, 最好不要动, 我再提交一个 PR 吧

@Zuoqiu-Yingyi
Copy link
Contributor Author

pin 住第三列,需要把前三列和选择列放到一个 div 中

这里完全可以将这个 div 设置为 position: sticky, 然后默认将 av__firstcol 列塞到该 div 里面作为第一个元素, 这样就可以兼容了

Vanessa219 added a commit that referenced this pull request Nov 10, 2023
@Vanessa219
Copy link
Member

主要是细节方面,比如但不限于以下问题:

  1. 添加行没有对齐
  2. 添加行上面线条较粗
  3. 选择列右侧多了竖线
  4. 光标移动到行上不需要出现选择框,仅当移动到选择列时才出现

pin 住的具体需求准备参考 notion 的,至于代码可以按照比较简洁的方式进行。

event.ts 中常量已经替换,可更新到最新代码。

@Zuoqiu-Yingyi
Copy link
Contributor Author

Zuoqiu-Yingyi commented Nov 10, 2023

  1. 添加行没有对齐

这里的对齐是指?

  1. 添加行上面线条较粗

已修复

  1. 选择列右侧多了竖线

这是特性, 首列实际上作为功能控件, 应与表格内容之间存在可见的分界线 (类似于 Excel 左侧行号与内容的分界线)

  1. 光标移动到行上不需要出现选择框,仅当移动到选择列时才出现

这是特性

  • 光标移动到选择列时出现选择框: 用户无法在需要选择时快速定位选择框 (需要进行如下 5 个步骤)
    1. 用户目光定位到首列
    2. 用户将光标移动至首列
    3. 选择框显示
    4. 用户目光定位到选择框
    5. 用户将光标移动到选择框并点击
  • 光标移动行上即出现选择框: 用户可在需要选择时快速定位选择框 (需要进行如下 2 个步骤)
    1. 用户目光定位到选择框
    2. 用户将光标移动到选择框并点击

pin 住的具体需求准备参考 notion 的,至于代码可以按照比较简洁的方式进行。

那先这样吧

event.ts 中常量已经替换,可更新到最新代码。

一些插件会为编辑器添加更多的面包屑栏, 此时仍然无法适配
不要在这基础上修修补补了, 我已经完美解决该问题, 且解耦抽象出一个注释清晰明了的工具函数...

export const stickyScrollY = (
view: HTMLElement, // 视口元素
container: HTMLElement, // 容器元素
topElements: IStickyElementY[] = [], // 顶部粘性元素
bottomElements: IStickyElementY[] = [], // 底部粘性元素
) => {
if (topElements.length === 0 && bottomElements.length === 0) {
return;
}
const viewRect = view.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
/**
* ┏---------------┓
* | view |
* ┗---------------┛ → viewRect.bottom
* ┌-----------┐ --→ containerRect.top
* | container |
* └-----------┘
* ====== OR ======
* ┌-----------┐
* | container |
* └-----------┘ --→ containerRect.bottom
* ┏---------------┓ → viewRect.top
* | view |
* ┗---------------┛
*/
if (viewRect.bottom <= containerRect.top || containerRect.bottom <= viewRect.top) {
return;
}
const topContext: IStickyContextY[] = topElements.map(item => {
const rect = item.element.getBoundingClientRect();
const base = Number.parseFloat(item.element.style.top) || 0;
item.offset ??= 0;
return {
...item,
rect,
base,
origin: {
top: rect.top - base,
bottom: rect.bottom - base,
},
current: {
top: rect.top,
bottom: rect.bottom,
},
target: {
top: null,
bottom: null,
},
style: {
top: null,
bottom: null,
}
};
});
const bottomContext: IStickyContextY[] = bottomElements.map(item => {
const rect = item.element.getBoundingClientRect();
const base = Number.parseFloat(item.element.style.bottom) || 0;
item.offset ??= 0;
return {
...item,
rect,
base,
origin: {
top: rect.top + base,
bottom: rect.bottom + base,
},
current: {
top: rect.top,
bottom: rect.bottom,
},
target: {
top: null,
bottom: null,
},
style: {
top: null,
bottom: null,
}
};
});
let handleTop = false;
let handleBottom = false;
switch (true) {
/**
* ┏---------------┓ → viewRect.top
* | |
* | view |
* | ┌-----------┐ | → containerRect.top
* ┗-╊-----------╊-┛ → viewRect.bottom
* | container |
* └-----------┘ --→ containerRect.bottom
*/
case viewRect.top <= containerRect.top
&& containerRect.top <= viewRect.bottom
&& viewRect.top <= viewRect.bottom:
handleBottom = true;
break;
/**
* ┏---------------┓ → viewRect.top
* | view |
* | ┌-----------┐ | → containerRect.top
* | | container | |
* | └-----------┘ | → containerRect.bottom
* ┗---------------┛ → viewRect.bottom
*/
case viewRect.top <= containerRect.top
&& containerRect.bottom <= viewRect.bottom:
break;
/**
* ┌-----------┐ --→ containerRect.top
* ┏-╊-----------╊-┓ → viewRect.top
* | | | |}→ view
* ┗-╊-----------╊-┛ → viewRect.bottom
* | container |
* └-----------┘ --→ containerRect.bottom
*/
case containerRect.top <= viewRect.top
&& viewRect.bottom <= containerRect.bottom:
handleTop = true;
handleBottom = true;
break;
/**
* ┌-----------┐ --→ containerRect.top
* | container |
* ┏-╊-----------╊-┓ → viewRect.top
* | └-----------┘ | → containerRect.bottom
* | view |
* ┗-|-----------|-┛ → viewRect.bottom
*/
case containerRect.top <= viewRect.top
&& viewRect.top <= containerRect.bottom
&& containerRect.bottom <= viewRect.bottom:
handleTop = true;
break;
default:
break;
}
if (handleTop) {
if (topContext.length > 0) {
topContext.reduceRight((next, current) => {
switch (true) {
/**
* ┌-----------┐ --→ containerRect.top
* ┏-╊-----------╊-┓ → viewRect.top
* | | ┌┄┄┄┄┄┄┄┐ | | → current.target.top - current.offset
* | | ├╌╌╌╌╌╌╌┤ | | → current.origin.top
* | | └╌╌╌╌╌╌╌┘ | | → current.origin.bottom
* | | ┌╌╌╌╌╌╌╌┐ | | → next.origin.top
* | | └╌╌╌╌╌╌╌┘ | | → next.origin.bottom
* ┗-╊-----------╊-┛ → viewRect.bottom
* └-----------┘ --→ containerRect.bottom
*/
case viewRect.top <= (current.origin.top - current.offset):
current.target.top = current.origin.top;
current.target.bottom = current.origin.bottom;
current.style.top = null;
break;
/**
* ┌-----------┐ --→ containerRect.top
* | ┌╌╌╌╌╌╌╌┐ | --→ current.origin.top
* | └╌╌╌╌╌╌╌┘ | --→ current.origin.bottom
* ┏-╊-----------╊-┓ → viewRect.top
* | | ┌-------┐ | | → current.target.top
* | | └-------┘ | | → current.target.bottom
* ┗-╊-----------╊-┛ → viewRect.bottom
* | container |
* └-----------┘ --→ containerRect.bottom
*/
default:
current.target.top = viewRect.top + current.offset;
current.target.bottom = current.target.top + current.rect.height;
const nextTop = next
? Math.min(next.target.top, next.origin.top, containerRect.bottom)
: containerRect.bottom;
if (nextTop < current.target.bottom) {
const diff = nextTop - current.target.bottom;
current.target.top += diff;
current.target.bottom += diff;
}
current.style.top = current.base + (current.target.top - current.current.top);
break;
}
return current;
}, null);
}
}
if (handleBottom) {
if (bottomContext.length > 0) {
bottomContext.reduce((last, current) => {
switch (true) {
/**
* ┌-----------┐ --→ containerRect.top
* ┏-╊-----------╊-┓ → viewRect.top
* | | ┌╌╌╌╌╌╌╌┐ | | → last.origin.top
* | | └╌╌╌╌╌╌╌┘ | | → last.origin.bottom
* | | ┌╌╌╌╌╌╌╌┐ | | → current.origin.top
* | | ├╌╌╌╌╌╌╌┤ | | → current.origin.bottom
* | | └┄┄┄┄┄┄┄┘ | | → current.target.bottom + current.offset
* ┗-╊-----------╊-┛ → viewRect.bottom
* └-----------┘ --→ containerRect.bottom
*/
case (current.origin.bottom + current.offset) <= viewRect.bottom:
current.target.top = current.origin.top;
current.target.bottom = current.origin.bottom;
current.style.bottom = null;
break;
/**
* ┌-----------┐ --→ containerRect.top
* ┏-╊-----------╊-┓ → viewRect.top
* | | ┌-------┐ | | → current.target.top
* | | └-------┘ | | → current.target.bottom
* ┗-╊-----------╊-┛ → viewRect.bottom
* | ┌╌╌╌╌╌╌╌┐ | --→ current.origin.top
* | └╌╌╌╌╌╌╌┘ | --→ current.origin.bottom
* | container |
* └-----------┘ --→ containerRect.bottom
*/
default:
current.target.bottom = viewRect.bottom - current.offset;
current.target.top = current.target.bottom - current.rect.height;
const lastBottom = last
? Math.max(last.target.bottom, last.origin.bottom, containerRect.top)
: containerRect.top;
if (current.target.top < lastBottom) {
const diff = lastBottom - current.target.top;
current.target.top += diff;
current.target.bottom += diff;
}
current.style.bottom = current.base - (current.target.bottom - current.current.bottom);
break;
}
return current;
}, null);
}
}
[...topContext, ...bottomContext].forEach(item => {
item.element.style.top = item.style.top ? `${item.style.top}px` : null;
item.element.style.bottom = item.style.bottom ? `${item.style.bottom}px` : null;
});
}

@Vanessa219
Copy link
Member

原有交互和你 PR 的特性我们还需要再斟酌下。

可以把面包屑问题的重现步骤录屏发一下么?以便再检查一下其他相关的地方有没有类似问题。

@Zuoqiu-Yingyi
Copy link
Contributor Author

可以把面包屑问题的重现步骤录屏发一下么?以便再检查一下其他相关的地方有没有类似问题。

安装一下集市中的 (伪)文档面包屑 syplugin-fakeDocBreadcrumb 插件即可

Vanessa219 added a commit that referenced this pull request Nov 13, 2023
Vanessa219 added a commit that referenced this pull request Nov 13, 2023
@Vanessa219
Copy link
Member

看上去是正常的

scroll.mp4

Vanessa219 added a commit that referenced this pull request Nov 14, 2023
Zuoqiu-Yingyi added a commit to Zuoqiu-Yingyi/siyuan that referenced this pull request Nov 14, 2023
@Zuoqiu-Yingyi
Copy link
Contributor Author

看上去是正常的

经过排查我定位到该问题实际上是因为横向滚动条导致的
REF: #9625 (comment)

@Vanessa219
Copy link
Member

要怎么重现?

@Zuoqiu-Yingyi
Copy link
Contributor Author

要怎么重现?

属性视图宽度超过文档宽度, 属性视图横向滚动条出现时尾行出现错位

@Vanessa219
Copy link
Member

横向滚动应该和面包屑关系不大。我这里还是重现不了你说的问题,麻烦录一下屏,谢谢。

QQ20231114-232602-HD.mp4

@Zuoqiu-Yingyi
Copy link
Contributor Author

横向滚动应该和面包屑关系不大。我这里还是重现不了你说的问题,麻烦录一下屏,谢谢。

多添加几行, 导致尾行超出视口启用类粘性布局时出现

@Vanessa219
Copy link
Member

还是无法重现

@Vanessa219
Copy link
Member

具体是什么样子呢?

QQ20231115-003652-HD.mp4

@Zuoqiu-Yingyi
Copy link
Contributor Author

Zuoqiu-Yingyi commented Nov 14, 2023

具体是什么样子呢?

QQ20231115-003652-HD.mp4

该视频在向上滚动时可以看出尾行位置在跳动
已于 #9655 修复

@Vanessa219
Copy link
Member

好的,我们在新 PR 上讨论这个问题吧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants