234 lines
6.8 KiB
TypeScript
234 lines
6.8 KiB
TypeScript
import * as React from "react";
|
|
import { useEffect, useState } from "react";
|
|
import MatchSizes from "./MatchSizes";
|
|
import MatchProperties from "./MatchProperties";
|
|
import RoundImage from "./RoundImage";
|
|
import SwapPositions from "./SwapPositions";
|
|
import InsertTitles from "./InsertTitles";
|
|
import ConfidentialButtons from "./ConfidentialButtons";
|
|
import DraftButtons from "./DraftButtons";
|
|
import ProgressBarButtons from "./ProgressBarButtons";
|
|
import AlignmentButtons from "./AlignmentButtons";
|
|
import GridGuidelineManager from "./GridGuidelineManager";
|
|
import { makeStyles, Text, Subtitle1, tokens, Theme, Spinner } from "@fluentui/react-components";
|
|
import { ShapeUnionRegular, SquareRegular, InfoRegular } from "@fluentui/react-icons";
|
|
|
|
interface AppProps {
|
|
title: string;
|
|
}
|
|
|
|
export type StatusType = "info" | "success" | "warning" | "error";
|
|
|
|
export interface StatusContextType {
|
|
statusMessage: string;
|
|
setStatusMessage: (message: string) => void;
|
|
statusType: StatusType;
|
|
setStatusType: (type: StatusType) => void;
|
|
isProcessing: boolean;
|
|
setIsProcessing: (processing: boolean) => void;
|
|
}
|
|
|
|
export const StatusContext = React.createContext<StatusContextType | undefined>(undefined);
|
|
|
|
export const useStatusContext = () => {
|
|
const context = React.useContext(StatusContext);
|
|
if (context === undefined) {
|
|
throw new Error('useStatusContext must be used within a StatusProvider');
|
|
}
|
|
return context;
|
|
};
|
|
|
|
const useStyles = makeStyles({
|
|
root: {
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
height: "100%",
|
|
padding: "16px",
|
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
|
backgroundColor: tokens.colorNeutralBackground1,
|
|
overflow: "auto",
|
|
maxHeight: "100vh",
|
|
},
|
|
statusContainer: {
|
|
marginTop: "4px",
|
|
padding: "8px 12px",
|
|
fontSize: "13px",
|
|
borderRadius: "6px",
|
|
backgroundColor: "#f3f2f1", // Light gray background
|
|
transition: "all 0.3s ease",
|
|
},
|
|
successStatus: {
|
|
backgroundColor: "#DFF6DD", // Light green background
|
|
color: "#107C10", // Green text
|
|
},
|
|
warningStatus: {
|
|
backgroundColor: "#FFF4CE", // Light yellow background
|
|
color: "#797673", // Dark gray text
|
|
},
|
|
errorStatus: {
|
|
backgroundColor: "#FDE7E9", // Light red background
|
|
color: "#A80000", // Red text
|
|
},
|
|
statusIcon: {
|
|
marginRight: "8px",
|
|
},
|
|
statusText: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
},
|
|
section: {
|
|
marginBottom: "6px",
|
|
padding: "12px",
|
|
borderRadius: "8px",
|
|
backgroundColor: tokens.colorNeutralBackground2,
|
|
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.06)",
|
|
transition: "all 0.2s ease",
|
|
":hover": {
|
|
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
|
|
},
|
|
},
|
|
sectionTitle: {
|
|
marginBottom: "12px",
|
|
fontWeight: tokens.fontWeightSemibold,
|
|
color: tokens.colorBrandForeground1,
|
|
},
|
|
footer: {
|
|
fontSize: "12px",
|
|
color: tokens.colorNeutralForeground3,
|
|
textAlign: "center",
|
|
marginTop: "auto",
|
|
padding: "8px 0",
|
|
},
|
|
});
|
|
|
|
const App: React.FC<AppProps> = () => {
|
|
const styles = useStyles();
|
|
const [theme, setTheme] = useState<string>("light");
|
|
|
|
// Status state
|
|
const [statusMessage, setStatusMessage] = useState("");
|
|
const [statusType, setStatusType] = useState<StatusType>("info");
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
// Check if macOS is in dark mode (would need to be expanded in production)
|
|
useEffect(() => {
|
|
// In a real implementation, we would listen to Office theme changes
|
|
const prefersDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
setTheme(prefersDarkMode ? "dark" : "light");
|
|
}, []);
|
|
|
|
// Auto-clear success messages after 5 seconds
|
|
useEffect(() => {
|
|
let timer: number;
|
|
if (statusType === "success" && statusMessage) {
|
|
timer = window.setTimeout(() => {
|
|
setStatusMessage("");
|
|
}, 5000);
|
|
}
|
|
return () => {
|
|
if (timer) clearTimeout(timer);
|
|
};
|
|
}, [statusMessage, statusType]);
|
|
|
|
const getStatusIcon = () => {
|
|
switch (statusType) {
|
|
case "success":
|
|
return <ShapeUnionRegular className={styles.statusIcon} />;
|
|
case "warning":
|
|
return <SquareRegular className={styles.statusIcon} />;
|
|
case "error":
|
|
return <InfoRegular className={styles.statusIcon} />;
|
|
default:
|
|
return null;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<StatusContext.Provider value={{
|
|
statusMessage,
|
|
setStatusMessage,
|
|
statusType,
|
|
setStatusType,
|
|
isProcessing,
|
|
setIsProcessing
|
|
}}>
|
|
<div className={styles.root} data-theme={theme}>
|
|
<div className={styles.section}>
|
|
<Subtitle1 block className={styles.sectionTitle}>
|
|
Content
|
|
</Subtitle1>
|
|
<MatchProperties />
|
|
<div style={{ marginTop: "8px" }}></div>
|
|
<MatchSizes />
|
|
<div style={{ marginTop: "8px" }}></div>
|
|
<SwapPositions />
|
|
<div style={{ marginTop: "8px" }}></div>
|
|
<InsertTitles />
|
|
<div style={{ marginTop: "8px" }}></div>
|
|
<RoundImage />
|
|
</div>
|
|
|
|
<div className={styles.section}>
|
|
<Subtitle1 block className={styles.sectionTitle}>
|
|
Progress Bar
|
|
</Subtitle1>
|
|
<ProgressBarButtons />
|
|
</div>
|
|
|
|
<div className={styles.section}>
|
|
<Subtitle1 block className={styles.sectionTitle}>
|
|
Confidential Marking
|
|
</Subtitle1>
|
|
<ConfidentialButtons />
|
|
</div>
|
|
|
|
<div className={styles.section}>
|
|
<Subtitle1 block className={styles.sectionTitle}>
|
|
Draft Watermark
|
|
</Subtitle1>
|
|
<DraftButtons />
|
|
</div>
|
|
|
|
<div className={styles.section}>
|
|
<Subtitle1 block className={styles.sectionTitle}>
|
|
Alignment
|
|
</Subtitle1>
|
|
<AlignmentButtons />
|
|
</div>
|
|
|
|
<div className={styles.section}>
|
|
<Subtitle1 block className={styles.sectionTitle}>
|
|
Layout
|
|
</Subtitle1>
|
|
<GridGuidelineManager />
|
|
</div>
|
|
|
|
{/* Status message area at the bottom */}
|
|
{isProcessing && (
|
|
<div className={styles.statusContainer}>
|
|
<div className={styles.statusText}>
|
|
<Spinner size="tiny" style={{ marginRight: "8px" }} />
|
|
Processing...
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{!isProcessing && statusMessage && (
|
|
<div className={`${styles.statusContainer} ${styles[`${statusType}Status`]}`}>
|
|
<div className={styles.statusText}>
|
|
{getStatusIcon()}
|
|
{statusMessage}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className={styles.footer}>
|
|
<Text size={100}>Edison • v1.0.0</Text>
|
|
</div>
|
|
</div>
|
|
</StatusContext.Provider>
|
|
);
|
|
};
|
|
|
|
export default App;
|