More reminder editor

This commit is contained in:
jude 2023-10-29 12:47:16 +00:00
parent 640bf16875
commit d5f1a2e3a1
9 changed files with 320 additions and 239 deletions

View File

@ -1,9 +1,9 @@
export const Message = ({ value }) => (
export const Content = ({ value }) => (
<>
<label class="is-sr-only">Message</label>
<label class="is-sr-only">Content</label>
<textarea
class="message-input autoresize discord-content"
placeholder="Message Content..."
placeholder="Content Content..."
maxlength={2000}
name="content"
rows={1}

View 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>
);
};

View File

@ -0,0 +1,15 @@
export const Description = ({ description }) => (
<>
<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}
></textarea>
</>
);

View File

@ -0,0 +1,15 @@
export const Title = ({ title }) => (
<>
<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}
></textarea>
</>
);

View File

@ -0,0 +1,100 @@
import { Author } from "./Author";
import { Title } from "./Title";
import { Description } from "./Description";
export const Embed = ({ reminder }) => {
return (
<div class="discord-embed">
<div class="embed-body">
<button class="change-color button is-rounded is-small">
<span class="is-sr-only">Choose embed color</span>
<i class="fas fa-eye-dropper"></i>
</button>
<div class="a">
<Author name={reminder.embed_author} icon={reminder.embed_author_url}></Author>
<Title title={reminder.embed_title}></Title>
<br></br>
<Description description={reminder.embed_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>
<div class="embed-footer-box">
<p class="image is-20x20 customizable">
<a>
<img
class="is-rounded embed_footer_url"
src="/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}
></textarea>
</div>
</div>
);
};

View 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>
);
};

View File

@ -1,11 +1,14 @@
import { fetchGuildChannels, fetchUserInfo, Reminder } from "../../api";
import { useQueries, useQuery } from "react-query";
import { useQueries } from "react-query";
import { QueryKeys } from "../../consts";
import { useParams } from "wouter";
import { Name } from "./Name";
import { Username } from "./Username";
import { Message } from "./Message";
import { Content } from "./Content";
import { ChannelSelector } from "./ChannelSelector";
import { useState } from "preact/hooks";
import { IntervalSelector } from "./IntervalSelector";
import { Embed } from "./Embed";
type Props = {
reminder: Reminder;
@ -14,7 +17,10 @@ type Props = {
export const EditReminder = ({ reminder }: Props) => {
const { guild } = useParams();
const [{ isSuccess, data: channel }] = useQueries([
const [
{ isSuccess: channelsFetched, data: guildChannels },
{ isSuccess: userFetched, data: userInfo },
] = useQueries([
{
queryKey: [QueryKeys.GUILD_CHANNELS, guild],
queryFn: () => fetchGuildChannels(guild),
@ -27,20 +33,27 @@ export const EditReminder = ({ reminder }: Props) => {
},
]);
if (!isSuccess) {
const [collapsed, setCollapsed] = useState(false);
if (!channelsFetched || !userFetched) {
// todo
return <></>;
}
const channelInfo = channel.find((c) => c.id === reminder.channel);
const channelInfo = guildChannels.find((c) => c.id === reminder.channel);
return (
<div class="reminderContent">
<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">
<button
class="button hide-box"
onClick={() => {
setCollapsed(!collapsed);
}}
>
<span class="is-sr-only">Hide reminder</span>
<i class="fas fa-chevron-down"></i>
</button>
@ -63,145 +76,8 @@ export const EditReminder = ({ reminder }: Props) => {
<div class="media-content">
<div class="content">
<Username value={reminder.username}></Username>
<Message value={reminder.content}></Message>
<div class="discord-embed">
<div class="embed-body">
<button class="change-color button is-rounded is-small">
<span class="is-sr-only">Choose embed color</span>
<i class="fas fa-eye-dropper"></i>
</button>
<div class="a">
<div class="embed-author-box">
<div class="a">
<p class="image is-24x24 customizable">
<a>
<img
class="is-rounded embed_author_url"
src="/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"
></textarea>
</div>
</div>
<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"
></textarea>
<br></br>
<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}
></textarea>
<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>
<div class="embed-footer-box">
<p class="image is-20x20 customizable">
<a>
<img
class="is-rounded embed_footer_url"
src="/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}
></textarea>
</div>
</div>
<Content value={reminder.content}></Content>
<Embed reminder={reminder}></Embed>
</div>
</div>
</article>
@ -225,7 +101,7 @@ export const EditReminder = ({ reminder }: Props) => {
type="datetime-local"
step="1"
name="time"
value={reminder.utc_time.toISO()}
value={reminder.utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss")}
></input>
</label>
</div>
@ -233,7 +109,7 @@ export const EditReminder = ({ reminder }: Props) => {
<div class="collapses split-controls">
<div>
<div class="patreon-only">
<div class={!userInfo.patreon && "is-locked"}>
<div class="patreon-invert foreground">
Intervals available on{" "}
<a href="https://patreon.com/jellywx">Patreon</a> or{" "}
@ -248,93 +124,11 @@ export const EditReminder = ({ reminder }: Props) => {
<i class="fas fa-question-circle"></i>
</a>
</label>
<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=""
></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=""
></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"
></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"
></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"
></input>
</label>
</span>
</div>
<button class="clear">
<span class="is-sr-only">Clear interval</span>
<span class="icon">
<i class="fas fa-trash"></i>
</span>
</button>
</div>
</div>
<IntervalSelector
months={reminder.interval_months}
days={reminder.interval_days}
seconds={reminder.interval_seconds}
></IntervalSelector>
</div>
<div class="field">
@ -346,6 +140,12 @@ export const EditReminder = ({ reminder }: Props) => {
type="datetime-local"
step="1"
name="expiration"
value={
reminder.expires !== null &&
reminder.expires.toFormat(
"yyyy-LL-dd'T'HH:mm:ss",
)
}
></input>
</label>
</div>

View File

@ -12,7 +12,11 @@ export const GuildEntry = ({ guild }: Props) => {
return (
<li>
<Link
class={guild.id === currentId.groups.id ? "is-active switch-pane" : "switch-pane"}
class={
currentId !== null && guild.id === currentId.groups.id
? "is-active switch-pane"
: "switch-pane"
}
data-pane="guild"
data-guild={guild.id}
data-name={guild.name}

View File

@ -6,5 +6,6 @@ export default defineConfig({
plugins: [preact()],
build: {
assetsDir: "static/assets",
sourcemap: true,
},
});