Skip to content

Commit

Permalink
feat: add draggable FAB & better plugin typings and definition (#4683)
Browse files Browse the repository at this point in the history
* feat: re-organize exports

* build: update pnpm lock file

* fix: can't resolve main

* feat: auto configure devtools data source hostname

* feat: sizing logo image

* fix: disable rspack to fix resolve issues

* feat: remove settings button

* feat: make action button draggable

* feat: add box shadow for framebox

* feat: use environment variable `HASH_SUFFIXED_VERSION` control whether use hash suffix for version

* build: update pnpm lock file

* feat: re-export type `Options`

* fix: property `devtools` is missing in return type

* build: update pnpm lock file

* feat: hosting client in local

* build: remove plugin devtools from the dependencies of app tools

* fix: not found export `RPC_SERVER_PATHNAME`

* feat: disable dynamic prefix

* build: update pnpm lock file
  • Loading branch information
Asuka109 authored Sep 21, 2023
1 parent 2675812 commit 8b340a4
Show file tree
Hide file tree
Showing 22 changed files with 432 additions and 298 deletions.
62 changes: 0 additions & 62 deletions .github/workflows/build-devtools-website.yml

This file was deleted.

52 changes: 6 additions & 46 deletions packages/devtools/client/modern.config.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,29 @@
import { URL } from 'url';
import path from 'path/posix';
import { execSync } from 'child_process';
import { logger } from '@modern-js/builder-shared';
import { appTools, defineConfig } from '@modern-js/app-tools';
import { proxyPlugin } from '@modern-js/plugin-proxy';
import { version } from './package.json';

const commitShort = execSync('git rev-parse --short=10 HEAD').toString().trim();
if (!commitShort.match(/^\w{10}$/)) {
throw new Error("Can't resolve git commit hash.");
}

const basename = new URL(process.env.DEPLOY_HOST || 'https://modernjs.dev');
if (process.env.BASENAME === 'version' || !process.env.BASENAME) {
basename.pathname = `/devtools/${version}`;
} else if (process.env.BASENAME === 'commit') {
basename.pathname = `/devtools/${commitShort}`;
} else if (process.env.BASENAME === 'false') {
basename.pathname = '/devtools';
} else {
basename.pathname = path.resolve('/devtools', process.env.BASENAME);
}

logger.info(
`Access client:`,
`${basename.href}?src=ws://localhost:8080/_modern_js/devtools/rpc`,
);

// https://modernjs.dev/en/configure/app/usage
export default defineConfig<'rspack'>({
runtime: {
router: {
basename: basename.pathname,
basename: '/_modern_js/devtools',
},
},
dev: {
assetPrefix: '/_modern_js/devtools',
port: 8780,
assetPrefix: basename.pathname,
proxy: {
[basename.href]: new URL(basename.pathname, 'http://localhost:8780').href,
},
},
source: {
preEntry: [require.resolve('modern-normalize/modern-normalize.css')],
globalVars: {
'process.env.PKG_VERSION': `${version}-${commitShort}`,
'process.env.PKG_VERSION': version,
},
},
output: {
assetPrefix: basename.href,
assetPrefix: '/_modern_js/devtools',
enableCssModuleTSDeclaration: true,
},
tools: {
devServer: {
client: {
host: 'localhost',
protocol: 'ws',
},
},
},
tools: {},
html: {},
plugins: [
appTools({
bundler: 'experimental-rspack',
}),
proxyPlugin(),
],
plugins: [appTools({ bundler: 'experimental-rspack' }), proxyPlugin()],
});
7 changes: 2 additions & 5 deletions packages/devtools/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"reset": "npx rimraf ./**/node_modules",
"dev": "modern dev",
"build": "modern build",
"deploy": "pnpm build && node scripts/build-redirects.js",
"start": "modern start",
"serve": "modern serve",
"new": "modern new",
Expand All @@ -16,7 +15,7 @@
"engines": {
"node": ">=14.0.0"
},
"dependencies": {
"devDependencies": {
"birpc": "0.2.13",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
Expand All @@ -38,9 +37,7 @@
"react-use": "^17.4.0",
"suspend-react": "^0.1.3",
"ufo": "^1.2.0",
"valtio": "^1.11.1"
},
"devDependencies": {
"valtio": "^1.11.1",
"@modern-js-app/eslint-config": "workspace:*",
"@modern-js/app-tools": "workspace:*",
"@modern-js/builder-shared": "workspace:*",
Expand Down
13 changes: 2 additions & 11 deletions packages/devtools/client/src/routes/RootTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Suspense } from 'react';
import styled from '@emotion/styled';
import { useSnapshot } from 'valtio';
import { Outlet, useNavigate, useLocation } from '@modern-js/runtime/router';
import { Box, Text, Button } from '@radix-ui/themes';
import { Box } from '@radix-ui/themes';
import _ from 'lodash';
import { GearIcon, HomeIcon } from '@radix-ui/react-icons';
import { HomeIcon } from '@radix-ui/react-icons';
import { useStore } from '@/stores';
import { InternalTab } from '@/types';
import Breadcrumbs from '@/components/Breadcrumbs';
Expand Down Expand Up @@ -48,10 +48,6 @@ export const RootTabs: React.FC = () => {
))}
</TabList>
<Box grow="1" />
<SettingButton>
<GearIcon />
<Text>Settings</Text>
</SettingButton>
</TabNavigator>
<TabContent>
<Box grow="0">
Expand All @@ -72,11 +68,6 @@ export const RootTabs: React.FC = () => {
);
};

const SettingButton = styled(Button)({
'--accent-9': 'var(--gray-5)',
'--accent-10': 'var(--gray-6)',
});

const IconBox = styled(Box)({
width: '2rem',
height: '2rem',
Expand Down
8 changes: 7 additions & 1 deletion packages/devtools/client/src/routes/overview/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const Page: React.FC = () => {
return (
<Flex direction="column" align="center">
<Flex gap="2">
<img src={store.assets.logo} />
<LogoImage src={store.assets.logo} />
<LogoHeading src={srcHeading} />
</Flex>
<Description>
Expand Down Expand Up @@ -120,3 +120,9 @@ const Description = styled(Text)({
const LogoHeading = styled.img({
width: '10rem',
});

const LogoImage = styled.img({
width: '2rem',
height: '2rem',
objectFit: 'contain',
});
6 changes: 1 addition & 5 deletions packages/devtools/kit/src/types/mount-point.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import _ from '@modern-js/utils/lodash';
import { parseURL, stringifyParsedURL } from 'ufo';

export interface MountPointFunctions {
getLocation: () => string;
}

export interface SetupClientOptions {
export interface SetupClientOptions extends Record<string, any> {
endpoint?: string;

version?: string | boolean;

dataSource?: string;
}
23 changes: 23 additions & 0 deletions packages/devtools/mount/src/components/Devtools/Action.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,27 @@
position: fixed;
bottom: 10px;
right: 10px;
width: max-content;
height: max-content;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
display: flex;
gap: 0.25rem;
border: 1px solid #011c64;
background: linear-gradient(to bottom, #212121, #1e1e1e, #212121);
box-shadow: 0 0 3px #011c64;
color: #b8b9bb;
border-radius: 999px;
align-items: center;
}

.fab :global(*) {
user-select: none;
pointer-events: none;
}

.logo {
width: 1.5rem;
height: 1.5rem;
object-fit: contain;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
interface CssExports {
container: string;
fab: string;
logo: string;
}
export const cssExports: CssExports;
export default cssExports;
96 changes: 80 additions & 16 deletions packages/devtools/mount/src/components/Devtools/Action.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,105 @@
import React from 'react';
import { useToggle } from 'react-use';
import { joinURL, withQuery } from 'ufo';
import { useGetSet, useToggle } from 'react-use';
import { withQuery, stringifyParsedURL, parseURL } from 'ufo';
import { SetupClientOptions } from '@modern-js/devtools-kit';
import Visible from '../Visible';
import styles from './Action.module.scss';
import FrameBox from './FrameBox';

const getDefaultRPC = () => {
const url = new URL('ws://localhost/_modern_js/devtools/rpc');
url.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
url.host = location.host;
return url.href;
const parseDataSource = (url: string) => {
const newSrc = parseURL(url);
return stringifyParsedURL({
protocol: location.protocol === 'https:' ? 'wss:' : 'ws:',
host: location.host,
...newSrc,
pathname: newSrc.pathname || '/_modern_js/devtools/rpc',
});
};

const useStickyDraggable = () => {
const [isDragging, setIsDragging] = useGetSet(false);
const handleMouseDown = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
const target = e.currentTarget;
if (!(target instanceof HTMLElement)) {
return;
}
const { offsetX, offsetY } = e.nativeEvent;
const handleMousemove = (e: MouseEvent) => {
if (e.movementX + e.movementY > 1) {
setIsDragging(true);
}

const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
const distances = [
{ prop: 'top', value: e.clientY } as const,
{ prop: 'bottom', value: window.innerHeight - e.clientY } as const,
{ prop: 'left', value: e.clientX } as const,
{ prop: 'right', value: window.innerWidth - e.clientX } as const,
];
const [primary, ...rest] = distances.sort((a, b) => a.value - b.value);
target.style[primary.prop] = '10px';
for (const unset of rest) {
target.style.removeProperty(unset.prop);
}
if (['top', 'bottom'].includes(primary.prop)) {
target.style.left = `${x}px`;
} else {
target.style.top = `${y}px`;
}
};
window.addEventListener('mousemove', handleMousemove);
window.addEventListener('blur', () => {
setTimeout(() => setIsDragging(false), 0);
window.removeEventListener('mousemove', handleMousemove);
});
window.addEventListener(
'mouseup',
() => {
setTimeout(() => setIsDragging(false), 0);
window.removeEventListener('mousemove', handleMousemove);
},
{ once: true },
);
};
return {
onMouseDown: handleMouseDown,
isDragging: isDragging(),
};
};

const DevtoolsAction: React.FC<SetupClientOptions> = props => {
const version = process.env.VERSION!;
const logoSrc = process.env._MODERN_DEVTOOLS_LOGO_SRC!;
const opts: Required<SetupClientOptions> = {
version,
dataSource: getDefaultRPC(),
endpoint: 'https://modernjs.dev/devtools',
...props,
dataSource: parseDataSource(props.dataSource ?? ''),
};
const [showDevtools, toggleDevtools] = useToggle(false);

const ver = opts.version === true ? version : opts.version;
let src = opts.endpoint;
ver && (src = joinURL(src, ver));
src = withQuery(src, { src: opts.dataSource });

const { isDragging, onMouseDown } = useStickyDraggable();

return (
<>
<div className={styles.fab}>
<button onClick={toggleDevtools}>Toggle DevTools</button>
</div>
<button
className={styles.fab}
onClick={() => {
isDragging || toggleDevtools();
}}
onMouseDown={onMouseDown}
>
<img className={styles.logo} src={logoSrc} alt="" />
<span>Toggle DevTools</span>
</button>
<Visible when={showDevtools} keepAlive={true}>
<div className={styles.container}>
<FrameBox src={src} />
<FrameBox
src={src}
style={{ pointerEvents: isDragging ? 'none' : 'auto' }}
/>
</div>
</Visible>
</>
Expand Down
Loading

0 comments on commit 8b340a4

Please sign in to comment.