Skip to content
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
- An issue where context menus were cut off in the rundown
- An issue with the palette not setting proper keys
- An issue with the palette not removing event listeners
- The escape key causes weird behaviour when editing a shortcut
- Closing shortcuts with the escape key prevents context menus in the edit mode
### Added
- Support for selecting multiple items at once with the shift key
- An API for managing context menus
- An API for managing the clipboard
- Search in context menus
- Keyboard control in context menus
- Allow for setting default on play and on end actions
- A modal stack for controlling the order in which modals are closed
- A prompt to save if unsaved changes are detected

## 1.0.0-beta.7
### Changed
Expand Down
12 changes: 9 additions & 3 deletions api/browser/clipboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@

const DIController = require('../../shared/DIController')

const InvalidArgumentError = require('../error/InvalidArgumentError')

class Clipboard {
/**
* Write a string into the clipboard
* @param { String } str A string to write
* @returns { Promise.<Boolean> }
* @returns { Promise.<boolean> }
*/
writeText (str) {
if (typeof str !== 'string') {
throw new InvalidArgumentError('Provided text is not a string and cannot be written to the clipboard')
}

return navigator.clipboard.writeText(str)
}

/**
* Read a string stored in the clipboard,
* will return an empty string
* if the clipboard is empty
* @returns { Promise.<String> }
* @returns { Promise.<string> }
*/
readText () {
return navigator.clipboard.readText()
Expand All @@ -27,7 +33,7 @@ class Clipboard {
/**
* Read the contents of the clipboard as a json object,
* will return undefined if unable to parse the data
* @returns { Promise.<Object | undefined> }
* @returns { Promise.<object?> }
*/
async readJson () {
try {
Expand Down
66 changes: 65 additions & 1 deletion api/browser/ui/contextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,38 @@

const DIController = require('../../../shared/DIController')

const MissingArgumentError = require('../../error/MissingArgumentError')
const InvalidArgumentError = require('../../error/InvalidArgumentError')

/**
* A threshold for how long the context menu has
* to have been open before an event can close it
*
* This it to prevent the same event to
* both open and close a context menu
*
* @type { Number }
* @type { number }
*/
const OPEN_THRESHOLD_MS = 100

/**
* @class UIContextMenu
*
* @typedef {{
* type: 'item' | 'divider',
* label: string?
* children: ContextMenuSpecItem[]?,
* onClick: Function.<void>?
* }} ContextMenuSpecItem
*
* @typedef {(ContextMenuSpecItem[])} ContextMenuSpec
*
* @typedef {{
* x: number,
* y: number,
* searchable: boolean?
* }} ContextMenuOpts
*/
class UIContextMenu {
#props
#openedAt
Expand All @@ -23,7 +44,19 @@ class UIContextMenu {
this.#props = props
}

/**
* Close any context menus
* that are currently open
*/
close () {
/*
Check that there is actually a
context menu that's currently open
*/
if (!this.#openedAt) {
return
}

/*
Check how long the context menu has been opened
to prevent it from closing on the same event that
Expand All @@ -32,10 +65,41 @@ class UIContextMenu {
if (Date.now() - this.#openedAt <= OPEN_THRESHOLD_MS) {
return
}

this.#openedAt = undefined
this.#props.Events.emitLocally('ui.contextMenu.close')
}

/**
* Open a context menu
* @param { ContextMenuSpec } spec
* @param { ContextMenuOpts } opts
*/
open (spec, opts) {
if (!spec) {
throw new MissingArgumentError('Missing required argument \'spec\'')
}

if (!Array.isArray(spec)) {
throw new InvalidArgumentError('Context menu spec must be an array')
}

if (!opts) {
throw new MissingArgumentError('Missing required argument \'opts\'')
}

if (typeof opts?.x !== 'number' || typeof opts?.y !== 'number') {
throw new InvalidArgumentError('Cannot open context menu without x and y position')
}

/*
Close any currently opened menu
before opening a new one
*/
if (this.#openedAt) {
this.close()
}

this.#openedAt = Date.now()
this.#props.Events.emitLocally('ui.contextMenu.open', spec, opts)
}
Expand Down
12 changes: 9 additions & 3 deletions app/components/Header/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import * as api from '../../api'

import './style.css'

const DEFAULT_TITLE = 'Unnamed'

function isMacOS () {
return window.APP.platform === 'darwin'
}
Expand All @@ -35,7 +37,7 @@ async function handleMaximize () {
bridge.commands.executeCommand('window.toggleMaximize')
}

export function Header ({ title = 'Bridge', features }) {
export function Header ({ title = DEFAULT_TITLE, features }) {
const [shared, applyShared] = React.useContext(SharedContext)
const [local] = React.useContext(LocalContext)

Expand Down Expand Up @@ -117,9 +119,13 @@ export function Header ({ title = 'Bridge', features }) {
<Preferences onClose={() => setPrefsOpen(false)} />
</Modal>
<Palette open={paletteIsOpen} onClose={() => handlePaletteClose()} />
<header className={`Header ${isMacOS() && isElectron() ? 'has-leftMargin' : ''}`} onDoubleClick={() => handleMaximize()}>
<div>
<header className={`Header ${isMacOS() && isElectron() ? 'hasLeftMargin' : ''}`} onDoubleClick={() => handleMaximize()}>
<div className='Header-title'>
{ featureShown('title') && title }
{
shared?._hasUnsavedChanges &&
<div className='Header-unsavedDot' />
}
</div>
<div className='Header-center'></div>
<div className='Header-block'>
Expand Down
17 changes: 16 additions & 1 deletion app/components/Header/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,25 @@ Add an extra margin when running
on macOS in order to leave space
for the traffic light
*/
.Header.has-leftMargin {
.Header.hasLeftMargin {
padding-left: 85px;
}

.Header-title {
display: flex;
align-items: center;
}

.Header-unsavedDot {
display: inline-block;
width: 6px;
height: 6px;
margin-left: 5px;

border-radius: 10px;
background: var(--base-color);
}

.Header-center {
position: absolute;
left: 50%;
Expand Down
42 changes: 17 additions & 25 deletions app/components/Notification/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react'
import { createPortal } from 'react-dom'

import './style.css'

Expand All @@ -21,29 +20,22 @@ export function Notification ({ type, size = 'normal', icon, title, description,
}

return (
<>
{
createPortal(
<div className={`Notification Notification--${type} Notification-size--${size}`}>
<div className='Notification-content'>
{
icon && <span className='Notification-contentSection Notification-icon'><Icon name={icon} color='var(--color-text)' /></span>
}
{
title && <span className='Notification-contentSection Notification-title'>{title}</span>
}
{
description && <span className='Notification-contentSection Notification-description'>{description}</span>
}
</div>
<div className='Notification-controls'>
{ controls }
{ closable && <button className='Notification-hideBtn Link' onClick={() => handleHideBtnClick()}>Dölj</button> }
</div>
</div>,
document.body
)
}
</>
<div className={`Notification Notification--${type} Notification-size--${size}`}>
<div className='Notification-content'>
{
icon && <span className='Notification-contentSection Notification-icon'><Icon name={icon} color='var(--color-text)' /></span>
}
{
title && <span className='Notification-contentSection Notification-title'>{title}</span>
}
{
description && <span className='Notification-contentSection Notification-description'>{description}</span>
}
</div>
<div className='Notification-controls'>
{ controls }
{ closable && <button className='Notification-hideBtn Link' onClick={() => handleHideBtnClick()}>Dölj</button> }
</div>
</div>
)
}
Loading