Use tabWidth 4 without actual tabs.

This commit is contained in:
Paul
2021-07-05 11:25:20 +01:00
parent 7bd33d8d34
commit b5a11d5c8f
180 changed files with 16619 additions and 16622 deletions

View File

@@ -9,198 +9,198 @@ import { RendererRoutines, RenderState, ScrollState } from "./types";
export const SMOOTH_SCROLL_ON_RECEIVE = false;
export class SingletonRenderer extends EventEmitter3 {
client?: Client;
channel?: string;
state: RenderState;
currentRenderer: RendererRoutines;
client?: Client;
channel?: string;
state: RenderState;
currentRenderer: RendererRoutines;
stale = false;
fetchingTop = false;
fetchingBottom = false;
stale = false;
fetchingTop = false;
fetchingBottom = false;
constructor() {
super();
constructor() {
super();
this.receive = this.receive.bind(this);
this.edit = this.edit.bind(this);
this.delete = this.delete.bind(this);
this.receive = this.receive.bind(this);
this.edit = this.edit.bind(this);
this.delete = this.delete.bind(this);
this.state = { type: "LOADING" };
this.currentRenderer = SimpleRenderer;
}
this.state = { type: "LOADING" };
this.currentRenderer = SimpleRenderer;
}
private receive(message: Message) {
this.currentRenderer.receive(this, message);
}
private receive(message: Message) {
this.currentRenderer.receive(this, message);
}
private edit(id: string, patch: Partial<Message>) {
this.currentRenderer.edit(this, id, patch);
}
private edit(id: string, patch: Partial<Message>) {
this.currentRenderer.edit(this, id, patch);
}
private delete(id: string) {
this.currentRenderer.delete(this, id);
}
private delete(id: string) {
this.currentRenderer.delete(this, id);
}
subscribe(client: Client) {
if (this.client) {
this.client.removeListener("message", this.receive);
this.client.removeListener("message/update", this.edit);
this.client.removeListener("message/delete", this.delete);
}
subscribe(client: Client) {
if (this.client) {
this.client.removeListener("message", this.receive);
this.client.removeListener("message/update", this.edit);
this.client.removeListener("message/delete", this.delete);
}
this.client = client;
client.addListener("message", this.receive);
client.addListener("message/update", this.edit);
client.addListener("message/delete", this.delete);
}
this.client = client;
client.addListener("message", this.receive);
client.addListener("message/update", this.edit);
client.addListener("message/delete", this.delete);
}
private setStateUnguarded(state: RenderState, scroll?: ScrollState) {
this.state = state;
this.emit("state", state);
private setStateUnguarded(state: RenderState, scroll?: ScrollState) {
this.state = state;
this.emit("state", state);
if (scroll) {
this.emit("scroll", scroll);
}
}
if (scroll) {
this.emit("scroll", scroll);
}
}
setState(id: string, state: RenderState, scroll?: ScrollState) {
if (id !== this.channel) return;
this.setStateUnguarded(state, scroll);
}
setState(id: string, state: RenderState, scroll?: ScrollState) {
if (id !== this.channel) return;
this.setStateUnguarded(state, scroll);
}
markStale() {
this.stale = true;
}
markStale() {
this.stale = true;
}
async init(id: string) {
this.channel = id;
this.stale = false;
this.setStateUnguarded({ type: "LOADING" });
await this.currentRenderer.init(this, id);
}
async init(id: string) {
this.channel = id;
this.stale = false;
this.setStateUnguarded({ type: "LOADING" });
await this.currentRenderer.init(this, id);
}
async reloadStale(id: string) {
if (this.stale) {
this.stale = false;
await this.init(id);
}
}
async reloadStale(id: string) {
if (this.stale) {
this.stale = false;
await this.init(id);
}
}
async loadTop(ref?: HTMLDivElement) {
if (this.fetchingTop) return;
this.fetchingTop = true;
async loadTop(ref?: HTMLDivElement) {
if (this.fetchingTop) return;
this.fetchingTop = true;
function generateScroll(end: string): ScrollState {
if (ref) {
let heightRemoved = 0;
let messageContainer = ref.children[0];
if (messageContainer) {
for (let child of Array.from(messageContainer.children)) {
// If this child has a ulid.
if (child.id?.length === 26) {
// Check whether it was removed.
if (child.id.localeCompare(end) === 1) {
heightRemoved +=
child.clientHeight +
// We also need to take into account the top margin of the container.
parseInt(
window
.getComputedStyle(child)
.marginTop.slice(0, -2),
);
}
}
}
}
function generateScroll(end: string): ScrollState {
if (ref) {
let heightRemoved = 0;
let messageContainer = ref.children[0];
if (messageContainer) {
for (let child of Array.from(messageContainer.children)) {
// If this child has a ulid.
if (child.id?.length === 26) {
// Check whether it was removed.
if (child.id.localeCompare(end) === 1) {
heightRemoved +=
child.clientHeight +
// We also need to take into account the top margin of the container.
parseInt(
window
.getComputedStyle(child)
.marginTop.slice(0, -2),
);
}
}
}
}
return {
type: "OffsetTop",
previousHeight: ref.scrollHeight - heightRemoved,
};
} else {
return {
type: "OffsetTop",
previousHeight: 0,
};
}
}
return {
type: "OffsetTop",
previousHeight: ref.scrollHeight - heightRemoved,
};
} else {
return {
type: "OffsetTop",
previousHeight: 0,
};
}
}
await this.currentRenderer.loadTop(this, generateScroll);
await this.currentRenderer.loadTop(this, generateScroll);
// Allow state updates to propagate.
setTimeout(() => (this.fetchingTop = false), 0);
}
// Allow state updates to propagate.
setTimeout(() => (this.fetchingTop = false), 0);
}
async loadBottom(ref?: HTMLDivElement) {
if (this.fetchingBottom) return;
this.fetchingBottom = true;
async loadBottom(ref?: HTMLDivElement) {
if (this.fetchingBottom) return;
this.fetchingBottom = true;
function generateScroll(start: string): ScrollState {
if (ref) {
let heightRemoved = 0;
let messageContainer = ref.children[0];
if (messageContainer) {
for (let child of Array.from(messageContainer.children)) {
// If this child has a ulid.
if (child.id?.length === 26) {
// Check whether it was removed.
if (child.id.localeCompare(start) === -1) {
heightRemoved +=
child.clientHeight +
// We also need to take into account the top margin of the container.
parseInt(
window
.getComputedStyle(child)
.marginTop.slice(0, -2),
);
}
}
}
}
function generateScroll(start: string): ScrollState {
if (ref) {
let heightRemoved = 0;
let messageContainer = ref.children[0];
if (messageContainer) {
for (let child of Array.from(messageContainer.children)) {
// If this child has a ulid.
if (child.id?.length === 26) {
// Check whether it was removed.
if (child.id.localeCompare(start) === -1) {
heightRemoved +=
child.clientHeight +
// We also need to take into account the top margin of the container.
parseInt(
window
.getComputedStyle(child)
.marginTop.slice(0, -2),
);
}
}
}
}
return {
type: "ScrollTop",
y: ref.scrollTop - heightRemoved,
};
} else {
return {
type: "ScrollToBottom",
};
}
}
return {
type: "ScrollTop",
y: ref.scrollTop - heightRemoved,
};
} else {
return {
type: "ScrollToBottom",
};
}
}
await this.currentRenderer.loadBottom(this, generateScroll);
await this.currentRenderer.loadBottom(this, generateScroll);
// Allow state updates to propagate.
setTimeout(() => (this.fetchingBottom = false), 0);
}
// Allow state updates to propagate.
setTimeout(() => (this.fetchingBottom = false), 0);
}
async jumpToBottom(id: string, smooth: boolean) {
if (id !== this.channel) return;
if (this.state.type === "RENDER" && this.state.atBottom) {
this.emit("scroll", { type: "ScrollToBottom", smooth });
} else {
await this.currentRenderer.init(this, id, true);
}
}
async jumpToBottom(id: string, smooth: boolean) {
if (id !== this.channel) return;
if (this.state.type === "RENDER" && this.state.atBottom) {
this.emit("scroll", { type: "ScrollToBottom", smooth });
} else {
await this.currentRenderer.init(this, id, true);
}
}
}
export const SingletonMessageRenderer = new SingletonRenderer();
export function useRenderState(id: string) {
const [state, setState] = useState<Readonly<RenderState>>(
SingletonMessageRenderer.state,
);
if (typeof id === "undefined") return;
const [state, setState] = useState<Readonly<RenderState>>(
SingletonMessageRenderer.state,
);
if (typeof id === "undefined") return;
function render(state: RenderState) {
setState(state);
}
function render(state: RenderState) {
setState(state);
}
useEffect(() => {
SingletonMessageRenderer.addListener("state", render);
return () => SingletonMessageRenderer.removeListener("state", render);
}, [id]);
useEffect(() => {
SingletonMessageRenderer.addListener("state", render);
return () => SingletonMessageRenderer.removeListener("state", render);
}, [id]);
return state;
return state;
}

View File

@@ -4,180 +4,180 @@ import { SMOOTH_SCROLL_ON_RECEIVE } from "../Singleton";
import { RendererRoutines } from "../types";
export const SimpleRenderer: RendererRoutines = {
init: async (renderer, id, smooth) => {
if (renderer.client!.websocket.connected) {
renderer
.client!.channels.fetchMessagesWithUsers(id, {}, true)
.then(({ messages: data }) => {
data.reverse();
let messages = data.map((x) => mapMessage(x));
renderer.setState(
id,
{
type: "RENDER",
messages,
atTop: data.length < 50,
atBottom: true,
},
{ type: "ScrollToBottom", smooth },
);
});
} else {
renderer.setState(id, { type: "WAITING_FOR_NETWORK" });
}
},
receive: async (renderer, message) => {
if (message.channel !== renderer.channel) return;
if (renderer.state.type !== "RENDER") return;
if (renderer.state.messages.find((x) => x._id === message._id)) return;
if (!renderer.state.atBottom) return;
init: async (renderer, id, smooth) => {
if (renderer.client!.websocket.connected) {
renderer
.client!.channels.fetchMessagesWithUsers(id, {}, true)
.then(({ messages: data }) => {
data.reverse();
let messages = data.map((x) => mapMessage(x));
renderer.setState(
id,
{
type: "RENDER",
messages,
atTop: data.length < 50,
atBottom: true,
},
{ type: "ScrollToBottom", smooth },
);
});
} else {
renderer.setState(id, { type: "WAITING_FOR_NETWORK" });
}
},
receive: async (renderer, message) => {
if (message.channel !== renderer.channel) return;
if (renderer.state.type !== "RENDER") return;
if (renderer.state.messages.find((x) => x._id === message._id)) return;
if (!renderer.state.atBottom) return;
let messages = [...renderer.state.messages, mapMessage(message)];
let atTop = renderer.state.atTop;
if (messages.length > 150) {
messages = messages.slice(messages.length - 150);
atTop = false;
}
let messages = [...renderer.state.messages, mapMessage(message)];
let atTop = renderer.state.atTop;
if (messages.length > 150) {
messages = messages.slice(messages.length - 150);
atTop = false;
}
renderer.setState(
message.channel,
{
...renderer.state,
messages,
atTop,
},
{ type: "StayAtBottom", smooth: SMOOTH_SCROLL_ON_RECEIVE },
);
},
edit: async (renderer, id, patch) => {
const channel = renderer.channel;
if (!channel) return;
if (renderer.state.type !== "RENDER") return;
renderer.setState(
message.channel,
{
...renderer.state,
messages,
atTop,
},
{ type: "StayAtBottom", smooth: SMOOTH_SCROLL_ON_RECEIVE },
);
},
edit: async (renderer, id, patch) => {
const channel = renderer.channel;
if (!channel) return;
if (renderer.state.type !== "RENDER") return;
let messages = [...renderer.state.messages];
let index = messages.findIndex((x) => x._id === id);
let messages = [...renderer.state.messages];
let index = messages.findIndex((x) => x._id === id);
if (index > -1) {
let message = { ...messages[index], ...mapMessage(patch) };
messages.splice(index, 1, message);
if (index > -1) {
let message = { ...messages[index], ...mapMessage(patch) };
messages.splice(index, 1, message);
renderer.setState(
channel,
{
...renderer.state,
messages,
},
{ type: "StayAtBottom" },
);
}
},
delete: async (renderer, id) => {
const channel = renderer.channel;
if (!channel) return;
if (renderer.state.type !== "RENDER") return;
renderer.setState(
channel,
{
...renderer.state,
messages,
},
{ type: "StayAtBottom" },
);
}
},
delete: async (renderer, id) => {
const channel = renderer.channel;
if (!channel) return;
if (renderer.state.type !== "RENDER") return;
let messages = [...renderer.state.messages];
let index = messages.findIndex((x) => x._id === id);
let messages = [...renderer.state.messages];
let index = messages.findIndex((x) => x._id === id);
if (index > -1) {
messages.splice(index, 1);
if (index > -1) {
messages.splice(index, 1);
renderer.setState(
channel,
{
...renderer.state,
messages,
},
{ type: "StayAtBottom" },
);
}
},
loadTop: async (renderer, generateScroll) => {
const channel = renderer.channel;
if (!channel) return;
renderer.setState(
channel,
{
...renderer.state,
messages,
},
{ type: "StayAtBottom" },
);
}
},
loadTop: async (renderer, generateScroll) => {
const channel = renderer.channel;
if (!channel) return;
const state = renderer.state;
if (state.type !== "RENDER") return;
if (state.atTop) return;
const state = renderer.state;
if (state.type !== "RENDER") return;
if (state.atTop) return;
const { messages: data } =
await renderer.client!.channels.fetchMessagesWithUsers(
channel,
{
before: state.messages[0]._id,
},
true,
);
const { messages: data } =
await renderer.client!.channels.fetchMessagesWithUsers(
channel,
{
before: state.messages[0]._id,
},
true,
);
if (data.length === 0) {
return renderer.setState(channel, {
...state,
atTop: true,
});
}
if (data.length === 0) {
return renderer.setState(channel, {
...state,
atTop: true,
});
}
data.reverse();
let messages = [...data.map((x) => mapMessage(x)), ...state.messages];
data.reverse();
let messages = [...data.map((x) => mapMessage(x)), ...state.messages];
let atTop = false;
if (data.length < 50) {
atTop = true;
}
let atTop = false;
if (data.length < 50) {
atTop = true;
}
let atBottom = state.atBottom;
if (messages.length > 150) {
messages = messages.slice(0, 150);
atBottom = false;
}
let atBottom = state.atBottom;
if (messages.length > 150) {
messages = messages.slice(0, 150);
atBottom = false;
}
renderer.setState(
channel,
{ ...state, atTop, atBottom, messages },
generateScroll(messages[messages.length - 1]._id),
);
},
loadBottom: async (renderer, generateScroll) => {
const channel = renderer.channel;
if (!channel) return;
renderer.setState(
channel,
{ ...state, atTop, atBottom, messages },
generateScroll(messages[messages.length - 1]._id),
);
},
loadBottom: async (renderer, generateScroll) => {
const channel = renderer.channel;
if (!channel) return;
const state = renderer.state;
if (state.type !== "RENDER") return;
if (state.atBottom) return;
const state = renderer.state;
if (state.type !== "RENDER") return;
if (state.atBottom) return;
const { messages: data } =
await renderer.client!.channels.fetchMessagesWithUsers(
channel,
{
after: state.messages[state.messages.length - 1]._id,
sort: "Oldest",
},
true,
);
const { messages: data } =
await renderer.client!.channels.fetchMessagesWithUsers(
channel,
{
after: state.messages[state.messages.length - 1]._id,
sort: "Oldest",
},
true,
);
if (data.length === 0) {
return renderer.setState(channel, {
...state,
atBottom: true,
});
}
if (data.length === 0) {
return renderer.setState(channel, {
...state,
atBottom: true,
});
}
let messages = [...state.messages, ...data.map((x) => mapMessage(x))];
let messages = [...state.messages, ...data.map((x) => mapMessage(x))];
let atBottom = false;
if (data.length < 50) {
atBottom = true;
}
let atBottom = false;
if (data.length < 50) {
atBottom = true;
}
let atTop = state.atTop;
if (messages.length > 150) {
messages = messages.slice(messages.length - 150);
atTop = false;
}
let atTop = state.atTop;
if (messages.length > 150) {
messages = messages.slice(messages.length - 150);
atTop = false;
}
renderer.setState(
channel,
{ ...state, atTop, atBottom, messages },
generateScroll(messages[0]._id),
);
},
renderer.setState(
channel,
{ ...state, atTop, atBottom, messages },
generateScroll(messages[0]._id),
);
},
};

View File

@@ -5,44 +5,44 @@ import { MessageObject } from "../../context/revoltjs/util";
import { SingletonRenderer } from "./Singleton";
export type ScrollState =
| { type: "Free" }
| { type: "Bottom"; scrollingUntil?: number }
| { type: "ScrollToBottom" | "StayAtBottom"; smooth?: boolean }
| { type: "OffsetTop"; previousHeight: number }
| { type: "ScrollTop"; y: number };
| { type: "Free" }
| { type: "Bottom"; scrollingUntil?: number }
| { type: "ScrollToBottom" | "StayAtBottom"; smooth?: boolean }
| { type: "OffsetTop"; previousHeight: number }
| { type: "ScrollTop"; y: number };
export type RenderState =
| {
type: "LOADING" | "WAITING_FOR_NETWORK" | "EMPTY";
}
| {
type: "RENDER";
atTop: boolean;
atBottom: boolean;
messages: MessageObject[];
};
| {
type: "LOADING" | "WAITING_FOR_NETWORK" | "EMPTY";
}
| {
type: "RENDER";
atTop: boolean;
atBottom: boolean;
messages: MessageObject[];
};
export interface RendererRoutines {
init: (
renderer: SingletonRenderer,
id: string,
smooth?: boolean,
) => Promise<void>;
init: (
renderer: SingletonRenderer,
id: string,
smooth?: boolean,
) => Promise<void>;
receive: (renderer: SingletonRenderer, message: Message) => Promise<void>;
edit: (
renderer: SingletonRenderer,
id: string,
partial: Partial<Message>,
) => Promise<void>;
delete: (renderer: SingletonRenderer, id: string) => Promise<void>;
receive: (renderer: SingletonRenderer, message: Message) => Promise<void>;
edit: (
renderer: SingletonRenderer,
id: string,
partial: Partial<Message>,
) => Promise<void>;
delete: (renderer: SingletonRenderer, id: string) => Promise<void>;
loadTop: (
renderer: SingletonRenderer,
generateScroll: (end: string) => ScrollState,
) => Promise<void>;
loadBottom: (
renderer: SingletonRenderer,
generateScroll: (start: string) => ScrollState,
) => Promise<void>;
loadTop: (
renderer: SingletonRenderer,
generateScroll: (end: string) => ScrollState,
) => Promise<void>;
loadBottom: (
renderer: SingletonRenderer,
generateScroll: (start: string) => ScrollState,
) => Promise<void>;
}