added TintSpinner component to SplitPanel, which ise used WeightEditPage to block input while the POST is being made

This commit is contained in:
Victor committed 2024-01-02 21:34:54 -05:00
1 parent fa4e8c7a73
commit 7aab2293b1
12 files changed
+127 -102

No files matched your search

@@ -1,7 +1,7 @@
import MakeRequest from "../MakeRequest";
export interface UpdateWeightRequest {
cover_image_media_file_token: string;
cover_image_media_file_token?: string;
description_markdown: string,
description_rendered_html: string,
title: string,
@@ -0,0 +1,62 @@
import React, { useState } from 'react';
import { a, useTransition } from "@react-spring/web";
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
import { Button, ImageInput } from "components/common";
import { WorkIndicator } from "components/svg";
import { faImage } from "@fortawesome/pro-solid-svg-icons";
import { basicTransition } from "resources";
interface Props {
currentPath?: string,
onClick: (x: any) => any,
status: FetchStatus
}
interface UploadCtrlProps {
onClick: (x: any) => any,
status: FetchStatus
}
const UploadControl = ({ onClick, status }: UploadCtrlProps) => {
const failure = status === FetchStatus.error;
const success = status === FetchStatus.success;
return status === FetchStatus.ready ? <Button {...{
className: "upload-control-btn",
label: "Upload image",
onClick,
variant: "secondary"
}} /> : <div {...{ className: "upload-control-indicator" }}>
<WorkIndicator {...{
failure,
stage: success ? 2 : 1,
success,
}} />
<span>{ success ? "Cover image uploaded" : "Uploading ..." }</span>
</div>;
};
export default function CoverImageInput({ currentPath, onClick, status, ...rest }: Props) {
const [editingImg, editingImgSet] = useState(0);
const transitions = useTransition(editingImg, basicTransition({}));
return transitions((style, i) => !currentPath || i ? <a.div {...{ style }}>
<ImageInput {...{ ...rest, disabled: status > 1, placeholderIcon: faImage }} >
<div {...{ className: "fy-cover-control" }}>
<UploadControl {...{ onClick, status }} />
</div>
</ImageInput>
</a.div> : <a.div {...{
className: "weight-initial-cover-img",
style: { ...style, backgroundImage: `url(${ currentPath })` }
}}>
<div>
<Button {...{
className: "upload-control-btn",
label: "Change cover image",
onClick: () => editingImgSet(1),
variant: "secondary"
}} />
</div>
</a.div>);
};
@@ -0,0 +1 @@
export { default } from "./CoverImageInput";
@@ -1,4 +1,5 @@
import React, { ReactNode } from "react";
import { TintSpinner } from "components/common";
// Header, Body, and Footer Interfaces and Components
interface HeaderProps {
@@ -30,6 +31,7 @@ const Footer: React.FC<FooterProps> = ({ children, padding }) => {
// Panel Interface
interface PanelProps {
busy?: boolean;
children: ReactNode;
dividerHeader?: boolean;
dividerFooter?: boolean;
@@ -42,7 +44,7 @@ const SplitPanel: React.FC<PanelProps> & {
Header: typeof Header;
Body: typeof Body;
Footer: typeof Footer;
} = ({ children, dividerHeader, dividerFooter, clear, className }) => {
} = ({ busy, children, dividerHeader, dividerFooter, clear, className }) => {
let header: ReactNode, body: ReactNode, footer: ReactNode;
const panelClassName = `${clear ? "panel-clear" : "panel"} ${
@@ -72,6 +74,7 @@ const SplitPanel: React.FC<PanelProps> & {
{body}
{dividerFooter && <hr className="m-0" />}
{footer}
{ <TintSpinner {...{ busy }} /> }
</div>
);
};
@@ -0,0 +1,14 @@
import React from "react";
import { a, useTransition } from "@react-spring/web";
import { basicTransition } from "resources";
import { Spinner } from "components/common";
interface Props {
busy?: boolean;
}
export default function TintSpinner({ busy }: Props) {
const transitions = useTransition(busy, basicTransition({}));
return transitions((style, i) => busy && <a.div {...{ className: "fy-tint-spinner", style }}><Spinner /></a.div>);
};
@@ -0,0 +1 @@
export { default } from "./TintSpinner";
@@ -4,6 +4,7 @@ import BasicVideo from "./BasicVideo";
import Button from "./Button";
import Checkbox from "./Checkbox";
import Container from "./Container";
import CoverImageInput from "./CoverImageInput";
import FileActions from "./FileActions";
import FileDetails from "./FileDetails";
import FileWrapper from "./FileWrapper";
@@ -23,6 +24,7 @@ import TempAudioPlayer from "./TempAudioPlayer";
import TempInput from "./TempInput";
import TempSelect from "./TempSelect";
import TempTextArea from "./TempTextArea";
import TintSpinner from "./TintSpinner";
export {
AudioBlobPreview,
@@ -31,6 +33,7 @@ export {
Button,
Checkbox,
Container,
CoverImageInput,
FileActions,
FileDetails,
FileWrapper,
@@ -49,5 +52,6 @@ export {
TempAudioPlayer,
TempInput,
TempSelect,
TempTextArea
TempTextArea,
TintSpinner
};
@@ -42,7 +42,7 @@ export default function useWeightFetch({ onRemove = () => {}, token }: Props) {
const update = () => {
writeStatusSet(FetchStatus.in_progress);
UpdateWeight(token,{
cover_image_media_file_token: imgMediaFile,
...imgMediaFile ? { cover_image_media_file_token: imgMediaFile } : {},
description_markdown: descriptionMD,
description_rendered_html: data?.description_rendered_html || "",
title,
@@ -0,0 +1,11 @@
import { config } from "@react-spring/web";
const basicTransition = ({ ...overwrite }) => ({
config: config.gentle,
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
...overwrite
});
export default basicTransition;
+2 -1
View File
@@ -1,4 +1,5 @@
import basicTransition from './basicTransition';
import baseColors from './baseColors';
import springs from './springs';
export { baseColors, springs }
export { basicTransition, baseColors, springs }
@@ -85,3 +85,17 @@
flex-direction: column;
}
}
.fy-tint-spinner {
top: 0;
width: 100%;
height: 100%;
background-color: rgba($panel, .6);
backdrop-filter: blur(2px);
border-radius: .5rem;
position: absolute;
z-index: 3;
display: flex;
align-items: center;
justify-content: center;
}
@@ -1,10 +1,8 @@
import React, { useState } from "react";
import { useParams } from "react-router-dom";
import { a, useTransition } from "@react-spring/web";
import {
faCircleExclamation,
faEye,
faImage,
faWaveform,
} from "@fortawesome/pro-solid-svg-icons";
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
@@ -12,18 +10,16 @@ import PageHeader from "components/layout/PageHeader";
import {
Button,
Container,
CoverImageInput,
Panel,
Skeleton,
SplitPanel,
ImageInput,
TempInput,
TempSelect,
TempTextArea,
} from "components/common";
import { WorkIndicator } from "components/svg";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
import { useWeightFetch } from "hooks";
import "./WeightEditPage.scss";
@@ -31,40 +27,6 @@ interface WeightEditPageProps {
sessionWrapper: SessionWrapper;
}
// UploadControl will eventually live elsewhere
const UploadControl = ({
onClick,
status,
}: {
onClick: (x: any) => any;
status: FetchStatus;
}) => {
const failure = status === FetchStatus.error;
const success = status === FetchStatus.success;
return status === FetchStatus.ready ? (
<Button
{...{
className: "upload-control-btn",
label: "Upload image",
onClick,
variant: "secondary",
}}
/>
) : (
<div {...{ className: "upload-control-indicator" }}>
<WorkIndicator
{...{
failure,
stage: success ? 2 : 1,
success,
}}
/>
<span>{success ? "Cover image uploaded" : "Uploading ..."}</span>
</div>
);
};
export default function WeightEditPage({
sessionWrapper,
}: WeightEditPageProps) {
@@ -87,20 +49,11 @@ export default function WeightEditPage({
uploadCoverImg,
update,
visibility,
writeStatus
} = useWeightFetch({ token: weight_token });
const src = new BucketConfig().getGcsUrl(
weight?.maybe_cover_image_public_bucket_path || ""
);
const [editingImg, editingImgSet] = useState(0);
const transitions = useTransition(editingImg, {
config: { tension: 120, friction: 15 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
});
const basePath = weight?.maybe_cover_image_public_bucket_path || "";
const currentPath = basePath ? new BucketConfig().getGcsUrl(basePath) : "";
usePrefixedDocumentTitle("Edit Voice");
@@ -161,57 +114,18 @@ export default function WeightEditPage({
</div>
</Panel>
) : (
<SplitPanel dividerFooter={true}>
<SplitPanel {...{ busy: writeStatus > 0, dividerFooter: true }}>
<SplitPanel.Body padding={true}>
<div {...{ className: "weight-editor row gy-3 gx-4" }}>
<div {...{ className: "col-12 col-lg-5" }}>
<label className="sub-title">Cover Image</label>
<div {...{ className: "fy-cover-img-input" }}>
{" "}
{weight?.maybe_cover_image_public_bucket_path &&
transitions((style, i) =>
!i ? (
<a.div
{...{
className: "weight-initial-cover-img",
style: {
...style,
backgroundImage: `url(${src})`,
},
}}
>
<div>
<Button
{...{
className: "upload-control-btn",
label: "Change cover image",
onClick: () => editingImgSet(1),
variant: "secondary",
}}
/>
</div>
</a.div>
) : (
<a.div {...{ style }}>
<ImageInput
{...{
...imageProps,
disabled: imgUploadStatus > 1,
placeholderIcon: faImage,
}}
>
<div {...{ className: "fy-cover-control" }}>
<UploadControl
{...{
onClick: uploadCoverImg,
status: imgUploadStatus,
}}
/>
</div>
</ImageInput>
</a.div>
)
)}{" "}
<CoverImageInput {...{
currentPath,
onClick: uploadCoverImg,
status: imgUploadStatus,
...imageProps
}} />
</div>
</div>
<div {...{ className: "col-lg-7 order-first order-lg-last" }}>