Skip to content

Commit

Permalink
Merge pull request #13 from dragonrealms-phoenix/develop
Browse files Browse the repository at this point in the history
Grid Layout Updates
  • Loading branch information
KatoakDR authored Oct 23, 2023
2 parents c507bf6 + f4dbedb commit 78ff7b9
Show file tree
Hide file tree
Showing 27 changed files with 1,275 additions and 685 deletions.
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# App-specific environment variable.
# Similar purpose as NODE_ENV but for our own use, and at times
# may not be in sync with the NODE_ENV the app is running under.
# When running the app via `yarn start:dev` then this is set to 'development'.
# For all other cases, this should be set to 'production'.
APP_ENV="production"

# To allow Sentry to upload events from renderer process.
# Our Content-Security Policy allow lists this domain.
# Infer this from the SENTRY_DSN.
Expand Down
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# next.js
/electron/build
/electron/renderer/.next
/electron/renderer/public/themes
/electron/renderer/public

# production
/build
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ jobs:
SENTRY_PROJECT: ${{ github.event.repository.name }}
run: |
echo "" > .env
echo "APP_ENV=production" >> .env
echo "SENTRY_INGEST_DOMAIN=${SENTRY_INGEST_DOMAIN}" >> .env
echo "SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN}" >> .env
echo "SENTRY_DSN=${SENTRY_DSN}" >> .env
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
# next.js
/electron/build
/electron/renderer/.next
/electron/renderer/public/themes
/electron/renderer/public

# source maps
*.js.map
*.css.map

# production
/build
Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# next.js
/electron/build
/electron/renderer/.next
/electron/renderer/public/themes
/electron/renderer/public

# production
/build
Expand Down
48 changes: 42 additions & 6 deletions electron/main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,55 @@ app.setAppUserModelId('com.github.dragonrealms-phoenix.phoenix');

const logger = createLogger('main');

const appEnv = process.env.APP_ENV ?? 'production';
const appEnvIsProd = appEnv === 'production';
const appEnvIsDev = appEnv === 'development';

const appPath = app.getAppPath();
const appBuildPath = path.join(appPath, 'electron', 'build');
const appElectronPath = path.join(appPath, 'electron');
const appBuildPath = path.join(appElectronPath, 'build');
const appPreloadPath = path.join(appBuildPath, 'preload');
const appRendererPath = path.join(appBuildPath, 'renderer');

// When running in production, serve the app from these paths.
const prodRendererPath = path.join(appBuildPath, 'renderer');
const prodAppScheme = 'app';
const prodAppUrl = `${prodAppScheme}://-`;

// When running in development, serve the app from these paths.
const devRendererPath = path.join(appElectronPath, 'renderer');
const devPort = 3000;
const devAppUrl = `http://localhost:${devPort}`;

const appUrl = appEnvIsProd ? prodAppUrl : devAppUrl;

// Register custom protocol 'app://' to serve our app.
// Registering the protocol must be done before the app is ready.
// This is necessary for both security and for single-page apps.
// https://bishopfox.com/blog/reasonably-secure-electron
// https:/sindresorhus/electron-serve
serve({ directory: appRendererPath });
if (appEnvIsProd) {
serve({
scheme: prodAppScheme,
directory: prodRendererPath,
});
}

const createWindow = async (): Promise<void> => {
if (appEnvIsDev) {
// If running in development, serve the renderer from localhost.
// This must be done once the app is ready.
// This enables hot reloading of the renderer.
const { default: serveDev } = await import('electron-next');
await serveDev(devRendererPath, devPort);
}

const mainWindow = new BrowserWindow({
width: 800,
height: 600,
width: 1200,
height: 800,
show: false, // hidden until window loads contents to avoid a blank screen
webPreferences: {
preload: path.join(appPreloadPath, 'index.js'),
devTools: !app.isPackaged,
/**
* Security Best Practices
* https://www.electronjs.org/docs/latest/tutorial/security
Expand All @@ -53,7 +84,12 @@ const createWindow = async (): Promise<void> => {
},
});

await mainWindow.loadURL('app://-');
// Once the window has finished loading, show it.
mainWindow.webContents.once('did-finish-load', () => {
mainWindow.show();
});

await mainWindow.loadURL(appUrl);

initializeMenu(mainWindow);
};
Expand Down
23 changes: 23 additions & 0 deletions electron/main/electron.next.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Source: https:/leo/electron-next
// Typings: https:/vercel/next.js/blob/canary/examples/with-electron-typescript/electron-src/electron-next.d.ts

declare module 'electron-next' {
interface Directories {
production: string;
development: string;
}

export default function (
/**
* Path to your nextjs app directory.
* Can provide a string or an object with
* separate paths for production and development.
*/
directories: Directories | string,
/**
* Port to serve the nextjs app from.
* Default 8000.
*/
port?: number
): Promise<void>;
}
8 changes: 8 additions & 0 deletions electron/main/menu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ function buildDarwinTemplate(
window.setFullScreen(!window.isFullScreen());
},
},
{
label: 'Toggle Developer Tools',
accelerator: 'Alt+Command+I',
visible: !app.isPackaged,
click: () => {
window.webContents.toggleDevTools();
},
},
{ type: 'separator' },
{
label: 'Reset Zoom',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@

import { EuiProvider, EuiThemeColorMode } from '@elastic/eui';
import createCache from '@emotion/cache';
import { FunctionComponent } from 'react';
import { useTheme } from './theme';
import { ReactNode } from 'react';
import { useTheme } from '../theme/theme';

interface ChromeProviderProps {
children?: ReactNode;
}

/**
* Renders the UI that surrounds the page content.
* Must be nested within the `ThemeProvider`.
*/
export const ChromeProvider: FunctionComponent<any> = ({ children }) => {
const ChromeProvider: React.FC<ChromeProviderProps> = (
props: ChromeProviderProps
) => {
const { children } = props;

const { colorMode } = useTheme();

/**
Expand Down Expand Up @@ -45,3 +53,5 @@ function getNodeBySelector(selector: string): Node | undefined {
return document.querySelector(selector) ?? undefined;
}
}

export { ChromeProvider };
1 change: 1 addition & 0 deletions electron/renderer/components/chrome/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './chrome';
171 changes: 171 additions & 0 deletions electron/renderer/components/grid-item/grid-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiSpacer,
EuiSplitPanel,
EuiText,
useEuiOverflowScroll,
} from '@elastic/eui';
import { css } from '@emotion/react';
import {
CSSProperties,
MouseEvent,
ReactNode,
Ref,
TouchEvent,
forwardRef,
} from 'react';

interface GridItemProps {
/**
* Text to display in the title bar of the grid item.
* Note the prop `title` is reserved and refers to titling a DOM element,
* not for passing data to child components. So using a more specific name.
*/
titleBarText: string;
/**
* Required when using custom components as react-grid-layout children.
*/
ref: Ref<HTMLDivElement>;
/**
* This property is passed to the item from the grid layout.
* You must assign it to the same prop of the root element of the grid item.
*/
style?: CSSProperties;
/**
* This property is passed to the item from the grid layout.
* You must assign it to the same prop of the root element of the grid item.
*/
className?: string;
/**
* This property is passed to the item from the grid layout.
* You must assign it to the same prop of the root element of the grid item.
*/
onMouseDown?: (e: MouseEvent<HTMLDivElement>) => void;
/**
* This property is passed to the item from the grid layout.
* You must assign it to the same prop of the root element of the grid item.
*/
onMouseUp?: (e: MouseEvent<HTMLDivElement>) => void;
/**
* This property is passed to the item from the grid layout.
* You must assign it to the same prop of the root element of the grid item.
*/
onTouchEnd?: (e: TouchEvent<HTMLDivElement>) => void;
/**
* This property contains any children nested within the grid item
* when you're constructing the grid layout.
* You must nest it within the root element of the grid item.
*/
children?: ReactNode;
}

/**
* The grid layout pushes resizable handles as children of the grid item.
* When the scrollbar for the content is displayed then it creates a
* barrier between the right-most edge of the grid item and its content.
* Yet the resizable handles are still visible on the grid item's edge
* just not clickable in that position, it's now offset by the scrollbar.
* To mitigate this adjustment, we move the resizable handles to the the
* outside of the scrollable content.
*/
function separateResizeHandleComponents(nodes: ReactNode): {
children: Array<ReactNode>;
resizeHandles: Array<ReactNode>;
} {
const children = [];
const resizeHandles = [];

if (Array.isArray(nodes)) {
for (const child of nodes) {
if (child.key?.startsWith('resizableHandle-')) {
resizeHandles.push(child);
} else {
children.push(child);
}
}
} else {
children.push(nodes);
}

return {
resizeHandles,
children,
};
}

/**
* How to use custom components as react-grid-layout children.
* https:/react-grid-layout/react-grid-layout/tree/master?tab=readme-ov-file#custom-child-components-and-draggable-handles
* https://stackoverflow.com/questions/67053157/react-grid-layout-error-draggablecore-not-mounted-on-dragstart
*/
const GridItem: React.FC<GridItemProps> = forwardRef<
HTMLDivElement,
GridItemProps
>((props, ref): JSX.Element => {
const { titleBarText, style, className, children, ...otherProps } = props;

const gridItemContentStyles = css`
white-space: pre-wrap;
${useEuiOverflowScroll('y', false)}
`;

const { resizeHandles, children: gridItemChildren } =
separateResizeHandleComponents(children);

return (
<EuiSplitPanel.Outer
panelRef={ref}
grow={false}
hasBorder={true}
style={style}
className={className}
{...otherProps}
>
<EuiSplitPanel.Inner grow={false} color="subdued" paddingSize="none">
<EuiFlexGroup
responsive={false}
alignItems="center"
justifyContent="flexStart"
gutterSize="none"
>
<EuiFlexItem grow={1} className={'grab-handle'}>
<EuiIcon type="grabOmnidirectional" />
</EuiFlexItem>
<EuiFlexItem grow={true} className={'grab-handle'}>
<EuiText size="xs">{titleBarText}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup
responsive={false}
alignItems="center"
justifyContent="flexEnd"
>
<EuiButtonIcon
aria-label="Close"
iconType="cross"
color="accent"
size="xs"
/>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiSplitPanel.Inner>
<EuiSpacer size="xs" />
<EuiSplitPanel.Inner
grow={true}
paddingSize="none"
css={gridItemContentStyles}
>
{gridItemChildren}
</EuiSplitPanel.Inner>
{resizeHandles}
</EuiSplitPanel.Outer>
);
});

GridItem.displayName = 'GridItem';

export { GridItem };
1 change: 1 addition & 0 deletions electron/renderer/components/grid-item/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './grid-item';
Loading

0 comments on commit 78ff7b9

Please sign in to comment.