Add reminder creator
This commit is contained in:
27
src/components/Reminder/ChannelSelector.tsx
Normal file
27
src/components/Reminder/ChannelSelector.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { QueryKeys } from "../../consts";
|
||||
import { useParams } from "wouter";
|
||||
import { fetchGuildChannels } from "../../api";
|
||||
|
||||
export const ChannelSelector = ({ channel }) => {
|
||||
const { guild } = useParams();
|
||||
|
||||
const { isSuccess, data } = useQuery({
|
||||
queryKey: [QueryKeys.GUILD_CHANNELS, guild],
|
||||
queryFn: () => fetchGuildChannels(guild),
|
||||
staleTime: 300,
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="control has-icons-left">
|
||||
<div class="select">
|
||||
<select name="channel" class="channel-selector" value={channel}>
|
||||
{isSuccess && data.map((c) => <option value={c.id}>{c.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-small is-left">
|
||||
<i class="fas fa-hashtag"></i>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
16
src/components/Reminder/Content.tsx
Normal file
16
src/components/Reminder/Content.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
export const Content = ({ value, onChange }) => (
|
||||
<>
|
||||
<label class="is-sr-only">Content</label>
|
||||
<textarea
|
||||
class="message-input autoresize discord-content"
|
||||
placeholder="Content Content..."
|
||||
maxlength={2000}
|
||||
name="content"
|
||||
rows={1}
|
||||
value={value}
|
||||
onChange={(ev) => {
|
||||
onChange(ev.currentTarget.value);
|
||||
}}
|
||||
></textarea>
|
||||
</>
|
||||
);
|
231
src/components/Reminder/CreateReminder.tsx
Normal file
231
src/components/Reminder/CreateReminder.tsx
Normal file
@ -0,0 +1,231 @@
|
||||
import { useParams } from "wouter";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useQueries } from "react-query";
|
||||
import { QueryKeys } from "../../consts";
|
||||
import { fetchGuildChannels, fetchUserInfo } from "../../api";
|
||||
import { Name } from "./Name";
|
||||
import { Username } from "./Username";
|
||||
import React from "react";
|
||||
import { Content } from "./Content";
|
||||
import { Embed } from "./Embed";
|
||||
import { ChannelSelector } from "./ChannelSelector";
|
||||
import { DateTime } from "luxon";
|
||||
import { IntervalSelector } from "./IntervalSelector";
|
||||
|
||||
export const CreateReminder = () => {
|
||||
const { guild } = useParams();
|
||||
const [reminder, setReminder] = useState({});
|
||||
|
||||
const [
|
||||
{ isSuccess: channelsFetched, data: guildChannels },
|
||||
{ isSuccess: userFetched, data: userInfo },
|
||||
] = useQueries([
|
||||
{
|
||||
queryKey: [QueryKeys.GUILD_CHANNELS, guild],
|
||||
queryFn: () => fetchGuildChannels(guild),
|
||||
staleTime: 300,
|
||||
},
|
||||
{
|
||||
queryKey: [QueryKeys.USER_DATA],
|
||||
queryFn: fetchUserInfo,
|
||||
staleTime: Infinity,
|
||||
},
|
||||
]);
|
||||
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
if (!channelsFetched || !userFetched) {
|
||||
// todo
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={collapsed ? "reminderContent is-collapsed" : "reminderContent"}>
|
||||
<div class="columns is-mobile column reminder-topbar">
|
||||
<Name value={""}></Name>
|
||||
<div class="hide-button-bar">
|
||||
<button
|
||||
class="button hide-box"
|
||||
onClick={() => {
|
||||
setCollapsed(!collapsed);
|
||||
}}
|
||||
>
|
||||
<span class="is-sr-only">Hide reminder</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns reminder-settings">
|
||||
<div class="column discord-frame">
|
||||
<article class="media">
|
||||
<figure class="media-left">
|
||||
<p class="image is-32x32 customizable">
|
||||
<a>
|
||||
<img
|
||||
class="is-rounded avatar"
|
||||
src="/static/img/bg.webp"
|
||||
alt="Image for discord avatar"
|
||||
></img>
|
||||
</a>
|
||||
</p>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<Username
|
||||
value={""}
|
||||
onChange={(username: string) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
username,
|
||||
}));
|
||||
}}
|
||||
></Username>
|
||||
<Content
|
||||
value={""}
|
||||
onChange={(content: string) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
content,
|
||||
}));
|
||||
}}
|
||||
></Content>
|
||||
<Embed reminder={{}} setReminder={setReminder}></Embed>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column settings">
|
||||
<div class="field channel-field">
|
||||
<div class="collapses">
|
||||
<label class="label" for="channelOption">
|
||||
Channel*
|
||||
</label>
|
||||
</div>
|
||||
<ChannelSelector channel={null}></ChannelSelector>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="label collapses">
|
||||
Time*
|
||||
<input
|
||||
class="input"
|
||||
type="datetime-local"
|
||||
step="1"
|
||||
name="time"
|
||||
value={DateTime.now().toFormat("yyyy-LL-dd'T'HH:mm:ss")}
|
||||
onChange={(ev) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
utc_time: DateTime.fromISO(
|
||||
ev.currentTarget.value,
|
||||
).toUTC(),
|
||||
}));
|
||||
}}
|
||||
></input>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="collapses split-controls">
|
||||
<div>
|
||||
<div
|
||||
class={userInfo.patreon ? "patreon-only" : "patreon-only is-locked"}
|
||||
>
|
||||
<div class="patreon-invert foreground">
|
||||
Intervals available on{" "}
|
||||
<a href="https://patreon.com/jellywx">Patreon</a> or{" "}
|
||||
<a href="https://gitea.jellypro.xyz/jude/reminder-bot">
|
||||
self-hosting
|
||||
</a>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">
|
||||
Interval{" "}
|
||||
<a class="foreground" href="/help/intervals">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<IntervalSelector
|
||||
months={0}
|
||||
days={0}
|
||||
seconds={0}
|
||||
></IntervalSelector>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="label">
|
||||
Expiration
|
||||
<input
|
||||
class="input"
|
||||
type="datetime-local"
|
||||
step="1"
|
||||
name="expiration"
|
||||
value={DateTime.now().toFormat(
|
||||
"yyyy-LL-dd'T'HH:mm:ss",
|
||||
)}
|
||||
></input>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns is-mobile tts-row">
|
||||
<div class="column has-text-centered">
|
||||
<div class="is-boxed">
|
||||
<label class="label">
|
||||
Enable TTS <input type="checkbox" name="tts"></input>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column has-text-centered">
|
||||
<div class="file is-small is-boxed">
|
||||
<label class="file-label">
|
||||
<input
|
||||
class="file-input"
|
||||
type="file"
|
||||
name="attachment"
|
||||
></input>
|
||||
<span class="file-cta">
|
||||
<span class="file-label">Add Attachment</span>
|
||||
<span class="file-icon">
|
||||
<i class="fas fa-upload"></i>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<div class="button-row-reminder">
|
||||
<button class="button is-success">
|
||||
<span>Create Reminder</span>{" "}
|
||||
<span class="icon">
|
||||
<i class="fas fa-sparkles"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="button-row-template">
|
||||
<div>
|
||||
<button class="button is-success is-outlined">
|
||||
<span>Create Template</span>{" "}
|
||||
<span class="icon">
|
||||
<i class="fas fa-file-spreadsheet"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="button is-outlined show-modal is-pulled-right">
|
||||
Load Template
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
227
src/components/Reminder/EditReminder.tsx
Normal file
227
src/components/Reminder/EditReminder.tsx
Normal file
@ -0,0 +1,227 @@
|
||||
import { fetchGuildChannels, fetchUserInfo, Reminder } from "../../api";
|
||||
import { useQueries } from "react-query";
|
||||
import { QueryKeys } from "../../consts";
|
||||
import { useParams } from "wouter";
|
||||
import { Name } from "./Name";
|
||||
import { Username } from "./Username";
|
||||
import { Content } from "./Content";
|
||||
import { ChannelSelector } from "./ChannelSelector";
|
||||
import { useState } from "preact/hooks";
|
||||
import { IntervalSelector } from "./IntervalSelector";
|
||||
import { Embed } from "./Embed";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
type Props = {
|
||||
reminder: Reminder;
|
||||
};
|
||||
|
||||
export const EditReminder = ({ reminder: initialReminder }: Props) => {
|
||||
const { guild } = useParams();
|
||||
const [reminder, setReminder] = useState(initialReminder);
|
||||
|
||||
const [
|
||||
{ isSuccess: channelsFetched, data: guildChannels },
|
||||
{ isSuccess: userFetched, data: userInfo },
|
||||
] = useQueries([
|
||||
{
|
||||
queryKey: [QueryKeys.GUILD_CHANNELS, guild],
|
||||
queryFn: () => fetchGuildChannels(guild),
|
||||
staleTime: 300,
|
||||
},
|
||||
{
|
||||
queryKey: [QueryKeys.USER_DATA],
|
||||
queryFn: fetchUserInfo,
|
||||
staleTime: Infinity,
|
||||
},
|
||||
]);
|
||||
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
if (!channelsFetched || !userFetched) {
|
||||
// todo
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const channelInfo = guildChannels.find((c) => c.id === reminder.channel);
|
||||
|
||||
return (
|
||||
<div class={collapsed ? "reminderContent is-collapsed" : "reminderContent"}>
|
||||
<div class="columns is-mobile column reminder-topbar">
|
||||
<div class="invert-collapses channel-bar">#{channelInfo.name}</div>
|
||||
<Name value={reminder.name}></Name>
|
||||
<div class="hide-button-bar">
|
||||
<button
|
||||
class="button hide-box"
|
||||
onClick={() => {
|
||||
setCollapsed(!collapsed);
|
||||
}}
|
||||
>
|
||||
<span class="is-sr-only">Hide reminder</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns reminder-settings">
|
||||
<div class="column discord-frame">
|
||||
<article class="media">
|
||||
<figure class="media-left">
|
||||
<p class="image is-32x32 customizable">
|
||||
<a>
|
||||
<img
|
||||
class="is-rounded avatar"
|
||||
src="/static/img/bg.webp"
|
||||
alt="Image for discord avatar"
|
||||
></img>
|
||||
</a>
|
||||
</p>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<Username
|
||||
value={reminder.username}
|
||||
onChange={(username: string) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
username,
|
||||
}));
|
||||
}}
|
||||
></Username>
|
||||
<Content
|
||||
value={reminder.content}
|
||||
onChange={(content: string) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
content,
|
||||
}));
|
||||
}}
|
||||
></Content>
|
||||
<Embed reminder={reminder} setReminder={setReminder}></Embed>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column settings">
|
||||
<div class="field channel-field">
|
||||
<div class="collapses">
|
||||
<label class="label" for="channelOption">
|
||||
Channel*
|
||||
</label>
|
||||
</div>
|
||||
<ChannelSelector channel={reminder.channel}></ChannelSelector>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="label collapses">
|
||||
Time*
|
||||
<input
|
||||
class="input"
|
||||
type="datetime-local"
|
||||
step="1"
|
||||
name="time"
|
||||
value={reminder.utc_time
|
||||
.toLocal()
|
||||
.toFormat("yyyy-LL-dd'T'HH:mm:ss")}
|
||||
onChange={(ev) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
utc_time: DateTime.fromISO(
|
||||
ev.currentTarget.value,
|
||||
).toUTC(),
|
||||
}));
|
||||
}}
|
||||
></input>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="collapses split-controls">
|
||||
<div>
|
||||
<div
|
||||
class={userInfo.patreon ? "patreon-only" : "patreon-only is-locked"}
|
||||
>
|
||||
<div class="patreon-invert foreground">
|
||||
Intervals available on{" "}
|
||||
<a href="https://patreon.com/jellywx">Patreon</a> or{" "}
|
||||
<a href="https://gitea.jellypro.xyz/jude/reminder-bot">
|
||||
self-hosting
|
||||
</a>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">
|
||||
Interval{" "}
|
||||
<a class="foreground" href="/help/intervals">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
</a>
|
||||
</label>
|
||||
<IntervalSelector
|
||||
months={reminder.interval_months}
|
||||
days={reminder.interval_days}
|
||||
seconds={reminder.interval_seconds}
|
||||
></IntervalSelector>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="label">
|
||||
Expiration
|
||||
<input
|
||||
class="input"
|
||||
type="datetime-local"
|
||||
step="1"
|
||||
name="expiration"
|
||||
value={
|
||||
reminder.expires !== null &&
|
||||
reminder.expires.toFormat(
|
||||
"yyyy-LL-dd'T'HH:mm:ss",
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns is-mobile tts-row">
|
||||
<div class="column has-text-centered">
|
||||
<div class="is-boxed">
|
||||
<label class="label">
|
||||
Enable TTS <input type="checkbox" name="tts"></input>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column has-text-centered">
|
||||
<div class="file is-small is-boxed">
|
||||
<label class="file-label">
|
||||
<input
|
||||
class="file-input"
|
||||
type="file"
|
||||
name="attachment"
|
||||
></input>
|
||||
<span class="file-cta">
|
||||
<span class="file-label">Add Attachment</span>
|
||||
<span class="file-icon">
|
||||
<i class="fas fa-upload"></i>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row-edit">
|
||||
<button class="button is-success save-btn">
|
||||
<span>Save</span>{" "}
|
||||
<span class="icon">
|
||||
<i class="fas fa-save"></i>
|
||||
</span>
|
||||
</button>
|
||||
<button class="button is-warning">{reminder.enabled ? "Disable" : "Enable"}</button>
|
||||
<button class="button is-danger delete-reminder">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
31
src/components/Reminder/Embed/Author.tsx
Normal file
31
src/components/Reminder/Embed/Author.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
export const Author = ({ name, icon }) => {
|
||||
return (
|
||||
<div class="embed-author-box">
|
||||
<div class="a">
|
||||
<p class="image is-24x24 customizable">
|
||||
<a>
|
||||
<img
|
||||
class="is-rounded embed_author_url"
|
||||
src={icon || "/static/img/bg.webp"}
|
||||
alt="Image for embed author"
|
||||
></img>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="b">
|
||||
<label class="is-sr-only" for="embedAuthor">
|
||||
Embed Author
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-embed-author message-input autoresize"
|
||||
placeholder="Embed Author..."
|
||||
rows={1}
|
||||
maxlength={256}
|
||||
name="embed_author"
|
||||
value={name}
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
91
src/components/Reminder/Embed/Color.tsx
Normal file
91
src/components/Reminder/Embed/Color.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
|
||||
export const Color = ({ color: colorProp, onChange }) => {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [color, setColor] = useState(colorProp);
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalOpen && (
|
||||
<ColorModal
|
||||
color={color}
|
||||
setModalOpen={setModalOpen}
|
||||
onSave={setColor}
|
||||
></ColorModal>
|
||||
)}
|
||||
<button
|
||||
class="change-color button is-rounded is-small"
|
||||
onClick={() => {
|
||||
setModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span class="is-sr-only">Choose embed color</span>
|
||||
<i class="fas fa-eye-dropper"></i>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ColorModal = ({ setModalOpen, color: colorProp, onSave }) => {
|
||||
const [color, setColor] = useState(colorProp);
|
||||
|
||||
return (
|
||||
<div class="modal is-active" id="pickColorModal">
|
||||
<div
|
||||
class="modal-background"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<label class="modal-card-title" for="colorInput">
|
||||
Select Color
|
||||
</label>
|
||||
<button
|
||||
class="delete close-modal"
|
||||
aria-label="close"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="colorpicker-container">
|
||||
<HexColorPicker color={color} onChange={setColor}></HexColorPicker>
|
||||
</div>
|
||||
<br></br>
|
||||
<input
|
||||
class="input"
|
||||
id="colorInput"
|
||||
value={color}
|
||||
onChange={(ev) => {
|
||||
setColor(ev.currentTarget.value);
|
||||
}}
|
||||
></input>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button class="button is-success" onChange={onSave}>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
class="button close-modal"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
<button
|
||||
class="modal-close is-large close-modal"
|
||||
aria-label="close"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
></button>
|
||||
</div>
|
||||
);
|
||||
};
|
18
src/components/Reminder/Embed/Description.tsx
Normal file
18
src/components/Reminder/Embed/Description.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
export const Description = ({ description, onChange }) => (
|
||||
<>
|
||||
<label class="is-sr-only" for="embedDescription">
|
||||
Embed Description
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-description message-input autoresize "
|
||||
placeholder="Embed Description..."
|
||||
maxlength={4096}
|
||||
name="embed_description"
|
||||
rows={1}
|
||||
value={description}
|
||||
onChange={(ev) => {
|
||||
onChange(ev.currentTarget.value);
|
||||
}}
|
||||
></textarea>
|
||||
</>
|
||||
);
|
24
src/components/Reminder/Embed/Footer.tsx
Normal file
24
src/components/Reminder/Embed/Footer.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
export const Footer = ({ footer, icon }) => (
|
||||
<div class="embed-footer-box">
|
||||
<p class="image is-20x20 customizable">
|
||||
<a>
|
||||
<img
|
||||
class="is-rounded embed_footer_url"
|
||||
src={icon || "/static/img/bg.webp"}
|
||||
alt="Footer profile-like image"
|
||||
></img>
|
||||
</a>
|
||||
</p>
|
||||
<label class="is-sr-only" for="embedFooter">
|
||||
Embed Footer text
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-embed-footer message-input autoresize "
|
||||
placeholder="Embed Footer..."
|
||||
maxlength={2048}
|
||||
name="embed_footer"
|
||||
rows={1}
|
||||
value={footer}
|
||||
></textarea>
|
||||
</div>
|
||||
);
|
18
src/components/Reminder/Embed/Title.tsx
Normal file
18
src/components/Reminder/Embed/Title.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
export const Title = ({ title, onChange }) => (
|
||||
<>
|
||||
<label class="is-sr-only" for="embedTitle">
|
||||
Embed Title
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-title message-input autoresize"
|
||||
placeholder="Embed Title..."
|
||||
maxlength={256}
|
||||
rows={1}
|
||||
name="embed_title"
|
||||
value={title}
|
||||
onChange={(ev) => {
|
||||
onChange(ev.currentTarget.value);
|
||||
}}
|
||||
></textarea>
|
||||
</>
|
||||
);
|
112
src/components/Reminder/Embed/index.tsx
Normal file
112
src/components/Reminder/Embed/index.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { Author } from "./Author";
|
||||
import { Title } from "./Title";
|
||||
import { Description } from "./Description";
|
||||
import { Footer } from "./Footer";
|
||||
import { Color } from "./Color";
|
||||
import { Reminder } from "../../../api";
|
||||
|
||||
type Props = {
|
||||
reminder: Reminder;
|
||||
setReminder: (reminder: Reminder) => {};
|
||||
};
|
||||
|
||||
function colorToInt(hex: string) {
|
||||
return parseInt(hex.substring(1), 16);
|
||||
}
|
||||
|
||||
export const Embed = ({ reminder, setReminder }) => {
|
||||
return (
|
||||
<div class="discord-embed">
|
||||
<div class="embed-body">
|
||||
<Color
|
||||
color={reminder.embed_color}
|
||||
onChange={(color: string) => {
|
||||
setReminder((reminder: Reminder) => ({
|
||||
...reminder,
|
||||
embed_color: colorToInt(color),
|
||||
}));
|
||||
}}
|
||||
></Color>
|
||||
<div class="a">
|
||||
<Author name={reminder.embed_author} icon={reminder.embed_author_url}></Author>
|
||||
<Title
|
||||
title={reminder.embed_title}
|
||||
onChange={(title: string) =>
|
||||
setReminder((reminder: Reminder) => ({
|
||||
...reminder,
|
||||
embed_title: title,
|
||||
}))
|
||||
}
|
||||
></Title>
|
||||
<br></br>
|
||||
<Description
|
||||
description={reminder.embed_description}
|
||||
onChange={(description: string) =>
|
||||
setReminder((reminder: Reminder) => ({
|
||||
...reminder,
|
||||
embed_description: description,
|
||||
}))
|
||||
}
|
||||
></Description>
|
||||
<br></br>
|
||||
|
||||
<div class="embed-multifield-box">
|
||||
<div data-inlined="1" class="embed-field-box">
|
||||
<label class="is-sr-only" for="embedFieldTitle">
|
||||
Field Title
|
||||
</label>
|
||||
<div class="is-flex">
|
||||
<textarea
|
||||
class="discord-field-title field-input message-input autoresize"
|
||||
placeholder="Field Title..."
|
||||
rows={1}
|
||||
maxlength={256}
|
||||
name="embed_field_title[]"
|
||||
></textarea>
|
||||
<button class="button is-small inline-btn">
|
||||
<span class="is-sr-only">Toggle field inline</span>
|
||||
<i class="fas fa-arrows-h"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label class="is-sr-only" for="embedFieldValue">
|
||||
Field Value
|
||||
</label>
|
||||
<textarea
|
||||
class="discord-field-value field-input message-input autoresize "
|
||||
placeholder="Field Value..."
|
||||
maxlength={1024}
|
||||
name="embed_field_value[]"
|
||||
rows={1}
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="b">
|
||||
<p class="image thumbnail customizable">
|
||||
<a>
|
||||
<img
|
||||
class="embed_thumbnail_url"
|
||||
src="/static/img/bg.webp"
|
||||
alt="Square thumbnail embedded image"
|
||||
></img>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="image is-400x300 customizable">
|
||||
<a>
|
||||
<img
|
||||
class="embed_image_url"
|
||||
src="/static/img/bg.webp"
|
||||
alt="Large embedded image"
|
||||
></img>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<Footer footer={reminder.embed_footer} icon={reminder.embed_footer_url}></Footer>
|
||||
</div>
|
||||
);
|
||||
};
|
115
src/components/Reminder/IntervalSelector.tsx
Normal file
115
src/components/Reminder/IntervalSelector.tsx
Normal file
@ -0,0 +1,115 @@
|
||||
import { useState } from "preact/hooks";
|
||||
|
||||
function divmod(a: number, b: number) {
|
||||
return [Math.floor(a / b), a % b];
|
||||
}
|
||||
|
||||
function secondsToHMS(seconds: number) {
|
||||
let hours: number, minutes: number;
|
||||
|
||||
[minutes, seconds] = divmod(seconds, 60);
|
||||
[hours, minutes] = divmod(minutes, 60);
|
||||
|
||||
return [hours, minutes, seconds];
|
||||
}
|
||||
|
||||
export const IntervalSelector = ({ months: monthsProp, days: daysProp, seconds: secondsProp }) => {
|
||||
const [months, setMonths] = useState(monthsProp);
|
||||
const [days, setDays] = useState(daysProp);
|
||||
const [seconds, setSeconds] = useState(secondsProp);
|
||||
|
||||
let [hours, minutes, secondsRem] = [0, 0, 0];
|
||||
if (seconds !== null) {
|
||||
[hours, minutes, secondsRem] = secondsToHMS(seconds);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="control intervalSelector">
|
||||
<div class="input interval-group">
|
||||
<div class="interval-group-left">
|
||||
<span class="no-break">
|
||||
<label>
|
||||
<span class="is-sr-only">Interval months</span>
|
||||
<input
|
||||
class="w2"
|
||||
type="text"
|
||||
pattern="\d*"
|
||||
name="interval_months"
|
||||
maxlength={2}
|
||||
placeholder=""
|
||||
value={months || ""}
|
||||
></input>{" "}
|
||||
<span class="half-rem"></span> months, <span class="half-rem"></span>
|
||||
</label>
|
||||
<label>
|
||||
<span class="is-sr-only">Interval days</span>
|
||||
<input
|
||||
class="w3"
|
||||
type="text"
|
||||
pattern="\d*"
|
||||
name="interval_days"
|
||||
maxlength={4}
|
||||
placeholder=""
|
||||
value={days || ""}
|
||||
></input>{" "}
|
||||
<span class="half-rem"></span> days, <span class="half-rem"></span>
|
||||
</label>
|
||||
</span>
|
||||
<span class="no-break">
|
||||
<label>
|
||||
<span class="is-sr-only">Interval hours</span>
|
||||
<input
|
||||
class="w2"
|
||||
type="text"
|
||||
pattern="\d*"
|
||||
name="interval_hours"
|
||||
maxlength={2}
|
||||
placeholder="HH"
|
||||
value={hours || ""}
|
||||
></input>
|
||||
:
|
||||
</label>
|
||||
<label>
|
||||
<span class="is-sr-only">Interval minutes</span>
|
||||
<input
|
||||
class="w2"
|
||||
type="text"
|
||||
pattern="\d*"
|
||||
name="interval_minutes"
|
||||
maxlength={2}
|
||||
placeholder="MM"
|
||||
value={minutes || ""}
|
||||
></input>
|
||||
:
|
||||
</label>
|
||||
<label>
|
||||
<span class="is-sr-only">Interval seconds</span>
|
||||
<input
|
||||
class="w2"
|
||||
type="text"
|
||||
pattern="\d*"
|
||||
name="interval_seconds"
|
||||
maxlength={2}
|
||||
placeholder="SS"
|
||||
value={secondsRem || ""}
|
||||
></input>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="clear"
|
||||
onClick={() => {
|
||||
setMonths(0);
|
||||
setDays(0);
|
||||
setSeconds(0);
|
||||
}}
|
||||
>
|
||||
<span class="is-sr-only">Clear interval</span>
|
||||
<span class="icon">
|
||||
<i class="fas fa-trash"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
17
src/components/Reminder/Name.tsx
Normal file
17
src/components/Reminder/Name.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
export const Name = ({ value }) => (
|
||||
<div class="name-bar">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="label sr-only">Reminder Name</label>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="Reminder Name"
|
||||
maxlength={100}
|
||||
value={value}
|
||||
></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
17
src/components/Reminder/Username.tsx
Normal file
17
src/components/Reminder/Username.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
export const Username = ({ value, onChange }) => {
|
||||
return (
|
||||
<div class="discord-message-header">
|
||||
<label class="is-sr-only">Username Override</label>
|
||||
<input
|
||||
class="discord-username message-input"
|
||||
placeholder="Username Override"
|
||||
maxlength={32}
|
||||
name="username"
|
||||
value={value}
|
||||
onChange={(ev) => {
|
||||
onChange(ev.currentTarget.value);
|
||||
}}
|
||||
></input>
|
||||
</div>
|
||||
);
|
||||
};
|
Reference in New Issue
Block a user