editor: add pybricks logo welcome

This commit is contained in:
David Lechner
2022-12-01 17:08:19 -06:00
committed by David Lechner
parent a0f05c00a4
commit 6a979cafa8
9 changed files with 4932 additions and 29 deletions
+3
View File
@@ -4,6 +4,9 @@
## [Unreleased]
### Added
- Added Pybricks logo when no files open.
### Changed
- Firmware restore for hubs with USB is now done in-app ([pybricks-code#1104]).
+1
View File
@@ -113,6 +113,7 @@
"style-loader": "^3.3.1",
"tailwindcss": "^3.2.4",
"terser-webpack-plugin": "^5.3.6",
"two.js": "^0.8.10",
"typed-redux-saga": "^1.5.0",
"typescript": "~4.9.3",
"usehooks-ts": "^2.9.1",
+6 -1
View File
@@ -16,6 +16,7 @@ import {
Text,
} from '@blueprintjs/core';
import { ContextMenu2, ResizeSensor2 } from '@blueprintjs/popover2';
import classNames from 'classnames';
import * as monaco from 'monaco-editor';
import tomorrowNightEightiesTheme from 'monaco-themes/themes/Tomorrow-Night-Eighties.json';
import xcodeTheme from 'monaco-themes/themes/Xcode_default.json';
@@ -29,6 +30,7 @@ import { compile } from '../mpy/actions';
import { useSelector } from '../reducers';
import { useSettingIsShowDocsEnabled } from '../settings/hooks';
import { isMacOS } from '../utils/os';
import Welcome from './Welcome';
import { editorActivateFile, editorCloseFile } from './actions';
import { useI18n } from './i18n';
import * as pybricksMicroPython from './pybricksMicroPython';
@@ -465,12 +467,14 @@ const Editor: React.VFC = () => {
};
});
const isEmpty = useSelector((s) => s.editor.openFileUuids.length === 0);
return (
<div className="pb-editor">
<EditorTabs onChange={() => editor?.focus()} />
<ResizeSensor2 onResize={() => editor?.layout()}>
<ContextMenu2
className="pb-editor-tabpanel"
className={classNames('pb-editor-tabpanel', isEmpty && 'pb-empty')}
role="tabpanel"
// NB: we have to create a new context menu each time it is
// shown in order to get some state, like canUndo and canRedo
@@ -478,6 +482,7 @@ const Editor: React.VFC = () => {
content={() => <EditorContextMenu editor={editor} />}
popoverProps={popoverProps}
>
<Welcome isVisible={isEmpty} />
<div className="pb-editor-monaco" ref={editorRef} />
</ContextMenu2>
</ResizeSensor2>
+166
View File
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
// welcome screen that is shown when no editor is open.
import { Colors } from '@blueprintjs/core';
import React, { useEffect, useRef } from 'react';
import Two from 'two.js';
import { useTernaryDarkMode } from 'usehooks-ts';
import logoSvg from './logo.svg';
const defaultRotation = -Math.PI / 9; // radians
const rotationSpeedIncrement = 0.1; // radians per second
type State = {
rotation: number;
rotationSpeed: number;
};
enum ActionType {
Stop,
ChangeSpeed,
Update,
}
type Action =
| {
type: ActionType.Stop;
}
| {
type: ActionType.ChangeSpeed;
amount: number;
}
| {
type: ActionType.Update;
timeDelta: number;
};
function reduce(state: State, action: Action): State {
switch (action.type) {
case ActionType.Stop:
return { ...state, rotation: defaultRotation, rotationSpeed: 0 };
case ActionType.ChangeSpeed:
return { ...state, rotationSpeed: state.rotationSpeed + action.amount };
case ActionType.Update:
return {
...state,
rotation: state.rotation + state.rotationSpeed * action.timeDelta,
};
default:
return state;
}
}
function getFillColor(isDarkMode: boolean): string {
return isDarkMode ? Colors.GRAY1 : Colors.GRAY5;
}
type WelcomeProps = {
isVisible: boolean;
};
const Welcome: React.VoidFunctionComponent<WelcomeProps> = ({ isVisible }) => {
const stateRef = useRef<State>({
rotation: defaultRotation,
rotationSpeed: 0,
});
const elementRef = useRef<HTMLDivElement>(null);
const { isDarkMode } = useTernaryDarkMode();
const fillColorRef = useRef('');
// HACK: we don't want to image to flash when switching dark mode
fillColorRef.current = getFillColor(isDarkMode);
useEffect(() => {
if (process.env.NODE_ENV === 'test') {
// jsdom doesn't support ResizeObserver. don't really need to test animation anyway.
return;
}
if (!isVisible) {
return;
}
// istanbul ignore if: should not happen
if (!elementRef.current) {
console.error('elementRef was null!');
return;
}
const two = new Two({ fitted: true }).appendTo(elementRef.current);
const logo = two.load(logoSvg, (g) => {
g.center();
two.add(logo);
two.play();
});
two.addEventListener('update', (_count, time: number) => {
stateRef.current = reduce(stateRef.current, {
type: ActionType.Update,
timeDelta: time / 1000,
});
logo.fill = fillColorRef.current;
logo.scale = Math.min(two.width, two.height) / 80;
logo.rotation = stateRef.current.rotation;
two.scene.position.x = two.width / 2;
two.scene.position.y = two.height / 2;
});
const observer = new ResizeObserver(() => {
two.fit();
});
observer.observe(elementRef.current);
const handleClick = (e: Event) => {
e.stopPropagation();
e.preventDefault();
stateRef.current = reduce(stateRef.current, {
type: ActionType.Stop,
});
};
two.renderer.domElement.addEventListener('pointerdown', handleClick, {
capture: true,
});
const handleWheel = (e: Event) => {
const we = e as WheelEvent;
stateRef.current = reduce(stateRef.current, {
type: ActionType.ChangeSpeed,
amount: rotationSpeedIncrement * Math.sign(-we.deltaY),
});
};
two.renderer.domElement.addEventListener('wheel', handleWheel, {
passive: true,
});
return () => {
two.renderer.domElement.removeEventListener('wheel', handleWheel);
two.renderer.domElement.removeEventListener('pointerdown', handleClick);
observer.disconnect();
two.removeEventListener('update');
elementRef.current?.removeChild(two.renderer.domElement);
two.clear();
};
}, [isVisible]);
return (
<div
className="pb-editor-welcome"
ref={elementRef}
onContextMenuCapture={(e) => {
e.stopPropagation();
e.preventDefault();
}}
/>
);
};
export default Welcome;
+14
View File
@@ -63,9 +63,23 @@
flex: 1 1 auto;
}
&-welcome {
display: none;
.pb-editor-tabpanel.pb-empty > & {
display: block;
width: 100%;
height: 100%;
}
}
&-monaco {
width: 100%;
height: 100%;
.pb-editor-tabpanel.pb-empty > & {
display: none;
}
}
&-placeholder {
+1
View File
@@ -0,0 +1 @@
<svg width="50" height="50" version="1.1" viewBox="0 0 26.458 26.458" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0,-270.54)" fill="#ffffff"><path d="m1.9234 276.13c-1.0655 0-1.9234 0.85655-1.9234 1.9213v10.588c0 1.0648 0.85787 1.9213 1.9234 1.9213h3.3755c1.0655 0 1.9234-0.85653 1.9234-1.9213v-0.47646h12.014v0.47646c0 1.0648 0.85787 1.9213 1.9234 1.9213h3.3755c1.0655 0 1.9234-0.85653 1.9234-1.9213v-10.588c0-1.0648-0.85786-1.9213-1.9234-1.9213h-3.3755c-5.4826 0.0358-11.132 0.0125-15.861 0zm0.49609 2.4123h21.644v9.617h-2.3978v-2.3916h-2.0691v-0.89193h-0.74311v0.89193h-1.664v-0.89193h-0.74001v0.89193h-1.664v-0.89193h-0.74001v0.89193h-1.664v-0.89193h-0.74311v0.89193h-1.664v-0.89193h-0.74104v0.89193h-1.664v-0.89193h-0.68316v0.89193h-2.0691v2.3916h-2.3978v-3.2835z"/><ellipse cx="8.4499" cy="281.03" rx="1.7076" ry="1.7064"/><ellipse cx="18.033" cy="281.03" rx="1.7076" ry="1.7064"/></g></svg>

After

Width:  |  Height:  |  Size: 913 B

+4708
View File
File diff suppressed because it is too large Load Diff
+25 -28
View File
@@ -1,30 +1,27 @@
{
"compilerOptions": {
"target": "es2018",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"downlevelIteration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src",
"test"
]
"compilerOptions": {
"target": "es2018",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": ".",
"paths": {
"two.js": ["src/two-override"]
},
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"downlevelIteration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "test"]
}
+8
View File
@@ -2525,6 +2525,7 @@ __metadata:
style-loader: ^3.3.1
tailwindcss: ^3.2.4
terser-webpack-plugin: ^5.3.6
two.js: ^0.8.10
typed-redux-saga: ^1.5.0
typescript: ~4.9.3
usehooks-ts: ^2.9.1
@@ -14769,6 +14770,13 @@ __metadata:
languageName: node
linkType: hard
"two.js@npm:^0.8.10":
version: 0.8.10
resolution: "two.js@npm:0.8.10"
checksum: 1550d4a406ca7580c981c7753cc7aab7f595b63f0488ff08cd6d05938faa308e62ac2d1719c9422d4361bac83c41295f63d4e2434ca763bd10bc5258def2cb47
languageName: node
linkType: hard
"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
version: 0.4.0
resolution: "type-check@npm:0.4.0"