diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index beefa1b..6968dfb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,12 @@ name: Build -on: push + +on: + push: + branches: + - main + pull_request: + branches: + - main jobs: build: diff --git a/package.json b/package.json index a166f77..81881d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeslide.net", - "version": "0.3.0", + "version": "0.3.5", "private": true, "dependencies": { "@ant-design/icons": "^4.8.1", diff --git a/src/App.tsx b/src/App.tsx index 4999714..2b40e74 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,6 +25,7 @@ export const App = () => { const selectedItem = useStore(getSelectedShape); const selectedSet = useStore(getSelectedItems); const sidebarWidth = useStore(s => s.ui.sidebarSize); + const footerHeight = useStore(s => s.ui.footerSize); const applicationMode = useStore(s => s.ui.selectedMode); const margin = { @@ -85,7 +86,9 @@ export const App = () => { - + + + { - + diff --git a/src/const/scripts.ts b/src/const/scripts.ts index 36f3048..c88e220 100644 --- a/src/const/scripts.ts +++ b/src/const/scripts.ts @@ -6,9 +6,17 @@ * Copyright (c) Do Duc Quan. All rights reserved. */ +import { vogues } from "./vogues" + export const scripts = { common: { animation: '', - reveal: JSON.stringify({ hash: true, backgroundTransition: "none" }, null, 4) + reveal: JSON.stringify({ + hash: true, + backgroundTransition: "none", + width: vogues.common.canvasWidth, + height: vogues.common.canvasHeight, + pdfMaxPagesPerSlide: 1 + }, null, 4) } } \ No newline at end of file diff --git a/src/const/vogues.ts b/src/const/vogues.ts index 06310bc..be09e3b 100644 --- a/src/const/vogues.ts +++ b/src/const/vogues.ts @@ -10,6 +10,9 @@ import { LineCurve, LineNode, LinePivot } from "@app/wireframes/interface"; export const vogues = { common: { + canvasWidth: 1280, + canvasHeight: 720, + close: 0, dragSize: 12, editorMargin: 13, editorPad: 10, @@ -17,16 +20,16 @@ export const vogues = { iconSmall: 16, iconDefault: 18, iconLarge: 20, - offset: 1, + offset: 20, maxImgSize: 300, shapeWidth: 38, sidebarCode: 600, - sidebarClose: 0, sidebarShape: 38, selectionThickness: 1, previewWidth: 128, previewHeight: 72, previewPadBot: 20, + projectName: 'Untitled Presentation', }, color: { blue: '#00f', @@ -37,6 +40,7 @@ export const vogues = { selectionLock: '#f00', selectionStroke: '#080', transparent: 'rgba(0, 0, 0, 0)', + white: '#fff', }, option: { strokeThickness: [0, 1, 2, 3, 4, 6, 8, 10], diff --git a/src/icons/icon.tsx b/src/icons/icon.tsx index a4a160f..6b5a964 100644 --- a/src/icons/icon.tsx +++ b/src/icons/icon.tsx @@ -164,6 +164,10 @@ export const TriangleIcon = () => ( ); +export const PageIcon = () => ( + +) + export const ParagraphIcon = () => ( ); diff --git a/src/style/_common.scss b/src/style/_common.scss index 5be7291..10ec540 100644 --- a/src/style/_common.scss +++ b/src/style/_common.scss @@ -342,8 +342,10 @@ header { &-segment { gap: 3px; - border-radius: 14px; + border-radius: 20px; padding: 3px 4px; + display: inline-flex; + background-color: $color-background; } } diff --git a/src/style/_vars.scss b/src/style/_vars.scss index 4bce60f..c6b1c13 100644 --- a/src/style/_vars.scss +++ b/src/style/_vars.scss @@ -1,6 +1,6 @@ /* stylelint-disable */ -$color-background: rgb(240, 242, 245); +$color-background: #f0f2f5; $color-border: #e8e8e8; $color-border-dark: darken($color-border, 5%); diff --git a/src/wireframes/components/AnimationView.tsx b/src/wireframes/components/AnimationView.tsx index 969a4d8..6010757 100644 --- a/src/wireframes/components/AnimationView.tsx +++ b/src/wireframes/components/AnimationView.tsx @@ -21,14 +21,19 @@ export const AnimationView = () => { const dispatch = useDispatch(); const diagram = useStore(getDiagram); const animation = useStore(s => s.ui.selectedAnimation); + const isFooter = useStore(s => s.ui.footerSize) == vogues.common.previewHeight ? 1 : 0; - const viewPadd = vogues.common.editorMargin * 2 + 10 * 3 + vogues.common.headerHeight + vogues.common.shapeWidth + (vogues.common.previewHeight + vogues.common.editorMargin + vogues.common.previewPadBot) + vogues.common.selectionThickness * 4; + const viewPadd = vogues.common.editorMargin * 2 + 10 * 3 + vogues.common.headerHeight + vogues.common.shapeWidth + isFooter * (vogues.common.previewHeight + vogues.common.editorMargin + vogues.common.previewPadBot) + vogues.common.selectionThickness * 4; const [viewHeight, setViewHeight] = useState(window.innerHeight - viewPadd); useEffect(() => { window.addEventListener('resize', () => setViewHeight(window.innerHeight - viewPadd)); }, []); + useEffect(() => { + setViewHeight(window.innerHeight - viewPadd); + }, [isFooter]); + if (!diagram) { return null; } diff --git a/src/wireframes/components/HeaderView.tsx b/src/wireframes/components/HeaderView.tsx index 84d9a38..b9a976f 100644 --- a/src/wireframes/components/HeaderView.tsx +++ b/src/wireframes/components/HeaderView.tsx @@ -10,7 +10,7 @@ import * as React from 'react'; import { Title } from '@app/core'; import { getEditor, useStore } from '@app/wireframes/model'; import { useLoading } from './actions'; -import { ArrangeHeader, FileHeader, PresentHeader, IdHeader, ModeHeader, ScriptHeader } from './headers'; +import { ArrangeHeader, FileHeader, PresentHeader, IdHeader, ModeHeader } from './headers'; import './styles/HeaderView.scss' export const HeaderView = React.memo(() => { @@ -65,8 +65,6 @@ export const HeaderView = React.memo(() => { - - diff --git a/src/wireframes/components/actions/index.ts b/src/wireframes/components/actions/index.ts index c1998b2..8c678a9 100644 --- a/src/wireframes/components/actions/index.ts +++ b/src/wireframes/components/actions/index.ts @@ -13,4 +13,5 @@ export * from './use-clipboard'; export * from './use-grouping'; export * from './use-history'; export * from './use-loading'; +export * from './use-server'; export * from './use-remove'; \ No newline at end of file diff --git a/src/wireframes/components/actions/use-server.ts b/src/wireframes/components/actions/use-server.ts new file mode 100644 index 0000000..36885a6 --- /dev/null +++ b/src/wireframes/components/actions/use-server.ts @@ -0,0 +1,153 @@ +import { Diagram, changeFrames, compileSlides, getEditor, getFilteredDiagrams, parseFrames, useStore } from "@app/wireframes/model"; +import { AbstractControl } from "@app/wireframes/shapes/utils/abstract-control"; +import { getPlugin } from "@app/wireframes/shapes/utils/abstract-plugin"; +import * as svg from '@svgdotjs/svg.js'; +import { Color } from "@app/core/utils/color"; +import { shapes } from "@app/const"; +import { MessageInstance } from "antd/es/message/interface"; +import { useDispatch } from "react-redux"; + +export function useServer() { + const dispatch = useDispatch(); + const diagrams = useStore(getFilteredDiagrams); + const editor = useStore(getEditor); + + const getItem = (diagram: Diagram, str: string) => { + // Split text using `=` symbol, ignoring those inside curly brackets + const regex = /=(?![^{]*})/; + const [id, json] = str.split(regex); + + // Get item + let item = diagram.items.get(id); + if (!item || !json) return { item: item, id: id }; + + // Parse str -> json + let corrected = json.replace(/'/g, '"'); + let jsonObj: {[index: string]: string} = JSON.parse(corrected); + + // Modify appearance if there are valid specifications + // e.g. Shape1 = {'TEXT': 'Hello, world!'} + const allKeys = Object.values(shapes.key); + for (let [key, value] of Object.entries(jsonObj)) { + if (!allKeys.includes(key)) continue; // Safe-check + + if (key.endsWith('COLOR')) { + const color = Color.fromValue(value).toNumber(); + item = item.setAppearance(key, color); + } else { + item = item.setAppearance(key, value); + } + } + + return { item: item, id: id }; + } + + const getSlides = () => { + let frame3D: string[][][] = new Array(diagrams.length); + + // Get frames + diagrams.map((diagram, i) => { + const frames = diagram.frames ?? []; + frame3D[i] = []; + + frames.map((frame, j) => { + const usedIDs: string[] = []; + frame3D[i][j] = []; + + for (let k = frame.length; k > 0; k--) { + const { item, id } = getItem(diagram, frame[k - 1]); + if (!item || usedIDs.includes(id)) continue; + usedIDs.push(id); + + // Get svg + const svgControl = new AbstractControl(getPlugin(item.renderer)); + const svgElement: svg.Element = svgControl.render(item, undefined); + const svgCode = svgElement.node.outerHTML; + + // Push object to the head of parent map + frame3D[i][j] = [svgCode, ...frame3D[i][j]]; + } + }) + }) + + // Reshape from 3D to 2D + let frame2D = []; + for (let row of frame3D) for (let e of row) frame2D.push(e); + + return { + fileName: editor.id, + title: editor.name, + backgroundColor: editor.color.toString(), + size: [editor.size.x, editor.size.y], + config: editor.revealConfig, + frame: frame2D, + }; + } + + const fetchParser = async () => { + for (let diagram of diagrams) { + const script = diagram.script; + if (!script) continue; + + const frames = await parseFrames(script); + dispatch(changeFrames(diagram.id, frames)); + } + } + + const fetchCompiler = async () => { + const { fileName, title, size, backgroundColor, config, frame } = getSlides(); + + const linkPresentation = await compileSlides(fileName, title, size, backgroundColor, config, frame); + + return linkPresentation; + } + + const fetchApi = () => { + // Fetch frames from parser + fetchParser(); + + // Fetch presentation from compiler + const links = fetchCompiler(); + return links; + } + + const fetchPdf = async (messageApi: MessageInstance, messageKey: string) => { + // Start compiling + messageApi.open({ key: messageKey, type: 'loading', content: 'Exporting presentation...' }); + + try { + const { linkPdf } = await fetchApi(); + window.open(linkPdf, '_blank'); + } catch (err) { + messageApi.error(`${err}`); + } finally { + messageApi.open({ + key: messageKey, + type: 'success', + content: `Preparing completed. Your presentation will be opened in a new tab.`, + duration: 1, + }); + } + } + + const fetchSlide = async (messageApi: MessageInstance, messageKey: string) => { + messageApi.open({ key: messageKey, type: 'loading', content: 'Preparing presentation...' }); + + try { + const { linkSlide } = await fetchApi(); + window.open(linkSlide, '_blank'); + } catch (err) { + messageApi.error(`${err}`); + } finally { + messageApi.open({ + key: messageKey, + type: 'success', + content: `Preparing completed. Your presentation will be opened in a new tab.`, + duration: 1, + }); + } + } + + return { slide: fetchSlide, pdf: fetchPdf }; +} + diff --git a/src/wireframes/components/headers/FileHeader.tsx b/src/wireframes/components/headers/FileHeader.tsx index 0063101..187fcb9 100644 --- a/src/wireframes/components/headers/FileHeader.tsx +++ b/src/wireframes/components/headers/FileHeader.tsx @@ -7,9 +7,9 @@ */ import { changeName, getEditor, useStore } from '@app/wireframes/model'; -import { Button, Dropdown, Form, Input } from 'antd'; +import { Button, Dropdown, Form, Input, message } from 'antd'; import { useEffect, useState } from 'react'; -import { useLoading } from '../actions'; +import { useLoading, useServer } from '../actions'; import { texts } from '@app/const/texts'; import { useDispatch } from 'react-redux'; import { FormModal, SettingModal } from '../modal'; @@ -19,9 +19,12 @@ import { MenuIcon } from '@app/style/icomoon/icomoon_icon'; export const FileHeader = () => { const dispatch = useDispatch(); const forLoading = useLoading(); + const forServer = useServer(); const editor = useStore(getEditor); + const [messageApi, contextHolder] = message.useMessage(); const [isRename, setIsRename] = useState(false); const [isSettings, setIsSettings] = useState(false); + const messageKey = 'GENERATE'; // Get editor's name let name = editor.name; @@ -81,6 +84,13 @@ export const FileHeader = () => { className: 'loading-action-item', disabled: forLoading.downloadDiagram.disabled, }, + { + key: texts.common.saveDiagramToFileTooltip, + label: texts.common.saveDiagramToFileTooltip, + icon: , + className: 'loading-action-item', + disabled: forLoading.downloadDiagram.disabled, + }, ]; const menuEvt: MenuProps['onClick'] = ({key}) => { @@ -94,11 +104,14 @@ export const FileHeader = () => { dispatch(forLoading.openDiagramAction.onAction); } else if (key == forLoading.downloadDiagram.label) { dispatch(forLoading.downloadDiagram.onAction); + } else if (texts.common.saveDiagramToFileTooltip) { + dispatch(forServer.pdf(messageApi, messageKey)); } } return ( <> + {contextHolder} { const id = !selectedItem ? '' : selectedItem.id; const [newId, setNewId] = useState(id); const [isUpdate, setIsUpdate] = useState(false); + const [messageApi, contextHolder] = message.useMessage(); const updateId = (e: React.ChangeEvent) => { setNewId(e.target.value); @@ -25,7 +26,12 @@ export const IdHeader = () => { }; const acceptUpdateId = () => { - dispatch(replaceId(diagram!, id, newId)); + try { + dispatch(replaceId(diagram!, id, newId)); + } catch (e) { + if (e instanceof Error) messageApi.error(e.message); + cancelUpdateId(); + } }; React.useEffect(() => { @@ -35,6 +41,7 @@ export const IdHeader = () => { if (!selectedItem) return <>; return ( <> + {contextHolder} diff --git a/src/wireframes/components/headers/ModeHeader.tsx b/src/wireframes/components/headers/ModeHeader.tsx index 414cd05..9c0b561 100644 --- a/src/wireframes/components/headers/ModeHeader.tsx +++ b/src/wireframes/components/headers/ModeHeader.tsx @@ -6,37 +6,56 @@ * Copyright (c) Do Duc Quan. All rights reserved. */ -import { Segmented } from "antd"; +import { Button, Tooltip } from "antd"; import * as React from "react"; -import { setMode, setSidebarSize } from "@app/wireframes/model"; -import { AnimationIcon, DesignIcon, IconOutline } from "@app/icons/icon"; +import { setFooterSize, setMode, setSidebarSize, useStore } from "@app/wireframes/model"; +import { AnimationIcon, PageIcon, IconOutline } from "@app/icons/icon"; import { useDispatch } from "react-redux"; -import { SegmentedValue } from "antd/es/segmented"; import { vogues } from "@app/const"; export const ModeHeader = React.memo(() => { const dispatch = useDispatch(); + const isAnimationOn = useStore(s => s.ui.sidebarSize) !== vogues.common.close; + const isPageOn = useStore(s => s.ui.footerSize) !== vogues.common.close; - const modeMenu = [ - { value: 'design', icon: }, - { value: 'animation', icon: }, - ]; + const togglePagePanel = () => { + if (isPageOn) { + dispatch(setFooterSize(vogues.common.close)); + } else { + dispatch(setFooterSize(vogues.common.previewHeight)); + } + } - const modeMenuEvt = (key: SegmentedValue) => { - if (key == 'design') { - dispatch(setSidebarSize(vogues.common.sidebarClose)); + const toggleAnimationPanel = () => { + if (isAnimationOn) { + dispatch(setSidebarSize(vogues.common.close)); dispatch(setMode('design')); } else { dispatch(setSidebarSize(vogues.common.sidebarCode)); dispatch(setMode('animation')); } - }; + } return ( - modeMenuEvt(value)} - /> + <> +
+ +
+ ) }); diff --git a/src/wireframes/components/headers/PresentHeader.tsx b/src/wireframes/components/headers/PresentHeader.tsx index 0baca98..f3df387 100644 --- a/src/wireframes/components/headers/PresentHeader.tsx +++ b/src/wireframes/components/headers/PresentHeader.tsx @@ -9,135 +9,30 @@ import { FundProjectionScreenOutlined } from "@ant-design/icons"; import { Button, message } from "antd"; import * as React from "react"; -import { getFilteredDiagrams, getEditor, useStore, Diagram, compileSlides } from "@app/wireframes/model"; -import { AbstractControl } from "@app/wireframes/shapes/utils/abstract-control"; -import * as svg from '@svgdotjs/svg.js'; -import { getPlugin } from "@app/wireframes/shapes/utils/abstract-plugin"; -import { Color } from "@app/core/utils/color"; -import { shapes } from "@app/const"; import { useState } from "react"; +import { useServer } from "../actions"; +import { useDispatch } from "react-redux"; export const PresentHeader = React.memo(() => { - const html = document.querySelector('.editor-diagram')?.innerHTML; - const messageKey = 'PRESENT'; - - const diagrams = useStore(getFilteredDiagrams); - const editor = useStore(getEditor); + const dispatch = useDispatch(); + const forServer = useServer(); const [messageApi, contextHolder] = message.useMessage(); const [loading, setLoading] = useState(false); - - const getItem = (diagram: Diagram, str: string) => { - // Split text using `=` symbol, ignoring those inside curly brackets - const regex = /=(?![^{]*})/; - const [id, json] = str.split(regex); - - // Get item - let item = diagram.items.get(id); - if (!item || !json) return { item: item, id: id }; - - // Parse str -> json - let corrected = json.replace(/'/g, '"'); - let jsonObj: {[index: string]: string} = JSON.parse(corrected); - - // Modify appearance if there are valid specifications - // e.g. Shape1 = {'TEXT': 'Hello, world!'} - const allKeys = Object.values(shapes.key); - for (let [key, value] of Object.entries(jsonObj)) { - if (!allKeys.includes(key)) continue; // Safe-check - - if (key.endsWith('COLOR')) { - const color = Color.fromValue(value).toNumber(); - item = item.setAppearance(key, color); - } else { - item = item.setAppearance(key, value); - } - } - - return { item: item, id: id }; - } - - const getSlides = () => { - let frame3D: string[][][] = new Array(diagrams.length); - - // Get frames - diagrams.map((diagram, i) => { - const frames = diagram.frames ?? []; - frame3D[i] = []; - - frames.map((frame, j) => { - const usedIDs: string[] = []; - frame3D[i][j] = []; - - for (let k = frame.length; k > 0; k--) { - const { item, id } = getItem(diagram, frame[k - 1]); - if (!item || usedIDs.includes(id)) continue; - usedIDs.push(id); - - // Get svg - const svgControl = new AbstractControl(getPlugin(item.renderer)); - const svgElement: svg.Element = svgControl.render(item, undefined); - const svgCode = svgElement.node.outerHTML; - - // Push object to the head of parent map - frame3D[i][j] = [svgCode, ...frame3D[i][j]]; - } - }) - }) - - // Reshape from 3D to 2D - let frame2D = []; - for (let row of frame3D) for (let e of row) frame2D.push(e); - - return { - fileName: editor.id, - title: editor.name, - backgroundColor: editor.color.toString(), - size: [editor.size.x, editor.size.y], - config: editor.revealConfig, - frame: frame2D, - }; - } - - const fetchApi = async () => { - const { fileName, title, size, backgroundColor, config, frame } = getSlides(); - - if (!html) { - messageApi.error('Empty slide. Cannot perform action'); - return; - } - - // Start compiling - setLoading(true); - messageApi.open({ - key: messageKey, - type: 'loading', - content: 'Preparing presentation...', - }); - - try { - const linkPresentation = await compileSlides(fileName, title, size, backgroundColor, config, frame); - - messageApi.open({ - key: messageKey, - type: 'success', - content: 'Preparing completed. Your presentation will be opened in a new tab.', - duration: 2, - }); - setLoading(false); - - window.open(linkPresentation); - } catch (err) { - messageApi.error(`${err}`); - setLoading(false); - } - } + const messageKey = 'PRESENT'; return ( <> {contextHolder} ) -}); +}); \ No newline at end of file diff --git a/src/wireframes/components/headers/ScriptHeader.tsx b/src/wireframes/components/headers/ScriptHeader.tsx deleted file mode 100644 index f111cd7..0000000 --- a/src/wireframes/components/headers/ScriptHeader.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * codeslide.net - * - * @license - * Forked from mydraft.cc by Sebastian Stehle - * Copyright (c) Do Duc Quan. All rights reserved. -*/ - -import React from "react" -import { SelectOutlined } from "@ant-design/icons" -import { Button, message } from "antd" -import { useDispatch } from "react-redux"; -import { changeFrames, getDiagram, parseFrames, useStore } from "@app/wireframes/model"; - -export const ScriptHeader = React.memo(() => { - const dispatch = useDispatch(); - const diagram = useStore(getDiagram); - const [messageApi, contextHolder] = message.useMessage(); - - if (!diagram) return null; - - const fetchFrames = async () => { - const script = diagram.script; - - if (!script) { - messageApi.error('Empty script. Cannot perform action'); - return; - }; - - try { - const frames = await parseFrames(script); - dispatch(changeFrames(diagram.id, frames)); - messageApi.success('Script is loaded successfully'); - } catch (error) { - messageApi.error(`${error}`); - } - } - - return ( - <> - {contextHolder} - - - ) -}) \ No newline at end of file diff --git a/src/wireframes/components/headers/index.ts b/src/wireframes/components/headers/index.ts index 05ba420..39e2a20 100644 --- a/src/wireframes/components/headers/index.ts +++ b/src/wireframes/components/headers/index.ts @@ -2,5 +2,4 @@ export * from './ArrangeHeader'; export * from './FileHeader'; export * from './IdHeader'; export * from './ModeHeader'; -export * from './PresentHeader'; -export * from './ScriptHeader'; \ No newline at end of file +export * from './PresentHeader'; \ No newline at end of file diff --git a/src/wireframes/components/settings/PresentSetting.tsx b/src/wireframes/components/settings/PresentSetting.tsx index 3a60119..57afb52 100644 --- a/src/wireframes/components/settings/PresentSetting.tsx +++ b/src/wireframes/components/settings/PresentSetting.tsx @@ -49,7 +49,7 @@ export const PresentSetting = React.memo(() => { const handleSrcCancel = () => { setIsScrChange(false); - setRevealScr(JSON.stringify(editor.revealConfig, null, 4)); + setRevealScr(editor.revealConfig); } return ( diff --git a/src/wireframes/components/styles/ToolView.scss b/src/wireframes/components/styles/ToolView.scss index 6462e0e..e796d60 100644 --- a/src/wireframes/components/styles/ToolView.scss +++ b/src/wireframes/components/styles/ToolView.scss @@ -9,6 +9,7 @@ line-height: 38px; justify-content: space-between; align-items: center; + overflow-x: scroll; } &-toggle { @@ -36,7 +37,6 @@ border-radius: 20px; padding: 3px 10px; flex-grow: 1; - overflow-x: scroll; } &-scroll { diff --git a/src/wireframes/components/tools/ZoomTool.tsx b/src/wireframes/components/tools/ZoomTool.tsx index d777b19..1805094 100644 --- a/src/wireframes/components/tools/ZoomTool.tsx +++ b/src/wireframes/components/tools/ZoomTool.tsx @@ -18,14 +18,15 @@ import type { MenuProps } from 'antd'; export const ZoomTool = React.memo(() => { const dispatch = useDispatch(); const editorSize = useStore(getEditor).size; - const sidebarSize = useStore(s => s.ui.sidebarSize); + const sidebarWidth = useStore(s => s.ui.sidebarSize); + const isFooter = useStore(s => s.ui.footerSize) == vogues.common.previewHeight ? 1 : 0; const [zoomValue, setZoomValue] = useState('Fit'); const zoomPad = { - vertical: vogues.common.editorMargin * 2 + vogues.common.editorPad * 3 + vogues.common.headerHeight + vogues.common.shapeWidth + (vogues.common.previewHeight + vogues.common.editorMargin + vogues.common.previewPadBot) + vogues.common.selectionThickness * 4, - horizontal: vogues.common.editorMargin * 4 + vogues.common.editorPad * 2 + vogues.common.sidebarShape + vogues.common.selectionThickness * 4, + vertical: vogues.common.editorMargin * 2 + vogues.common.editorPad * 3 + vogues.common.headerHeight + vogues.common.shapeWidth + isFooter * (vogues.common.previewHeight + vogues.common.editorMargin + vogues.common.previewPadBot) + vogues.common.selectionThickness * 4, + horizontal: vogues.common.editorMargin * 4 + vogues.common.editorPad * 3 + vogues.common.sidebarShape + vogues.common.selectionThickness * 4, } - const [areaSize, setAreaSize] = useState(new Vec2(window.innerWidth - zoomPad.horizontal - sidebarSize, window.innerHeight - zoomPad.vertical)); + const [areaSize, setAreaSize] = useState(new Vec2(window.innerWidth - zoomPad.horizontal - sidebarWidth, window.innerHeight - zoomPad.vertical)); const isZoom = (key: string) => { setZoomValue(key); @@ -33,7 +34,7 @@ export const ZoomTool = React.memo(() => { }; const getWindowSize = () => { - setAreaSize(new Vec2(window.innerWidth - zoomPad.horizontal - sidebarSize, window.innerHeight - zoomPad.vertical)); + setAreaSize(new Vec2(window.innerWidth - zoomPad.horizontal - sidebarWidth, window.innerHeight - zoomPad.vertical)); } // Get area size value on resizing window @@ -54,8 +55,7 @@ export const ZoomTool = React.memo(() => { getWindowSize(); isZoom(zoomValue); } - - }, [sidebarSize, editorSize]); + }, [isFooter, sidebarWidth, editorSize]); const getZoomValue = (value: string) => { switch (value) { diff --git a/src/wireframes/model/actions/api.ts b/src/wireframes/model/actions/api.ts index a7c76f4..587d7df 100644 --- a/src/wireframes/model/actions/api.ts +++ b/src/wireframes/model/actions/api.ts @@ -76,6 +76,9 @@ export const compileSlides = async (fileName: string, title: string, size: numbe if (!response.ok) throw Error(response.statusText); const data = await response.json(); - const link = `${SERVER_URL}/${data.link}`; - return link; + + const linkSlide = `${SERVER_URL}/${data.slidePath}`; + const linkPdf = `${SERVER_URL}/${data.pdfPath}`; + + return { linkSlide, linkPdf }; } \ No newline at end of file diff --git a/src/wireframes/model/actions/items.ts b/src/wireframes/model/actions/items.ts index 818f158..62fdf8b 100644 --- a/src/wireframes/model/actions/items.ts +++ b/src/wireframes/model/actions/items.ts @@ -98,24 +98,29 @@ export function buildItems(builder: ActionReducerMapBuilder) { const { diagramId, itemId, id } = action.payload; return state.updateDiagram(diagramId, diagram => { - // Get current shape + // If current id is the same as new id, return + if (itemId === id) return diagram; + + // If new id is already existed, throw error + if (diagram.items.has(id)) throw new Error(`Cannot perform action! Item with id ${id} already existed.`); + + // If shape is not existed, return const shape = diagram.items.get(itemId); if (!shape) return diagram; // Dublicate item with assigning new id - let { id: defaultId, newDiagram } = IDHelper.nextId(diagram, shape.renderer); const newProps = { - id: id || defaultId, + id: id, renderer: shape.renderer, appearance: shape.appearance, transform: shape.transform, }; const newShape = DiagramItem.createShape(newProps); - newDiagram = newDiagram.addShape(newShape).selectItems([id]); + diagram = diagram.addShape(newShape).selectItems([id]); // Remove old item - const set = DiagramItemSet.createFromDiagram([itemId], newDiagram); - return newDiagram.removeItems(set!); + const set = DiagramItemSet.createFromDiagram([itemId], diagram); + return diagram.removeItems(set!); }); }) .addCase(renameItems, (state, action) => { @@ -139,7 +144,7 @@ export function buildItems(builder: ActionReducerMapBuilder) { diagram = diagram.updateItems(set.allShapes.map(x => x.id), item => { const boundsOld = item.bounds(diagram); - const boundsNew = boundsOld.moveBy(boundsOld.size.mul(new Vec2(offsetByX, offsetByY))); + const boundsNew = boundsOld.moveBy(new Vec2(offsetByX, offsetByY)); return item.transformByBounds(boundsOld, boundsNew); }); diff --git a/src/wireframes/model/actions/ui.ts b/src/wireframes/model/actions/ui.ts index 47c4cf0..b39abee 100644 --- a/src/wireframes/model/actions/ui.ts +++ b/src/wireframes/model/actions/ui.ts @@ -33,6 +33,11 @@ export const setSidebarSize = return { payload: { size } }; }); +export const setFooterSize = + createAction('ui/footerSize', (size: number) => { + return { payload: { size } }; + }); + export const setMode = createAction('ui/mode', (mode: ModeType) => { return { payload: { mode } }; @@ -75,6 +80,9 @@ export function ui(initialState: UIState): Reducer { .addCase(setSidebarSize, (state, action) => { state.sidebarSize = action.payload.size; }) + .addCase(setFooterSize, (state, action) => { + state.footerSize = action.payload.size; + }) .addCase(setMode, (state, action) => { state.selectedMode = action.payload.mode; }) diff --git a/src/wireframes/model/editor-state.ts b/src/wireframes/model/editor-state.ts index 2e8e9c5..799e37e 100644 --- a/src/wireframes/model/editor-state.ts +++ b/src/wireframes/model/editor-state.ts @@ -7,7 +7,7 @@ */ import { Color, ImmutableList, ImmutableMap, MathHelper, Record, Vec2 } from '@app/core/utils'; -import { scripts } from '@app/const'; +import { scripts, vogues } from '@app/const'; import { Diagram } from './diagram'; import { UndoableState } from './undoable-state'; @@ -105,8 +105,8 @@ export class EditorState extends Record { diagrams: ImmutableMap.of(diagrams), diagramIds: ImmutableList.of(diagramIds), id: MathHelper.guid(), - size: size || new Vec2(1280, 720), - name: name || 'Untitled Presentation', + size: size || new Vec2(vogues.common.canvasWidth, vogues.common.canvasHeight), + name: name || vogues.common.projectName, revealConfig: scripts.common.reveal, }; diff --git a/src/wireframes/model/ui-state.ts b/src/wireframes/model/ui-state.ts index 008fdf0..ac48596 100644 --- a/src/wireframes/model/ui-state.ts +++ b/src/wireframes/model/ui-state.ts @@ -19,9 +19,12 @@ export interface UIState { // The info toast from any loading operation. infoToast?: string; - // The size for right sidebar + // The size for right sidebar. sidebarSize: number; + // The size for pages section. + footerSize: number; + // The mode for the application. selectedMode: ModeType; @@ -41,10 +44,11 @@ export interface UIStateInStore { export const createInitialUIState: () => UIState = () => { return { + footerSize: vogues.common.previewHeight, zoom: 1, selectedColor: 'palette', selectedMode: 'design', selectedAnimation: 'script', - sidebarSize: vogues.common.sidebarClose, + sidebarSize: vogues.common.close, }; };