From d94a667e0cb1d464a4359b46ff2c3bc7353317c7 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 7 Feb 2025 23:46:24 +0500 Subject: [PATCH 1/2] added mobile/table/desktop preview option with landscape/portrait mode --- client/packages/lowcoder/package.json | 2 + .../lowcoder/src/comps/editorState.tsx | 13 +++ .../lowcoder/src/pages/common/header.tsx | 1 + .../src/pages/common/previewHeader.tsx | 41 ++++++++- .../lowcoder/src/pages/editor/editorView.tsx | 88 ++++++++++++++++++- client/yarn.lock | 21 +++++ 6 files changed, 164 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json index 779b51784..6af544f6e 100644 --- a/client/packages/lowcoder/package.json +++ b/client/packages/lowcoder/package.json @@ -52,6 +52,7 @@ "file-saver": "^2.0.5", "github-markdown-css": "^5.1.0", "hotkeys-js": "^3.8.7", + "html5-device-mockups": "^3.2.1", "immer": "^9.0.7", "less": "^4.1.3", "lodash": "^4.17.21", @@ -67,6 +68,7 @@ "react": "^18.2.0", "react-best-gradient-color-picker": "^3.0.10", "react-colorful": "^5.5.1", + "react-device-mockups": "^0.1.12", "react-documents": "^1.2.1", "react-dom": "^18.2.0", "react-draggable": "^4.4.4", diff --git a/client/packages/lowcoder/src/comps/editorState.tsx b/client/packages/lowcoder/src/comps/editorState.tsx index 81b462201..de87b38b7 100644 --- a/client/packages/lowcoder/src/comps/editorState.tsx +++ b/client/packages/lowcoder/src/comps/editorState.tsx @@ -35,6 +35,9 @@ export type CompInfo = { type SelectSourceType = "editor" | "leftPanel" | "addComp" | "rightPanel"; +export type DeviceType = "desktop" | "tablet" | "mobile"; +export type DeviceOrientation = "landscape" | "portrait"; + /** * All editor states are placed here and are still immutable. * @@ -56,6 +59,8 @@ export class EditorState { readonly selectedBottomResType?: BottomResTypeEnum; readonly showResultCompName: string = ""; readonly selectSource?: SelectSourceType; // the source of select type + readonly deviceType: DeviceType = "desktop"; + readonly deviceOrientation: DeviceOrientation = "portrait"; private readonly setEditorState: ( fn: (editorState: EditorState) => EditorState @@ -357,6 +362,14 @@ export class EditorState { this.changeState({ editorModeStatus: newEditorModeStatus }); } + setDeviceType(type: DeviceType) { + this.changeState({ deviceType: type }); + } + + setDeviceOrientation(orientation: DeviceOrientation) { + this.changeState({ deviceOrientation: orientation }); + } + setDragging(dragging: boolean) { if (this.isDragging === dragging) { return; diff --git a/client/packages/lowcoder/src/pages/common/header.tsx b/client/packages/lowcoder/src/pages/common/header.tsx index 0b32ef396..c44ca5619 100644 --- a/client/packages/lowcoder/src/pages/common/header.tsx +++ b/client/packages/lowcoder/src/pages/common/header.tsx @@ -22,6 +22,7 @@ import { Layout, Left, Middle, + MobileAppIcon, ModuleIcon, PackUpIcon, RefreshIcon, diff --git a/client/packages/lowcoder/src/pages/common/previewHeader.tsx b/client/packages/lowcoder/src/pages/common/previewHeader.tsx index 769d3c8fc..89aafa153 100644 --- a/client/packages/lowcoder/src/pages/common/previewHeader.tsx +++ b/client/packages/lowcoder/src/pages/common/previewHeader.tsx @@ -15,12 +15,17 @@ import ProfileDropdown from "./profileDropdown"; import { trans } from "i18n"; import { Logo } from "@lowcoder-ee/assets/images"; import { AppPermissionDialog } from "../../components/PermissionDialog/AppPermissionDialog"; -import { useMemo, useState } from "react"; +import { useContext, useMemo, useState } from "react"; import { getBrandingConfig } from "../../redux/selectors/configSelectors"; import { HeaderStartDropdown } from "./headerStartDropdown"; import { useParams } from "react-router"; import { AppPathParams } from "constants/applicationConstants"; import React from "react"; +import Segmented from "antd/es/segmented"; +import MobileOutlined from "@ant-design/icons/MobileOutlined"; +import TabletOutlined from "@ant-design/icons/TabletOutlined"; +import DesktopOutlined from "@ant-design/icons/DesktopOutlined"; +import { DeviceOrientation, DeviceType, EditorContext } from "@lowcoder-ee/comps/editorState"; const HeaderFont = styled.div<{ $bgColor: string }>` font-weight: 500; @@ -130,6 +135,7 @@ export function HeaderProfile(props: { user: User }) { const PreviewHeaderComp = () => { const params = useParams(); + const editorState = useContext(EditorContext); const user = useSelector(getUser); const application = useSelector(currentApplication); const isPublicApp = useSelector(isPublicApplication); @@ -197,9 +203,42 @@ const PreviewHeaderComp = () => { ); + + const headerMiddle = ( + <> + {/* Devices */} + + options={[ + { value: 'mobile', icon: }, + { value: 'tablet', icon: }, + { value: 'desktop', icon: }, + ]} + value={editorState.deviceType} + onChange={(value) => { + editorState.setDeviceType(value); + }} + /> + + {/* Orientation */} + {editorState.deviceType !== 'desktop' && ( + + options={[ + { value: 'portrait', label: "Portrait" }, + { value: 'landscape', label: "Landscape" }, + ]} + value={editorState.deviceOrientation} + onChange={(value) => { + editorState.setDeviceOrientation(value); + }} + /> + )} + + ); + return (
diff --git a/client/packages/lowcoder/src/pages/editor/editorView.tsx b/client/packages/lowcoder/src/pages/editor/editorView.tsx index bbdbfcb99..d26c5e8ba 100644 --- a/client/packages/lowcoder/src/pages/editor/editorView.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorView.tsx @@ -30,10 +30,12 @@ import { UserGuideLocationState, } from "pages/tutorials/tutorialsConstant"; import React, { + ReactNode, Suspense, lazy, useCallback, useContext, + useEffect, useLayoutEffect, useMemo, useState, @@ -58,6 +60,7 @@ import EditorSkeletonView from "./editorSkeletonView"; import { getCommonSettings } from "@lowcoder-ee/redux/selectors/commonSettingSelectors"; import { isEqual, noop } from "lodash"; import { AppSettingContext, AppSettingType } from "@lowcoder-ee/comps/utils/appSettingContext"; +import Flex from "antd/es/flex"; // import { BottomSkeleton } from "./bottom/BottomContent"; const Header = lazy( @@ -251,6 +254,13 @@ export const EditorWrapper = styled.div` flex: 1 1 0; `; +const DeviceWrapperInner = styled(Flex)` + margin: 20px 0 0; + .screen { + overflow: auto; + } +`; + interface EditorViewProps { uiComp: InstanceType; preloadComp: InstanceType; @@ -298,6 +308,64 @@ const aggregationSiderItems = [ } ]; +const DeviceWrapper = ({ + deviceType, + deviceOrientation, + children, +}: { + deviceType: string, + deviceOrientation: string, + children: ReactNode, +}) => { + const [Wrapper, setWrapper] = useState(null); + + useEffect(() => { + const loadWrapper = async () => { + if (deviceType === "tablet") { + await import('html5-device-mockups/dist/device-mockups.min.css'); + const { IPad } = await import("react-device-mockups"); + setWrapper(() => IPad); + } else if (deviceType === "mobile") { + await import('html5-device-mockups/dist/device-mockups.min.css'); + const { IPhone7 } = await import("react-device-mockups"); + setWrapper(() => IPhone7); + } else { + setWrapper(() => null); + } + }; + + loadWrapper(); + }, [deviceType]); + + const deviceWidth = useMemo(() => { + if (deviceType === 'tablet' && deviceOrientation === 'portrait') { + return 700; + } + if (deviceType === 'tablet' && deviceOrientation === 'landscape') { + return 1000; + } + if (deviceType === 'mobile' && deviceOrientation === 'portrait') { + return 400; + } + if (deviceType === 'mobile' && deviceOrientation === 'landscape') { + return 800; + } + }, [deviceType, deviceOrientation]); + + if (!Wrapper) return <>{children}; + + return ( + + + {children} + + + ); +} + function EditorView(props: EditorViewProps) { const { uiComp } = props; const params = useParams(); @@ -416,6 +484,24 @@ function EditorView(props: EditorViewProps) { uiComp, ]); + const uiCompViewWrapper = useMemo(() => { + if (isViewMode) return uiComp.getView(); + + return ( + + {uiComp.getView()} + + ) + }, [ + uiComp, + isViewMode, + editorState.deviceType, + editorState.deviceOrientation, + ]); + // we check if we are on the public cloud const isLowCoderDomain = window.location.hostname === 'app.lowcoder.cloud'; const isLocalhost = window.location.hostname === 'localhost'; @@ -455,7 +541,7 @@ function EditorView(props: EditorViewProps) { {!hideBodyHeader && } - {uiComp.getView()} + {uiCompViewWrapper}
{hookCompViews} diff --git a/client/yarn.lock b/client/yarn.lock index 79dd8bcbe..b4842f23f 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -11455,6 +11455,13 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"html5-device-mockups@npm:^3.2.1": + version: 3.2.1 + resolution: "html5-device-mockups@npm:3.2.1" + checksum: abba0bccc6398313102a9365203092a7c0844879d1b0492168279c516c9462d2a7e016045be565bc183e3405a1ae4929402eaceb1952abdbf16f1580afa68df3 + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -14159,6 +14166,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: file-saver: ^2.0.5 github-markdown-css: ^5.1.0 hotkeys-js: ^3.8.7 + html5-device-mockups: ^3.2.1 http-proxy-middleware: ^2.0.6 immer: ^9.0.7 less: ^4.1.3 @@ -14175,6 +14183,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: react: ^18.2.0 react-best-gradient-color-picker: ^3.0.10 react-colorful: ^5.5.1 + react-device-mockups: ^0.1.12 react-documents: ^1.2.1 react-dom: ^18.2.0 react-draggable: ^4.4.4 @@ -17672,6 +17681,18 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"react-device-mockups@npm:^0.1.12": + version: 0.1.12 + resolution: "react-device-mockups@npm:0.1.12" + peerDependencies: + html5-device-mockups: ^3.2.1 + prop-types: ^15.5.4 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 738e969802c32810c2ca3ca3bd6c9bacf9b3d7adda0569c4f5c7fb1d68bab860ac7bb5a50aa2677d852143cb30ab8520e556c4dc7f53be154fd16ca08a9ba32c + languageName: node + linkType: hard + "react-documents@npm:^1.2.1": version: 1.2.1 resolution: "react-documents@npm:1.2.1" From c3aa49acb300c84cfea94a69129630f8eb799888 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Sat, 8 Feb 2025 00:22:08 +0500 Subject: [PATCH 2/2] use canvas width to update screen info --- .../src/comps/hooks/screenInfoComp.tsx | 22 ++++++++++++++----- .../lowcoder/src/pages/common/header.tsx | 1 - 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/client/packages/lowcoder/src/comps/hooks/screenInfoComp.tsx b/client/packages/lowcoder/src/comps/hooks/screenInfoComp.tsx index 64ca994f3..d813749a2 100644 --- a/client/packages/lowcoder/src/comps/hooks/screenInfoComp.tsx +++ b/client/packages/lowcoder/src/comps/hooks/screenInfoComp.tsx @@ -1,5 +1,6 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { hookToStateComp } from "../generators/hookToComp"; +import { CanvasContainerID } from "@lowcoder-ee/index.sdk"; enum ScreenTypes { Mobile = 'mobile', @@ -19,9 +20,13 @@ type ScreenInfo = { } function useScreenInfo() { - const getDeviceType = () => { - if (window.innerWidth < 768) return ScreenTypes.Mobile; - if (window.innerWidth < 889) return ScreenTypes.Tablet; + const canvasContainer = document.getElementById(CanvasContainerID); + const canvas = document.getElementsByClassName('lowcoder-app-canvas')?.[0]; + const canvasWidth = canvasContainer?.clientWidth || canvas?.clientWidth; + + const getDeviceType = (width: number) => { + if (width < 768) return ScreenTypes.Mobile; + if (width < 889) return ScreenTypes.Tablet; return ScreenTypes.Desktop; } const getFlagsByDeviceType = (deviceType: ScreenType) => { @@ -41,16 +46,17 @@ function useScreenInfo() { const getScreenInfo = useCallback(() => { const { innerWidth, innerHeight } = window; - const deviceType = getDeviceType(); + const deviceType = getDeviceType(canvasWidth || window.innerWidth); const flags = getFlagsByDeviceType(deviceType); return { width: innerWidth, height: innerHeight, + canvasWidth, deviceType, ...flags }; - }, []) + }, [canvasWidth]) const [screenInfo, setScreenInfo] = useState({}); @@ -64,6 +70,10 @@ function useScreenInfo() { return () => window.removeEventListener('resize', updateScreenInfo); }, [ updateScreenInfo ]) + useEffect(() => { + updateScreenInfo(); + }, [canvasWidth]); + return screenInfo; } diff --git a/client/packages/lowcoder/src/pages/common/header.tsx b/client/packages/lowcoder/src/pages/common/header.tsx index c44ca5619..0b32ef396 100644 --- a/client/packages/lowcoder/src/pages/common/header.tsx +++ b/client/packages/lowcoder/src/pages/common/header.tsx @@ -22,7 +22,6 @@ import { Layout, Left, Middle, - MobileAppIcon, ModuleIcon, PackUpIcon, RefreshIcon,