Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit59324cd

Browse files
authored
Merge pull request#1513 from lowcoder-org/feat/mobile-preview
Enable device based preview (mobile/tablet/desktop) with orientations (landscape/portrait)
2 parents9491cb9 +c3aa49a commit59324cd

File tree

6 files changed

+179
-8
lines changed

6 files changed

+179
-8
lines changed

‎client/packages/lowcoder/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"file-saver":"^2.0.5",
5353
"github-markdown-css":"^5.1.0",
5454
"hotkeys-js":"^3.8.7",
55+
"html5-device-mockups":"^3.2.1",
5556
"immer":"^9.0.7",
5657
"less":"^4.1.3",
5758
"lodash":"^4.17.21",
@@ -67,6 +68,7 @@
6768
"react":"^18.2.0",
6869
"react-best-gradient-color-picker":"^3.0.10",
6970
"react-colorful":"^5.5.1",
71+
"react-device-mockups":"^0.1.12",
7072
"react-documents":"^1.2.1",
7173
"react-dom":"^18.2.0",
7274
"react-draggable":"^4.4.4",

‎client/packages/lowcoder/src/comps/editorState.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export type CompInfo = {
3535

3636
typeSelectSourceType="editor"|"leftPanel"|"addComp"|"rightPanel";
3737

38+
exporttypeDeviceType="desktop"|"tablet"|"mobile";
39+
exporttypeDeviceOrientation="landscape"|"portrait";
40+
3841
/**
3942
* All editor states are placed here and are still immutable.
4043
*
@@ -56,6 +59,8 @@ export class EditorState {
5659
readonlyselectedBottomResType?:BottomResTypeEnum;
5760
readonlyshowResultCompName:string="";
5861
readonlyselectSource?:SelectSourceType;// the source of select type
62+
readonlydeviceType:DeviceType="desktop";
63+
readonlydeviceOrientation:DeviceOrientation="portrait";
5964

6065
privatereadonlysetEditorState:(
6166
fn:(editorState:EditorState)=>EditorState
@@ -357,6 +362,14 @@ export class EditorState {
357362
this.changeState({editorModeStatus:newEditorModeStatus});
358363
}
359364

365+
setDeviceType(type:DeviceType){
366+
this.changeState({deviceType:type});
367+
}
368+
369+
setDeviceOrientation(orientation:DeviceOrientation){
370+
this.changeState({deviceOrientation:orientation});
371+
}
372+
360373
setDragging(dragging:boolean){
361374
if(this.isDragging===dragging){
362375
return;

‎client/packages/lowcoder/src/comps/hooks/screenInfoComp.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import{useCallback,useEffect,useState}from"react";
1+
import{useCallback,useEffect,useMemo,useState}from"react";
22
import{hookToStateComp}from"../generators/hookToComp";
3+
import{CanvasContainerID}from"@lowcoder-ee/index.sdk";
34

45
enumScreenTypes{
56
Mobile='mobile',
@@ -19,9 +20,13 @@ type ScreenInfo = {
1920
}
2021

2122
functionuseScreenInfo(){
22-
constgetDeviceType=()=>{
23-
if(window.innerWidth<768)returnScreenTypes.Mobile;
24-
if(window.innerWidth<889)returnScreenTypes.Tablet;
23+
constcanvasContainer=document.getElementById(CanvasContainerID);
24+
constcanvas=document.getElementsByClassName('lowcoder-app-canvas')?.[0];
25+
constcanvasWidth=canvasContainer?.clientWidth||canvas?.clientWidth;
26+
27+
constgetDeviceType=(width:number)=>{
28+
if(width<768)returnScreenTypes.Mobile;
29+
if(width<889)returnScreenTypes.Tablet;
2530
returnScreenTypes.Desktop;
2631
}
2732
constgetFlagsByDeviceType=(deviceType:ScreenType)=>{
@@ -41,16 +46,17 @@ function useScreenInfo() {
4146

4247
constgetScreenInfo=useCallback(()=>{
4348
const{ innerWidth, innerHeight}=window;
44-
constdeviceType=getDeviceType();
49+
constdeviceType=getDeviceType(canvasWidth||window.innerWidth);
4550
constflags=getFlagsByDeviceType(deviceType);
4651

4752
return{
4853
width:innerWidth,
4954
height:innerHeight,
55+
canvasWidth,
5056
deviceType,
5157
...flags
5258
};
53-
},[])
59+
},[canvasWidth])
5460

5561
const[screenInfo,setScreenInfo]=useState<ScreenInfo>({});
5662

@@ -64,6 +70,10 @@ function useScreenInfo() {
6470
return()=>window.removeEventListener('resize',updateScreenInfo);
6571
},[updateScreenInfo])
6672

73+
useEffect(()=>{
74+
updateScreenInfo();
75+
},[canvasWidth]);
76+
6777
returnscreenInfo;
6878
}
6979

‎client/packages/lowcoder/src/pages/common/previewHeader.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@ import ProfileDropdown from "./profileDropdown";
1515
import{trans}from"i18n";
1616
import{Logo}from"@lowcoder-ee/assets/images";
1717
import{AppPermissionDialog}from"../../components/PermissionDialog/AppPermissionDialog";
18-
import{useMemo,useState}from"react";
18+
import{useContext,useMemo,useState}from"react";
1919
import{getBrandingConfig}from"../../redux/selectors/configSelectors";
2020
import{HeaderStartDropdown}from"./headerStartDropdown";
2121
import{useParams}from"react-router";
2222
import{AppPathParams}from"constants/applicationConstants";
2323
importReactfrom"react";
24+
importSegmentedfrom"antd/es/segmented";
25+
importMobileOutlinedfrom"@ant-design/icons/MobileOutlined";
26+
importTabletOutlinedfrom"@ant-design/icons/TabletOutlined";
27+
importDesktopOutlinedfrom"@ant-design/icons/DesktopOutlined";
28+
import{DeviceOrientation,DeviceType,EditorContext}from"@lowcoder-ee/comps/editorState";
2429

2530
constHeaderFont=styled.div<{$bgColor:string}>`
2631
font-weight: 500;
@@ -130,6 +135,7 @@ export function HeaderProfile(props: { user: User }) {
130135

131136
constPreviewHeaderComp=()=>{
132137
constparams=useParams<AppPathParams>();
138+
consteditorState=useContext(EditorContext);
133139
constuser=useSelector(getUser);
134140
constapplication=useSelector(currentApplication);
135141
constisPublicApp=useSelector(isPublicApplication);
@@ -197,9 +203,42 @@ const PreviewHeaderComp = () => {
197203
<HeaderProfileuser={user}/>
198204
</Wrapper>
199205
);
206+
207+
constheaderMiddle=(
208+
<>
209+
{/* Devices */}
210+
<Segmented<DeviceType>
211+
options={[
212+
{value:'mobile',icon:<MobileOutlined/>},
213+
{value:'tablet',icon:<TabletOutlined/>},
214+
{value:'desktop',icon:<DesktopOutlined/>},
215+
]}
216+
value={editorState.deviceType}
217+
onChange={(value)=>{
218+
editorState.setDeviceType(value);
219+
}}
220+
/>
221+
222+
{/* Orientation */}
223+
{editorState.deviceType!=='desktop'&&(
224+
<Segmented<DeviceOrientation>
225+
options={[
226+
{value:'portrait',label:"Portrait"},
227+
{value:'landscape',label:"Landscape"},
228+
]}
229+
value={editorState.deviceOrientation}
230+
onChange={(value)=>{
231+
editorState.setDeviceOrientation(value);
232+
}}
233+
/>
234+
)}
235+
</>
236+
);
237+
200238
return(
201239
<Header
202240
headerStart={headerStart}
241+
headerMiddle={headerMiddle}
203242
headerEnd={headerEnd}
204243
style={{backgroundColor:brandingConfig?.headerColor}}
205244
/>

‎client/packages/lowcoder/src/pages/editor/editorView.tsx

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ import {
3030
UserGuideLocationState,
3131
}from"pages/tutorials/tutorialsConstant";
3232
importReact,{
33+
ReactNode,
3334
Suspense,
3435
lazy,
3536
useCallback,
3637
useContext,
38+
useEffect,
3739
useLayoutEffect,
3840
useMemo,
3941
useState,
@@ -58,6 +60,7 @@ import EditorSkeletonView from "./editorSkeletonView";
5860
import{getCommonSettings}from"@lowcoder-ee/redux/selectors/commonSettingSelectors";
5961
import{isEqual,noop}from"lodash";
6062
import{AppSettingContext,AppSettingType}from"@lowcoder-ee/comps/utils/appSettingContext";
63+
importFlexfrom"antd/es/flex";
6164
// import { BottomSkeleton } from "./bottom/BottomContent";
6265

6366
constHeader=lazy(
@@ -251,6 +254,13 @@ export const EditorWrapper = styled.div`
251254
flex: 1 1 0;
252255
`;
253256

257+
constDeviceWrapperInner=styled(Flex)`
258+
margin: 20px 0 0;
259+
.screen {
260+
overflow: auto;
261+
}
262+
`;
263+
254264
interfaceEditorViewProps{
255265
uiComp:InstanceType<typeofUIComp>;
256266
preloadComp:InstanceType<typeofPreloadComp>;
@@ -298,6 +308,64 @@ const aggregationSiderItems = [
298308
}
299309
];
300310

311+
constDeviceWrapper=({
312+
deviceType,
313+
deviceOrientation,
314+
children,
315+
}:{
316+
deviceType:string,
317+
deviceOrientation:string,
318+
children:ReactNode,
319+
})=>{
320+
const[Wrapper,setWrapper]=useState<React.ElementType|null>(null);
321+
322+
useEffect(()=>{
323+
constloadWrapper=async()=>{
324+
if(deviceType==="tablet"){
325+
awaitimport('html5-device-mockups/dist/device-mockups.min.css');
326+
const{ IPad}=awaitimport("react-device-mockups");
327+
setWrapper(()=>IPad);
328+
}elseif(deviceType==="mobile"){
329+
awaitimport('html5-device-mockups/dist/device-mockups.min.css');
330+
const{ IPhone7}=awaitimport("react-device-mockups");
331+
setWrapper(()=>IPhone7);
332+
}else{
333+
setWrapper(()=>null);
334+
}
335+
};
336+
337+
loadWrapper();
338+
},[deviceType]);
339+
340+
constdeviceWidth=useMemo(()=>{
341+
if(deviceType==='tablet'&&deviceOrientation==='portrait'){
342+
return700;
343+
}
344+
if(deviceType==='tablet'&&deviceOrientation==='landscape'){
345+
return1000;
346+
}
347+
if(deviceType==='mobile'&&deviceOrientation==='portrait'){
348+
return400;
349+
}
350+
if(deviceType==='mobile'&&deviceOrientation==='landscape'){
351+
return800;
352+
}
353+
},[deviceType,deviceOrientation]);
354+
355+
if(!Wrapper)return<>{children}</>;
356+
357+
return(
358+
<DeviceWrapperInnerjustify="center">
359+
<Wrapper
360+
orientation={deviceOrientation}
361+
width={deviceWidth}
362+
>
363+
{children}
364+
</Wrapper>
365+
</DeviceWrapperInner>
366+
);
367+
}
368+
301369
functionEditorView(props:EditorViewProps){
302370
const{ uiComp}=props;
303371
constparams=useParams<AppPathParams>();
@@ -416,6 +484,24 @@ function EditorView(props: EditorViewProps) {
416484
uiComp,
417485
]);
418486

487+
constuiCompViewWrapper=useMemo(()=>{
488+
if(isViewMode)returnuiComp.getView();
489+
490+
return(
491+
<DeviceWrapper
492+
deviceType={editorState.deviceType}
493+
deviceOrientation={editorState.deviceOrientation}
494+
>
495+
{uiComp.getView()}
496+
</DeviceWrapper>
497+
)
498+
},[
499+
uiComp,
500+
isViewMode,
501+
editorState.deviceType,
502+
editorState.deviceOrientation,
503+
]);
504+
419505
// we check if we are on the public cloud
420506
constisLowCoderDomain=window.location.hostname==='app.lowcoder.cloud';
421507
constisLocalhost=window.location.hostname==='localhost';
@@ -455,7 +541,7 @@ function EditorView(props: EditorViewProps) {
455541
{!hideBodyHeader&&<PreviewHeader/>}
456542
<EditorContainerWithViewMode>
457543
<ViewBody$hideBodyHeader={hideBodyHeader}$height={height}>
458-
{uiComp.getView()}
544+
{uiCompViewWrapper}
459545
</ViewBody>
460546
<divstyle={{zIndex:Layers.hooksCompContainer}}>
461547
{hookCompViews}

‎client/yarn.lock

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11455,6 +11455,13 @@ coolshapes-react@lowcoder-org/coolshapes-react:
1145511455
languageName: node
1145611456
linkType: hard
1145711457

11458+
"html5-device-mockups@npm:^3.2.1":
11459+
version: 3.2.1
11460+
resolution: "html5-device-mockups@npm:3.2.1"
11461+
checksum: abba0bccc6398313102a9365203092a7c0844879d1b0492168279c516c9462d2a7e016045be565bc183e3405a1ae4929402eaceb1952abdbf16f1580afa68df3
11462+
languageName: node
11463+
linkType: hard
11464+
1145811465
"http-cache-semantics@npm:^4.1.1":
1145911466
version: 4.1.1
1146011467
resolution: "http-cache-semantics@npm:4.1.1"
@@ -14159,6 +14166,7 @@ coolshapes-react@lowcoder-org/coolshapes-react:
1415914166
file-saver: ^2.0.5
1416014167
github-markdown-css: ^5.1.0
1416114168
hotkeys-js: ^3.8.7
14169+
html5-device-mockups: ^3.2.1
1416214170
http-proxy-middleware: ^2.0.6
1416314171
immer: ^9.0.7
1416414172
less: ^4.1.3
@@ -14175,6 +14183,7 @@ coolshapes-react@lowcoder-org/coolshapes-react:
1417514183
react: ^18.2.0
1417614184
react-best-gradient-color-picker: ^3.0.10
1417714185
react-colorful: ^5.5.1
14186+
react-device-mockups: ^0.1.12
1417814187
react-documents: ^1.2.1
1417914188
react-dom: ^18.2.0
1418014189
react-draggable: ^4.4.4
@@ -17672,6 +17681,18 @@ coolshapes-react@lowcoder-org/coolshapes-react:
1767217681
languageName: node
1767317682
linkType: hard
1767417683

17684+
"react-device-mockups@npm:^0.1.12":
17685+
version: 0.1.12
17686+
resolution: "react-device-mockups@npm:0.1.12"
17687+
peerDependencies:
17688+
html5-device-mockups: ^3.2.1
17689+
prop-types: ^15.5.4
17690+
react: ^15.0.0 || ^16.0.0 || ^17.0.0
17691+
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0
17692+
checksum: 738e969802c32810c2ca3ca3bd6c9bacf9b3d7adda0569c4f5c7fb1d68bab860ac7bb5a50aa2677d852143cb30ab8520e556c4dc7f53be154fd16ca08a9ba32c
17693+
languageName: node
17694+
linkType: hard
17695+
1767517696
"react-documents@npm:^1.2.1":
1767617697
version: 1.2.1
1767717698
resolution: "react-documents@npm:1.2.1"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp