Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add shortcuts component #877

Merged
merged 5 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion packages/@dcl/inspector/src/components/Renderer/Renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ import {
ZOOM_IN_ALT,
ZOOM_OUT_ALT,
ZOOM_OUT,
RESET_CAMERA
RESET_CAMERA,
DUPLICATE,
DUPLICATE_ALT
} from '../../hooks/useHotkey'
import { analytics, Event } from '../../lib/logic/analytics'
import { Warnings } from '../Warnings'
import { CameraSpeed } from './CameraSpeed'
import { Shortcuts } from './Shortcuts'

import './Renderer.css'

Expand Down Expand Up @@ -86,6 +89,18 @@ const Renderer: React.FC = () => {
void sdk.sceneContext.operations.dispatch()
}, [sdk])

const duplicateSelectedEntities = useCallback(() => {
if (!sdk) return
const camera = sdk.scene.activeCamera!
camera.detachControl()
const selectedEntitites = sdk.sceneContext.operations.getSelectedEntities()
selectedEntitites.forEach((entity) => sdk.sceneContext.operations.duplicateEntity(entity))
void sdk.sceneContext.operations.dispatch()
setTimeout(() => {
camera.attachControl(canvasRef.current, true)
}, 100)
}, [sdk])

const copySelectedEntities = useCallback(() => {
if (!sdk) return
const selectedEntitites = sdk.sceneContext.operations.getSelectedEntities()
Expand Down Expand Up @@ -123,6 +138,7 @@ const Renderer: React.FC = () => {
useHotkey([ZOOM_IN, ZOOM_IN_ALT], zoomIn, canvasRef.current)
useHotkey([ZOOM_OUT, ZOOM_OUT_ALT], zoomOut, canvasRef.current)
useHotkey([RESET_CAMERA], resetCamera, canvasRef.current)
useHotkey([DUPLICATE, DUPLICATE_ALT], duplicateSelectedEntities, canvasRef.current)

const getDropPosition = async () => {
const pointerCoords = await getPointerCoords(sdk!.scene)
Expand Down Expand Up @@ -239,6 +255,7 @@ const Renderer: React.FC = () => {
{isLoading && <Loading />}
<Warnings />
<CameraSpeed />
<Shortcuts canvas={canvasRef} onResetCamera={resetCamera} onZoomIn={zoomIn} onZoomOut={zoomOut} />
<canvas ref={canvasRef} id="canvas" touch-action="none" />
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
.Shortcuts {
--shortcuts-bottom: 8px;
--shortcuts-right: 8px;
--shortcuts-button-height: 30px;
--shortcuts-button-width: 30px;

position: absolute;
bottom: 8px;
right: 8px;
z-index: 1;
}

.Shortcuts .Buttons {
display: flex;
flex-direction: row;
gap: 8px;
}

.Shortcuts .Buttons .Button {
display: flex;
align-items: center;
justify-content: center;
height: 30px;
width: 30px;
padding: 5px;
border-radius: 4px;
background-color: var(--base-16);
}

.Shortcuts .Buttons .Button:hover {
background-color: var(--base-17);
}

.Shortcuts .Buttons .Button.Active {
background-color: var(--base-02);
}

.Shortcuts .Buttons .Button.Active svg {
color: var(--base-21);
}

.Shortcuts .Buttons .Button svg {
color: var(--base-02);
}

.Shortcuts .Buttons .ZoomButtons {
display: flex;
}

.Shortcuts .Buttons .ZoomButtons .Button:first-of-type {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 1.5px solid var(--base-19);
}

.Shortcuts .Buttons .ZoomButtons .Button:last-of-type {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 1.5px solid var(--base-19);
}

.Shortcuts > .Overlay {
display: flex;
flex-direction: column;
position: absolute;
width: 298px;
overflow-y: auto;
right: 0;
bottom: calc(var(--shortcuts-bottom) + var(--shortcuts-button-height) + 8px);
background-color: var(--base-19);
padding: 13px 12px;
border-radius: 4px;
gap: 16px;
}

.Shortcuts > .Overlay h2.Header {
font-size: 14px;
font-weight: 500;
line-height: 17px;
color: var(--base-01);
margin-bottom: 0;
}

.Shortcuts > .Overlay h5.SubHeader,
.Shortcuts > .Overlay .Item .Title,
.Shortcuts > .Overlay .Item .Description,
.Shortcuts > .Overlay .Item .Description .Key {
font-size: 11px;
font-weight: 500;
color: var(--base-01);
margin-bottom: 0;
}

.Shortcuts > .Overlay h5.SubHeader {
color: var(--base-09);
text-transform: uppercase;
}

.Shortcuts > .Overlay .Items {
display: flex;
flex-direction: column;
gap: 8px;
}

.Shortcuts > .Overlay .Items .Item {
display: flex;
gap: 4px;
padding: 8px 0;
border-bottom: 1px solid var(--base-18);
}

.Shortcuts > .Overlay .Items .Item:last-of-type {
border-bottom: none;
}

.Shortcuts > .Overlay .Item .Title {
display: flex;
flex: 1;
align-items: center;
}

.Shortcuts > .Overlay .Item .Description {
display: flex;
flex: 1;
align-items: center;
gap: 4px;
line-height: 14px;
}

.Shortcuts > .Overlay .Item .Description .Key {
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
height: 18px;
border-radius: 4px;
background-color: var(--base-13);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React, { useCallback, useMemo } from 'react'
import cx from 'classnames'
import {
MdOutlineZoomIn as ZoomInIcon,
MdOutlineZoomOut as ZoomOutIcon,
MdKeyboard as KeyboardIcon
} from 'react-icons/md'
import { HiOutlineViewfinderCircle as ResetCameraIcon } from 'react-icons/hi2'

import { useContainerSize } from '../../../hooks/useContainerSize'
import { useOutsideClick } from '../../../hooks/useOutsideClick'
import { Button } from '../../Button'
import { InfoTooltip } from '../../ui'
import { Props } from './types'

import './Shortcuts.css'

const ICON_SIZE = 18

const Shortcuts: React.FC<Props> = ({ canvas, onResetCamera, onZoomIn, onZoomOut }) => {
const [showShortcuts, setShowShortcuts] = React.useState(false)
const { height } = useContainerSize(canvas)

const maxOverlayHeight = useMemo(() => {
return (height ?? 600) - 60
}, [height])

const handleToggleShortcutsOverlay = useCallback(
(e: React.MouseEvent<HTMLButtonElement> | MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setShowShortcuts((value) => !value)
},
[showShortcuts, setShowShortcuts]
)

const overlayRef = useOutsideClick(handleToggleShortcutsOverlay)

return (
<div className="Shortcuts">
<div className="Buttons">
<Button onClick={onResetCamera}>
<ResetCameraIcon size={ICON_SIZE} />
</Button>
<div className="ZoomButtons">
<Button onClick={onZoomIn}>
<ZoomInIcon size={ICON_SIZE} />
</Button>
<Button onClick={onZoomOut}>
<ZoomOutIcon size={ICON_SIZE} />
</Button>
</div>
<InfoTooltip
text="View Shortcuts"
trigger={
<Button className={cx({ Active: showShortcuts })} onClick={handleToggleShortcutsOverlay}>
<KeyboardIcon size={ICON_SIZE} />
</Button>
}
openOnTriggerMouseEnter={!showShortcuts}
closeOnTriggerClick={true}
position="top center"
/>
</div>
{showShortcuts && (
<div ref={overlayRef} className="Overlay" style={{ maxHeight: maxOverlayHeight }}>
<h2 className="Header">Shortcuts</h2>
<div className="Items">
<h5 className="SubHeader">General</h5>
<div className="Item">
<div className="Title">Pan Camera</div>
<div className="Description">
<span className="Key">W</span>
<span className="Key">A</span>
<span className="Key">S</span>
<span className="Key">D</span>
</div>
</div>
<div className="Item">
<div className="Title">Select Multiple Items</div>
<div className="Description">
Hold<span className="Key">ctrl</span>and click
</div>
</div>
<div className="Item">
<div className="Title">Save</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">S</span>
</div>
</div>
<div className="Item">
<div className="Title">Undo</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">Z</span>
</div>
</div>
<div className="Item">
<div className="Title">Redo</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">Y</span>
</div>
</div>
<div className="Item">
<div className="Title">Copy</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">C</span>
</div>
</div>
<div className="Item">
<div className="Title">Paste</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">V</span>
</div>
</div>
<div className="Item">
<div className="Title">Reset Camera</div>
<div className="Description">
<span className="Key">space</span>
</div>
</div>
</div>
<div className="Items">
<h5 className="SubHeader">Item Selected</h5>
<div className="Item">
<div className="Title">Snap to Grid</div>
<div className="Description">
Hold<span className="Key">shift</span>
</div>
</div>
<div className="Item">
<div className="Title">Toggle Positioning</div>
<div className="Description">
<span className="Key">M</span>
</div>
</div>
<div className="Item">
<div className="Title">Toggle Rotating</div>
<div className="Description">
<span className="Key">R</span>
</div>
</div>
<div className="Item">
<div className="Title">Toggle Scaling</div>
<div className="Description">
<span className="Key">X</span>
</div>
</div>
<div className="Item">
<div className="Title">Duplicate</div>
<div className="Description">
<span className="Key">ctrl</span>+<span className="Key">D</span>
</div>
</div>
<div className="Item">
<div className="Title">Delete</div>
<div className="Description">
<span className="Key">del</span>or<span className="Key">backspace</span>
</div>
</div>
</div>
</div>
)}
</div>
)
}

export default React.memo(Shortcuts)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Shortcuts from './Shortcuts'
export { Shortcuts }

Check warning on line 2 in packages/@dcl/inspector/src/components/Renderer/Shortcuts/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/@dcl/inspector/src/components/Renderer/Shortcuts/index.ts#L2

Added line #L2 was not covered by tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Props {
canvas: React.RefObject<HTMLCanvasElement>
onResetCamera: () => void
onZoomIn: () => void
onZoomOut: () => void
}

Check warning on line 6 in packages/@dcl/inspector/src/components/Renderer/Shortcuts/types.ts

View check run for this annotation

Codecov / codecov/patch

packages/@dcl/inspector/src/components/Renderer/Shortcuts/types.ts#L2-L6

Added lines #L2 - L6 were not covered by tests
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import { withSdk } from '../../../hoc/withSdk'
import { useComponentValue } from '../../../hooks/sdk/useComponentValue'
import { useSelectedEntity } from '../../../hooks/sdk/useSelectedEntity'
import { useOutsideClick } from '../../../hooks/useOutsideClick'
import { useHotkey } from '../../../hooks/useHotkey'
import { useSnapToggle } from '../../../hooks/editor/useSnap'
import { useGizmoAlignment } from '../../../hooks/editor/useGizmoAlignment'
import { ROOT } from '../../../lib/sdk/tree'
import { GizmoType } from '../../../lib/utils/gizmo'
import { ToolbarButton } from '../ToolbarButton'
import { Snap } from './Snap'

import './Gizmos.css'
import { useGizmoAlignment } from '../../../hooks/editor/useGizmoAlignment'

export const Gizmos = withSdk(({ sdk }) => {
const [showPanel, setShowPanel] = useState(false)
Expand All @@ -40,6 +41,10 @@ export const Gizmos = withSdk(({ sdk }) => {
[selection, setSelection]
)

useHotkey(['M'], handlePositionGizmo)
useHotkey(['R'], handleRotationGizmo)
useHotkey(['X'], handleScaleGizmo)

const {
isPositionGizmoWorldAligned,
isRotationGizmoWorldAligned,
Expand Down
Loading
Loading