feat First implementation of plugins system
feat: Added PCSX2 integration feat: Revamped UI a bit made it look better on light mode
This commit is contained in:
parent
d85268fad7
commit
a78e75335f
95 changed files with 2639 additions and 1259 deletions
|
|
@ -11,14 +11,14 @@ import { CSSProperties } from "react";
|
|||
export type ButtonStyle = 'base' | 'accent' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
const styles = {
|
||||
base: 'bg-base-200 text-base-content active:bg-base-300! active:text-base-content! active:ring-offset-base-content',
|
||||
accent: "bg-accent text-accent-content active:bg-base-content! active:text-base-content active:ring-offset-accent",
|
||||
primary: "bg-primary text-primary-content active:bg-base-content! active:text-base-content! active:ring-offset-primary",
|
||||
secondary: "bg-secondary text-secondary-content active:bg-base-content! active:text-base-content! active:ring-offset-secondary",
|
||||
info: "bg-info text-info-content active:bg-base-content! active:text-base-content! active:ring-offset-info",
|
||||
success: "bg-success text-success-content active:bg-base-content! active:text-base-content! active:ring-offset-success",
|
||||
warning: "bg-warning text-warning-content active:bg-base-content! active:text-base-content! active:ring-offset-warning",
|
||||
error: "bg-error text-error-content active:bg-base-content! active:text-base-content! active:ring-offset-error",
|
||||
base: 'dark:bg-base-200 light:bg-base-300 text-base-content active:not-disabled:bg-base-300! active:not-disabled:text-base-content! active:not-disabled:ring-offset-base-content',
|
||||
accent: "bg-accent text-accent-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:ring-offset-accent",
|
||||
primary: "bg-primary text-primary-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:not-disabled:ring-offset-primary",
|
||||
secondary: "bg-secondary text-secondary-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:not-disabled:ring-offset-secondary",
|
||||
info: "bg-info text-info-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:not-disabled:ring-offset-info",
|
||||
success: "bg-success text-success-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:not-disabled:ring-offset-success",
|
||||
warning: "bg-warning text-warning-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:not-disabled:ring-offset-warning",
|
||||
error: "bg-error text-error-content active:not-disabled:bg-base-100! active:not-disabled:text-base-content! active:not-disabled:ring-offset-error",
|
||||
};
|
||||
|
||||
export function Button (data: {
|
||||
|
|
@ -31,6 +31,8 @@ export function Button (data: {
|
|||
shortcutLabel?: string;
|
||||
focusClassName?: string;
|
||||
cssStyle?: CSSProperties;
|
||||
tooltip?: string;
|
||||
tooltipType?: "base" | "accent" | "error";
|
||||
} & InteractParams & FocusParams)
|
||||
{
|
||||
const { ref, focused, focusKey } = useFocusable({
|
||||
|
|
@ -49,8 +51,10 @@ export function Button (data: {
|
|||
ref={ref}
|
||||
onClick={e => data.onAction?.(e.nativeEvent)}
|
||||
disabled={data.disabled}
|
||||
data-tooltip={data.tooltip}
|
||||
data-tooltip_type={data.tooltipType}
|
||||
style={data.cssStyle}
|
||||
className={twMerge("flex items-center justify-center px-4 py-2 disabled:bg-base-200/40 disabled:text-base-content/40 cursor-pointer rounded-3xl md:text-lg not-control-mouse:focused:drop-shadow-lg border border-base-content/5 not-control-mouse:focused:bg-base-content not-control-mouse:focused:text-base-100 control-mouse:hover:bg-base-content control-mouse:hover:text-base-100 active:transition-none active:ring-offset-4",
|
||||
className={twMerge("flex items-center justify-center px-4 py-2 disabled:bg-base-200/40 disabled:text-base-content/40 not-disabled:cursor-pointer rounded-3xl md:text-lg not-control-mouse:focused:drop-shadow-lg border border-base-content/5 not-control-mouse:focused:bg-base-content not-control-mouse:focused:text-base-100 control-mouse:hover:not-disabled:bg-base-content control-mouse:hover:not-disabled:text-base-100 active:not-disabled:transition-none active:not-disabled:ring-offset-4",
|
||||
styles[data.style ?? 'base'],
|
||||
focused ? data.focusClassName : undefined,
|
||||
classNames({
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { twMerge } from "tailwind-merge";
|
|||
import { useFocusable } from "@noriginmedia/norigin-spatial-navigation";
|
||||
import { ContextDialog, ContextList, DialogEntry } from "../ContextDialog";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { FOCUS_KEYS } from "@/mainview/scripts/types";
|
||||
|
||||
export function OptionDropdown (data: {
|
||||
name: string;
|
||||
|
|
@ -38,7 +39,7 @@ export function OptionDropdown (data: {
|
|||
setOpen(true);
|
||||
}} className={'flex items-center justify-center border h-10 border-base-content/30 px-4 py-2 rounded-full cursor-pointer grow not-in-focused:bg-base-200 focusable focusable-accent hover:border-base-content hover:bg-base-content hover:text-base-300'}>{data.value}<ChevronDown /></button>
|
||||
</label>
|
||||
{open && <ContextDialog id={`${data.name}-context`} open={true} close={handleClose}>
|
||||
{open && <ContextDialog id={`${data.name}-context`} preferredChildFocusKey={FOCUS_KEYS.CONTEXT_DIALOG_OPTION(`${data.name}-context`, String(data.values.indexOf(data.value ?? '')))} open={true} close={handleClose}>
|
||||
<ContextList options={data.values.map((v, i) => ({
|
||||
content: v,
|
||||
id: String(i),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function OptionInput (data: {
|
|||
className?: string;
|
||||
placeholder?: string;
|
||||
icon?: JSX.Element;
|
||||
value?: string;
|
||||
value?: string | boolean;
|
||||
defaultValue?: string | boolean;
|
||||
autocomplete?: HTMLInputAutoCompleteAttribute;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
|
|
@ -58,7 +58,7 @@ export function OptionInput (data: {
|
|||
id={data.name}
|
||||
data-focus={"input"}
|
||||
name={data.name}
|
||||
value={data.value}
|
||||
value={String(data.value)}
|
||||
defaultValue={typeof data.defaultValue === 'string' ? data.defaultValue : undefined}
|
||||
type={data.type}
|
||||
autoComplete={data.autocomplete}
|
||||
|
|
@ -68,24 +68,22 @@ export function OptionInput (data: {
|
|||
onBlur={data.onBlur}
|
||||
defaultChecked={typeof data.defaultValue === 'boolean' ? data.defaultValue : undefined}
|
||||
className={twMerge(
|
||||
"flex text-base-content px-4 py-2 items-center justify-center border border-base-content/20 grow rounded-full focus:ring-base-content in-focused:bg-base-200 focusable focusable-accent focus:not-focused:ring-7 control-mouse:ring-0! hover:border-base-content",
|
||||
"flex text-base-content px-4 py-2 items-center justify-center border bg-base-200 border-base-content/20 grow rounded-full focus:ring-base-content in-focused:bg-base-100 focusable focusable-accent focus:not-focused:ring-7 control-mouse:ring-0! hover:border-base-content",
|
||||
data.className
|
||||
)}
|
||||
/>}
|
||||
{data.type === 'checkbox' && <div className="toggle toggle-xl before:size-6 h-8 border-base-content/30 rounded-full before:rounded-full text-base-content not-in-focus:bg-base-200 focused-child:border-0 ml-1 ring-7 hover:border-base-content focusable focusable-accent">
|
||||
{data.type === 'checkbox' && <div className="toggle toggle-xl toggle-success before:size-6 h-8 border-base-content/30 rounded-full before:bg-base-100 before:rounded-full text-base-content not-in-focus:bg-base-200 focused-child:border-0 ml-1 ring-7 hover:border-base-content focusable has-checked:bg-success not-has-checked:bg-error">
|
||||
<input
|
||||
ref={inputRef}
|
||||
id={data.name}
|
||||
name={data.name}
|
||||
value={data.value}
|
||||
defaultValue={typeof data.defaultValue === 'string' ? data.defaultValue : undefined}
|
||||
checked={Boolean(data.value)}
|
||||
type={data.type}
|
||||
autoComplete={data.autocomplete}
|
||||
onFocus={handleFocus}
|
||||
placeholder={data.placeholder}
|
||||
onChange={e => data.onChange?.(typeof data.defaultValue === 'boolean' ? e.target.checked : e.target.value)}
|
||||
onChange={e => data.onChange?.(e.target.checked)}
|
||||
onBlur={data.onBlur}
|
||||
defaultChecked={typeof data.defaultValue === 'boolean' ? data.defaultValue : undefined}
|
||||
className={twMerge(
|
||||
data.className
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ import FilePicker from "../FilePicker";
|
|||
import { setFocus } from "@noriginmedia/norigin-spatial-navigation";
|
||||
import { getSettingQuery, setSettingMutation } from "@queries/settings";
|
||||
|
||||
type KeysWithValueAssignableTo<T, Value> = {
|
||||
[K in keyof T]: Exclude<T[K], undefined> extends Value ? K : never;
|
||||
}[keyof T];
|
||||
|
||||
export interface PathSettingsOptionParams
|
||||
{
|
||||
label: string;
|
||||
|
|
@ -68,11 +64,8 @@ export function PathSettingsOptionBase (data: PathSettingsOptionParams & {
|
|||
|
||||
useEffect(() =>
|
||||
{
|
||||
if (!data.isDirty)
|
||||
{
|
||||
data.setLocalValue(String(defaultValue));
|
||||
}
|
||||
}, [data.isDirty, defaultValue]);
|
||||
data.setLocalValue(String(defaultValue));
|
||||
}, [defaultValue]);
|
||||
|
||||
const handleSelectPath = (path: string) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
import { HTMLInputTypeAttribute, JSX, useCallback, useState } from "react";
|
||||
import { HTMLInputTypeAttribute, JSX, useCallback, useEffect, useState } from "react";
|
||||
import { SettingsType } from "../../../shared/constants";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { OptionSpace } from "./OptionSpace";
|
||||
import { OptionInput } from "./OptionInput";
|
||||
import { getSettingQuery, setSettingMutation } from "@queries/settings";
|
||||
|
||||
type KeysWithValueAssignableTo<T, Value> = {
|
||||
[K in keyof T]: Exclude<T[K], undefined> extends Value ? K : never;
|
||||
}[keyof T];
|
||||
|
||||
export function SettingsOption (data: {
|
||||
label: string;
|
||||
id: KeysWithValueAssignableTo<SettingsType, string>;
|
||||
id: KeysWithValueAssignableTo<SettingsType, string | boolean>;
|
||||
type: HTMLInputTypeAttribute;
|
||||
placeholder?: string;
|
||||
icon?: JSX.Element;
|
||||
|
|
@ -19,10 +15,16 @@ export function SettingsOption (data: {
|
|||
})
|
||||
{
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const [localValue, setLocalValue] = useState<string | undefined>();
|
||||
useQuery(getSettingQuery(data.id));
|
||||
const [localValue, setLocalValue] = useState<string | boolean | undefined>();
|
||||
const { data: serverValue } = useQuery(getSettingQuery(data.id));
|
||||
const setMutation = useMutation(setSettingMutation(data.id));
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setLocalValue(serverValue as any);
|
||||
setDirty(false);
|
||||
}, [serverValue]);
|
||||
|
||||
const handleSave = useCallback(() =>
|
||||
{
|
||||
if (dirty)
|
||||
|
|
@ -43,7 +45,14 @@ export function SettingsOption (data: {
|
|||
onChange={(v) =>
|
||||
{
|
||||
setLocalValue(v);
|
||||
setDirty(true);
|
||||
|
||||
if (data.type === 'checkbox')
|
||||
{
|
||||
setMutation.mutate(v);
|
||||
} else
|
||||
{
|
||||
setDirty(true);
|
||||
}
|
||||
}}
|
||||
value={localValue}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue