Use local storage for UI configuration

This commit is contained in:
Stash Dev 2019-04-02 11:34:27 -07:00
parent 9098699249
commit e5b9db2821
8 changed files with 137 additions and 10 deletions

View file

@ -17,6 +17,7 @@
"bulma": "0.7.4",
"formik": "1.5.1",
"graphql": "14.1.1",
"localforage": "1.7.3",
"lodash": "4.17.11",
"node-sass": "4.11.0",
"query-string": "6.2.0",

View file

@ -8,6 +8,7 @@ import React, { FunctionComponent, useEffect, useState } from "react";
import { IBaseProps } from "../../models";
import { SettingsAboutPanel } from "./SettingsAboutPanel";
import { SettingsConfigurationPanel } from "./SettingsConfigurationPanel";
import { SettingsInterfacePanel } from "./SettingsInterfacePanel";
import { SettingsLogsPanel } from "./SettingsLogsPanel";
import { SettingsTasksPanel } from "./SettingsTasksPanel/SettingsTasksPanel";
@ -39,6 +40,7 @@ export const Settings: FunctionComponent<IProps> = (props: IProps) => {
defaultSelectedTabId={getTabId()}
>
<Tab id="configuration" title="Configuration" panel={<SettingsConfigurationPanel />} />
<Tab id="interface" title="Interface Configuration" panel={<SettingsInterfacePanel />} />
<Tab id="tasks" title="Tasks" panel={<SettingsTasksPanel />} />
<Tab id="logs" title="Logs" panel={<SettingsLogsPanel />} />
<Tab id="about" title="About" panel={<SettingsAboutPanel />} />

View file

@ -0,0 +1,35 @@
import {
Checkbox,
FormGroup,
H4,
} from "@blueprintjs/core";
import _ from "lodash";
import React, { FunctionComponent } from "react";
import { useInterfaceLocalForage } from "../../hooks/LocalForage";
interface IProps {}
export const SettingsInterfacePanel: FunctionComponent<IProps> = () => {
const {data, setData} = useInterfaceLocalForage();
return (
<>
<H4>User Interface</H4>
<FormGroup
label="Scene / Marker Wall"
helperText="Configuration for wall items"
>
<Checkbox
checked={!!data ? data.wall.textContainerEnabled : true}
label="Display title and tags"
onChange={() => {
if (!data) { return; }
const newSettings = _.cloneDeep(data);
newSettings.wall.textContainerEnabled = !data.wall.textContainerEnabled;
setData(newSettings);
}}
/>
</FormGroup>
</>
);
};

View file

@ -55,8 +55,6 @@ export const Stats: FunctionComponent = () => {
This is still an early version, some things are still a work in progress.
* Filters for performers and studios only supports one item, even though it's a multi select.
* All of the task buttons in settings do work, but provide no feedback in the UI currently.
* The tasks tab is the only tab with content in the settings menu.
TODO:
* List view for scenes / performers

View file

@ -39,8 +39,8 @@
.scene-wall-item-container {
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
// align-items: center;
// overflow: hidden; // Commented out since it shows gaps in the wall
position: relative;
width: 100%;
height: 100%;

View file

@ -2,6 +2,7 @@ import _ from "lodash";
import React, { FunctionComponent, useRef, useState } from "react";
import { Link } from "react-router-dom";
import * as GQL from "../../core/generated-graphql";
import { useInterfaceLocalForage } from "../../hooks/LocalForage";
import { VideoHoverHook } from "../../hooks/VideoHover";
import { TextUtils } from "../../utils/text";
@ -16,6 +17,8 @@ interface IWallItemProps {
export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProps) => {
const [videoPath, setVideoPath] = useState<string | undefined>(undefined);
const videoHoverHook = VideoHoverHook.useVideoHover({resetOnMouseLeave: true});
const interfaceSettings = useInterfaceLocalForage();
const showTextContainer = !!interfaceSettings.data ? interfaceSettings.data.wall.textContainerEnabled : true;
function onMouseEnter() {
VideoHoverHook.onMouseEnter(videoHoverHook);
@ -101,12 +104,14 @@ export const WallItem: FunctionComponent<IWallItemProps> = (props: IWallItemProp
ref={videoHoverHook.videoEl}
/>
<img src={previewSrc} />
<div className="scene-wall-item-text-container">
<div style={{lineHeight: 1}}>
{title}
</div>
{tags}
</div>
{showTextContainer ?
<div className="scene-wall-item-text-container">
<div style={{lineHeight: 1}}>
{title}
</div>
{tags}
</div> : undefined
}
</Link>
</div>
</div>

View file

@ -0,0 +1,67 @@
import localForage from "localforage";
import _ from "lodash";
import React from "react";
interface IInterfaceWallConfig {
textContainerEnabled: boolean;
}
export interface IInterfaceConfig {
wall: IInterfaceWallConfig;
}
type ValidTypes = IInterfaceConfig | undefined;
interface ILocalForage<T> {
data: T;
setData: React.Dispatch<React.SetStateAction<T>>;
error: Error | null;
}
export function useInterfaceLocalForage(): ILocalForage<IInterfaceConfig | undefined> {
const result = useLocalForage("interface");
// Set defaults
React.useEffect(() => {
if (result.data === undefined) {
result.setData({
wall: {
textContainerEnabled: true,
},
});
}
});
return result;
}
function useLocalForage(item: string): ILocalForage<ValidTypes> {
const [json, setJson] = React.useState<ValidTypes>(undefined);
const prevJson = React.useRef<ValidTypes>(undefined);
React.useEffect(() => {
async function runAsync() {
if (typeof json !== "undefined" && !_.isEqual(json, prevJson.current)) {
await localForage.setItem(item, JSON.stringify(json));
}
prevJson.current = json;
}
runAsync();
});
const [err, setErr] = React.useState(null);
React.useEffect(() => {
async function runAsync() {
try {
const serialized = await localForage.getItem<any>(item);
const parsed = JSON.parse(serialized);
if (typeof json === "undefined" && !Object.is(parsed, null)) {
setErr(null);
setJson(parsed);
}
} catch (error) {
setErr(error);
}
}
runAsync();
});
return {data: json, setData: setJson, error: err};
}

View file

@ -5448,6 +5448,11 @@ ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
immer@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d"
@ -6751,6 +6756,13 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
dependencies:
immediate "~3.0.5"
listr-silent-renderer@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
@ -6838,6 +6850,13 @@ loader-utils@1.2.3, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.
emojis-list "^2.0.0"
json5 "^1.0.1"
localforage@1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.3.tgz#0082b3ca9734679e1bd534995bdd3b24cf10f204"
integrity sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==
dependencies:
lie "3.1.1"
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"