feat: toasts customizability (#497)
* fix: Toasts always using the info icon * feat: Toast Position Customizability * feat: Toast duration customizability * fix: send toast button wide lol * feat: toast auto close customizability * fix: SettingNumber being bad
This commit is contained in:
parent
73a55ee58d
commit
978732a8fa
24 changed files with 281 additions and 280 deletions
|
|
@ -248,7 +248,7 @@ gallery: string[] }
|
|||
|
||||
export type SettingProfileModel = { name: string; java_id: number | null; res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null; os_extra: SettingsOsExtra | null }
|
||||
|
||||
export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; log_debug_info: boolean; show_tanstack_dev_tools: boolean }
|
||||
export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; log_debug_info: boolean; show_tanstack_dev_tools: boolean; toast_position: ToastPosition; toast_duration: number; toast_auto_close: boolean }
|
||||
|
||||
export type SettingsOsExtra = { enable_gamemode: boolean | null }
|
||||
|
||||
|
|
@ -256,6 +256,8 @@ export type SkinVariant = "classic" | "slim"
|
|||
|
||||
export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated"
|
||||
|
||||
export type ToastPosition = "top-right" | "top-left" | "bottom-right" | "bottom-left"
|
||||
|
||||
export type Update = { version: string }
|
||||
|
||||
/**
|
||||
|
|
@ -317,7 +319,7 @@ export type VersionType =
|
|||
*/
|
||||
"old_beta"
|
||||
|
||||
const ARGS_MAP = { 'debug':'{"getFullDebugInfoParsed":[],"getLocale":[],"getType":[],"hasDevSettings":[],"isInDev":[],"getFamily":[],"getPackageVersion":[],"getOsVersion":[],"getGitCommitHash":[],"getPlatform":[],"getBuildTimestamp":[],"getOsDistro":[],"getArch":[],"getFullDebugInfo":[],"getFullDebugInfoParsedString":[],"openDevTools":[]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"process":["event"],"message":["event"],"ingress":["event"]}', 'core':'{"searchPackages":["provider","query"],"getLinkedPackages":["cluster_id"],"getProcessLogTail":["id","max_lines"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"updateClusterById":["id","request"],"getLogs":["id"],"getGameVersions":[],"refreshAccounts":[],"syncCluster":["cluster_id"],"removeCape":["access_token"],"getClusterById":["id"],"openMsaLogin":[],"refreshAccount":["uuid"],"convertUsernameUUID":["username_uuid"],"getRunningProcessesByClusterId":["cluster_id"],"setDiscordRPCMessage":["message"],"open":["input"],"getClusters":[],"getProfileOrDefault":["name"],"getLogByName":["id","name"],"getPackage":["provider","slug"],"getUsers":[],"createSettingsProfile":["name"],"removeCluster":["id"],"getPackageBody":["provider","body"],"killProcess":["pid"],"changeSkin":["access_token","skin_url","skin_variant"],"getGlobalProfile":[],"getUser":["uuid"],"removeUser":["uuid"],"getDefaultUser":["fallback"],"getMultiplePackages":["provider","slugs"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"changeCape":["access_token","cape_uuid"],"launchCluster":["id","uuid","search_for_java"],"createCluster":["options"],"updateClusterProfile":["name","profile"],"getWorlds":["id"],"getScreenshots":["id"],"getLoadersForVersion":["mc_version"],"getUsersFromAuthor":["provider","author"],"isClusterRunning":["cluster_id"],"setClusterStage":["id","stage"],"setDefaultUser":["uuid"],"getRunningProcesses":[],"writeSettings":["setting"],"readSettings":[],"removePackage":["cluster_id","package_hash"],"fetchMinecraftProfile":["uuid"],"fetchLoggedInProfile":["access_token"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"togglePackage":["cluster_id","package_hash"],"installModpack":["modpack","cluster_id"]}', 'oneclient':'{"getVersions":[],"extractBundleOverrides":["bundle_path","cluster_id"],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"getBundlesFor":["cluster_id"],"isBundleSyncing":[],"installUpdate":[],"cacheArt":["path"],"refreshArt":["path"],"checkForUpdate":[],"getClustersGroupedByMajor":[],"updateBundlePackages":["cluster_id"]}' }
|
||||
const ARGS_MAP = { 'events':'{"ingress":["event"],"process":["event"],"message":["event"]}', 'folders':'{"openCluster":["folder_name"],"fromCluster":["folder_name"]}', 'debug':'{"getLocale":[],"getPlatform":[],"getOsVersion":[],"getOsDistro":[],"isInDev":[],"getArch":[],"getFullDebugInfo":[],"getFullDebugInfoParsedString":[],"getPackageVersion":[],"getBuildTimestamp":[],"getType":[],"getGitCommitHash":[],"openDevTools":[],"getFamily":[],"getFullDebugInfoParsed":[],"hasDevSettings":[]}', 'oneclient':'{"getBundlesFor":["cluster_id"],"isBundleSyncing":[],"extractBundleOverrides":["bundle_path","cluster_id"],"installUpdate":[],"getClustersGroupedByMajor":[],"updateBundlePackages":["cluster_id"],"cacheArt":["path"],"checkForUpdate":[],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"refreshArt":["path"],"getVersions":[]}', 'core':'{"removePackage":["cluster_id","package_hash"],"getLinkedPackages":["cluster_id"],"refreshAccount":["uuid"],"createSettingsProfile":["name"],"changeCape":["access_token","cape_uuid"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"isClusterRunning":["cluster_id"],"convertUsernameUUID":["username_uuid"],"setDiscordRPCMessage":["message"],"removeUser":["uuid"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"syncCluster":["cluster_id"],"getRunningProcessesByClusterId":["cluster_id"],"removeCluster":["id"],"open":["input"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"getWorlds":["id"],"getScreenshots":["id"],"killProcess":["pid"],"getUsers":[],"removeCape":["access_token"],"getClusters":[],"getUsersFromAuthor":["provider","author"],"installModpack":["modpack","cluster_id"],"fetchMinecraftProfile":["uuid"],"getUser":["uuid"],"getPackageBody":["provider","body"],"getLoadersForVersion":["mc_version"],"fetchLoggedInProfile":["access_token"],"getLogByName":["id","name"],"changeSkin":["access_token","skin_url","skin_variant"],"writeSettings":["setting"],"getDefaultUser":["fallback"],"setDefaultUser":["uuid"],"getLogs":["id"],"updateClusterProfile":["name","profile"],"getClusterById":["id"],"launchCluster":["id","uuid","search_for_java"],"refreshAccounts":[],"getProcessLogTail":["id","max_lines"],"getGlobalProfile":[],"getRunningProcesses":[],"openMsaLogin":[],"createCluster":["options"],"getProfileOrDefault":["name"],"getGameVersions":[],"searchPackages":["provider","query"],"updateClusterById":["id","request"],"getPackage":["provider","slug"],"togglePackage":["cluster_id","package_hash"],"getMultiplePackages":["provider","slugs"],"setClusterStage":["id","stage"],"readSettings":[],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"]}' }
|
||||
export type Router = { 'folders': { fromCluster: (folderName: string) => Promise<string>,
|
||||
openCluster: (folderName: string) => Promise<null> },
|
||||
'oneclient': { getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
|
||||
|
|
@ -347,9 +349,6 @@ getFullDebugInfo: () => Promise<DebugInfoData>,
|
|||
getFullDebugInfoParsed: () => Promise<DebugInfoParsedLine[]>,
|
||||
getFullDebugInfoParsedString: () => Promise<string>,
|
||||
hasDevSettings: () => Promise<boolean> },
|
||||
'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
message: (event: MessagePayload) => Promise<void>,
|
||||
process: (event: ProcessPayload) => Promise<void> },
|
||||
'core': { getClusters: () => Promise<ClusterModel[]>,
|
||||
getClusterById: (id: number) => Promise<ClusterModel | null>,
|
||||
removeCluster: (id: number) => Promise<null>,
|
||||
|
|
@ -403,7 +402,10 @@ changeCape: (accessToken: string, capeUuid: string) => Promise<MojangFullPlayerP
|
|||
removeCape: (accessToken: string) => Promise<MojangFullPlayerProfile>,
|
||||
convertUsernameUUID: (usernameUuid: string) => Promise<MowojangProfile>,
|
||||
setDiscordRPCMessage: (message: string) => Promise<null>,
|
||||
open: (input: string) => Promise<null> } };
|
||||
open: (input: string) => Promise<null> },
|
||||
'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
message: (event: MessagePayload) => Promise<void>,
|
||||
process: (event: ProcessPayload) => Promise<void> } };
|
||||
|
||||
|
||||
export type { InferCommandOutput }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { copyDebugInfo } from '@/hooks/useDebugInfo';
|
||||
import { useCopyDebugInfo } from '@/hooks/useDebugInfo';
|
||||
import { bindings } from '@/main';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import { Button } from '@onelauncher/common/components';
|
||||
|
|
@ -6,6 +6,7 @@ import { Button } from '@onelauncher/common/components';
|
|||
export function MadeBy() {
|
||||
const { data: version } = useCommand(['getPackageVersion'], () => bindings.debug.getPackageVersion());
|
||||
const { data: isInDev } = useCommand(['isInDev'], () => bindings.debug.isInDev());
|
||||
const copyDebugInfo = useCopyDebugInfo();
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import { TitleCase } from '@/utils/string';
|
||||
import { Dropdown } from '@onelauncher/common/components';
|
||||
|
||||
export function SettingDropdown<T extends string>({ setting, options }: { setting: [T, (value: T) => void]; options: Array<T> }) {
|
||||
return (
|
||||
<Dropdown onSelectionChange={key => setting[1](key as T)} selectedKey={setting[0]}>
|
||||
{options.map(option => <Dropdown.Item id={option} key={option}>{TitleCase(option)}</Dropdown.Item>)}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { TextField } from '@onelauncher/common/components';
|
||||
import { useState } from 'react';
|
||||
|
||||
type NumberSetting = [
|
||||
number,
|
||||
(value: number) => void,
|
||||
];
|
||||
|
||||
export function SettingNumber({ setting, placeholder, min, max }: { setting: NumberSetting; placeholder?: string; min?: number; max?: number }) {
|
||||
const [inputValue, setInputValue] = useState(String(setting[0]));
|
||||
|
||||
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const value = event.currentTarget.value;
|
||||
setInputValue(value);
|
||||
const num = Number(value);
|
||||
if (!Number.isNaN(num))
|
||||
setting[1](num);
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
let num = Number(inputValue);
|
||||
|
||||
if (Number.isNaN(num)) {
|
||||
setInputValue(String(setting[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
if (min !== undefined)
|
||||
num = Math.max(min, num);
|
||||
if (max !== undefined)
|
||||
num = Math.min(max, num);
|
||||
|
||||
setInputValue(String(num));
|
||||
setting[1](num);
|
||||
}
|
||||
|
||||
return (
|
||||
<TextField
|
||||
max={max}
|
||||
min={min}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
type="number"
|
||||
value={inputValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { TextField } from '@onelauncher/common/components';
|
||||
|
||||
type StringSetting = [
|
||||
string,
|
||||
(value: string) => void,
|
||||
];
|
||||
|
||||
export function SettingString({ setting, placeholder }: { setting: StringSetting; placeholder?: string }) {
|
||||
return (
|
||||
<TextField
|
||||
onChange={event => setting[1](event.currentTarget.value)}
|
||||
placeholder={placeholder}
|
||||
value={setting[0]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ type BooleanSetting = [
|
|||
(value: boolean) => void,
|
||||
];
|
||||
|
||||
export function SettingsSwitch({ setting }: { setting: BooleanSetting }) {
|
||||
export function SettingSwitch({ setting }: { setting: BooleanSetting }) {
|
||||
return (
|
||||
<Switch
|
||||
isSelected={setting[0]}
|
||||
5
apps/oneclient/frontend/src/components/Settings/index.ts
Normal file
5
apps/oneclient/frontend/src/components/Settings/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export * from './SettingDropdown';
|
||||
export * from './SettingNumber';
|
||||
export * from './SettingsRow';
|
||||
export * from './SettingString';
|
||||
export * from './SettingSwitch';
|
||||
|
|
@ -13,8 +13,7 @@ export * from './ManageSkinButton';
|
|||
export * from './Markdown';
|
||||
export * from './Navbar';
|
||||
export * from './overlay';
|
||||
export * from './SettingsRow';
|
||||
export * from './SettingSwitch';
|
||||
export * from './Settings';
|
||||
export * from './SheetPage';
|
||||
export * from './SkinViewer';
|
||||
export * from './Spinner';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { DebugInfoArray } from '@/hooks/useDebugInfo';
|
||||
import { Overlay } from '@/components';
|
||||
import { copyDebugInfo, useDebugInfo } from '@/hooks/useDebugInfo';
|
||||
import { useCopyDebugInfo, useDebugInfo } from '@/hooks/useDebugInfo';
|
||||
import { Button } from '@onelauncher/common/components';
|
||||
|
||||
export function DebugInfo() {
|
||||
|
|
@ -14,6 +14,7 @@ export function DebugInfo() {
|
|||
}
|
||||
|
||||
export function RawDebugInfo({ debugInfo }: { debugInfo: DebugInfoArray }) {
|
||||
const copyDebugInfo = useCopyDebugInfo();
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Overlay, SettingsRow, SettingsSwitch } from '@/components';
|
||||
import { copyDebugInfo } from '@/hooks/useDebugInfo';
|
||||
import { Overlay, SettingsRow, SettingSwitch } from '@/components';
|
||||
import { useCopyDebugInfo } from '@/hooks/useDebugInfo';
|
||||
import { useSettings } from '@/hooks/useSettings';
|
||||
import { bindings } from '@/main';
|
||||
import { Button } from '@onelauncher/common/components';
|
||||
|
|
@ -7,6 +7,7 @@ import { Link } from '@tanstack/react-router';
|
|||
|
||||
export function SuperSecretDevOptions() {
|
||||
const { createSetting } = useSettings();
|
||||
const copyDebugInfo = useCopyDebugInfo();
|
||||
|
||||
return (
|
||||
<Overlay.Dialog>
|
||||
|
|
@ -14,11 +15,11 @@ export function SuperSecretDevOptions() {
|
|||
|
||||
<div>
|
||||
<SettingsRow description="Enable The Tanstack Dev Tools and shows debug page" title="Show Dev stuff">
|
||||
<SettingsSwitch setting={createSetting('show_tanstack_dev_tools')} />
|
||||
<SettingSwitch setting={createSetting('show_tanstack_dev_tools')} />
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow description="WARNING! This requires a restart to apply. Logs out debug info" title="Log Debug Info">
|
||||
<SettingsSwitch setting={createSetting('log_debug_info')} />
|
||||
<SettingSwitch setting={createSetting('log_debug_info')} />
|
||||
</SettingsRow>
|
||||
|
||||
<div className="grid grid-cols-4 gap-3 justify-items-center">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { HTMLAttributes } from 'react';
|
|||
import type { ToastContentProps, ToastOptions } from 'react-toastify';
|
||||
import { Spinner } from '@/components';
|
||||
import { bindings } from '@/main';
|
||||
import { toast, toastUpdate } from '@/utils/toast';
|
||||
import { toastUpdate, useToast } from '@/utils/toast';
|
||||
import { Button } from '@onelauncher/common/components';
|
||||
import { AlertCircleIcon, AlertTriangleIcon, CheckCircleIcon, InfoCircleIcon, XCloseIcon } from '@untitled-theme/icons-react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
|
@ -48,7 +48,7 @@ export function Toast({
|
|||
|
||||
return (
|
||||
<div className={container()}>
|
||||
<ToastIcon isLoading={toastProps.isLoading} type={toastProps.type} />
|
||||
<ToastIcon isLoading={toastProps.isLoading} type={data.type} />
|
||||
|
||||
<div className="flex flex-col">
|
||||
<h4 className="text-lg font-medium">{data.title}</h4>
|
||||
|
|
@ -66,7 +66,12 @@ export function Toast({
|
|||
</div>
|
||||
|
||||
<div className="w-full h-1 absolute left-0 right-0 bottom-0">
|
||||
<ToastProgressBar closeToast={closeToast} isPaused={isPaused} toastProps={toastProps} />
|
||||
<ToastProgressBar
|
||||
closeToast={closeToast}
|
||||
data={data}
|
||||
isPaused={isPaused}
|
||||
toastProps={toastProps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -76,20 +81,29 @@ function ToastProgressBar({
|
|||
isPaused,
|
||||
toastProps,
|
||||
closeToast,
|
||||
data,
|
||||
}: {
|
||||
toastProps: ToastOptions;
|
||||
isPaused: boolean;
|
||||
closeToast: () => void;
|
||||
data: ToastData;
|
||||
}) {
|
||||
let progress = toastProps.progress;
|
||||
|
||||
if (data.shouldAutoClose === false) {
|
||||
isPaused = true;
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
const attrs: HTMLAttributes<HTMLDivElement> = {};
|
||||
|
||||
if (typeof toastProps.progress === 'number') {
|
||||
if (typeof progress === 'number') {
|
||||
attrs.style = {
|
||||
transition: 'width 0.2s ease-in-out',
|
||||
width: `${toastProps.progress * 100}%`,
|
||||
width: `${progress * 100}%`,
|
||||
};
|
||||
|
||||
if (toastProps.progress >= 1)
|
||||
if (progress >= 1)
|
||||
attrs.onTransitionEnd = closeToast;
|
||||
}
|
||||
else {
|
||||
|
|
@ -128,6 +142,7 @@ const ingressNames: Record<(IngressType & string) | (keyof UnionToIntersection<I
|
|||
export function Toasts() {
|
||||
const toastIngressMapRef = useRef(new Map<string, ToastId>());
|
||||
const listenerRef = useRef(false);
|
||||
const toast = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (listenerRef.current)
|
||||
|
|
@ -180,7 +195,6 @@ export function Toasts() {
|
|||
return (
|
||||
<ToastContainer
|
||||
newestOnTop
|
||||
position="bottom-right"
|
||||
transition={cssTransition({
|
||||
enter: 'animate-fade animate-duration-75',
|
||||
exit: 'animate-fade animate-duration-75 animate-reverse',
|
||||
|
|
@ -194,7 +208,7 @@ function ToastIcon({
|
|||
type,
|
||||
}: {
|
||||
isLoading: boolean | undefined;
|
||||
type: string;
|
||||
type: ToastData['type'];
|
||||
}) {
|
||||
if (isLoading)
|
||||
return <Spinner size="extraSmall" />;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { DebugInfoParsedLine } from '@/bindings.gen';
|
||||
import type { ShortcutEvent } from '@tauri-apps/plugin-global-shortcut';
|
||||
import { bindings } from '@/main';
|
||||
import { toast } from '@/utils/toast';
|
||||
import { useToast } from '@/utils/toast';
|
||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||
import { register } from '@tauri-apps/plugin-global-shortcut';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
|
@ -22,17 +22,22 @@ export function useDebugInfo(): DebugInfoArray {
|
|||
return debugInfo;
|
||||
}
|
||||
|
||||
export async function copyDebugInfo() {
|
||||
const info = await bindings.debug.getFullDebugInfoParsedString();
|
||||
writeText(info);
|
||||
toast({
|
||||
type: 'info',
|
||||
title: 'Debug Info',
|
||||
message: 'Debug info has been copied to clipboard',
|
||||
});
|
||||
export function useCopyDebugInfo() {
|
||||
const toast = useToast();
|
||||
|
||||
return async function copyDebugInfo() {
|
||||
const info = await bindings.debug.getFullDebugInfoParsedString();
|
||||
writeText(info);
|
||||
toast({
|
||||
type: 'info',
|
||||
title: 'Debug Info',
|
||||
message: 'Debug info has been copied to clipboard',
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function useDebugKeybind() {
|
||||
const copyDebugInfo = useCopyDebugInfo();
|
||||
const handleKeybind = (event: ShortcutEvent) => {
|
||||
if (event.state !== 'Pressed')
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { ToOptions } from '@tanstack/react-router';
|
||||
import { bindings } from '@/main';
|
||||
import { ReplaceVariables } from '@/utils/string';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import { useLocation } from '@tanstack/react-router';
|
||||
import { useEffect } from 'react';
|
||||
|
|
@ -39,11 +40,6 @@ export const ResolvedPathNames: Record<URLPath, string> = {
|
|||
'/onboarding/language': 'Preparing OneClient',
|
||||
};
|
||||
|
||||
// Credit - https://github.com/DuckySoLucky/hypixel-discord-chat-bridge/blob/d3ea84a26ebf094c8191d50b4954549e2dd4dc7f/src/contracts/helperFunctions.js#L216-L225
|
||||
function ReplaceVariables(template: string, variables: Record<string, any>) {
|
||||
return template.replace(/\{(\w+)\}/g, (match: any, name: string | number) => variables[name] ?? match);
|
||||
}
|
||||
|
||||
export function useDiscordRPC() {
|
||||
const location = useLocation();
|
||||
const clusterId = location.search.clusterId ?? 0;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { bindings } from '@/main';
|
||||
import { toast } from '@/utils/toast';
|
||||
import { useToast } from '@/utils/toast';
|
||||
import { getMessageFromError, isLauncherError } from '@onelauncher/common';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
|
@ -8,6 +8,7 @@ export function useLaunchCluster(): { launch: (clusterId: number | undefined | n
|
|||
export function useLaunchCluster(clusterId: number | undefined | null): { launch: () => void; showDownloadWarning: boolean; setShowDownloadWarning: (show: boolean) => void; forceLaunch: () => void };
|
||||
|
||||
export function useLaunchCluster(clusterId?: number | undefined | null) {
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const [showDownloadWarning, setShowDownloadWarning] = useState(false);
|
||||
const [lastAttemptedId, setLastAttemptedId] = useState<number | null>(null);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { PlayerAnimation } from 'skinview3d';
|
|||
import { ImportSkinModal, Overlay, RemoveSkinCapeModal, SheetPage, SkinViewer } from '@/components';
|
||||
import { bindings } from '@/main';
|
||||
import { getSkinUrl } from '@/utils/minecraft';
|
||||
import { toast } from '@/utils/toast';
|
||||
import { useToast } from '@/utils/toast';
|
||||
import { Button } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { dataDir, downloadDir, join } from '@tauri-apps/api/path';
|
||||
|
|
@ -111,6 +111,7 @@ export function MissingAccountData({ validSearch }: { validSearch: boolean }) {
|
|||
|
||||
function RouteComponent() {
|
||||
const { profileData, profile, queryClient, validSearch, playerData } = Route.useRouteContext();
|
||||
const toast = useToast();
|
||||
|
||||
const [capes, setCapes] = useState<Array<Cape>>([]);
|
||||
const [selectedCape, setSelectedCape] = useState<string>('');
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import type { ToastData } from '@/utils/toast';
|
||||
import { MinecraftAuthErrorModal, minecraftAuthErrors, Overlay, RawDebugInfo, SettingsRow, SettingsSwitch, SheetPage } from '@/components';
|
||||
import type { ToastData, ToastOptions } from '@/utils/toast';
|
||||
import { MinecraftAuthErrorModal, minecraftAuthErrors, Overlay, RawDebugInfo, SettingNumber, SettingsRow, SettingSwitch, SheetPage } from '@/components';
|
||||
import { useDebugInfo } from '@/hooks/useDebugInfo';
|
||||
import { useSettings } from '@/hooks/useSettings';
|
||||
import { bindings } from '@/main';
|
||||
import { toast, ToastTypes } from '@/utils/toast';
|
||||
import { TitleCase } from '@/utils/string';
|
||||
import { ToastPositions, ToastTypes, useToast } from '@/utils/toast';
|
||||
import { Button, Dropdown, TextField } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { dataDir, join } from '@tauri-apps/api/path';
|
||||
|
|
@ -81,26 +82,22 @@ function AuthError() {
|
|||
);
|
||||
}
|
||||
|
||||
// Credit - https://github.com/DuckySoLucky/hypixel-discord-chat-bridge/blob/52887f12fce3bc9ebe91befaf394f289abd234a1/src/contracts/helperFunctions.js#L260-L278
|
||||
function TitleCase(str: string): string {
|
||||
if (!str)
|
||||
return '';
|
||||
|
||||
if (typeof str !== 'string')
|
||||
return '';
|
||||
|
||||
return str
|
||||
.toLowerCase()
|
||||
.replaceAll('_', ' ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
function Toasts() {
|
||||
const [title, setTitle] = useState<string>('Title');
|
||||
const [message, setMessage] = useState<string | undefined>('Message');
|
||||
const [type, setType] = useState<ToastData['type']>('info');
|
||||
const [position, setPosition] = useState<ToastOptions['position'] | 'undefined'>('undefined');
|
||||
const [duration, setDuration] = useState<number>(5000);
|
||||
const [autoClose, setAutoClose] = useState<boolean>(true);
|
||||
const toast = useToast();
|
||||
|
||||
const sendToast = () => toast({
|
||||
type,
|
||||
title,
|
||||
message,
|
||||
position: position === 'undefined' ? undefined : position,
|
||||
autoClose: autoClose ? duration : false,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
|
|
@ -113,16 +110,30 @@ function Toasts() {
|
|||
<p>Message:</p>
|
||||
<TextField className="flex-1" onChange={e => setMessage(e.target.value)} value={message} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-4 items-center">
|
||||
<p>Toast Type:</p>
|
||||
<p>Type:</p>
|
||||
<Dropdown onSelectionChange={key => setType(key as ToastData['type'])} selectedKey={type}>
|
||||
{ToastTypes.map(type => <Dropdown.Item id={type} key={type}>{TitleCase(type)}</Dropdown.Item>)}
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
<Button onPress={() => toast({ type, title, message })} size="normal">Send Toast</Button>
|
||||
<div className="flex flex-row gap-4 items-center">
|
||||
<p>Position:</p>
|
||||
<Dropdown onSelectionChange={key => setPosition(key as ToastOptions['position'])} selectedKey={position}>
|
||||
<Dropdown.Item id="undefined" key="undefined">Config</Dropdown.Item>
|
||||
{ToastPositions.map(type => <Dropdown.Item id={type} key={type}>{TitleCase(type)}</Dropdown.Item>)}
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div className="flex flex-row gap-4 items-center">
|
||||
<p>Duration:</p>
|
||||
<SettingNumber max={60000} min={500} setting={[duration, (value: number) => setDuration(value)]} />
|
||||
</div>
|
||||
<div className="flex flex-row gap-4 items-center">
|
||||
<p>Auto Close:</p>
|
||||
<SettingSwitch setting={[autoClose, (value: boolean) => setAutoClose(value)]} />
|
||||
</div>
|
||||
|
||||
<Button onPress={sendToast} size="normal">Send Toast</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -136,19 +147,19 @@ function Settings() {
|
|||
<div className="flex flex-row gap-4 flex-wrap">
|
||||
|
||||
<SettingsRow description="WARNING! This requires a restart to apply. Logs out debug info" title="Log Debug Info">
|
||||
<SettingsSwitch setting={createSetting('log_debug_info')} />
|
||||
<SettingSwitch setting={createSetting('log_debug_info')} />
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow description="Enable The Tanstack Dev Tools and shows debug page" title="Show Dev stuff">
|
||||
<SettingsSwitch setting={createSetting('show_tanstack_dev_tools')} />
|
||||
<SettingSwitch setting={createSetting('show_tanstack_dev_tools')} />
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow description="Seen onboarding" title="Seen Onboarding">
|
||||
<SettingsSwitch setting={createSetting('seen_onboarding')} />
|
||||
<SettingSwitch setting={createSetting('seen_onboarding')} />
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow description="Use Grid On Mods List" title="Use Grid On Mods List">
|
||||
<SettingsSwitch setting={createSetting('mod_list_use_grid')} />
|
||||
<SettingSwitch setting={createSetting('mod_list_use_grid')} />
|
||||
</SettingsRow>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { SettingsRow, SettingsSwitch } from '@/components';
|
||||
import { SettingDropdown, SettingNumber, SettingsRow, SettingSwitch } from '@/components';
|
||||
import { useSettings } from '@/hooks/useSettings';
|
||||
import { Sidebar } from '@/routes/app/settings/route';
|
||||
import { THEMES } from '@/utils/theming';
|
||||
import { ToastPositions, useToast } from '@/utils/toast';
|
||||
import { Button } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { Monitor01Icon, PackageIcon, Speedometer04Icon } from '@untitled-theme/icons-react';
|
||||
|
||||
export const Route = createFileRoute('/app/settings/appearance')({
|
||||
component: RouteComponent,
|
||||
|
|
@ -11,210 +11,34 @@ export const Route = createFileRoute('/app/settings/appearance')({
|
|||
|
||||
function RouteComponent() {
|
||||
const { createSetting } = useSettings();
|
||||
const toast = useToast();
|
||||
|
||||
const previewToast = () => toast({ type: 'info', title: 'Preview Toast', message: 'This is an preview of what a toast would look like' });
|
||||
|
||||
return (
|
||||
<Sidebar.Page>
|
||||
{/* <ScrollableContainer> */}
|
||||
<div className="h-full">
|
||||
<h1 className="text-xl">Appearance</h1>
|
||||
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
<PrimaryThemeCard />
|
||||
<div className="grid grid-cols-3 h-full gap-4">
|
||||
{/* <For each={Object.keys(THEMES) as (keyof typeof THEMES)[]}>
|
||||
{theme => (
|
||||
<For each={THEMES[theme ?? 'OneLauncher'].variants}>
|
||||
{variant => (
|
||||
<ThemeCard
|
||||
setTheme={setTheme}
|
||||
themeSelector={`${theme}-${variant.name}`}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
)}
|
||||
</For> */}
|
||||
<SettingsRow.Header>Toasts</SettingsRow.Header>
|
||||
|
||||
{Object.keys(THEMES).map((theme) => {
|
||||
return THEMES[theme as keyof typeof THEMES].variants.map((variant) => {
|
||||
return (
|
||||
<ThemeCard key={`${theme}-${variant.name}`} setTheme={() => { }} themeSelector={`${theme}-${variant.name}`} />
|
||||
);
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SettingsRow
|
||||
description="Change the look of the package list."
|
||||
icon={<PackageIcon />}
|
||||
title="Package List Style"
|
||||
>
|
||||
<p>i hope i'll fix this</p>
|
||||
<SettingsRow description="Where the toast will show up" title="Position">
|
||||
<SettingDropdown options={ToastPositions} setting={createSetting('toast_position')} />
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
description="Uses custom window frame for the launcher."
|
||||
icon={<Monitor01Icon />}
|
||||
title="Custom Window Frame"
|
||||
>
|
||||
<SettingsSwitch setting={createSetting('native_window_frame')} />
|
||||
<SettingsRow description="Should the toast expire and auto close" title="Auto Close">
|
||||
<SettingSwitch setting={createSetting('toast_auto_close')} />
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
description="Disables all animations in the launcher."
|
||||
icon={<Speedometer04Icon />}
|
||||
title="Disable Animations"
|
||||
>
|
||||
{/* <SettingsSwitch setting={createSetting("disable_animations")}/> */}
|
||||
<SettingsRow description="How long the toast should stay. In milliseconds" title="Duration">
|
||||
<SettingNumber max={60000} min={500} setting={createSetting('toast_duration')} />
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow description="Preview a Toast" title="Preview Toast">
|
||||
<Button onClick={previewToast} size="normal">Preview</Button>
|
||||
</SettingsRow>
|
||||
|
||||
</div>
|
||||
{/* </ScrollableContainer> */}
|
||||
</Sidebar.Page>
|
||||
);
|
||||
}
|
||||
|
||||
interface ThemeCardProps {
|
||||
themeSelector: string;
|
||||
setTheme: (merged: string) => void;
|
||||
};
|
||||
|
||||
function ThemeCard({
|
||||
themeSelector,
|
||||
setTheme,
|
||||
}: ThemeCardProps) {
|
||||
return (
|
||||
<div className={`h-min theme-${themeSelector}`} onClick={() => setTheme(themeSelector)}>
|
||||
<svg
|
||||
fill="none"
|
||||
height="78"
|
||||
viewBox="0 0 126 78"
|
||||
width="126"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
className="fill-page-elevated"
|
||||
height="78"
|
||||
rx="8"
|
||||
width="126"
|
||||
/>
|
||||
<rect
|
||||
className="stroke-border"
|
||||
height="77"
|
||||
rx="7.5"
|
||||
stroke-opacity="0.1"
|
||||
width="125"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
/>
|
||||
<path
|
||||
className="stroke-fg-primary"
|
||||
d="M8 16H116"
|
||||
stroke-linecap="round"
|
||||
stroke-width="5"
|
||||
/>
|
||||
<path
|
||||
className="stroke-fg-secondary"
|
||||
d="M8 26H56"
|
||||
stroke-linecap="round"
|
||||
stroke-width="3"
|
||||
/>
|
||||
<rect
|
||||
className="fill-brand"
|
||||
fill-opacity="0.5"
|
||||
height="16"
|
||||
rx="4"
|
||||
width="64"
|
||||
x="54"
|
||||
y="54"
|
||||
/>
|
||||
<path d="M54 58C54 55.7909 55.7909 54 58 54H86V70H58C55.7909 70 54 68.2091 54 66V58Z" fill="rgb(var(--clr-brand))" />
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PrimaryThemeCard() {
|
||||
return (
|
||||
<div className="w-[296] h-[183]">
|
||||
<svg
|
||||
fill="none"
|
||||
height="183"
|
||||
viewBox="0 0 296 183"
|
||||
width="296"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
className="fill-page"
|
||||
height="183"
|
||||
rx="16"
|
||||
width="296"
|
||||
/>
|
||||
<rect
|
||||
className="stroke-border/10"
|
||||
height="180"
|
||||
rx="14.5"
|
||||
strokeWidth="1.5"
|
||||
width="293"
|
||||
x="1.5"
|
||||
y="1.5"
|
||||
/>
|
||||
<rect
|
||||
className="fill-page-elevated"
|
||||
height="123"
|
||||
rx="8"
|
||||
width="168"
|
||||
x="112"
|
||||
y="44"
|
||||
/>
|
||||
<rect
|
||||
className="stroke-border/5"
|
||||
height="122"
|
||||
rx="7.5"
|
||||
width="167"
|
||||
x="112.5"
|
||||
y="44.5"
|
||||
/>
|
||||
<rect
|
||||
className="fill-brand/50"
|
||||
height="16"
|
||||
rx="4"
|
||||
width="64"
|
||||
x="208"
|
||||
y="143"
|
||||
/>
|
||||
<path
|
||||
className="stroke-fg-primary"
|
||||
d="M132 64H240"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="5"
|
||||
/>
|
||||
<path
|
||||
className="stroke-fg-secondary"
|
||||
d="M132 74H180"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="3"
|
||||
/>
|
||||
<path
|
||||
className="stroke-code-error"
|
||||
d="M132 94H216"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="stroke-code-warn"
|
||||
d="M132 104H192"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="stroke-code-info"
|
||||
d="M132 114H210"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path className="fill-brand" d="M208 147C208 144.791 209.791 143 212 143H240V159H212C209.791 159 208 157.209 208 155V147Z" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { DebugInfo, Overlay, SettingsRow, SettingsSwitch } from '@/components';
|
||||
import { DebugInfo, Overlay, SettingsRow, SettingSwitch } from '@/components';
|
||||
import { useSettings } from '@/hooks/useSettings';
|
||||
import { Sidebar } from '@/routes/app/settings/route';
|
||||
import { Button } from '@onelauncher/common/components';
|
||||
|
|
@ -32,11 +32,11 @@ function RouteComponent() {
|
|||
<SettingsRow.Header>Dev Tools</SettingsRow.Header>
|
||||
|
||||
<SettingsRow description="WARNING! This requires a restart to apply. Logs out debug info" title="Log Debug Info">
|
||||
<SettingsSwitch setting={createSetting('log_debug_info')} />
|
||||
<SettingSwitch setting={createSetting('log_debug_info')} />
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow description="Enable The Tanstack Dev Tools and shows debug page" title="Show Dev stuff">
|
||||
<SettingsSwitch setting={createSetting('show_tanstack_dev_tools')} />
|
||||
<SettingSwitch setting={createSetting('show_tanstack_dev_tools')} />
|
||||
</SettingsRow>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { SettingsRow, SettingsSwitch } from '@/components';
|
||||
import { SettingsRow, SettingSwitch } from '@/components';
|
||||
import { useSettings } from '@/hooks/useSettings';
|
||||
import { bindings } from '@/main';
|
||||
import { Sidebar } from '@/routes/app/settings/route';
|
||||
|
|
@ -34,7 +34,7 @@ function RouteComponent() {
|
|||
icon={<Link03Icon />}
|
||||
title="Discord RPC"
|
||||
>
|
||||
<SettingsSwitch setting={createSetting('discord_enabled')} />
|
||||
<SettingSwitch setting={createSetting('discord_enabled')} />
|
||||
</SettingsRow>
|
||||
|
||||
{/* <SettingsRow
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { JSX } from 'react';
|
||||
import { MadeBy, SheetPage } from '@/components';
|
||||
import { createFileRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router';
|
||||
import { CodeSnippet02Icon, RefreshCcw02Icon, Rocket02Icon, Sliders04Icon } from '@untitled-theme/icons-react';
|
||||
import { Brush01Icon, CodeSnippet02Icon, RefreshCcw02Icon, Rocket02Icon, Sliders04Icon } from '@untitled-theme/icons-react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Route = createFileRoute('/app/settings')({
|
||||
|
|
@ -35,7 +35,7 @@ function RouteComponent() {
|
|||
links={{
|
||||
'Launcher Settings': [
|
||||
[<Rocket02Icon key="general" />, 'General', '/'],
|
||||
// [<Brush01Icon key="appearence" />, 'Appearance', '/appearance'],
|
||||
[<Brush01Icon key="appearence" />, 'Appearance', '/appearance'],
|
||||
// [<Key01Icon />, 'APIs', '/apis'],
|
||||
// [<Globe01Icon />, 'Language', '/language'],
|
||||
],
|
||||
|
|
|
|||
21
apps/oneclient/frontend/src/utils/string.ts
Normal file
21
apps/oneclient/frontend/src/utils/string.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Credit - https://github.com/DuckySoLucky/hypixel-discord-chat-bridge/blob/d3ea84a26ebf094c8191d50b4954549e2dd4dc7f/src/contracts/helperFunctions.js#L216-L225
|
||||
export function ReplaceVariables(template: string, variables: Record<string, any>) {
|
||||
return template.replace(/\{(\w+)\}/g, (match: any, name: string | number) => variables[name] ?? match);
|
||||
}
|
||||
|
||||
// Credit - https://github.com/DuckySoLucky/hypixel-discord-chat-bridge/blob/52887f12fce3bc9ebe91befaf394f289abd234a1/src/contracts/helperFunctions.js#L260-L278
|
||||
export function TitleCase(str: string): string {
|
||||
if (!str)
|
||||
return '';
|
||||
|
||||
if (typeof str !== 'string')
|
||||
return '';
|
||||
|
||||
return str
|
||||
.toLowerCase()
|
||||
.replaceAll('_', ' ')
|
||||
.replaceAll('-', ' ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import type { ToastPosition } from '@/bindings.gen';
|
||||
import type { UpdateOptions } from 'react-toastify';
|
||||
import { Toast } from '@/components/overlay';
|
||||
import { useSettings } from '@/hooks/useSettings';
|
||||
import { toast as toastify } from 'react-toastify';
|
||||
|
||||
export type ToastId = string | number;
|
||||
|
|
@ -8,6 +10,7 @@ export interface ToastData {
|
|||
type: 'info' | 'success' | 'warning' | 'error';
|
||||
title: string;
|
||||
message?: string | undefined;
|
||||
shouldAutoClose?: boolean | undefined;
|
||||
}
|
||||
export const ToastTypes: Array<ToastData['type']> = ['info', 'success', 'warning', 'error'];
|
||||
|
||||
|
|
@ -15,24 +18,43 @@ export interface ToastOptions extends ToastData {
|
|||
progress?: number;
|
||||
isLoading?: boolean | undefined;
|
||||
autoClose?: number | false | undefined;
|
||||
position?: ToastPosition;
|
||||
}
|
||||
export const ToastPositions: Array<NonNullable<ToastOptions['position']>> = ['top-right', 'top-left', 'bottom-right', 'bottom-left'];
|
||||
|
||||
export function toast({
|
||||
progress,
|
||||
isLoading,
|
||||
autoClose = 5000,
|
||||
...data
|
||||
}: ToastOptions): ToastId {
|
||||
return toastify(Toast, {
|
||||
data,
|
||||
export function useToast() {
|
||||
const { setting } = useSettings();
|
||||
|
||||
return function toast({
|
||||
progress,
|
||||
isLoading,
|
||||
autoClose,
|
||||
closeOnClick: false,
|
||||
closeButton: false,
|
||||
hideProgressBar: true,
|
||||
icon: false,
|
||||
});
|
||||
position,
|
||||
...data
|
||||
}: ToastOptions): ToastId {
|
||||
if (position === undefined)
|
||||
position = setting('toast_position');
|
||||
|
||||
if (autoClose === undefined)
|
||||
autoClose = setting('toast_duration');
|
||||
|
||||
if (autoClose === false)
|
||||
data.shouldAutoClose = false;
|
||||
else
|
||||
data.shouldAutoClose = setting('toast_auto_close');
|
||||
|
||||
return toastify(Toast, {
|
||||
data,
|
||||
progress,
|
||||
isLoading,
|
||||
autoClose,
|
||||
closeOnClick: false,
|
||||
closeButton: false,
|
||||
hideProgressBar: true,
|
||||
icon: false,
|
||||
position,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function toastUpdate(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
use onelauncher_entity::setting_profiles;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
|
||||
use crate::utils::io;
|
||||
use crate::{LauncherResult, send_warning};
|
||||
|
||||
use super::Dirs;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Type)]
|
||||
pub enum ToastPosition {
|
||||
#[serde(rename = "top-right")]
|
||||
TopRight,
|
||||
#[serde(rename = "top-left")]
|
||||
TopLeft,
|
||||
#[serde(rename = "bottom-right")]
|
||||
BottomRight,
|
||||
#[serde(rename = "bottom-left")]
|
||||
BottomLeft,
|
||||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(default)]
|
||||
|
|
@ -23,6 +36,11 @@ pub struct Settings {
|
|||
#[cfg(feature = "tauri")]
|
||||
pub log_debug_info: bool,
|
||||
pub show_tanstack_dev_tools: bool,
|
||||
|
||||
// Toasts
|
||||
pub toast_position: ToastPosition,
|
||||
pub toast_duration: u32,
|
||||
pub toast_auto_close: bool,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
|
|
@ -41,6 +59,11 @@ impl Default for Settings {
|
|||
#[cfg(feature = "tauri")]
|
||||
log_debug_info: tauri::is_dev(),
|
||||
show_tanstack_dev_tools: tauri::is_dev(),
|
||||
|
||||
// Toasts
|
||||
toast_position: ToastPosition::BottomRight,
|
||||
toast_duration: 5000,
|
||||
toast_auto_close: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue