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, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy