2021-01-22 14:59:05 +11:00
|
|
|
import React, {
|
|
|
|
|
useEffect,
|
|
|
|
|
useState,
|
|
|
|
|
useContext,
|
|
|
|
|
useCallback,
|
|
|
|
|
useRef,
|
|
|
|
|
} from "react";
|
2020-11-26 16:29:10 +11:00
|
|
|
import * as Comlink from "comlink";
|
2021-01-27 16:24:13 +11:00
|
|
|
import { decode } from "@msgpack/msgpack";
|
2020-05-19 16:21:01 +10:00
|
|
|
|
2021-02-06 13:32:38 +11:00
|
|
|
import { useAuth } from "./AuthContext";
|
|
|
|
|
import { useDatabase } from "./DatabaseContext";
|
2020-05-19 16:21:01 +10:00
|
|
|
|
2020-11-26 16:29:10 +11:00
|
|
|
import DatabaseWorker from "worker-loader!../workers/DatabaseWorker"; // eslint-disable-line import/no-webpack-loader-syntax
|
|
|
|
|
|
2020-05-19 16:21:01 +10:00
|
|
|
import { tokens as defaultTokens } from "../tokens";
|
|
|
|
|
|
|
|
|
|
const TokenDataContext = React.createContext();
|
|
|
|
|
|
2020-09-11 16:56:40 +10:00
|
|
|
const cachedTokenMax = 100;
|
|
|
|
|
|
2021-01-22 14:59:05 +11:00
|
|
|
const worker = Comlink.wrap(new DatabaseWorker());
|
|
|
|
|
|
2020-05-19 16:21:01 +10:00
|
|
|
export function TokenDataProvider({ children }) {
|
2021-02-06 13:32:38 +11:00
|
|
|
const { database, databaseStatus } = useDatabase();
|
|
|
|
|
const { userId } = useAuth();
|
2020-05-19 16:21:01 +10:00
|
|
|
|
2021-02-08 16:53:56 +11:00
|
|
|
/**
|
|
|
|
|
* Contains all tokens without any file data,
|
|
|
|
|
* to ensure file data is present call loadTokens
|
|
|
|
|
*/
|
2020-05-19 16:21:01 +10:00
|
|
|
const [tokens, setTokens] = useState([]);
|
2020-11-26 16:29:10 +11:00
|
|
|
const [tokensLoading, setTokensLoading] = useState(true);
|
2020-05-19 16:21:01 +10:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2020-10-23 22:16:18 +11:00
|
|
|
if (!userId || !database || databaseStatus === "loading") {
|
2020-05-19 16:21:01 +10:00
|
|
|
return;
|
|
|
|
|
}
|
2021-02-08 16:53:56 +11:00
|
|
|
function getDefaultTokens() {
|
2020-05-19 19:03:36 +10:00
|
|
|
const defaultTokensWithIds = [];
|
|
|
|
|
for (let defaultToken of defaultTokens) {
|
|
|
|
|
defaultTokensWithIds.push({
|
|
|
|
|
...defaultToken,
|
2020-05-24 15:18:30 +10:00
|
|
|
id: `__default-${defaultToken.name}`,
|
2020-05-19 19:03:36 +10:00
|
|
|
owner: userId,
|
2020-10-01 22:32:21 +10:00
|
|
|
group: "default",
|
2020-05-19 19:03:36 +10:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return defaultTokensWithIds;
|
2020-05-19 16:21:01 +10:00
|
|
|
}
|
|
|
|
|
|
2021-02-08 16:53:56 +11:00
|
|
|
// Loads tokens without the file data to save memory
|
2020-05-19 19:03:36 +10:00
|
|
|
async function loadTokens() {
|
2021-01-27 16:24:13 +11:00
|
|
|
let storedTokens = [];
|
|
|
|
|
// Try to load tokens with worker, fallback to database if failed
|
|
|
|
|
const packedTokens = await worker.loadData("tokens");
|
|
|
|
|
if (packedTokens) {
|
|
|
|
|
storedTokens = decode(packedTokens);
|
|
|
|
|
} else {
|
|
|
|
|
console.warn("Unable to load tokens with worker, loading may be slow");
|
2021-02-08 16:53:56 +11:00
|
|
|
await database.table("tokens").each((token) => {
|
|
|
|
|
const { file, resolutions, ...rest } = token;
|
|
|
|
|
storedTokens.push(rest);
|
|
|
|
|
});
|
2021-01-27 16:24:13 +11:00
|
|
|
}
|
2020-05-19 19:03:36 +10:00
|
|
|
const sortedTokens = storedTokens.sort((a, b) => b.created - a.created);
|
2021-02-08 16:53:56 +11:00
|
|
|
const defaultTokensWithIds = getDefaultTokens();
|
2020-05-19 19:03:36 +10:00
|
|
|
const allTokens = [...sortedTokens, ...defaultTokensWithIds];
|
|
|
|
|
setTokens(allTokens);
|
2020-11-26 16:29:10 +11:00
|
|
|
setTokensLoading(false);
|
2020-05-19 19:03:36 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadTokens();
|
2020-10-23 22:16:18 +11:00
|
|
|
}, [userId, database, databaseStatus]);
|
2020-05-19 19:03:36 +10:00
|
|
|
|
2021-01-22 14:59:05 +11:00
|
|
|
const tokensRef = useRef(tokens);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
tokensRef.current = tokens;
|
|
|
|
|
}, [tokens]);
|
|
|
|
|
|
|
|
|
|
const getToken = useCallback((tokenId) => {
|
|
|
|
|
return tokensRef.current.find((token) => token.id === tokenId);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const getTokenFromDB = useCallback(
|
|
|
|
|
async (tokenId) => {
|
|
|
|
|
let token = await database.table("tokens").get(tokenId);
|
|
|
|
|
return token;
|
|
|
|
|
},
|
|
|
|
|
[database]
|
|
|
|
|
);
|
2020-09-11 16:56:40 +10:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Keep up to cachedTokenMax amount of tokens that you don't own
|
|
|
|
|
* Sorted by when they we're last used
|
|
|
|
|
*/
|
2021-01-22 14:59:05 +11:00
|
|
|
const updateCache = useCallback(async () => {
|
2020-09-11 16:56:40 +10:00
|
|
|
const cachedTokens = await database
|
|
|
|
|
.table("tokens")
|
|
|
|
|
.where("owner")
|
|
|
|
|
.notEqual(userId)
|
|
|
|
|
.sortBy("lastUsed");
|
|
|
|
|
if (cachedTokens.length > cachedTokenMax) {
|
|
|
|
|
const cacheDeleteCount = cachedTokens.length - cachedTokenMax;
|
|
|
|
|
const idsToDelete = cachedTokens
|
|
|
|
|
.slice(0, cacheDeleteCount)
|
|
|
|
|
.map((token) => token.id);
|
|
|
|
|
database.table("tokens").where("id").anyOf(idsToDelete).delete();
|
|
|
|
|
setTokens((prevTokens) => {
|
|
|
|
|
return prevTokens.filter((token) => !idsToDelete.includes(token.id));
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-01-22 14:59:05 +11:00
|
|
|
}, [database, userId]);
|
|
|
|
|
|
|
|
|
|
const addToken = useCallback(
|
|
|
|
|
async (token) => {
|
|
|
|
|
await database.table("tokens").add(token);
|
|
|
|
|
if (token.owner !== userId) {
|
|
|
|
|
await updateCache();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[database, updateCache, userId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const removeToken = useCallback(
|
|
|
|
|
async (id) => {
|
|
|
|
|
await database.table("tokens").delete(id);
|
|
|
|
|
},
|
|
|
|
|
[database]
|
|
|
|
|
);
|
2020-05-19 22:15:08 +10:00
|
|
|
|
2021-01-22 14:59:05 +11:00
|
|
|
const removeTokens = useCallback(
|
|
|
|
|
async (ids) => {
|
|
|
|
|
await database.table("tokens").bulkDelete(ids);
|
|
|
|
|
},
|
|
|
|
|
[database]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const updateToken = useCallback(
|
|
|
|
|
async (id, update) => {
|
|
|
|
|
const change = { ...update, lastModified: Date.now() };
|
|
|
|
|
await database.table("tokens").update(id, change);
|
|
|
|
|
setTokens((prevTokens) => {
|
|
|
|
|
const newTokens = [...prevTokens];
|
|
|
|
|
const i = newTokens.findIndex((token) => token.id === id);
|
|
|
|
|
if (i > -1) {
|
|
|
|
|
newTokens[i] = { ...newTokens[i], ...change };
|
|
|
|
|
}
|
|
|
|
|
return newTokens;
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[database]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const updateTokens = useCallback(
|
|
|
|
|
async (ids, update) => {
|
|
|
|
|
const change = { ...update, lastModified: Date.now() };
|
|
|
|
|
await Promise.all(
|
|
|
|
|
ids.map((id) => database.table("tokens").update(id, change))
|
|
|
|
|
);
|
|
|
|
|
setTokens((prevTokens) => {
|
|
|
|
|
const newTokens = [...prevTokens];
|
|
|
|
|
for (let id of ids) {
|
|
|
|
|
const i = newTokens.findIndex((token) => token.id === id);
|
|
|
|
|
if (i > -1) {
|
|
|
|
|
newTokens[i] = { ...newTokens[i], ...change };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return newTokens;
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[database]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const putToken = useCallback(
|
|
|
|
|
async (token) => {
|
|
|
|
|
await database.table("tokens").put(token);
|
|
|
|
|
setTokens((prevTokens) => {
|
|
|
|
|
const newTokens = [...prevTokens];
|
|
|
|
|
const i = newTokens.findIndex((t) => t.id === token.id);
|
|
|
|
|
if (i > -1) {
|
|
|
|
|
newTokens[i] = { ...newTokens[i], ...token };
|
|
|
|
|
} else {
|
|
|
|
|
newTokens.unshift(token);
|
|
|
|
|
}
|
|
|
|
|
return newTokens;
|
|
|
|
|
});
|
|
|
|
|
if (token.owner !== userId) {
|
|
|
|
|
await updateCache();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[database, updateCache, userId]
|
|
|
|
|
);
|
2020-05-19 19:03:36 +10:00
|
|
|
|
2021-02-08 16:53:56 +11:00
|
|
|
const loadTokens = useCallback(
|
|
|
|
|
async (tokenIds) => {
|
|
|
|
|
const loadedTokens = await database.table("tokens").bulkGet(tokenIds);
|
|
|
|
|
const loadedTokensById = loadedTokens.reduce((obj, token) => {
|
|
|
|
|
obj[token.id] = token;
|
|
|
|
|
return obj;
|
|
|
|
|
}, {});
|
|
|
|
|
setTokens((prevTokens) => {
|
|
|
|
|
return prevTokens.map((prevToken) => {
|
|
|
|
|
if (prevToken.id in loadedTokensById) {
|
|
|
|
|
return loadedTokensById[prevToken.id];
|
|
|
|
|
} else {
|
|
|
|
|
return prevToken;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[database]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const unloadTokens = useCallback(async () => {
|
|
|
|
|
setTokens((prevTokens) => {
|
|
|
|
|
return prevTokens.map((prevToken) => {
|
|
|
|
|
const { file, ...rest } = prevToken;
|
|
|
|
|
return rest;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
2021-02-14 18:35:42 +11:00
|
|
|
// Create DB observable to sync creating and deleting
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!database || databaseStatus === "loading") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleMapChanges(changes) {
|
|
|
|
|
for (let change of changes) {
|
|
|
|
|
if (change.table === "tokens") {
|
|
|
|
|
if (change.type === 1) {
|
|
|
|
|
// Created
|
|
|
|
|
const token = change.obj;
|
|
|
|
|
setTokens((prevTokens) => [token, ...prevTokens]);
|
|
|
|
|
} else if (change.type === 3) {
|
|
|
|
|
// Deleted
|
|
|
|
|
const id = change.key;
|
|
|
|
|
setTokens((prevTokens) => {
|
|
|
|
|
const filtered = prevTokens.filter((token) => token.id !== id);
|
|
|
|
|
return filtered;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
database.on("changes", handleMapChanges);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
database.on("changes").unsubscribe(handleMapChanges);
|
|
|
|
|
};
|
|
|
|
|
}, [database, databaseStatus]);
|
|
|
|
|
|
2020-05-19 19:03:36 +10:00
|
|
|
const ownedTokens = tokens.filter((token) => token.owner === userId);
|
|
|
|
|
|
2020-05-22 20:43:07 +10:00
|
|
|
const tokensById = tokens.reduce((obj, token) => {
|
|
|
|
|
obj[token.id] = token;
|
|
|
|
|
return obj;
|
|
|
|
|
}, {});
|
|
|
|
|
|
2020-05-19 19:03:36 +10:00
|
|
|
const value = {
|
|
|
|
|
tokens,
|
|
|
|
|
ownedTokens,
|
|
|
|
|
addToken,
|
|
|
|
|
removeToken,
|
2020-10-01 22:32:21 +10:00
|
|
|
removeTokens,
|
2020-05-19 19:03:36 +10:00
|
|
|
updateToken,
|
2020-10-01 22:32:21 +10:00
|
|
|
updateTokens,
|
2020-05-19 19:03:36 +10:00
|
|
|
putToken,
|
2020-05-19 22:15:08 +10:00
|
|
|
getToken,
|
2020-05-22 20:43:07 +10:00
|
|
|
tokensById,
|
2020-11-26 16:29:10 +11:00
|
|
|
tokensLoading,
|
2021-01-22 14:59:05 +11:00
|
|
|
getTokenFromDB,
|
2021-02-08 16:53:56 +11:00
|
|
|
loadTokens,
|
|
|
|
|
unloadTokens,
|
2020-05-19 19:03:36 +10:00
|
|
|
};
|
2020-05-19 16:21:01 +10:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<TokenDataContext.Provider value={value}>
|
|
|
|
|
{children}
|
|
|
|
|
</TokenDataContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-06 13:32:38 +11:00
|
|
|
export function useTokenData() {
|
|
|
|
|
const context = useContext(TokenDataContext);
|
|
|
|
|
if (context === undefined) {
|
|
|
|
|
throw new Error("useTokenData must be used within a TokenDataProvider");
|
|
|
|
|
}
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-19 16:21:01 +10:00
|
|
|
export default TokenDataContext;
|