enhancement(web): add react suspense and improve DX (#1089)

* add react suspense, fix broken stuff, clean up code, improve DX

enhancement: added react suspense + spinner to show loading (still can be added in certain places)
chore: cleaned up Header/NavBar code
chore: cleaned up DeleteModal code
chore: cleaned up other relevant code
enhancement: changed remove button style to be much more pleasant (see e.g. filter tabs)
fix: made active tab on filters page to be blue (as it should've been) when active
fix: fixed ghost delimiter which was only visible when DeleteModal was active in FormButtonGroup
chore: removed most of linter warnings/errors
fix: fixed incorrect/double modal transition in FilterExternalItem
fix: fixed incorrect z-height on Options popover in Settings/IRC (would've been visible when Add new was clicked)
enhancement: improved robustness of all Context classes to support seamless new-feature expansion (#866)
enhancement: improved expand logic (see #994 comments)

* reverted irc expand view to previous design

* forgot to propagate previous z-height fix

* jinxed it

* add license header to new files

---------

Co-authored-by: martylukyy <35452459+martylukyy@users.noreply.github.com>
Co-authored-by: Kyle Sanderson <kyle.leet@gmail.com>
This commit is contained in:
stacksmash76 2023-09-10 12:35:43 +02:00 committed by GitHub
parent cbf668e87c
commit 2fed48e0dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 845 additions and 737 deletions

View file

@ -4,53 +4,13 @@
*/
import { newRidgeState } from "react-ridge-state";
export const InitializeGlobalContext = () => {
const auth_ctx = localStorage.getItem("auth");
if (auth_ctx)
AuthContext.set(JSON.parse(auth_ctx));
const settings_ctx = localStorage.getItem("settings");
if (settings_ctx) {
SettingsContext.set(JSON.parse(settings_ctx));
} else {
// Only check for light theme, otherwise dark theme is the default
SettingsContext.set((state) => ({
...state,
darkTheme: !(
window.matchMedia !== undefined &&
window.matchMedia("(prefers-color-scheme: light)").matches
)
}));
}
const filterList_ctx = localStorage.getItem("filterList");
if (filterList_ctx) {
FilterListContext.set(JSON.parse(filterList_ctx));
}
};
import type { StateWithValue } from "react-ridge-state";
interface AuthInfo {
username: string;
isLoggedIn: boolean;
}
export const AuthContext = newRidgeState<AuthInfo>(
{
username: "",
isLoggedIn: false
},
{
onSet: (new_state) => {
try {
localStorage.setItem("auth", JSON.stringify(new_state));
} catch (e) {
console.log("An error occurred while trying to modify the local auth context state.");
console.log("Error:", e);
}
}
}
);
interface SettingsType {
debug: boolean;
checkForUpdates: boolean;
@ -60,83 +20,111 @@ interface SettingsType {
hideWrappedText: boolean;
}
export const SettingsContext = newRidgeState<SettingsType>(
{
debug: false,
checkForUpdates: true,
darkTheme: true,
scrollOnNewLog: false,
indentLogLines: false,
hideWrappedText: false
},
{
onSet: (new_state) => {
try {
document.documentElement.classList.toggle("dark", new_state.darkTheme);
localStorage.setItem("settings", JSON.stringify(new_state));
} catch (e) {
console.log("An error occurred while trying to modify the local settings context state.");
console.log("Error:", e);
}
}
}
);
export type FilterListState = {
indexerFilter: string[],
indexerFilter: string[];
sortOrder: string;
status: string;
};
// Default values
const AuthContextDefaults: AuthInfo = {
username: "",
isLoggedIn: false
};
const SettingsContextDefaults: SettingsType = {
debug: false,
checkForUpdates: true,
darkTheme: true,
scrollOnNewLog: false,
indentLogLines: false,
hideWrappedText: false
};
const FilterListContextDefaults: FilterListState = {
indexerFilter: [],
sortOrder: "",
status: ""
};
// eslint-disable-next-line
function ContextMerger<T extends {}>(
key: string,
defaults: T,
ctxState: StateWithValue<T>
) {
const storage = localStorage.getItem(key);
if (!storage) {
// Nothing to do. We already have a value thanks to react-ridge-state.
return;
}
try {
const json = JSON.parse(storage);
if (json === null) {
console.warn(`JSON localStorage value for '${key}' context state is null`);
return;
}
Object.keys(defaults).forEach((key) => {
const propName = key as unknown as keyof T;
// Check if JSON in localStorage is missing newly added key
if (!Object.prototype.hasOwnProperty.call(json, key)) {
// ... and default-initialize it.
json[propName] = defaults[propName];
}
});
ctxState.set(json);
} catch (e) {
console.error(`Failed to merge ${key} context state: ${e}`);
}
}
export const InitializeGlobalContext = () => {
ContextMerger<AuthInfo>("auth", AuthContextDefaults, AuthContext);
ContextMerger<SettingsType>(
"settings",
SettingsContextDefaults,
SettingsContext
);
ContextMerger<FilterListState>(
"filterList",
FilterListContextDefaults,
FilterListContext
);
};
function DefaultSetter<T>(name: string, newState: T, prevState: T) {
try {
localStorage.setItem(name, JSON.stringify(newState));
} catch (e) {
console.error(
`An error occurred while trying to modify '${name}' context state: ${e}`
);
console.warn(` --> prevState: ${prevState}`);
console.warn(` --> newState: ${newState}`);
}
}
export const AuthContext = newRidgeState<AuthInfo>(AuthContextDefaults, {
onSet: (newState, prevState) => DefaultSetter("auth", newState, prevState)
});
export const SettingsContext = newRidgeState<SettingsType>(
SettingsContextDefaults,
{
onSet: (newState, prevState) => {
document.documentElement.classList.toggle("dark", newState.darkTheme);
DefaultSetter("settings", newState, prevState);
}
}
);
export const FilterListContext = newRidgeState<FilterListState>(
FilterListContextDefaults,
{
indexerFilter: [],
sortOrder: "",
status: ""
},
{
onSet: (new_state) => {
try {
localStorage.setItem("filterList", JSON.stringify(new_state));
} catch (e) {
console.log("An error occurred while trying to modify the local filter list context state.");
console.log("Error:", e);
}
}
}
);
export type IrcNetworkState = {
id: number;
name: string;
status: string;
};
export type IrcBufferType = "NICK" | "CHANNEL" | "SERVER";
export type IrcBufferState = {
id: number;
name: string;
type: IrcBufferType;
messages: string[];
};
export type IrcState = {
networks: Map<string, IrcNetworkState>;
buffers: Map<string, IrcBufferState>
};
export const IrcContext = newRidgeState<IrcState>(
{
networks: new Map(),
buffers: new Map()
},
{
onSet: (new_state) => {
try {
console.log("set irc state", new_state);
} catch (e) {
console.log("Error:", e);
}
}
onSet: (newState, prevState) => DefaultSetter("filterList", newState, prevState)
}
);