Frontend: SSE and pagination fixes
ci/woodpecker/push/ociImagePush Pipeline was successful Details

This commit is contained in:
Natty 2024-11-25 17:20:22 +01:00
parent ff6458ea2e
commit 8aa2a4dac4
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
2 changed files with 86 additions and 61 deletions

View File

@ -67,9 +67,11 @@ import {
computed,
ComputedRef,
isRef,
nextTick,
onActivated,
onDeactivated,
ref,
shallowRef,
watch,
} from "vue";
import * as misskey from "calckey-js";
@ -78,6 +80,7 @@ import {
getScrollContainer,
getScrollPosition,
isTopVisible,
onScrollBottom,
onScrollTop,
} from "@/scripts/scroll";
import MkButton from "@/components/MkButton.vue";
@ -85,7 +88,7 @@ import { magTransProperty } from "@/scripts-mag/mag-util";
import { i18n } from "@/i18n";
export type Paging<
E extends keyof misskey.Endpoints = keyof misskey.Endpoints,
E extends keyof misskey.Endpoints = keyof misskey.Endpoints
> = {
endpoint: E;
limit: number;
@ -119,7 +122,7 @@ const props = withDefaults(
}>(),
{
displayLimit: 30,
},
}
);
const emit = defineEmits<{
@ -129,8 +132,8 @@ const emit = defineEmits<{
type Item = { id: string; createdAt?: string; created_at?: string } & any;
const rootEl = ref<HTMLElement>();
const items = ref<Item[]>([]);
const queue = ref<Item[]>([]);
const items = shallowRef<Item[]>([]);
const queue = shallowRef<Item[]>([]);
const offset = ref(0);
const fetching = ref(true);
const moreFetching = ref(false);
@ -166,7 +169,7 @@ const init = async (): Promise<void> => {
(err) => {
error.value = true;
fetching.value = false;
},
}
);
};
@ -206,7 +209,7 @@ const refresh = async (): Promise<void> => {
(err) => {
error.value = true;
fetching.value = false;
},
}
);
};
@ -241,8 +244,8 @@ const fetchMore = async (): Promise<void> => {
magTransProperty(
lastItem,
"createdAt",
"created_at",
),
"created_at"
)
).getTime()
: undefined,
untilId: lastItem?.id ?? undefined,
@ -259,7 +262,7 @@ const fetchMore = async (): Promise<void> => {
},
(err) => {
moreFetching.value = false;
},
}
);
};
@ -276,53 +279,66 @@ const isFresh = (): boolean => {
const pos = getScrollPosition(rootEl.value);
const viewHeight = container.clientHeight;
const height = container.scrollHeight;
const isBottom = pos + viewHeight > height - 32;
return isBottom;
return pos + viewHeight > height - 32;
} else {
const isTop =
return (
isBackTop.value ||
(document.body.contains(rootEl.value) &&
isTopVisible(rootEl.value));
return isTop;
(document.body.contains(rootEl.value) && isTopVisible(rootEl.value))
);
}
};
const unqueue = () => {
const queueRemoved = [...queue.value].reverse();
queue.value = [];
if (props.pagination.reversed) {
items.value = [...items.value, ...queueRemoved].slice(
0,
-props.displayLimit
);
} else {
items.value = [...queueRemoved, ...items.value].slice(
0,
props.displayLimit
);
}
};
const prepend = (item: Item): void => {
if (props.pagination.reversed) {
if (isFresh()) {
items.value = items.value.slice(-props.displayLimit);
hasMore.value = true;
}
items.value.push(item);
} else {
if (isFresh()) {
// Prepend the item
items.value = [item, ...items.value].slice(0, props.displayLimit);
} else {
if (!rootEl.value) {
items.value.unshift(item);
return;
}
if (!queue.value.length) {
onScrollTop(rootEl.value, () => {
const queueRemoved = [...queue.value].reverse();
queue.value = [];
items.value = [...queueRemoved, ...items.value].slice(
0,
props.displayLimit,
);
});
}
queue.value = [...queue.value, item].slice(-props.displayLimit);
}
if (!rootEl.value) {
queue.value = [...queue.value, item].slice(-props.displayLimit);
return;
}
};
const append = (item: Item): void => {
items.value.push(item);
if (!isFresh()) {
if (!queue.value.length) {
(props.pagination.reversed ? onScrollBottom : onScrollTop)(
rootEl.value,
() => {
nextTick(unqueue);
}
);
}
queue.value = [...queue.value, item].slice(-props.displayLimit);
return;
}
if (items.value.length > props.displayLimit) {
queue.value = [...queue.value, item].slice(-props.displayLimit);
if (!queue.value.length) {
nextTick(unqueue);
}
return;
}
if (props.pagination.reversed) {
items.value = [...items.value, item];
} else {
items.value = [item, ...items.value];
}
};
const removeItem = (finder: (item: Item) => boolean): boolean => {
@ -331,7 +347,7 @@ const removeItem = (finder: (item: Item) => boolean): boolean => {
return false;
}
items.value.splice(i, 1);
items.value = items.value.toSpliced(i, 1);
return true;
};
@ -341,7 +357,9 @@ const updateItem = (id: Item["id"], replacer: (old: Item) => Item): boolean => {
return false;
}
items.value[i] = replacer(items.value[i]);
const newItems = [...items.value];
newItems[i] = replacer(items.value[i]);
items.value = newItems;
return true;
};
@ -349,14 +367,10 @@ if (props.pagination.params && isRef(props.pagination.params)) {
watch(props.pagination.params, init, { deep: true });
}
watch(
queue,
(a, b) => {
if (a.length === 0 && b.length === 0) return;
emit("queue", queue.value.length);
},
{ deep: true },
);
watch(queue, (a, b) => {
if (a.length === 0 && b.length === 0) return;
emit("queue", queue.value.length);
});
init();
@ -375,7 +389,6 @@ defineExpose({
reload,
refresh,
prepend,
append,
removeItem,
updateItem,
isFresh,
@ -387,6 +400,7 @@ defineExpose({
.fade-leave-active {
transition: opacity 0.125s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
@ -398,9 +412,11 @@ defineExpose({
margin-right: auto;
}
}
.list > :deep(._button) {
margin-inline: auto;
margin-bottom: 16px;
&:last-of-type:not(:first-child) {
margin-top: 16px;
}

View File

@ -113,7 +113,16 @@ export class MagEventChannel extends EventEmitter<{
let buf = "";
while (true) {
const res = await Promise.race([reader.read(), this.closePromise]);
const res = await Promise.race([
reader.read().catch((e) => {
console.error(e);
return {
done: true,
value: undefined,
};
}),
this.closePromise,
]);
if (res === "cancelled") break;
@ -122,7 +131,7 @@ export class MagEventChannel extends EventEmitter<{
setTimeout(
() => this.connect(),
this.backoffBase *
Math.pow(this.backoffFactor, this.attempts)
Math.pow(this.backoffFactor, this.attempts)
);
this.attempts++;
break;