fastcomments-react-native-sdk
Version:
React Native FastComments Components. Add live commenting to any React Native application.
166 lines (151 loc) • 7.37 kB
text/typescript
import {ImmutableArray, ImmutableObject, none, State} from "@hookstate/core";
import {EditorNodeNewLine, EditorNodeWithoutChildren} from "./node-types";
// We used to use a doubly linked list but it was a huge pain.
export function graphToListStateWithNewlines(nodes: State<EditorNodeNewLine[]>): (State<EditorNodeNewLine> | State<EditorNodeWithoutChildren>)[] {
const result: (State<EditorNodeNewLine> | State<EditorNodeWithoutChildren>)[] = [];
for (const node of nodes) {
result.push(node);
if (node.children) {
(node.children as State<EditorNodeWithoutChildren[]>).forEach(function (child) {
result.push(child);
});
}
}
return result;
}
export function graphToListStateWithoutNewlines(nodes: State<EditorNodeNewLine[]>): State<EditorNodeWithoutChildren>[] {
const result: State<EditorNodeWithoutChildren>[] = [];
for (const node of nodes) {
if (node.children) {
(node.children as State<EditorNodeWithoutChildren[]>).forEach(function (child) {
result.push(child);
});
}
}
return result;
}
export function graphToListWithNewlines(nodes: ImmutableArray<EditorNodeNewLine>): ImmutableObject<(EditorNodeNewLine | EditorNodeWithoutChildren)>[] {
const result: ImmutableObject<(EditorNodeNewLine | EditorNodeWithoutChildren)>[] = [];
for (const node of nodes) {
result.push(node);
if (node.children) {
(node.children as EditorNodeWithoutChildren[]).forEach(function (child) {
result.push(child);
});
}
}
return result;
}
// just easier type system wise if this is its own thing
export function graphToListWithoutNewlines(nodes: EditorNodeNewLine[]): EditorNodeWithoutChildren[] {
const result: (EditorNodeNewLine | EditorNodeWithoutChildren)[] = [];
for (const node of nodes) {
if (node.children) {
(node.children as EditorNodeWithoutChildren[]).forEach(function (child) {
result.push(child);
});
}
}
return result as EditorNodeWithoutChildren[];
}
export function getStatePrev(nodes: State<EditorNodeNewLine[]>, id: number): State<EditorNodeWithoutChildren> | null {
const list = graphToListStateWithoutNewlines(nodes);
const index = list.findIndex((searchingNode) => searchingNode.id.get() === id);
// without this check - weird behavior in delete flow
if (index - 1 < 0) {
return null;
}
return list[index - 1] as State<EditorNodeWithoutChildren>;
}
export function getStateNext(nodes: State<EditorNodeNewLine[]>, id: number): State<EditorNodeWithoutChildren> | null {
const list = graphToListStateWithoutNewlines(nodes);
const index = list.findIndex((searchingNode) => searchingNode.id.get() === id);
// without this check - weird behavior in delete flow
if (index + 1 > list.length - 1) {
return null;
}
return list[index + 1] as State<EditorNodeWithoutChildren>;
}
// we should always have a node (at least a text node) so this should never return null! otherwise is a bug.
export function getStateLast(nodes: State<EditorNodeNewLine[]>): State<EditorNodeWithoutChildren> {
let newlineCount = nodes.length;
while (newlineCount--) {
const line = nodes[newlineCount];
if (line && line !== none && line.children && line.children!.get()!.length > 0) {
// @ts-ignore fuck typescript
return line.children[line.children.length - 1];
}
}
throw new Error('getStateLast could not find a visible node other than a newline! This should not happen.');
}
// we should always have a node (at least a text node) so this should never return null! otherwise is a bug.
export function getLast(nodes: EditorNodeNewLine[]): EditorNodeWithoutChildren {
let newlineCount = nodes.length;
while (newlineCount--) {
const line = nodes[newlineCount];
if (line && line !== none && line.children && line.children!.length > 0) {
// @ts-ignore fuck typescript
return line.children[line.children.length - 1];
}
}
throw new Error('getLast could not find a visible node other than a newline! This should not happen.');
}
export function getNext(nodes: EditorNodeNewLine[], id: number): EditorNodeWithoutChildren | null {
const list = graphToListWithoutNewlines(nodes);
const index = list.findIndex((searchingNode) => searchingNode.id === id);
// without this check - weird behavior in delete flow
if (index + 1 > list.length - 1) {
return null;
}
return list[index + 1];
}
export function getPrev(nodes: EditorNodeNewLine[], id: number): EditorNodeWithoutChildren | null {
const list = graphToListWithoutNewlines(nodes);
const index = list.findIndex((searchingNode) => searchingNode.id === id);
// without this check - weird behavior in delete flow
if (index - 1 < 0) {
return null;
}
return list[index - 1];
}
export function getStateByIdFromMap(nodes: Record<string, State<EditorNodeWithoutChildren>>, id: number) {
return nodes[id];
}
export function getLastFocused(graph: EditorNodeNewLine[]) {
// MAYBE OPTIMIZE: we had a ton of problems with keeping track of the last selected node with storing references to a raw js object or State<>
// due to hookstatejs (or maybe not using it correctly). Maybe a React expert can help us :)
// UPDATE: Probably way to do it is editorState = { lastNode?: Node } - this way optionals are supported via usehookstate
const hasBeenFocused = graphToListWithoutNewlines(graph);
if (hasBeenFocused.length === 0) {
// it really feels like we are using the library wrong if we have to cast like this?
const children = graph[0].children; // we ALWAYS have a root node
if (children && children.length > 0) {
return children[0];
} else {
throw new Error('Could not determine current node!!!'); // if this happens it's a bug.
}
}
hasBeenFocused.sort((a, b) => {
return b.lastFocusTime! - a.lastFocusTime!;
});
return hasBeenFocused[0];
}
export function getLastFocusedState(graph: State<EditorNodeNewLine[]>) {
// MAYBE OPTIMIZE: we had a ton of problems with keeping track of the last selected node with storing references to a raw js object or State<>
// due to hookstatejs (or maybe not using it correctly). Maybe a React expert can help us :)
// UPDATE: Probably way to do it is editorState = { lastNode?: Node } - this way optionals are supported via usehookstate
const hasBeenFocused = graphToListStateWithoutNewlines(graph);
if (hasBeenFocused.length === 0) {
// it really feels like we are using the library wrong if we have to cast like this?
const children = graph[0].children as unknown as State<EditorNodeWithoutChildren>[]; // we ALWAYS have a root node
if (children && children.length > 0) {
return children[0];
} else {
throw new Error('Could not determine current node!!!'); // if this happens it's a bug.
}
}
hasBeenFocused.sort((a, b) => {
return b.lastFocusTime.get({stealth: true})! - a.lastFocusTime.get({stealth: true})!;
});
return hasBeenFocused[0];
}