From 34708e7c26379c35a95379a29069c119091f6214 Mon Sep 17 00:00:00 2001 From: Jeff Wall Date: Thu, 11 Dec 2025 19:51:57 -0500 Subject: [PATCH 01/18] added a component interface and a powerpoint component with scaling capabilities --- open-fin-al/package.json | 1 + .../src/View/Component/IViewComponent.ts | 21 +++++ .../Component/Learn/PowerPointComponent.tsx | 83 +++++++++++++++++++ .../LearningModule/LearningModulePage.jsx | 3 +- .../LearningModule/Slideshow/PowerPoint.jsx | 67 ++++++++++++--- open-fin-al/src/global.d.ts | 1 + open-fin-al/src/index.css | 12 +++ open-fin-al/tsconfig.json | 1 + 8 files changed, 175 insertions(+), 14 deletions(-) create mode 100644 open-fin-al/src/View/Component/IViewComponent.ts create mode 100644 open-fin-al/src/View/Component/Learn/PowerPointComponent.tsx create mode 100644 open-fin-al/src/global.d.ts diff --git a/open-fin-al/package.json b/open-fin-al/package.json index ae362802..dadc2d96 100644 --- a/open-fin-al/package.json +++ b/open-fin-al/package.json @@ -45,6 +45,7 @@ "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", "@types/jest": "^30.0.0", + "@types/react": "^19.2.7", "@vercel/webpack-asset-relocator-loader": "^1.7.3", "babel-loader": "^10.0.0", "browserify-fs": "^1.0.0", diff --git a/open-fin-al/src/View/Component/IViewComponent.ts b/open-fin-al/src/View/Component/IViewComponent.ts new file mode 100644 index 00000000..6bacd267 --- /dev/null +++ b/open-fin-al/src/View/Component/IViewComponent.ts @@ -0,0 +1,21 @@ +export interface IViewComponent { + height: number; // for exact height value + width: number; // for exact width value + isContainer: boolean; // can the component contain other components + resizable: boolean; + maintainAspectRatio: boolean; + heightRatio: number; // for dynamic height + widthRatio: number; // for dynamic width + heightWidthRatioMultiplier: number; // multiplier to maintain aspect ratio when resizing + visible: boolean; // does the component start visible or hidden + enabled: boolean; // is the component usable; allows for visibility but not interaction + + label: string; // for natural language processing search + description: string; // for natural language processing search + tags: string[]; // for natural language processing search + + minimumProficiencyRequirements: Map; // Map of + requiresInternet: boolean; + + calculateRatioMultiplier(): number; +} \ No newline at end of file diff --git a/open-fin-al/src/View/Component/Learn/PowerPointComponent.tsx b/open-fin-al/src/View/Component/Learn/PowerPointComponent.tsx new file mode 100644 index 00000000..c00b8125 --- /dev/null +++ b/open-fin-al/src/View/Component/Learn/PowerPointComponent.tsx @@ -0,0 +1,83 @@ +import React, { Component } from "react"; +import { IViewComponent } from "../IViewComponent"; +import { PowerPoint } from "../../LearningModule/Slideshow/PowerPoint.jsx"; + +interface PowerPointComponentState { + availableWidth: number; +} + +export class PowerPointComponent extends Component<{}, PowerPointComponentState> implements IViewComponent { + height: number = null; + width: number = null; + isContainer: boolean = false; + resizable: boolean = true; + maintainAspectRatio: boolean = true; + widthRatio: number = 16; + heightRatio: number = 9; + heightWidthRatioMultiplier: number = 56; + visible: boolean = true; + enabled: boolean = true; + label: string = "PowerPoint Learning Module"; + description: string = "Component for displaying PowerPoint presentations"; + tags: string[] = ["PowerPoint", "Presentation", "Slideshow", "Learning Module"]; + minimumProficiencyRequirements: Map = null; + requiresInternet: boolean = true; + + containerRef: React.RefObject; + observer: ResizeObserver | null = null; + pptxPath: string = ""; + + constructor(props: any = {}) { + super(props); + this.containerRef = React.createRef(); + this.state = { + availableWidth: 0 + }; + + this.pptxPath = props['pptxPath'] || ""; + + this.calculateRatioMultiplier = this.calculateRatioMultiplier.bind(this); + } + + componentDidMount() { + this.observer = new ResizeObserver(() => { + if (this.containerRef.current) { + this.setState({ + availableWidth: this.containerRef.current.offsetWidth, + }); + } + }); + + if (this.containerRef.current) { + this.observer.observe(this.containerRef.current); + } + } + + componentWillUnmount() { + if (this.observer) { + this.observer.disconnect(); + } + } + + calculateRatioMultiplier(): number { + this.heightWidthRatioMultiplier = Math.floor(this.state.availableWidth / this.widthRatio); + + this.width = this.heightWidthRatioMultiplier * this.widthRatio; + this.height = this.heightWidthRatioMultiplier * this.heightRatio; + + return this.heightWidthRatioMultiplier; + } + + render(): React.ReactNode { + const multiplier = this.calculateRatioMultiplier(); + const ready = multiplier > 0 && this.width > 0 && this.height > 0; + + return ( +
+ {ready && ( + + )} +
+ ); + } +} \ No newline at end of file diff --git a/open-fin-al/src/View/LearningModule/LearningModulePage.jsx b/open-fin-al/src/View/LearningModule/LearningModulePage.jsx index 49ec1489..5ccebd67 100644 --- a/open-fin-al/src/View/LearningModule/LearningModulePage.jsx +++ b/open-fin-al/src/View/LearningModule/LearningModulePage.jsx @@ -10,6 +10,7 @@ import { } from "react-router-dom"; import { PowerPoint } from "./Slideshow/PowerPoint"; +import { PowerPointComponent } from "../Component/Learn/PowerPointComponent"; export function LearningModulePage(props) { const location = useLocation(); @@ -18,7 +19,7 @@ export function LearningModulePage(props) { window.console.log(location.state); return (
- +
); } diff --git a/open-fin-al/src/View/LearningModule/Slideshow/PowerPoint.jsx b/open-fin-al/src/View/LearningModule/Slideshow/PowerPoint.jsx index 92c50b48..9ba89546 100644 --- a/open-fin-al/src/View/LearningModule/Slideshow/PowerPoint.jsx +++ b/open-fin-al/src/View/LearningModule/Slideshow/PowerPoint.jsx @@ -12,14 +12,14 @@ function PowerPoint(props) { const navigate = useNavigate(); const [isDisabled, setIsDisabled] = useState(false); - const containerRef = useRef(null); + const slideshowContainerRef = useRef(null); const viewerRef = useRef(null); const [currentSlide, setCurrentSlide] = useState(0); const [totalSlides, setTotalSlides] = useState(0); + const naturalSizeRef = useRef({ width: 0, height: 0 }); const pptxPath = props.pptxPath; - window.console.log(pptxPath); //navigate to the learn base page @@ -31,15 +31,56 @@ function PowerPoint(props) { // Initialize pptx-preview // ----------------------------- useEffect(() => { - if (!containerRef.current) return; + window.console.log(props.width, props.height); + if (!slideshowContainerRef.current) return; + if (!viewerRef.current) { - viewerRef.current = initPptxPreview(containerRef.current, { - width: 900, - height: 506, + viewerRef.current = initPptxPreview(slideshowContainerRef.current, { + width: props.width, + height: props.height, }); + + naturalSizeRef.current = { + width: props.width, + height: props.height, + }; } }, []); + useEffect(() => { + window.console.log(props.width, props.height); + /*const container = slideshowContainerRef.current; + + // Grab *one* slide's inner content (canvas/svg/img) + const inner = container.querySelector( + ".pptx-preview-slide-wrapper > *" + ); + if (!inner) return;*/ + + const { width: baseW, height: baseH } = naturalSizeRef.current; + if (!baseW || !baseH) return; + + /*const rect = inner.getBoundingClientRect(); + if (!rect.width || !rect.height) return;*/ + + const containerWidth = props.width; + const containerHeight = props.height; + + const scale = Math.min( + containerWidth / baseW, + containerHeight / baseH + ); + + window.console.log("Calculated scale:", scale); + + // Expose scale as a CSS variable on the container + //container.style.setProperty("--pptx-scale", String(scale)); + slideshowContainerRef.current.style.setProperty( + "--pptx-scale", + String(scale) + ); + }, [props.width, props.height]); + useEffect(() => { if (!viewerRef.current || !pptxPath) return; @@ -59,8 +100,8 @@ function PowerPoint(props) { await viewerRef.current.preview(arrayBuffer); - if (containerRef.current) { - const slides = containerRef.current.querySelectorAll( + if (slideshowContainerRef.current) { + const slides = slideshowContainerRef.current.querySelectorAll( ".pptx-preview-slide-wrapper" ); setTotalSlides(slides.length); @@ -77,9 +118,9 @@ function PowerPoint(props) { }, [pptxPath]); const showSlide = (index) => { - if (!containerRef.current) return; + if (!slideshowContainerRef.current) return; - const slides = containerRef.current.querySelectorAll( + const slides = slideshowContainerRef.current.querySelectorAll( ".pptx-preview-slide-wrapper" ); @@ -110,10 +151,10 @@ function PowerPoint(props) {
{/* pptx-preview will render into this div */}