mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-25 03:41:02 +00:00
feat: notification preview on theme editor
This commit is contained in:
@@ -21,6 +21,7 @@ const updateCustomTheme = async (
|
|||||||
|
|
||||||
if (theme.isActive) {
|
if (theme.isActive) {
|
||||||
WindowManager.mainWindow?.webContents.send("css-injected", code);
|
WindowManager.mainWindow?.webContents.send("css-injected", code);
|
||||||
|
WindowManager.notificationWindow?.webContents.send("css-injected", code);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,10 @@ import { db, gamesSublevel, levelKeys } from "@main/level";
|
|||||||
import { orderBy, slice } from "lodash-es";
|
import { orderBy, slice } from "lodash-es";
|
||||||
import type {
|
import type {
|
||||||
AchievementCustomNotificationPosition,
|
AchievementCustomNotificationPosition,
|
||||||
AchievementNotificationInfo,
|
|
||||||
ScreenState,
|
ScreenState,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
import { AuthPage } from "@shared";
|
import { AuthPage, generateAchievementCustomNotificationTest } from "@shared";
|
||||||
import { isStaging } from "@main/constants";
|
import { isStaging } from "@main/constants";
|
||||||
|
|
||||||
export class WindowManager {
|
export class WindowManager {
|
||||||
@@ -377,23 +376,7 @@ export class WindowManager {
|
|||||||
this.notificationWindow?.webContents.send(
|
this.notificationWindow?.webContents.send(
|
||||||
"on-achievement-unlocked",
|
"on-achievement-unlocked",
|
||||||
userPreferences.achievementCustomNotificationPosition ?? "top_left",
|
userPreferences.achievementCustomNotificationPosition ?? "top_left",
|
||||||
[
|
[generateAchievementCustomNotificationTest(t, language)]
|
||||||
{
|
|
||||||
title: t("test_achievement_notification_title", {
|
|
||||||
ns: "notifications",
|
|
||||||
lng: language,
|
|
||||||
}),
|
|
||||||
description: t("test_achievement_notification_description", {
|
|
||||||
ns: "notifications",
|
|
||||||
lng: language,
|
|
||||||
}),
|
|
||||||
iconUrl: "https://cdn.losbroxas.org/favicon.svg",
|
|
||||||
points: 100,
|
|
||||||
isHidden: false,
|
|
||||||
isRare: false,
|
|
||||||
isPlatinum: false,
|
|
||||||
},
|
|
||||||
] as AchievementNotificationInfo[]
|
|
||||||
);
|
);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
@@ -419,7 +402,7 @@ export class WindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const editorWindow = new BrowserWindow({
|
const editorWindow = new BrowserWindow({
|
||||||
width: 600,
|
width: 720,
|
||||||
height: 720,
|
height: 720,
|
||||||
minWidth: 600,
|
minWidth: 600,
|
||||||
minHeight: 540,
|
minHeight: 540,
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import {
|
||||||
|
AchievementCustomNotificationPosition,
|
||||||
|
AchievementNotificationInfo,
|
||||||
|
} from "@types";
|
||||||
|
import cn from "classnames";
|
||||||
|
import "./achievement-notification.scss";
|
||||||
|
|
||||||
|
interface AchievementNotificationProps {
|
||||||
|
position: AchievementCustomNotificationPosition;
|
||||||
|
currentAchievement: AchievementNotificationInfo;
|
||||||
|
isClosing: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AchievementNotificationItem({
|
||||||
|
position,
|
||||||
|
currentAchievement,
|
||||||
|
isClosing,
|
||||||
|
}: Readonly<AchievementNotificationProps>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("achievement-notification", {
|
||||||
|
[position]: true,
|
||||||
|
closing: isClosing,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn("achievement-notification__container", {
|
||||||
|
[position]: true,
|
||||||
|
closing: isClosing,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="achievement-notification__content">
|
||||||
|
<img
|
||||||
|
src={currentAchievement.iconUrl}
|
||||||
|
alt={currentAchievement.title}
|
||||||
|
className="achievement-notification__icon"
|
||||||
|
/>
|
||||||
|
<div className="achievement-notification__text-container">
|
||||||
|
<p className="achievement-notification__title">
|
||||||
|
{currentAchievement.title}
|
||||||
|
</p>
|
||||||
|
<p className="achievement-notification__description">
|
||||||
|
{currentAchievement.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
@use "../../scss/globals.scss";
|
||||||
|
|
||||||
|
.collapsed-menu {
|
||||||
|
&__button {
|
||||||
|
height: 72px;
|
||||||
|
padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: globals.$background-color;
|
||||||
|
color: globals.$muted-color;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all ease 0.2s;
|
||||||
|
gap: globals.$spacing-unit;
|
||||||
|
font-size: globals.$body-font-size;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: globals.$active-opacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__chevron {
|
||||||
|
transition: transform ease 0.2s;
|
||||||
|
|
||||||
|
&--open {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.4s cubic-bezier(0, 1, 0, 1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { ChevronDownIcon } from "@primer/octicons-react";
|
||||||
|
import "./collapsed-menu.scss";
|
||||||
|
|
||||||
|
export interface CollapsedMenuProps {
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CollapsedMenu({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}: Readonly<CollapsedMenuProps>) {
|
||||||
|
const content = useRef<HTMLDivElement>(null);
|
||||||
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
|
const [height, setHeight] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (content.current && content.current.scrollHeight !== height) {
|
||||||
|
setHeight(isOpen ? content.current.scrollHeight : 0);
|
||||||
|
} else if (!isOpen) {
|
||||||
|
setHeight(0);
|
||||||
|
}
|
||||||
|
}, [isOpen, children, height]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="collapsed-menu">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
className="collapsed-menu__button"
|
||||||
|
>
|
||||||
|
<ChevronDownIcon
|
||||||
|
className={`collapsed-menu__chevron ${
|
||||||
|
isOpen ? "collapsed-menu__chevron--open" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<span>{title}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref={content}
|
||||||
|
className="collapsed-menu__content"
|
||||||
|
style={{
|
||||||
|
maxHeight: `${height}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,12 +18,13 @@ export function SelectField({
|
|||||||
options = [{ key: "-", value: value?.toString() || "-", label: "-" }],
|
options = [{ key: "-", value: value?.toString() || "-", label: "-" }],
|
||||||
theme = "primary",
|
theme = "primary",
|
||||||
onChange,
|
onChange,
|
||||||
}: SelectProps) {
|
className,
|
||||||
|
}: Readonly<SelectProps>) {
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
const id = useId();
|
const id = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="select-field__container">
|
<div className={cn("select-field__container", className)}>
|
||||||
{label && (
|
{label && (
|
||||||
<label htmlFor={id} className="select-field__label">
|
<label htmlFor={id} className="select-field__label">
|
||||||
{label}
|
{label}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import achievementSound from "@renderer/assets/audio/achievement.wav";
|
import achievementSound from "@renderer/assets/audio/achievement.wav";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import cn from "classnames";
|
|
||||||
import "./achievement-notification.scss";
|
|
||||||
import {
|
import {
|
||||||
AchievementCustomNotificationPosition,
|
AchievementCustomNotificationPosition,
|
||||||
AchievementNotificationInfo,
|
AchievementNotificationInfo,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
|
import { injectCustomCss } from "@renderer/helpers";
|
||||||
|
import { AchievementNotificationItem } from "@renderer/components/achievements/notification/achievement-notification";
|
||||||
|
|
||||||
const NOTIFICATION_TIMEOUT = 4000;
|
const NOTIFICATION_TIMEOUT = 40000;
|
||||||
|
|
||||||
export function AchievementNotification() {
|
export function AchievementNotification() {
|
||||||
const { t } = useTranslation("achievement");
|
const { t } = useTranslation("achievement");
|
||||||
@@ -51,8 +51,7 @@ export function AchievementNotification() {
|
|||||||
isRare: false,
|
isRare: false,
|
||||||
isPlatinum: false,
|
isPlatinum: false,
|
||||||
points: 0,
|
points: 0,
|
||||||
iconUrl:
|
iconUrl: "https://cdn.losbroxas.org/favicon.svg",
|
||||||
"https://avatars.githubusercontent.com/u/164102380?s=400&u=01a13a7b4f0c642f7e547b8e1d70440ea06fa750&v=4",
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -133,37 +132,32 @@ export function AchievementNotification() {
|
|||||||
}
|
}
|
||||||
}, [achievements]);
|
}, [achievements]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadAndApplyTheme = async () => {
|
||||||
|
const activeTheme = await window.electron.getActiveCustomTheme();
|
||||||
|
console.log("activeTheme", activeTheme);
|
||||||
|
if (activeTheme?.code) {
|
||||||
|
injectCustomCss(activeTheme.code);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadAndApplyTheme();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = window.electron.onCssInjected((cssString) => {
|
||||||
|
injectCustomCss(cssString);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => unsubscribe();
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!isVisible || !currentAchievement) return null;
|
if (!isVisible || !currentAchievement) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<AchievementNotificationItem
|
||||||
className={cn("achievement-notification", {
|
currentAchievement={currentAchievement}
|
||||||
[position]: true,
|
isClosing={isClosing}
|
||||||
closing: isClosing,
|
position={position}
|
||||||
})}
|
/>
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cn("achievement-notification__container", {
|
|
||||||
[position]: true,
|
|
||||||
closing: isClosing,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="achievement-notification__content">
|
|
||||||
<img
|
|
||||||
src={currentAchievement.iconUrl}
|
|
||||||
alt={currentAchievement.title}
|
|
||||||
className="achievement-notification__icon"
|
|
||||||
/>
|
|
||||||
<div className="achievement-notification__text-container">
|
|
||||||
<p className="achievement-notification__title">
|
|
||||||
{currentAchievement.title}
|
|
||||||
</p>
|
|
||||||
<p className="achievement-notification__description">
|
|
||||||
{currentAchievement.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,35 +22,43 @@ interface FormValues {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_THEME_CODE = `
|
const DEFAULT_THEME_CODE = `/*
|
||||||
/*
|
Here you can edit CSS for your theme and apply it on Hydra.
|
||||||
Here you can edit CSS for your theme and apply it on Hydra.
|
There are a few classes already in place, you can use them to style the launcher.
|
||||||
There are a few classes already in place, you can use them to style the launcher.
|
|
||||||
|
|
||||||
If you want to learn more about how to run Hydra in dev mode (which will allow you to inspect the DOM and view the classes)
|
If you want to learn more about how to run Hydra in dev mode (which will allow you to inspect the DOM and view the classes)
|
||||||
or how to publish your theme in the theme store, you can check the docs:
|
or how to publish your theme in the theme store, you can check the docs:
|
||||||
https://docs.hydralauncher.gg/
|
https://docs.hydralauncher.gg/themes.html
|
||||||
|
|
||||||
Happy hacking!
|
Happy hacking!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
.header {}
|
.header {}
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
.sidebar {}
|
.sidebar {}
|
||||||
|
|
||||||
/* Main content */
|
/* Main content */
|
||||||
.container__content {}
|
.container__content {}
|
||||||
|
|
||||||
/* Bottom panel */
|
/* Bottom panel */
|
||||||
.bottom-panel {}
|
.bottom-panel {}
|
||||||
|
|
||||||
/* Toast */
|
/* Toast */
|
||||||
.toast {}
|
.toast {}
|
||||||
|
|
||||||
/* Button */
|
/* Button */
|
||||||
.button {}
|
.button {}
|
||||||
|
|
||||||
|
/* Custom Achievement Notification */
|
||||||
|
.achievement-notification {}
|
||||||
|
.achievement-notification__container {}
|
||||||
|
.achievement-notification__content {}
|
||||||
|
.achievement-notification__icon {}
|
||||||
|
.achievement-notification__text-container {}
|
||||||
|
.achievement-notification__title {}
|
||||||
|
.achievement-notification__description {}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -36,14 +36,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__editor {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
background-color: globals.$dark-background-color;
|
background-color: globals.$dark-background-color;
|
||||||
padding: globals.$spacing-unit globals.$spacing-unit * 2;
|
padding: globals.$spacing-unit globals.$spacing-unit * 2;
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 50;
|
|
||||||
|
|
||||||
&-actions {
|
&-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -78,4 +80,15 @@
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__notification-preview {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
&__select-variation {
|
||||||
|
flex: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import "./theme-editor.scss";
|
import "./theme-editor.scss";
|
||||||
import Editor from "@monaco-editor/react";
|
import Editor from "@monaco-editor/react";
|
||||||
import { Theme } from "@types";
|
import { AchievementCustomNotificationPosition, Theme } from "@types";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { Button } from "@renderer/components";
|
import { Button, SelectField } from "@renderer/components";
|
||||||
import { CheckIcon } from "@primer/octicons-react";
|
import { CheckIcon } from "@primer/octicons-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
|
import { injectCustomCss } from "@renderer/helpers";
|
||||||
|
import { AchievementNotificationItem } from "@renderer/components/achievements/notification/achievement-notification";
|
||||||
|
import { generateAchievementCustomNotificationTest } from "@shared";
|
||||||
|
import { CollapsedMenu } from "@renderer/components/collapsed-menu/collapsed-menu";
|
||||||
|
|
||||||
|
const notificationVariations = {
|
||||||
|
default: "default",
|
||||||
|
rare: "rare",
|
||||||
|
platinum: "platinum",
|
||||||
|
hidden: "hidden",
|
||||||
|
};
|
||||||
|
|
||||||
export default function ThemeEditor() {
|
export default function ThemeEditor() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
@@ -14,9 +25,30 @@ export default function ThemeEditor() {
|
|||||||
const [code, setCode] = useState("");
|
const [code, setCode] = useState("");
|
||||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||||
|
|
||||||
|
const [isClosingNotifications, setIsClosingNotifications] = useState(false);
|
||||||
|
|
||||||
const themeId = searchParams.get("themeId");
|
const themeId = searchParams.get("themeId");
|
||||||
|
|
||||||
const { t } = useTranslation("settings");
|
const { t, i18n } = useTranslation("settings");
|
||||||
|
|
||||||
|
const [notificationVariation, setNotificationVariation] =
|
||||||
|
useState<keyof typeof notificationVariations>("default");
|
||||||
|
|
||||||
|
const achievementPreview = useMemo(() => {
|
||||||
|
return {
|
||||||
|
achievement: {
|
||||||
|
...generateAchievementCustomNotificationTest(t, i18n.language),
|
||||||
|
isRare: notificationVariation === "rare",
|
||||||
|
isHidden: notificationVariation === "hidden",
|
||||||
|
isPlatinum: notificationVariation === "platinum",
|
||||||
|
},
|
||||||
|
position: "top_center" as AchievementCustomNotificationPosition,
|
||||||
|
};
|
||||||
|
}, [t, i18n.language, notificationVariation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.document.title = "Hydra - Theme Editor";
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (themeId) {
|
if (themeId) {
|
||||||
@@ -33,12 +65,17 @@ export default function ThemeEditor() {
|
|||||||
if (theme) {
|
if (theme) {
|
||||||
await window.electron.updateCustomTheme(theme.id, code);
|
await window.electron.updateCustomTheme(theme.id, code);
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
|
setIsClosingNotifications(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
injectCustomCss(code);
|
||||||
|
setIsClosingNotifications(false);
|
||||||
|
}, 450);
|
||||||
}
|
}
|
||||||
}, [code, theme]);
|
}, [code, theme]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === "s") {
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "s") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleSave();
|
handleSave();
|
||||||
}
|
}
|
||||||
@@ -71,21 +108,62 @@ export default function ThemeEditor() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Editor
|
<div className="theme-editor__editor">
|
||||||
theme="vs-dark"
|
<div
|
||||||
defaultLanguage="css"
|
style={{
|
||||||
value={code}
|
position: "absolute",
|
||||||
onChange={handleEditorChange}
|
top: 0,
|
||||||
options={{
|
left: 0,
|
||||||
minimap: { enabled: false },
|
right: 0,
|
||||||
fontSize: 14,
|
bottom: 0,
|
||||||
lineNumbers: "on",
|
}}
|
||||||
wordWrap: "on",
|
>
|
||||||
automaticLayout: true,
|
<Editor
|
||||||
}}
|
theme="vs-dark"
|
||||||
/>
|
defaultLanguage="css"
|
||||||
|
value={code}
|
||||||
|
onChange={handleEditorChange}
|
||||||
|
options={{
|
||||||
|
minimap: { enabled: false },
|
||||||
|
fontSize: 14,
|
||||||
|
lineNumbers: "on",
|
||||||
|
wordWrap: "on",
|
||||||
|
automaticLayout: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="theme-editor__footer">
|
<div className="theme-editor__footer">
|
||||||
|
<CollapsedMenu title="Notification Preview">
|
||||||
|
<div className="theme-editor__notification-preview">
|
||||||
|
<SelectField
|
||||||
|
className="theme-editor__notification-preview__select-variation"
|
||||||
|
label="Notification Variation"
|
||||||
|
options={Object.values(notificationVariations).map(
|
||||||
|
(variation) => {
|
||||||
|
return {
|
||||||
|
key: variation,
|
||||||
|
value: variation,
|
||||||
|
label: variation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
onChange={(value) =>
|
||||||
|
setNotificationVariation(
|
||||||
|
value.target.value as keyof typeof notificationVariations
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AchievementNotificationItem
|
||||||
|
position={achievementPreview.position}
|
||||||
|
currentAchievement={achievementPreview.achievement}
|
||||||
|
isClosing={isClosingNotifications}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CollapsedMenu>
|
||||||
|
|
||||||
<div className="theme-editor__footer-actions">
|
<div className="theme-editor__footer-actions">
|
||||||
<Button onClick={handleSave}>
|
<Button onClick={handleSave}>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
AchievementCustomNotificationPosition,
|
||||||
|
AchievementNotificationInfo,
|
||||||
|
} from "@types";
|
||||||
|
|
||||||
export enum Downloader {
|
export enum Downloader {
|
||||||
RealDebrid,
|
RealDebrid,
|
||||||
Torrent,
|
Torrent,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import { charMap } from "./char-map";
|
import { charMap } from "./char-map";
|
||||||
import { Downloader } from "./constants";
|
import { Downloader } from "./constants";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
import { AchievementNotificationInfo } from "@types";
|
||||||
|
|
||||||
export * from "./constants";
|
export * from "./constants";
|
||||||
|
|
||||||
@@ -175,3 +176,24 @@ export const formatDate = (
|
|||||||
if (isNaN(new Date(date).getDate())) return "N/A";
|
if (isNaN(new Date(date).getDate())) return "N/A";
|
||||||
return format(date, language == "en" ? "MM-dd-yyyy" : "dd/MM/yyyy");
|
return format(date, language == "en" ? "MM-dd-yyyy" : "dd/MM/yyyy");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateAchievementCustomNotificationTest = (
|
||||||
|
t: any,
|
||||||
|
language?: string
|
||||||
|
): AchievementNotificationInfo => {
|
||||||
|
return {
|
||||||
|
title: t("test_achievement_notification_title", {
|
||||||
|
ns: "notifications",
|
||||||
|
lng: language ?? "en",
|
||||||
|
}),
|
||||||
|
description: t("test_achievement_notification_description", {
|
||||||
|
ns: "notifications",
|
||||||
|
lng: language ?? "en",
|
||||||
|
}),
|
||||||
|
iconUrl: "https://cdn.losbroxas.org/favicon.svg",
|
||||||
|
points: 100,
|
||||||
|
isHidden: false,
|
||||||
|
isRare: false,
|
||||||
|
isPlatinum: false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user