mirror of
https://github.com/revoltchat/revite.git
synced 2024-12-24 22:52:09 -05:00
feat(mobx): migrate audio settings
This commit is contained in:
parent
c7df0088fc
commit
120e6a35d8
13 changed files with 147 additions and 188 deletions
|
@ -1,29 +0,0 @@
|
|||
import call_join from "./call_join.mp3";
|
||||
import call_leave from "./call_leave.mp3";
|
||||
import message from "./message.mp3";
|
||||
import outbound from "./outbound.mp3";
|
||||
|
||||
const SoundMap: { [key in Sounds]: string } = {
|
||||
message,
|
||||
outbound,
|
||||
call_join,
|
||||
call_leave,
|
||||
};
|
||||
|
||||
export type Sounds = "message" | "outbound" | "call_join" | "call_leave";
|
||||
export const SOUNDS_ARRAY: Sounds[] = [
|
||||
"message",
|
||||
"outbound",
|
||||
"call_join",
|
||||
"call_leave",
|
||||
];
|
||||
|
||||
export function playSound(sound: Sounds) {
|
||||
const file = SoundMap[sound];
|
||||
const el = new Audio(file);
|
||||
try {
|
||||
el.play();
|
||||
} catch (err) {
|
||||
console.error("Failed to play audio file", file, err);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import { EmojiPacks } from "../../redux/reducers/settings";
|
||||
export type EmojiPack = "mutant" | "twemoji" | "noto" | "openmoji";
|
||||
|
||||
let EMOJI_PACK = "mutant";
|
||||
let EMOJI_PACK: EmojiPack = "mutant";
|
||||
const REVISION = 3;
|
||||
|
||||
export function setGlobalEmojiPack(pack: EmojiPacks) {
|
||||
export function setGlobalEmojiPack(pack: EmojiPack) {
|
||||
EMOJI_PACK = pack;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,8 @@ import {
|
|||
} from "../../../lib/renderer/Singleton";
|
||||
|
||||
import { useApplicationState } from "../../../mobx/State";
|
||||
import { dispatch, getState } from "../../../redux";
|
||||
import { Reply } from "../../../redux/reducers/queue";
|
||||
|
||||
import { SoundContext } from "../../../context/Settings";
|
||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||
import {
|
||||
FileUploader,
|
||||
|
@ -123,7 +121,6 @@ export default observer(({ channel }: Props) => {
|
|||
});
|
||||
const [typing, setTyping] = useState<boolean | number>(false);
|
||||
const [replies, setReplies] = useState<Reply[]>([]);
|
||||
const playSound = useContext(SoundContext);
|
||||
const { openScreen } = useIntermediate();
|
||||
const client = useContext(AppContext);
|
||||
const translate = useTranslation();
|
||||
|
@ -242,7 +239,7 @@ export default observer(({ channel }: Props) => {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
playSound("outbound");
|
||||
state.settings.sounds.playSound("outbound");
|
||||
|
||||
state.queue.add(nonce, channel._id, {
|
||||
_id: nonce,
|
||||
|
@ -351,7 +348,7 @@ export default observer(({ channel }: Props) => {
|
|||
|
||||
setMessage();
|
||||
setReplies([]);
|
||||
playSound("outbound");
|
||||
state.settings.sounds.playSound("outbound");
|
||||
|
||||
if (files.length > CAN_UPLOAD_AT_ONCE) {
|
||||
setUploadState({
|
||||
|
|
|
@ -2,8 +2,7 @@ import styled from "styled-components";
|
|||
|
||||
import { Text } from "preact-i18n";
|
||||
|
||||
import { EmojiPack } from "../../../mobx/stores/Settings";
|
||||
|
||||
import { EmojiPack } from "../../common/Emoji";
|
||||
import mutantSVG from "./mutant_emoji.svg";
|
||||
import notoSVG from "./noto_emoji.svg";
|
||||
import openmojiSVG from "./openmoji_emoji.svg";
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
// This code is more or less redundant, but settings has so little state
|
||||
// updates that I can't be asked to pass everything through props each
|
||||
// time when I can just use the Context API.
|
||||
//
|
||||
// Replace references to SettingsContext with connectState in the future
|
||||
// if it does cause problems though.
|
||||
//
|
||||
// This now also supports Audio stuff.
|
||||
import defaultsDeep from "lodash.defaultsdeep";
|
||||
|
||||
import { createContext } from "preact";
|
||||
import { useMemo } from "preact/hooks";
|
||||
|
||||
import { connectState } from "../redux/connector";
|
||||
import {
|
||||
DEFAULT_SOUNDS,
|
||||
Settings,
|
||||
SoundOptions,
|
||||
} from "../redux/reducers/settings";
|
||||
|
||||
import { playSound, Sounds } from "../assets/sounds/Audio";
|
||||
import { Children } from "../types/Preact";
|
||||
|
||||
export const SettingsContext = createContext<Settings>({});
|
||||
export const SoundContext = createContext<(sound: Sounds) => void>(null!);
|
||||
|
||||
interface Props {
|
||||
children?: Children;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
function SettingsProvider({ settings, children }: Props) {
|
||||
const play = useMemo(() => {
|
||||
const enabled: SoundOptions = defaultsDeep(
|
||||
settings.notification?.sounds ?? {},
|
||||
DEFAULT_SOUNDS,
|
||||
);
|
||||
return (sound: Sounds) => {
|
||||
if (enabled[sound]) {
|
||||
playSound(sound);
|
||||
}
|
||||
};
|
||||
}, [settings.notification]);
|
||||
|
||||
return (
|
||||
<SettingsContext.Provider value={settings}>
|
||||
<SoundContext.Provider value={play}>
|
||||
{children}
|
||||
</SoundContext.Provider>
|
||||
</SettingsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectState<Omit<Props, "settings">>(
|
||||
SettingsProvider,
|
||||
(state) => {
|
||||
return {
|
||||
settings: state.settings,
|
||||
};
|
||||
},
|
||||
);
|
|
@ -4,7 +4,6 @@ import State from "../redux/State";
|
|||
|
||||
import { Children } from "../types/Preact";
|
||||
import Locale from "./Locale";
|
||||
import Settings from "./Settings";
|
||||
import Theme from "./Theme";
|
||||
import Intermediate from "./intermediate/Intermediate";
|
||||
import Client from "./revoltjs/RevoltClient";
|
||||
|
@ -17,13 +16,11 @@ export default function Context({ children }: { children: Children }) {
|
|||
return (
|
||||
<Router basename={import.meta.env.BASE_URL}>
|
||||
<State>
|
||||
<Settings>
|
||||
<Locale>
|
||||
<Intermediate>
|
||||
<Client>{children}</Client>
|
||||
</Intermediate>
|
||||
</Locale>
|
||||
</Settings>
|
||||
<Locale>
|
||||
<Intermediate>
|
||||
<Client>{children}</Client>
|
||||
</Intermediate>
|
||||
</Locale>
|
||||
<Theme />
|
||||
</State>
|
||||
</Router>
|
||||
|
|
|
@ -9,21 +9,9 @@ import { useCallback, useContext, useEffect } from "preact/hooks";
|
|||
import { useTranslation } from "../../lib/i18n";
|
||||
|
||||
import { useApplicationState } from "../../mobx/State";
|
||||
import { connectState } from "../../redux/connector";
|
||||
import {
|
||||
getNotificationState,
|
||||
Notifications,
|
||||
shouldNotify,
|
||||
} from "../../redux/reducers/notifications";
|
||||
import { NotificationOptions } from "../../redux/reducers/settings";
|
||||
|
||||
import { SoundContext } from "../Settings";
|
||||
import { AppContext } from "./RevoltClient";
|
||||
|
||||
interface Props {
|
||||
options?: NotificationOptions;
|
||||
}
|
||||
|
||||
const notifications: { [key: string]: Notification } = {};
|
||||
|
||||
async function createNotification(
|
||||
|
@ -38,10 +26,11 @@ async function createNotification(
|
|||
}
|
||||
}
|
||||
|
||||
function Notifier({ options }: Props) {
|
||||
function Notifier() {
|
||||
const translate = useTranslation();
|
||||
const showNotification = options?.desktopEnabled ?? false;
|
||||
const notifs = useApplicationState().notifications;
|
||||
const state = useApplicationState();
|
||||
const notifs = state.notifications;
|
||||
const showNotification = state.settings.get("notifications:desktop");
|
||||
|
||||
const client = useContext(AppContext);
|
||||
const { guild: guild_id, channel: channel_id } = useParams<{
|
||||
|
@ -49,14 +38,13 @@ function Notifier({ options }: Props) {
|
|||
channel: string;
|
||||
}>();
|
||||
const history = useHistory();
|
||||
const playSound = useContext(SoundContext);
|
||||
|
||||
const message = useCallback(
|
||||
async (msg: Message) => {
|
||||
if (msg.channel_id === channel_id && document.hasFocus()) return;
|
||||
if (!notifs.shouldNotify(msg)) return;
|
||||
|
||||
playSound("message");
|
||||
state.settings.sounds.playSound("message");
|
||||
if (!showNotification) return;
|
||||
|
||||
let title;
|
||||
|
@ -209,7 +197,7 @@ function Notifier({ options }: Props) {
|
|||
channel_id,
|
||||
client,
|
||||
notifs,
|
||||
playSound,
|
||||
state,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -257,7 +245,7 @@ function Notifier({ options }: Props) {
|
|||
};
|
||||
}, [
|
||||
client,
|
||||
playSound,
|
||||
state,
|
||||
guild_id,
|
||||
channel_id,
|
||||
showNotification,
|
||||
|
@ -285,27 +273,17 @@ function Notifier({ options }: Props) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const NotifierComponent = connectState(
|
||||
Notifier,
|
||||
(state) => {
|
||||
return {
|
||||
options: state.settings.notification,
|
||||
};
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
export default function NotificationsComponent() {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/server/:server/channel/:channel">
|
||||
<NotifierComponent />
|
||||
<Notifier />
|
||||
</Route>
|
||||
<Route path="/channel/:channel">
|
||||
<NotifierComponent />
|
||||
<Notifier />
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<NotifierComponent />
|
||||
<Notifier />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
|
|
|
@ -4,17 +4,13 @@ import { mapToRecord } from "../../lib/conversion";
|
|||
|
||||
import { Fonts, MonospaceFonts, Overrides } from "../../context/Theme";
|
||||
|
||||
import { Sounds } from "../../assets/sounds/Audio";
|
||||
import { EmojiPack } from "../../components/common/Emoji";
|
||||
|
||||
import Persistent from "../interfaces/Persistent";
|
||||
import Store from "../interfaces/Store";
|
||||
import SAudio, { SoundOptions } from "./helpers/SAudio";
|
||||
import STheme from "./helpers/STheme";
|
||||
|
||||
export type SoundOptions = {
|
||||
[key in Sounds]?: boolean;
|
||||
};
|
||||
|
||||
export type EmojiPack = "mutant" | "twemoji" | "noto" | "openmoji";
|
||||
|
||||
interface ISettings {
|
||||
"notifications:desktop": boolean;
|
||||
"notifications:sounds": SoundOptions;
|
||||
|
@ -37,6 +33,7 @@ export default class Settings implements Store, Persistent<ISettings> {
|
|||
private data: ObservableMap<string, unknown>;
|
||||
|
||||
theme: STheme;
|
||||
sounds: SAudio;
|
||||
|
||||
/**
|
||||
* Construct new Settings store.
|
||||
|
@ -46,6 +43,7 @@ export default class Settings implements Store, Persistent<ISettings> {
|
|||
makeAutoObservable(this);
|
||||
|
||||
this.theme = new STheme(this);
|
||||
this.sounds = new SAudio(this);
|
||||
}
|
||||
|
||||
get id() {
|
||||
|
|
107
src/mobx/stores/helpers/SAudio.ts
Normal file
107
src/mobx/stores/helpers/SAudio.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { makeAutoObservable, computed, action } from "mobx";
|
||||
|
||||
import Settings from "../Settings";
|
||||
import call_join from "./call_join.mp3";
|
||||
import call_leave from "./call_leave.mp3";
|
||||
import message from "./message.mp3";
|
||||
import outbound from "./outbound.mp3";
|
||||
|
||||
export type Sounds = "message" | "outbound" | "call_join" | "call_leave";
|
||||
|
||||
export interface Sound {
|
||||
enabled: boolean;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export type SoundOptions = {
|
||||
[key in Sounds]?: Partial<Sound>;
|
||||
};
|
||||
|
||||
export const DefaultSoundPack: { [key in Sounds]: string } = {
|
||||
message,
|
||||
outbound,
|
||||
call_join,
|
||||
call_leave,
|
||||
};
|
||||
|
||||
export const ALL_SOUNDS: Sounds[] = [
|
||||
"message",
|
||||
"outbound",
|
||||
"call_join",
|
||||
"call_leave",
|
||||
];
|
||||
export const DEFAULT_SOUNDS: Sounds[] = ["message", "call_join", "call_leave"];
|
||||
|
||||
/**
|
||||
* Helper class for reading and writing themes.
|
||||
*/
|
||||
export default class SAudio {
|
||||
private settings: Settings;
|
||||
private cache: Map<string, HTMLAudioElement>;
|
||||
|
||||
/**
|
||||
* Construct a new sound helper.
|
||||
* @param settings Settings parent class
|
||||
*/
|
||||
constructor(settings: Settings) {
|
||||
this.settings = settings;
|
||||
makeAutoObservable(this);
|
||||
|
||||
this.cache = new Map();
|
||||
|
||||
// Asynchronously load Audio files into cache.
|
||||
setTimeout(() => this.loadCache(), 0);
|
||||
}
|
||||
|
||||
@action setEnabled(sound: Sounds, enabled: boolean) {
|
||||
const obj = this.settings.get("notifications:sounds");
|
||||
this.settings.set("notifications:sounds", {
|
||||
...obj,
|
||||
[sound]: {
|
||||
...obj?.[sound],
|
||||
enabled,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@computed getSound(sound: Sounds, options?: SoundOptions): Sound {
|
||||
return {
|
||||
path: DefaultSoundPack[sound],
|
||||
enabled: DEFAULT_SOUNDS.includes(sound),
|
||||
...(options ?? this.settings.get("notifications:sounds"))?.[sound],
|
||||
};
|
||||
}
|
||||
|
||||
@computed getState(): ({ id: Sounds } & Sound)[] {
|
||||
const options = this.settings.get("notifications:sounds");
|
||||
return ALL_SOUNDS.map((id) => {
|
||||
return { id, ...this.getSound(id, options) };
|
||||
});
|
||||
}
|
||||
|
||||
getAudio(path: string) {
|
||||
if (this.cache.has(path)) {
|
||||
return this.cache.get(path)!;
|
||||
} else {
|
||||
const el = new Audio(path);
|
||||
this.cache.set(path, el);
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
loadCache() {
|
||||
this.getState().map(({ path }) => this.getAudio(path));
|
||||
}
|
||||
|
||||
playSound(sound: Sounds) {
|
||||
const definition = this.getSound(sound);
|
||||
if (definition.enabled) {
|
||||
const audio = this.getAudio(definition.path);
|
||||
try {
|
||||
audio.play();
|
||||
} catch (err) {
|
||||
console.error("Hit error while playing", sound + ":", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +1,19 @@
|
|||
import defaultsDeep from "lodash.defaultsdeep";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { urlBase64ToUint8Array } from "../../../lib/conversion";
|
||||
|
||||
import { useApplicationState } from "../../../mobx/State";
|
||||
import { dispatch } from "../../../redux";
|
||||
import { connectState } from "../../../redux/connector";
|
||||
import {
|
||||
DEFAULT_SOUNDS,
|
||||
NotificationOptions,
|
||||
SoundOptions,
|
||||
} from "../../../redux/reducers/settings";
|
||||
import { NotificationOptions } from "../../../redux/reducers/settings";
|
||||
|
||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import Checkbox from "../../../components/ui/Checkbox";
|
||||
|
||||
import { SOUNDS_ARRAY } from "../../../assets/sounds/Audio";
|
||||
|
||||
interface Props {
|
||||
options?: NotificationOptions;
|
||||
}
|
||||
|
@ -28,6 +21,7 @@ interface Props {
|
|||
export function Component({ options }: Props) {
|
||||
const client = useContext(AppContext);
|
||||
const { openScreen } = useIntermediate();
|
||||
const sounds = useApplicationState().settings.sounds;
|
||||
const [pushEnabled, setPushEnabled] = useState<undefined | boolean>(
|
||||
undefined,
|
||||
);
|
||||
|
@ -42,10 +36,6 @@ export function Component({ options }: Props) {
|
|||
});
|
||||
}, []);
|
||||
|
||||
const enabledSounds: SoundOptions = defaultsDeep(
|
||||
options?.sounds ?? {},
|
||||
DEFAULT_SOUNDS,
|
||||
);
|
||||
return (
|
||||
<div className={styles.notifications}>
|
||||
<h3>
|
||||
|
@ -125,24 +115,12 @@ export function Component({ options }: Props) {
|
|||
<h3>
|
||||
<Text id="app.settings.pages.notifications.sounds" />
|
||||
</h3>
|
||||
{SOUNDS_ARRAY.map((key) => (
|
||||
{sounds.getState().map(({ id, enabled }) => (
|
||||
<Checkbox
|
||||
key={key}
|
||||
checked={!!enabledSounds[key]}
|
||||
onChange={(enabled) =>
|
||||
dispatch({
|
||||
type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
|
||||
options: {
|
||||
sounds: {
|
||||
...options?.sounds,
|
||||
[key]: enabled,
|
||||
},
|
||||
},
|
||||
})
|
||||
}>
|
||||
<Text
|
||||
id={`app.settings.pages.notifications.sound.${key}`}
|
||||
/>
|
||||
key={id}
|
||||
checked={enabled}
|
||||
onChange={(enabled) => sounds.setEnabled(id, enabled)}>
|
||||
<Text id={`app.settings.pages.notifications.sound.${id}`} />
|
||||
</Checkbox>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -2,9 +2,10 @@ import type { Theme, ThemeOptions } from "../../context/Theme";
|
|||
|
||||
import { setGlobalEmojiPack } from "../../components/common/Emoji";
|
||||
|
||||
import type { Sounds } from "../../assets/sounds/Audio";
|
||||
import type { SyncUpdateAction } from "./sync";
|
||||
|
||||
type Sounds = "message" | "outbound" | "call_join" | "call_leave";
|
||||
|
||||
export type SoundOptions = {
|
||||
[key in Sounds]?: boolean;
|
||||
};
|
||||
|
|
|
@ -78,11 +78,10 @@ export function UI() {
|
|||
|
||||
render(
|
||||
<>
|
||||
<Theme>
|
||||
<UIDemo>
|
||||
<UI />
|
||||
</UIDemo>
|
||||
</Theme>
|
||||
<UIDemo>
|
||||
<UI />
|
||||
</UIDemo>
|
||||
<Theme />
|
||||
</>,
|
||||
document.getElementById("app")!,
|
||||
);
|
||||
|
|
|
@ -3877,11 +3877,6 @@ serialize-javascript@^4.0.0:
|
|||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
shade-blend-color@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shade-blend-color/-/shade-blend-color-1.0.0.tgz#cfa10d3673a22ba31d552a0e793b708bc24be0bc"
|
||||
integrity sha512-Tnp/ppF5h3YhPCpeHiZJ2DRnvmo4luu9qpMhuksCT+QInIXJ9alA3Vd9klfEi+RY8Oh7MaK5vzH/qcLo892L1g==
|
||||
|
||||
shallowequal@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
|
||||
|
|
Loading…
Reference in a new issue