Skip to content

Instantly share code, notes, and snippets.

@laukaichung
Created December 3, 2022 04:17
Show Gist options
  • Save laukaichung/87335dcae16875d9ac7cfcea7da76c40 to your computer and use it in GitHub Desktop.
Save laukaichung/87335dcae16875d9ac7cfcea7da76c40 to your computer and use it in GitHub Desktop.
import rfdc from "rfdc";
import { CrudInterface, Database, OmitId, Table } from "brackets-manager";
import create from "zustand";
import { devtools, persist, StateStorage } from "zustand/middleware";
import {
Group,
Match,
MatchGame,
Participant,
Round,
Stage,
} from "brackets-model";
import localForage from "localforage";
const zustandStorage: StateStorage = {
getItem: async (name: string): Promise<string | null> => {
return (await localForage.getItem(name)) || null;
},
setItem: async (name: string, value: string): Promise<void> => {
await localForage.setItem(name, value);
},
removeItem: async (name: string): Promise<void> => {
await localForage.removeItem(name);
},
};
const clone = rfdc();
class State implements Database {
stage: Stage[];
group: Group[];
round: Round[];
match: Match[];
match_game: MatchGame[];
participant: Participant[];
}
export const useBracketManagerStorage = create<State>()(
devtools(
persist(
(set, getState) => {
const state = getState();
return {
stage: state?.stage || [],
group: state?.group || [],
round: state?.round || [],
match: state?.match || [],
match_game: state?.match_game || [],
participant: state?.participant || [],
};
},
{
name: "tournament",
getStorage: () => zustandStorage,
}
)
)
);
export class ZustandDatabase implements CrudInterface {
/**
* @param data "import" data from external
*/
setData(data: Database): void {
useBracketManagerStorage.setState(data);
}
/**
* @param partial Filter
*/
makeFilter(partial: any): (entry: any) => boolean {
return (entry: any): boolean => {
let result = true;
for (const key of Object.keys(partial))
result = result && entry[key] === partial[key];
return result;
};
}
/**
* Clearing all of the data
*/
reset(): void {
useBracketManagerStorage.setState({
participant: [],
stage: [],
group: [],
round: [],
match: [],
match_game: [],
});
}
insert<T>(table: Table, value: OmitId<T>): Promise<number>;
/**
* Inserts multiple values in the database.
*
* @param table Where to insert.
* @param values What to insert.
*/
insert<T>(table: Table, values: OmitId<T>[]): Promise<boolean>;
/**
* Implementation of insert
*
* @param table Where to insert.
* @param values What to insert.
*/
insert<T>(
table: Table,
values: OmitId<T> | OmitId<T>[]
): Promise<number> | Promise<boolean> {
const data = useBracketManagerStorage.getState();
let id =
data[table].length > 0
? Math.max(...data[table].map((d) => d.id)) + 1
: 0;
if (!Array.isArray(values)) {
try {
// @ts-ignore
data[table].push({ id, ...values });
setState(data, table);
} catch (error) {
return new Promise<number>((resolve) => {
resolve(-1);
});
}
return new Promise<number>((resolve) => {
resolve(id);
});
}
try {
values.map((object) => {
// @ts-ignore
data[table].push({ id: id++, ...object });
});
setState(data, table);
} catch (error) {
return new Promise<boolean>((resolve) => {
resolve(false);
});
}
return new Promise<boolean>((resolve) => {
resolve(true);
});
}
/**
* Gets all data from a table in the database.
*
* @param table Where to get from.
*/
select<T>(table: Table): Promise<T[] | null>;
/**
* Gets specific data from a table in the database.
*
* @param table Where to get from.
* @param id What to get.
*/
select<T>(table: Table, id: number): Promise<T | null>;
/**
* Gets data from a table in the database with a filter.
*
* @param table Where to get from.
* @param filter An object to filter data.
*/
select<T>(table: Table, filter: Partial<T>): Promise<T[] | null>;
/**
* @param table Where to get from.
* @param arg Arg.
*/
select<T>(table: Table, arg?: number | Partial<T>): Promise<T[] | null> {
const data = useBracketManagerStorage.getState();
try {
if (arg === undefined) {
return new Promise<T[]>((resolve) => {
// @ts-ignore
resolve(data[table].map(clone));
});
}
if (typeof arg === "number") {
return new Promise<T[]>((resolve) => {
console.log(table, data[table]);
// @ts-ignore
resolve(clone(data[table].find((d) => d.id === arg)));
});
}
return new Promise<T[] | null>((resolve) => {
// @ts-ignore
resolve(data[table].filter(this.makeFilter(arg)).map(clone));
});
} catch (error) {
return new Promise<null>((resolve) => {
resolve(null);
});
}
}
/**
* Updates data in a table.
*
* @param table Where to update.
* @param id What to update.
* @param value How to update.
*/
update<T>(table: Table, id: number, value: T): Promise<boolean>;
/**
* Updates data in a table.
*
* @param table Where to update.
* @param filter An object to filter data.
* @param value How to update.
*/
update<T>(
table: Table,
filter: Partial<T>,
value: Partial<T>
): Promise<boolean>;
/**
* Updates data in a table.
*
* @param table Where to update.
* @param arg
* @param value How to update.
*/
update<T>(
table: Table,
arg: number | Partial<T>,
value?: Partial<T>
): Promise<boolean> {
const data = useBracketManagerStorage.getState();
if (typeof arg === "number") {
try {
// @ts-ignore
data[table][arg] = value;
setState(data, table);
return new Promise<boolean>((resolve) => {
resolve(true);
});
} catch (error) {
return new Promise<boolean>((resolve) => {
resolve(false);
});
}
}
// @ts-ignore
const values = data[table].filter(this.makeFilter(arg));
if (!values) {
return new Promise<boolean>((resolve) => {
resolve(false);
});
}
values.forEach((v: { id: any }) => {
const existing = data[table][v.id];
for (const key in value) {
if (
// @ts-ignore
existing[key] &&
// @ts-ignore
typeof existing[key] === "object" &&
typeof value[key] === "object"
) {
// @ts-ignore
Object.assign(existing[key], value[key]); // For opponent objects, this does a deep merge of level 2.
} else {
// @ts-ignore
existing[key] = value[key]; // Otherwise, do a simple value assignment.
}
}
data[table][v.id] = existing;
setState(data, table);
});
return new Promise<boolean>((resolve) => {
resolve(true);
});
}
/**
* Empties a table completely.
*
* @param table Where to delete everything.
*/
delete(table: Table): Promise<boolean>;
/**
* Delete data in a table, based on a filter.
*
* @param table Where to delete in.
* @param filter An object to filter data.
*/
delete<T>(table: Table, filter: Partial<T>): Promise<boolean>;
/**
* Delete data in a table, based on a filter.
*
* @param table Where to delete in.
* @param filter An object to filter data.
*/
delete<T>(table: Table, filter?: Partial<T>): Promise<boolean> {
const data = useBracketManagerStorage.getState();
const values = data[table];
if (!values) {
return new Promise<boolean>((resolve) => {
resolve(false);
});
}
if (!filter) {
useBracketManagerStorage.setState({
[table]: [],
});
return new Promise<boolean>((resolve) => {
resolve(true);
});
}
const predicate = this.makeFilter(filter);
const negativeFilter = (value: any): boolean => !predicate(value);
// @ts-ignore
data[table] = values.filter(negativeFilter);
setState(data, table);
return new Promise<boolean>((resolve) => {
resolve(true);
});
}
}
function setState(data: State, table: Table) {
useBracketManagerStorage.setState({
[table]: [...data[table]],
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment