Frontend: SSE and pagination fixes
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
This commit is contained in:
parent
ff6458ea2e
commit
8aa2a4dac4
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue