feat(xod-client): implement contextmenu in the Project Browser, add Add Library button to ProjectBrowser PanelToolbar, update tests to work properly with new contextmenu & replace title attributes in PatchGroupItems with data-id attributes

This commit is contained in:
Kirill Shumilov
2017-12-06 20:42:44 +03:00
parent 0a84d5aeb2
commit 40b29fd915
38 changed files with 571 additions and 379 deletions

View File

@@ -36,12 +36,12 @@ const height = 850;
// open a patch group in project browser
const libname = 'xod/core';
await page.click(`.patch-group-trigger[title="${libname}"]`);
await page.click(`.patch-group-trigger[data-id="${libname}"]`);
await page.waitFor(400); // wait for an animation to finish
const nodeToAdd = 'add';
// select a patch in project browser
await page.click(`.PatchGroupItem[title="${nodeToAdd}"] .PatchGroupItem__label`);
await page.click(`.PatchGroupItem[data-id="${nodeToAdd}"] .PatchGroupItem__label`);
await page.waitFor('.PatchGroupItem.isSelected');
// placing nodes
@@ -61,8 +61,12 @@ const height = 850;
}
for (let row = ROWS; row >= 0; row -= 1) {
// press 'add node' button
await page.click('.PatchGroupItem.isSelected .add-node');
// open contextmenu
await page.click('.PatchGroupItem.isSelected .contextmenu', { button: 'right' });
await page.waitFor(100); // wait for fade In of contextmenu
await page.waitForSelector('.ContextMenu--PatchGroupItem');
// press 'Place' button
await page.click('.ContextMenu--PatchGroupItem .react-contextmenu-item[data-id="place"]');
await page.waitForSelector('.Node.is-selected');
const nodeElementHandle = await page.$('.Node.is-selected');

View File

@@ -88,7 +88,7 @@ describe('IDE: Blink project', () => {
it('drags a node in place', () =>
assert.eventually.deepEqual(
ide.page.dragNode('clock', 150, 10).getLocation(),
{ x: 387, y: 81 }
{ x: 389, y: 81 }
)
);

View File

@@ -65,7 +65,7 @@ function closePopup(client) {
//-----------------------------------------------------------------------------
// Project browser
//-----------------------------------------------------------------------------
const getSelectorForPatchInProjectBrowser = nodeName => `.PatchGroupItem[title="${nodeName}"]`;
const getSelectorForPatchInProjectBrowser = nodeName => `.PatchGroupItem[data-id="${nodeName}"]`;
function findProjectBrowser(client) {
return client.element('.ProjectBrowser');
@@ -81,6 +81,12 @@ function findPatchGroup(client, groupTitle) {
.then(() => client.element(selector));
}
function findPatchGroupItem(client, name) {
const selector = getSelectorForPatchInProjectBrowser(name);
return findProjectBrowser(client).waitForExist(selector, 5000)
.then(() => client.element(selector));
}
function assertPatchGroupCollapsed(client, groupTitle) {
return assert.eventually.include(
findPatchGroup(client, groupTitle).getAttribute('class'),
@@ -114,26 +120,42 @@ function assertNodeUnavailableInProjectBrowser(client, nodeName) {
function scrollToPatchInProjectBrowser(client, name) {
return scrollTo(
client,
'.PatchTypeSelector .inner-container',
'.ProjectBrowser',
getSelectorForPatchInProjectBrowser(name)
);
}
function selectPatchInProjectBrowser(client, name) {
const patch = client.element(getSelectorForPatchInProjectBrowser(name));
const patch = findPatchGroupItem(client, name);
return hasClass('isSelected', patch).then(
selected => (
selected ? Promise.resolve() : client.click(getSelectorForPatchInProjectBrowser(name))
selected => (selected
? Promise.resolve()
: client.click(getSelectorForPatchInProjectBrowser(name))
)
);
}
function openProjectBrowserPatchContextMenu(client, name) {
const selector = getSelectorForPatchInProjectBrowser(name);
return selectPatchInProjectBrowser(client, name)
.then(() => client.waitForVisible(`${selector} .contextmenu`))
.then(() => findPatchGroupItem(client, name).rightClick('.contextmenu'));
}
function findProjectBrowserPatchContextMenu(client) {
return client.element('.ContextMenu--PatchGroupItem');
}
function openPatchFromProjectBrowser(client, name) {
return client.doubleClick(getSelectorForPatchInProjectBrowser(name));
}
function clickDeletePatchButton(client, name) {
return client.click(`${getSelectorForPatchInProjectBrowser(name)} span[title="Delete patch"]`);
return openProjectBrowserPatchContextMenu(client, name)
.then(
() => findProjectBrowserPatchContextMenu(client).click('.react-contextmenu-item[data-id="delete"]')
);
}
function assertPatchSelected(client, name) {
@@ -144,7 +166,10 @@ function assertPatchSelected(client, name) {
}
function clickAddNodeButton(client, name) {
return client.element(`${getSelectorForPatchInProjectBrowser(name)} .add-node`).click();
return openProjectBrowserPatchContextMenu(client, name)
.then(
() => findProjectBrowserPatchContextMenu(client).click('.react-contextmenu-item[data-id="place"]')
);
}
function expandPatchGroup(client, groupTitle) {
@@ -234,7 +259,6 @@ function addNode(client, type, dragX, dragY) {
function deletePatch(client, type) {
return client.waitForVisible(getSelectorForPatchInProjectBrowser(type))
.then(() => scrollToPatchInProjectBrowser(client, type))
.then(() => selectPatchInProjectBrowser(client, type))
.then(() => clickDeletePatchButton(client, type))
.then(() => findProjectBrowser(client).click('.PopupConfirm button.Button--primary'));
}
@@ -329,6 +353,7 @@ const API = {
findLink,
findNode,
findPatchGroup,
findPatchGroupItem,
findPin,
findPopup,
getCodeboxValue,
@@ -336,6 +361,7 @@ const API = {
scrollToPatchInProjectBrowser,
selectPatchInProjectBrowser,
openPatchFromProjectBrowser,
openProjectBrowserPatchContextMenu,
deletePatch,
expandPatchGroup,
// LibSuggester

View File

@@ -39,6 +39,7 @@
"react-click-outside": "^2.2.0",
"react-codemirror": "^1.0.0",
"react-collapsible": "^2.0.3",
"react-contextmenu": "^2.9.1",
"react-custom-scroll": "^2.0.0",
"react-dnd": "^2.5.1",
"react-dnd-html5-backend": "^2.5.1",

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="0%" y1="65%" x2="0%" y2="0%" id="grad">
<stop stop-color="#CCCCCC" offset="0%"></stop>
<stop stop-color="#CCCCCC" stop-opacity="0.3" offset="100%"></stop>
</linearGradient>
</defs>
<g stroke="none" fill="#CCCCCC" fill-rule="evenodd">
<g transform="translate(7.234928, 3.804138)">
<path d="M3.22194653,8.5042815 L3.37501987,9.075559 C3.43468126,9.29821833 3.66196692,9.43085249 3.88265771,9.37171857 L6.11372131,8.77390688 C6.33438445,8.71478037 6.46493017,8.48626558 6.40526878,8.26360626 L6.25219544,7.69232875 C5.30564341,7.94595661 4.51643548,8.15742423 3.22194653,8.5042815 Z"></path>
<path d="M4.77608366,2.03378173 L6.05751679,6.8161553 L3.02729552,7.62810064 L0.993183359,0.0366906991 L2.70318474,0.0366906991 L2.70318474,2.03378173 L4.77608366,2.03378173 Z" fill="url(#grad)"></path>
</g>
<g transform="translate(4.091679, 1.000000)">
<path d="M3.1621524,1.02243328 L3.1621524,0.436378977 C3.1621524,0.207931678 2.97691094,0.0226902189 2.74846364,0.0226902189 L0.438696641,0.0226902189 C0.210249343,0.0226902189 0.0250078832,0.207931678 0.0250078832,0.436378977 L0.0250078832,1.02243328 L3.1621524,1.02243328 Z"></path>
<path d="M0.0250364963,11 L0.0250364963,11.5860543 C0.0250364963,11.814473 0.210249343,11.9997145 0.438725254,11.9997145 L2.74849226,11.9997145 C2.97693955,11.9997145 3.16218101,11.814473 3.16218101,11.5860543 L3.16218101,11 C2.18223824,11 1.36519007,11 0.0250364963,11 Z"></path>
<polygon points="0.0250364963 1.5 3.1621524 1.5 3.1621524 10.5 0.0250364963 10.5"></polygon>
</g>
<g transform="translate(0.000000, 1.000000)">
<path d="M3.15142247,1.02243328 L3.15142247,0.436378977 C3.15142247,0.207931678 2.96620963,0.0226902189 2.73773372,0.0226902189 L0.427995327,0.0226902189 C0.199519416,0.0226902189 0.0143065693,0.207931678 0.0143065693,0.436378977 L0.0143065693,1.02243328 L3.15142247,1.02243328 Z"></path>
<path d="M0.0143065693,11 L0.0143065693,11.5860543 C0.0143065693,11.814473 0.199519416,11.9997145 0.427995327,11.9997145 L2.73773372,11.9997145 C2.96620963,11.9997145 3.15142247,11.814473 3.15142247,11.5860543 L3.15142247,11 C2.17153693,11 1.35448876,11 0.0143065693,11 Z"></path>
<polygon points="0.0143065693 1.5 3.15142247 1.5 3.15142247 10.5 0.0143065693 10.5"></polygon>
</g>
<polygon fill-rule="nonzero" points="10.9985241 5 10.9985241 3.01317902 9 3.01317902 9 1.98682098 10.9985241 1.98682098 10.9985241 0 11.9975593 0 11.9975593 1.98682098 14 1.98682098 14 3.01317902 11.9975593 3.01317902 11.9975593 5"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="12px" height="13px" viewBox="0 0 12 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" fill-rule="nonzero" fill="#CCCCCC">
<path d="M12,0.5 L0,0.5 L0,2 L12,2 L12,0.5 Z M12,4.5 L0,4.5 L0,6 L12,6 L12,4.5 Z M6.63512939e-16,10 L5,10 L5,8.5 L0,8.5 L6.63512939e-16,10 Z M6,8.5 L9.00078452,13 L12,8.5 L6,8.5 Z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 448 B

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="13px" viewBox="0 0 14 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" fill="none" fill-rule="evenodd">
<g transform="translate(11.234928, 6.804138) rotate(-15.000000) translate(-11.234928, -6.804138) translate(9.234928, 0.804138)" fill="#6965BD">
<path d="M3.1621524,1.02243328 L3.1621524,0.436378977 C3.1621524,0.207931678 2.97691094,0.0226902189 2.74846364,0.0226902189 L0.438696641,0.0226902189 C0.210249343,0.0226902189 0.0250078832,0.207931678 0.0250078832,0.436378977 L0.0250078832,1.02243328 L3.1621524,1.02243328 Z"></path>
<path d="M0.0250364963,11 L0.0250364963,11.5860543 C0.0250364963,11.814473 0.210249343,11.9997145 0.438725254,11.9997145 L2.74849226,11.9997145 C2.97693955,11.9997145 3.16218101,11.814473 3.16218101,11.5860543 L3.16218101,11 C2.18223824,11 1.36519007,11 0.0250364963,11 Z"></path>
<polygon points="0.0250364963 1.5 3.1621524 1.5 3.1621524 10.5 0.0250364963 10.5"></polygon>
</g>
<g transform="translate(4.091679, 1.000000)" fill="#2E8F6C">
<path d="M3.1621524,1.02243328 L3.1621524,0.436378977 C3.1621524,0.207931678 2.97691094,0.0226902189 2.74846364,0.0226902189 L0.438696641,0.0226902189 C0.210249343,0.0226902189 0.0250078832,0.207931678 0.0250078832,0.436378977 L0.0250078832,1.02243328 L3.1621524,1.02243328 Z"></path>
<path d="M0.0250364963,11 L0.0250364963,11.5860543 C0.0250364963,11.814473 0.210249343,11.9997145 0.438725254,11.9997145 L2.74849226,11.9997145 C2.97693955,11.9997145 3.16218101,11.814473 3.16218101,11.5860543 L3.16218101,11 C2.18223824,11 1.36519007,11 0.0250364963,11 Z"></path>
<polygon points="0.0250364963 1.5 3.1621524 1.5 3.1621524 10.5 0.0250364963 10.5"></polygon>
</g>
<g transform="translate(0.000000, 1.000000)" fill="#B99039">
<path d="M3.15142247,1.02243328 L3.15142247,0.436378977 C3.15142247,0.207931678 2.96620963,0.0226902189 2.73773372,0.0226902189 L0.427995327,0.0226902189 C0.199519416,0.0226902189 0.0143065693,0.207931678 0.0143065693,0.436378977 L0.0143065693,1.02243328 L3.15142247,1.02243328 Z"></path>
<path d="M0.0143065693,11 L0.0143065693,11.5860543 C0.0143065693,11.814473 0.199519416,11.9997145 0.427995327,11.9997145 L2.73773372,11.9997145 C2.96620963,11.9997145 3.15142247,11.814473 3.15142247,11.5860543 L3.15142247,11 C2.17153693,11 1.35448876,11 0.0143065693,11 Z"></path>
<polygon points="0.0143065693 1.5 3.15142247 1.5 3.15142247 10.5 0.0143065693 10.5"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="9px" height="12px" viewBox="0 0 9 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" fill-rule="nonzero" fill="#CCCCCC">
<path d="M4,4 L4,0 L0,0 L0,12 L8,12 L8.00999451,4 L4,4 Z M5.01281203,0.00772932331 L5.01281203,3.00772932 L8.01281203,3.00772932 L5.01281203,0.00772932331 Z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="11px" height="14px" viewBox="0 0 11 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" fill="#86CC23" fill-rule="evenodd">
<g transform="translate(0.000000, 1.909091)">
<path d="M5.432,0.159090909 C6.56727273,0.159090909 7.48681818,1.07927273 7.48681818,2.21454545 C7.48681818,3.34918182 6.56727273,4.26936364 5.432,4.26936364 C4.29672727,4.26936364 3.37654545,3.34918182 3.37654545,2.21454545 C3.37654545,1.07927273 4.29672727,0.159090909 5.432,0.159090909 Z" id="Shape"></path>
<path d="M10.7112727,11.8853636 L10.7112727,10.4363636 L7.62745455,3.24290909 C7.62745455,3.24290909 7.34745455,4.53472727 5.712,4.83063636 C7.46390909,7.56381818 10.7112727,11.8853636 10.7112727,11.8853636 Z" id="Shape"></path>
<path d="M0.106272727,11.9318182 L0.106272727,10.4840909 L3.18945455,3.28936364 C3.18945455,3.28936364 3.46945455,4.58181818 5.10490909,4.87772727 C3.353,7.61027273 0.106272727,11.9318182 0.106272727,11.9318182 Z" id="Shape"></path>
</g>
<g transform="translate(0.909091, 8.545455)" fill-rule="nonzero">
<path d="M7.87822867,0.923438142 C7.50410323,1.18949595 7.09820278,1.40829478 6.66962926,1.57415679 C6.03362099,1.82033432 5.35590536,1.94836364 4.661,1.94836364 C4.01340655,1.94836364 3.38143404,1.8376875 2.78591106,1.62394047 C2.31041663,1.45362267 1.86254208,1.22021084 1.45394051,0.930613746 L0.875695852,1.74647716 C1.35850858,2.08867155 1.88746937,2.36434183 2.44839443,2.56525998 C3.15165621,2.81767708 3.89789035,2.94836364 4.661,2.94836364 C5.48006451,2.94836364 6.28010443,2.79722566 7.0305757,2.50674334 C7.53681808,2.31082274 8.01612837,2.05245266 8.45777133,1.73838004 L7.87822867,0.923438142 Z" id="Shape"></path>
</g>
<g transform="translate(4.454545, 0.000000)">
<rect x="0" y="0" width="1.90909091" height="1.27272727"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -67,7 +67,7 @@ $selected: $cyan;
$error: $red;
$chrome-bg: $dark;
$chrome-title: $coal-bright;
$chrome-title-bg: $coal-bright;
$chrome-title-button-hover: $dark;
$chrome-outlines: $coal;

View File

@@ -0,0 +1,21 @@
@mixin icon($url) {
&:before {
content: '';
display: block;
line-height: 0;
width: 100%;
height: 100%;
background: url($url) no-repeat center center;
}
}
.icon-newpatch {
@include icon('../assets/icons/newpatch.svg');
}
.icon-addlib {
@include icon('../assets/icons/addlib.svg');
}
.icon-contextmenu {
@include icon('../assets/icons/contextmenu.svg');
}

View File

@@ -59,19 +59,14 @@ $color-tabs-debugger-text: #82b948;
$sidebar-width: 200px;
$sidebar-color-bg: #3d3d3d;
$sidebar-color-bg-even: #444444;
$sidebar-color-bg-hover: #5f5f5f;
$sidebar-color-bg-selected: #616077;
$sidebar-color-text: #cdcbc7;
$sidebar-color-text-hover: white;
$sidebar-color-border: #595959;
$sidebar-color-bg-tabs: #373737;
$sidebar-color-bg-tabs-selected: #cccccc;
$sidebar-color-text-selected-tab: #000;
$sidebar-color-bg: $chrome-bg;
$sidebar-color-bg-even: $chrome-bg;
$sidebar-color-bg-hover: $grey;
$sidebar-color-bg-selected: $blackberry;
$sidebar-color-bg-selected-hover: $blackberry-light;
$sidebar-color-text: $chalk;
$sidebar-color-text-hover: $chalk;
$sidebar-color-border: $chrome-outlines;
$input-color-text: #cccccc;
$input-color-bg: #373737;

View File

@@ -49,7 +49,7 @@
display: inline-block;
background: $sidebar-color-bg-even;
border: none;
color: $sidebar-color-bg-tabs-selected;
color: $chalk;
outline: none;
height: 24px;

View File

@@ -0,0 +1,84 @@
.ContextMenu {
&--hide {
display: none;
}
}
.react-contextmenu {
min-width: 140px;
padding: 0;
margin: 2px 0 0;
font-family: $font-family-normal;
font-size: $font-size-m;
color: $chalk;
text-align: left;
background-color: $chrome-bg;
background-clip: border-box;
border: 1px solid $chrome-outlines;
box-shadow: 5px 5px 10px -5px rgba(0,0,0,.5);
outline: none;
opacity: 0;
pointer-events: none;
transition: opacity 100ms ease !important;
user-select: none;
z-index: 1000;
&--visible {
opacity: 1;
pointer-events: auto;
}
&-item {
padding: 6px 12px;
line-height: 1;
color: $chalk;
text-align: inherit;
white-space: nowrap;
background: transparent;
border: 0;
cursor: pointer;
outline: 0;
.accelerator {
float: right;
color: $light-grey;
}
&--active, &--selected, &:active {
color: $chalk;
background-color: $blackberry;
text-decoration: none;
}
&--disabled, &--disabled:hover {
color: $light-grey;
background: transparent;
cursor: default;
}
&--divider, &--divider:hover {
background: transparent;
padding: 0;
margin: 3px 0;
height: 1px;
background-color: $coal-bright;
cursor: default;
}
}
&-sumbenu {
padding: 0;
}
&-submenu > &-item:after {
content: "";
font-size: $font-size-s;
display: inline-block;
position: absolute;
right: 6px;
}
}

View File

@@ -6,25 +6,29 @@
font-family: $font-family-normal;
text-decoration: none;
position: relative;
border: 1px solid $sidebar-color-border;
border-bottom: none;
padding: 10px 10px 10px 30px;
border-top: 1px solid $sidebar-color-border;
padding: 10px 10px 9px 30px;
color: $sidebar-color-text;
user-select: none;
line-height: 1em;
&.my:before,
&.library:before {
font-family: 'FontAwesome';
font-size: 13px;
content: '';
position: absolute;
left: 10px;
top: 10px;
left: 6px;
top: 8px;
display: block;
content: '\f007'; // TODO: bg image
width: 14px;
height: 14px;
background: url(../assets/icons/project.svg) no-repeat;
}
&.library:before {
content: '\f02d'; // TODO: bg image
width: 14px;
height: 14px;
background: url(../assets/icons/libs.svg) no-repeat;
}
}

View File

@@ -26,7 +26,7 @@
right: 0;
top: 0;
box-sizing: border-box;
padding: 7px 6px 8px 1px;
padding: 0 8px 0 8px;
font-size: 14px; // for FA icons
&:before {
@@ -39,6 +39,16 @@
left: -8px;
background: linear-gradient(to left, $sidebar-color-bg-hover, rgba(0, 0, 0, 0));
}
& > * {
margin-left: 2px;
}
.contextmenu {
width: 30px;
height: 30px;
@extend .icon-contextmenu;
}
}
&:nth-child(even) {
@@ -55,6 +65,17 @@
background: linear-gradient(to left, $sidebar-color-bg-selected, rgba(0, 0, 0, 0));
}
}
&:hover {
background-color: $sidebar-color-bg-selected-hover;
.PatchGroupItem__hover-buttons {
background-color: $sidebar-color-bg-selected-hover;
&:before {
background: linear-gradient(to left, $sidebar-color-bg-selected-hover, rgba(0, 0, 0, 0));
}
}
}
}
&:hover {

View File

@@ -1,36 +0,0 @@
.PatchTypeSelector {
height: calc(100% - 30px); // 30px is a height of .ProjectBrowserToolbar
.options {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
.option {
flex: 1;
margin: 0;
padding: 9px;
text-align: center;
cursor: pointer;
background-color: $sidebar-color-bg-tabs;
color: $sidebar-color-text;
font-family: $font-family-normal;
font-size: $font-size-s;
user-select: none;
border: 1px solid $sidebar-color-border;
border-left: 0;
white-space: nowrap;
&:first-child {
border-left: 1px solid $sidebar-color-border;
}
&.selected {
background-color: $sidebar-color-bg-tabs-selected;
color: $sidebar-color-text-selected-tab;
cursor: default;
}
}
}

View File

@@ -6,7 +6,7 @@
font-size: $font-size-l;
padding: 3px;
background-color: $sidebar-color-bg;
background-color: $chrome-title-bg;
button {
display: inline-block;
@@ -30,12 +30,26 @@
&:hover, &:focus {
color: $sidebar-color-text-hover;
}
&.addlib{
@extend .icon-addlib;
}
&.newpatch{
@extend .icon-newpatch;
}
&.contextmenu {
@extend .icon-contextmenu;
}
}
&-left {
&-title {
font-size: $font-size-m;
color: $light-grey-bright;
float: left;
line-height: 26px;
padding-left: 3px;
}
&-right {
&-buttons {
float: right;
}
}
}

View File

@@ -7,6 +7,7 @@
flex-wrap: nowrap;
background: $sidebar-color-bg;
border-right: 2px solid $chrome-outlines;
.title {
display: block;

View File

@@ -8,7 +8,7 @@
content: '';
display: block;
width: 100%;
height: 1px;
height: 2px;
background: $sidebar-color-border;
}
@@ -19,9 +19,9 @@
cursor: row-resize;
&:hover {
background: rgba($sidebar-color-border, 0.4);
background: $dark-light;
&:before {
background: lighten($sidebar-color-border, 10);
background: $sidebar-color-border;
}
}
}

View File

@@ -1,10 +1,6 @@
.Workarea-container {
flex-grow: 1;
}
.Workarea {
position: relative;
height: 100%;
display: flex;
flex-direction: column;
flex-grow: 1;
}

View File

@@ -1,6 +1,7 @@
@import
'abstracts/colors',
'abstracts/variables',
'abstracts/icons',
'abstracts/functions',
'abstracts/mixins';
@@ -21,6 +22,7 @@
'components/Button',
'components/CloseButton',
'components/Comment',
'components/ContextMenu',
'components/CppImplementationEditor',
'components/Debugger',
'components/Editor',
@@ -36,7 +38,6 @@
'components/PatchGroup',
'components/PatchGroupItem',
'components/PatchSVG',
'components/PatchTypeSelector',
'components/PatchWrapper',
'components/Pin',
'components/PinLabel',

View File

@@ -20,6 +20,8 @@ export const PASTE_ENTITIES = 'PASTE_ENTITIES';
export const TAB_CLOSE = 'TAB_CLOSE';
export const TAB_SORT = 'TAB_SORT';
export const SHOW_HELPBAR = 'SHOW_HELPBAR';
export const HIDE_HELPBAR = 'HIDE_HELPBAR';
export const TOGGLE_HELPBAR = 'TOGGLE_HELPBAR';
export const SHOW_SUGGESTER = 'SHOW_SUGGESTER';

View File

@@ -262,6 +262,12 @@ export const sortTabs = newOrderObject => ({
payload: newOrderObject,
});
export const hideHelpbar = () => ({
type: ActionType.HIDE_HELPBAR,
});
export const showHelpbar = () => ({
type: ActionType.SHOW_HELPBAR,
});
export const toggleHelpbar = () => ({
type: ActionType.TOGGLE_HELPBAR,
});

View File

@@ -58,7 +58,10 @@ class LibSuggester extends React.Component {
componentDidMount() {
if (this.input) {
// A hack to avoid typing any character into input when pressing Hotkey
setTimeout(() => this.input.focus(), 1);
setTimeout(() => {
this.input.focus();
this.props.onInitialFocus();
}, 1);
}
}
@@ -234,12 +237,14 @@ class LibSuggester extends React.Component {
LibSuggester.defaultProps = {
addClassName: '',
onBlur: () => {},
onInitialFocus: () => {},
};
LibSuggester.propTypes = {
addClassName: PropTypes.string,
onInstallLibrary: PropTypes.func.isRequired,
onBlur: PropTypes.func,
onInitialFocus: PropTypes.func,
};
export default LibSuggester;

View File

@@ -58,6 +58,7 @@ export const FOCUS_AREAS = {
INSPECTOR: 'INSPECTOR',
WORKAREA: 'WORKAREA',
NODE_SUGGESTER: 'NODE_SUGGESTER',
LIB_SUGGESTER: 'LIB_SUGGESTER',
};
export const CLIPBOARD_DATA_TYPE = 'text/xod-entities';

View File

@@ -32,7 +32,6 @@ import Inspector from '../components/Inspector';
import Debugger from '../../debugger/containers/Debugger';
import Breadcrumbs from '../../debugger/containers/Breadcrumbs';
import Sidebar from '../components/Sidebar';
import Workarea from '../../utils/components/Workarea';
import SnackBar from '../../messages/containers/SnackBar';
import { RenderableSelection } from '../../types';
@@ -55,6 +54,12 @@ class Editor extends React.Component {
this.showSuggester = this.showSuggester.bind(this);
this.hideSuggester = this.hideSuggester.bind(this);
this.onProjectBrowserFocus = this.onProjectBrowserFocus.bind(this);
this.onInspectorFocus = this.onInspectorFocus.bind(this);
this.onWorkareaFocus = this.onWorkareaFocus.bind(this);
this.onNodeSuggesterFocus = this.onNodeSuggesterFocus.bind(this);
this.onLibSuggesterFocus = this.onLibSuggesterFocus.bind(this);
this.patchSize = this.props.size;
this.updatePatchImplementationDebounced =
@@ -77,6 +82,22 @@ class Editor extends React.Component {
return this.props.actions.installLibraries([libParams]);
}
onProjectBrowserFocus() {
this.props.actions.setFocusedArea(FOCUS_AREAS.PROJECT_BROWSER);
}
onInspectorFocus() {
this.props.actions.setFocusedArea(FOCUS_AREAS.INSPECTOR);
}
onWorkareaFocus() {
this.props.actions.setFocusedArea(FOCUS_AREAS.WORKAREA);
}
onNodeSuggesterFocus() {
this.props.actions.setFocusedArea(FOCUS_AREAS.NODE_SUGGESTER);
}
onLibSuggesterFocus() {
this.props.actions.setFocusedArea(FOCUS_AREAS.LIB_SUGGESTER);
}
getHotkeyHandlers() {
return {
[COMMAND.UNDO]: () => this.props.actions.undo(this.props.currentPatchPath),
@@ -169,7 +190,7 @@ class Editor extends React.Component {
index={patchesIndex}
onAddNode={this.onAddNode}
onBlur={this.hideSuggester}
onInitialFocus={() => this.props.actions.setFocusedArea(FOCUS_AREAS.NODE_SUGGESTER)}
onInitialFocus={this.onNodeSuggesterFocus}
onHighlight={this.props.actions.highlightSugessterItem}
/>
) : null;
@@ -179,6 +200,7 @@ class Editor extends React.Component {
addClassName={(this.props.isHelpbarVisible) ? 'with-helpbar' : ''}
onInstallLibrary={this.onInstallLibrary}
onBlur={this.props.actions.hideLibSuggester}
onInitialFocus={this.onLibSuggesterFocus}
/>
) : null;
@@ -207,13 +229,13 @@ class Editor extends React.Component {
>
<FocusTrap
className="ProjectBrowser-container"
onFocus={() => this.props.actions.setFocusedArea(FOCUS_AREAS.PROJECT_BROWSER)}
onFocus={this.onProjectBrowserFocus}
>
<ProjectBrowser />
</FocusTrap>
<FocusTrap
className="Inspector-container"
onFocus={() => this.props.actions.setFocusedArea(FOCUS_AREAS.INSPECTOR)}
onFocus={this.onInspectorFocus}
>
<Inspector
selection={selection}
@@ -223,21 +245,19 @@ class Editor extends React.Component {
/>
</FocusTrap>
</Sidebar>
{suggester}
{libSuggester}
<FocusTrap
className="Workarea-container"
onFocus={() => this.props.actions.setFocusedArea(FOCUS_AREAS.WORKAREA)}
className="Workarea"
onFocus={this.onWorkareaFocus}
>
<Workarea>
<Tabs />
{DebugSessionStopButton}
{this.renderOpenedPatchTab()}
{suggester}
{libSuggester}
{BreadcrumbsContainer}
{this.renderOpenedImplementationEditorTabs()}
{DebuggerContainer}
<SnackBar />
</Workarea>
<Tabs />
{DebugSessionStopButton}
{this.renderOpenedPatchTab()}
{BreadcrumbsContainer}
{this.renderOpenedImplementationEditorTabs()}
{DebuggerContainer}
<SnackBar />
</FocusTrap>
<Helpbar />
<AccountPane />

View File

@@ -553,6 +553,10 @@ const editorReducer = (state = {}, action) => {
//
// helpbar
//
case EAT.HIDE_HELPBAR:
return R.over(R.lensProp('isHelpbarVisible'), R.F, state);
case EAT.SHOW_HELPBAR:
return R.over(R.lensProp('isHelpbarVisible'), R.T, state);
case EAT.TOGGLE_HELPBAR:
return R.over(R.lensProp('isHelpbarVisible'), R.not, state);

View File

@@ -1,14 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import Collapsible from 'react-collapsible';
import 'font-awesome/scss/font-awesome.scss';
import { noop } from '../../utils/ramda';
const PatchGroup = ({ name, children, type, onClose }) => (
<Collapsible
classParentString="PatchGroup"
trigger={<span className="patch-group-trigger" title={name}>{name}</span>}
trigger={
<span className="patch-group-trigger" data-id={name}>{name}</span>
}
triggerClassName={type}
triggerOpenedClassName={type}
transitionTime={100}

View File

@@ -5,8 +5,9 @@ import cn from 'classnames';
import { DragSource } from 'react-dnd';
import { getEmptyImage } from 'react-dnd-html5-backend';
import { Icon } from 'react-fa';
import 'font-awesome/scss/font-awesome.scss';
import { ContextMenuTrigger } from 'react-contextmenu';
import { PATCH_GROUP_CONTEXT_MENU_ID } from '../constants';
import { DRAGGED_ENTITY_TYPE } from '../../editor/constants';
const dragSource = {
@@ -63,6 +64,7 @@ class PatchGroupItem extends React.Component {
onClick,
onDoubleClick,
connectDragSource,
collectPropsFn,
...restProps
} = this.props;
@@ -76,24 +78,30 @@ class PatchGroupItem extends React.Component {
);
return connectDragSource(
<div
title={label}
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
role="button"
data-id={label}
className={classNames}
onClick={onClick}
onContextMenuCapture={onClick}
{...R.omit(['patchPath', 'onBeginDrag', 'connectDragPreview', 'dead'], restProps)}
>
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
className="PatchGroupItem__label"
onClick={onClick}
onDoubleClick={onDoubleClick}
role="button"
<ContextMenuTrigger
id={PATCH_GROUP_CONTEXT_MENU_ID}
holdToDisplay={-1}
collect={collectPropsFn}
>
{(dead) ? deadIcon : null}
{label}
</div>
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
className="PatchGroupItem__label"
onDoubleClick={onDoubleClick}
role="button"
>
{(dead) ? deadIcon : null}
{label}
</div>
</ContextMenuTrigger>
<div className="PatchGroupItem__hover-buttons">
{hoverButtons}
{/* placeholder for context menu opener */}
{/* <i className="fa fa-bars" /> */}
</div>
</div>
);
@@ -113,6 +121,7 @@ PatchGroupItem.propTypes = {
connectDragSource: PropTypes.func.isRequired,
connectDragPreview: PropTypes.func.isRequired,
onBeginDrag: PropTypes.func.isRequired,
collectPropsFn: PropTypes.func.isRequired,
};
export default DragSource( // eslint-disable-line new-cap

View File

@@ -0,0 +1,96 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { ContextMenu, MenuItem, connectMenu } from 'react-contextmenu';
import { PATCH_GROUP_CONTEXT_MENU_ID } from '../constants';
const callCallbackWithPatchPath = onClick => (event, data) => onClick(data.patchPath);
const PatchGroupItemContextMenu = (props) => {
const trigger = (props.trigger) ? props.trigger : {};
const renamePatch = (trigger.isLocalPatch)
? (
<MenuItem
onClick={callCallbackWithPatchPath(props.onPatchRename)}
// data-id special attribute that used by func tests
attributes={{ 'data-id': 'rename' }}
>
Rename
</MenuItem>
) : null;
const deletePatch = (trigger.isLocalPatch)
? (
<MenuItem
onClick={callCallbackWithPatchPath(props.onPatchDelete)}
attributes={{ 'data-id': 'delete' }}
>
Delete
</MenuItem>
) : null;
const cls = cn('ContextMenu ContextMenu--PatchGroupItem', {
// It's a hack to prevent rendering contextmenu
// after click something with wrong menu items
'ContextMenu--hide': !props.trigger,
});
return (
<ContextMenu
id={PATCH_GROUP_CONTEXT_MENU_ID}
className={cls}
>
<MenuItem
onClick={callCallbackWithPatchPath(props.onPatchAdd)}
disabled={!trigger.canAdd}
attributes={{ 'data-id': 'place' }}
>
<span className="accelerator">
drag&drop
</span>
Place
</MenuItem>
<MenuItem divider />
<MenuItem
onClick={callCallbackWithPatchPath(props.onPatchOpen)}
attributes={{ 'data-id': 'open' }}
>
<span className="accelerator">
click&times;2
</span>
Open
</MenuItem>
{renamePatch}
{deletePatch}
<MenuItem divider />
<MenuItem
onClick={callCallbackWithPatchPath(props.onPatchHelp)}
attributes={{ 'data-id': 'help' }}
>
<span className="accelerator">
h
</span>
Help
</MenuItem>
</ContextMenu>
);
};
PatchGroupItemContextMenu.propTypes = {
trigger: PropTypes.shape({
/* eslint-disable react/no-unused-prop-types */
patchPath: PropTypes.string.isRequired,
canAdd: PropTypes.bool.isRequired,
isLocalPatch: PropTypes.bool.isRequired,
/* eslint-enable react/no-unused-prop-types */
}),
onPatchAdd: PropTypes.func.isRequired,
onPatchOpen: PropTypes.func.isRequired,
onPatchDelete: PropTypes.func.isRequired,
onPatchRename: PropTypes.func.isRequired,
onPatchHelp: PropTypes.func.isRequired,
};
export default connectMenu(PATCH_GROUP_CONTEXT_MENU_ID)(PatchGroupItemContextMenu);

View File

@@ -1,75 +0,0 @@
import R from 'ramda';
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { noop } from '../../utils/ramda';
/* eslint-disable jsx-a11y/no-static-element-interactions */
class PatchTypeSelector extends React.PureComponent {
constructor(props) {
super(props);
// TODO: enforse non-empty props.options?
// TODO: check that initialSelectedKey is equal to one of option's keys?
const selectedOptionKey = R.ifElse(
R.propSatisfies(R.isNil, 'initialSelectedKey'),
R.path(['options', 0, 'key']),
R.prop('initialSelectedKey')
)(props);
this.state = { selectedOptionKey };
}
onSelect(selectedOptionKey) {
if (selectedOptionKey === this.state.selectedOptionKey) {
return;
}
this.setState({ selectedOptionKey });
this.props.onChange(selectedOptionKey);
}
render() {
const { options, children } = this.props;
const { selectedOptionKey } = this.state;
return (
<div className="PatchTypeSelector">
<ul className="options">
{options.map(({ key, name }) =>
<li
key={key}
className={cn('option', { selected: key === selectedOptionKey })}
onClick={() => this.onSelect(key)}
>
{name}
</li>
)}
</ul>
{children(selectedOptionKey)}
</div>
);
}
}
PatchTypeSelector.displayName = 'PatchTypeSelector';
PatchTypeSelector.propTypes = {
options: PropTypes.arrayOf(
PropTypes.shape({
key: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
name: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
})
).isRequired,
onChange: PropTypes.func.isRequired,
children: PropTypes.func.isRequired,
};
PatchTypeSelector.defaultProps = {
onChange: noop,
};
export default PatchTypeSelector;

View File

@@ -1,16 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Icon } from 'react-fa';
const ProjectBrowserToolbar = ({ onClickAddPatch }) => (
const ProjectBrowserToolbar = ({ onClickAddPatch, onClickAddLibrary }) => (
<div className="ProjectBrowserToolbar">
<div className="ProjectBrowserToolbar-left">
<div className="ProjectBrowserToolbar-title">
Project Browser
</div>
<div className="ProjectBrowserToolbar-buttons">
<button
className="newpatch"
title="Add patch"
onClick={onClickAddPatch}
>
<Icon name="file" />
</button>
/>
<button
className="addlib"
title="Add library"
onClick={onClickAddLibrary}
/>
</div>
</div>
);
@@ -19,6 +25,7 @@ ProjectBrowserToolbar.displayName = 'ProjectBrowserToolbar';
ProjectBrowserToolbar.propTypes = {
onClickAddPatch: PropTypes.func.isRequired,
onClickAddLibrary: PropTypes.func.isRequired,
};
export default ProjectBrowserToolbar;

View File

@@ -0,0 +1,3 @@
// Id of context menu to show it
// eslint-disable-next-line import/prefer-default-export
export const PATCH_GROUP_CONTEXT_MENU_ID = 'PATCH_GROUP_CONTEXT_MENU_ID';

View File

@@ -1,12 +1,12 @@
import R from 'ramda';
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import CustomScroll from 'react-custom-scroll';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Icon } from 'react-fa';
import { HotKeys } from 'react-hotkeys';
import { ContextMenuTrigger } from 'react-contextmenu';
import $ from 'sanctuary-def';
import {
@@ -28,23 +28,15 @@ import * as PopupSelectors from '../../popups/selectors';
import * as EditorSelectors from '../../editor/selectors';
import { COMMAND } from '../../utils/constants';
import { noop } from '../../utils/ramda';
import sanctuaryPropType from '../../utils/sanctuaryPropType';
import PatchGroup from '../components/PatchGroup';
import PatchGroupItem from '../components/PatchGroupItem';
import PatchTypeSelector from '../components/PatchTypeSelector';
import ProjectBrowserPopups from '../components/ProjectBrowserPopups';
import ProjectBrowserToolbar from '../components/ProjectBrowserToolbar';
import PatchGroupItemContextMenu from '../components/PatchGroupItemContextMenu';
import { getUtmSiteUrl } from '../../utils/urls';
import { IconGuide } from '../../utils/components/IconGuide';
const PATCH_TYPE = {
ALL: 'all',
MY: 'my',
LIBRARY: 'library',
};
import { PATCH_GROUP_CONTEXT_MENU_ID } from '../constants';
const pickPatchPartsForComparsion = R.map(R.pick(['dead', 'path']));
@@ -69,17 +61,21 @@ const pickPropsForComparsion = R.compose(
class ProjectBrowser extends React.Component {
constructor(props) {
super(props);
this.patchRenderers = {
[PATCH_TYPE.MY]: this.renderLocalPatches.bind(this),
[PATCH_TYPE.LIBRARY]: this.renderLibraryPatches.bind(this),
};
this.state = {};
this.renderPatches = this.renderPatches.bind(this);
this.deselectIfInLibrary = this.deselectIfInLibrary.bind(this);
this.deselectIfInLocalPatches = this.deselectIfInLocalPatches.bind(this);
this.onAddNode = this.onAddNode.bind(this);
this.onRenameHotkey = this.onRenameHotkey.bind(this);
this.onDeleteHotkey = this.onDeleteHotkey.bind(this);
this.onClickAddLibrary = this.onClickAddLibrary.bind(this);
this.onPatchHelpClicked = this.onPatchHelpClicked.bind(this);
this.renderItem = this.renderItem.bind(this);
this.renderLocalPatches = this.renderLocalPatches.bind(this);
this.renderLibraryPatches = this.renderLibraryPatches.bind(this);
}
shouldComponentUpdate(nextProps) {
@@ -112,6 +108,15 @@ class ProjectBrowser extends React.Component {
this.props.actions.requestDelete(selectedPatchPath);
}
onClickAddLibrary(event) {
event.stopPropagation();
this.props.actions.showLibSuggester();
}
onPatchHelpClicked() {
this.props.actions.showHelpbar();
}
getHotkeyHandlers() {
return {
[COMMAND.ADD_PATCH]: this.props.actions.requestCreatePatch,
@@ -121,36 +126,14 @@ class ProjectBrowser extends React.Component {
};
}
localPatchesHoveredButtons(patchPath) {
const {
requestRename,
requestDelete,
} = this.props.actions;
return [
<Icon
key="delete"
name="trash"
title="Delete patch"
className="hover-button"
onClick={() => requestDelete(patchPath)}
/>,
<Icon
key="rename"
name="pencil"
title="Rename patch"
className="hover-button"
onClick={() => requestRename(patchPath)}
/>,
this.renderAddNodeButton(patchPath),
];
}
libraryPatchesHoveredButtons(path) {
return [
this.renderDocsButton(path),
this.renderAddNodeButton(path),
];
getCollectPropsFn(patchPath) {
const { currentPatchPath } = this.props;
const canAdd = currentPatchPath !== patchPath;
return () => ({
patchPath,
canAdd,
isLocalPatch: isPathLocal(patchPath),
});
}
deselectIfInLocalPatches() {
@@ -167,50 +150,27 @@ class ProjectBrowser extends React.Component {
};
}
renderAddNodeButton(patchPath) {
const { currentPatchPath } = this.props;
const isCurrentPatch = currentPatchPath === patchPath;
const canAdd = !isCurrentPatch;
const classNames = cn('hover-button add-node', { disabled: !canAdd });
const action = canAdd ? () => this.onAddNode(patchPath) : noop;
return (
<Icon
key="add"
name="plus-circle"
title="Add node"
className={classNames}
onClick={action}
/>
);
}
renderDocsButton(patchPath) { // eslint-disable-line class-methods-use-this
return (
<a
href={getUtmSiteUrl(`/libs/${patchPath}`, 'docs', 'project-browser')}
target="_blank"
rel="noopener noreferrer"
className="hover-button"
key="patch-guide-button"
title="Open documentation in web browser"
// eslint-disable-next-line class-methods-use-this
renderHoveredButtons(collectPropsFn) {
return [
<ContextMenuTrigger
key="contextMenuTrigger"
id={PATCH_GROUP_CONTEXT_MENU_ID}
renderTag="div"
attributes={{
className: 'contextmenu',
}}
collect={collectPropsFn}
holdToDisplay={0}
>
<IconGuide
className="project-browser--guide-button"
width="14px"
height="14px"
fill="#FFF"
/>
</a>
);
{/* Component needs at least one child :-( */}
<span />
</ContextMenuTrigger>,
];
}
renderLocalPatches() {
renderItem({ path, dead }) {
const {
projectName,
localPatches,
currentPatchPath,
selectedPatchPath,
} = this.props;
@@ -221,7 +181,9 @@ class ProjectBrowser extends React.Component {
startDraggingPatch,
} = this.props.actions;
const renderItem = ({ path, dead }) => (
const collectPropsFn = this.getCollectPropsFn(path);
return (
<PatchGroupItem
key={path}
patchPath={path}
@@ -232,9 +194,17 @@ class ProjectBrowser extends React.Component {
onBeginDrag={startDraggingPatch}
isSelected={path === selectedPatchPath}
onClick={() => setSelection(path)}
hoverButtons={this.localPatchesHoveredButtons(path)}
collectPropsFn={collectPropsFn}
hoverButtons={this.renderHoveredButtons(collectPropsFn)}
/>
);
}
renderLocalPatches() {
const {
projectName,
localPatches,
} = this.props;
return (
<PatchGroup
@@ -243,18 +213,18 @@ class ProjectBrowser extends React.Component {
name={projectName}
onClose={this.deselectIfInLocalPatches}
>
{R.map(renderItem, localPatches)}
{localPatches.map(this.renderItem)}
</PatchGroup>
);
}
renderLibraryPatches() {
const { libs, installingLibs, selectedPatchPath } = this.props;
const { setSelection, switchPatch, startDraggingPatch } = this.props.actions;
const { libs, installingLibs } = this.props;
const installingLibsComponents = R.map(
({ owner, name, version }) => ({
name: `${owner}/${name}`,
component: (<div
key={`${owner}/${name}/${version}`}
className="PatchGroup PatchGroup--installing library"
>
<span className="name">{owner}/{name}</span>
@@ -279,19 +249,7 @@ class ProjectBrowser extends React.Component {
name={libName}
onClose={this.deselectIfInLibrary(libName)}
>
{libPatches.map(({ path, dead }) =>
<PatchGroupItem
key={path}
patchPath={path}
dead={dead}
label={getBaseName(path)}
isSelected={path === selectedPatchPath}
onClick={() => setSelection(path)}
onDoubleClick={() => switchPatch(path)}
onBeginDrag={startDraggingPatch}
hoverButtons={this.libraryPatchesHoveredButtons(path)}
/>
)}
{libPatches.map(this.renderItem)}
</PatchGroup>),
})),
R.toPairs
@@ -304,16 +262,13 @@ class ProjectBrowser extends React.Component {
)(libComponents, installingLibsComponents);
}
renderPatches(patchType) {
const rendererKeys = patchType === PATCH_TYPE.ALL
? [PATCH_TYPE.MY, PATCH_TYPE.LIBRARY]
: R.of(patchType);
renderPatches() {
return (
// "calc(100% - 30px)" cause patch filtering buttons are 30px height
<CustomScroll heightRelativeToParent="calc(100% - 30px)">
<div className="patches-list">
{rendererKeys.map(k => this.patchRenderers[k]())}
{this.renderLocalPatches()}
{this.renderLibraryPatches()}
</div>
</CustomScroll>
);
@@ -338,17 +293,17 @@ class ProjectBrowser extends React.Component {
/>
<ProjectBrowserToolbar
onClickAddPatch={this.props.actions.requestCreatePatch}
onClickAddLibrary={this.onClickAddLibrary}
/>
{this.renderPatches()}
<PatchGroupItemContextMenu
ref={(c) => { this.patchContextMenuRef = c; }}
onPatchAdd={this.onAddNode}
onPatchOpen={this.props.actions.switchPatch}
onPatchDelete={this.props.actions.requestDelete}
onPatchRename={this.props.actions.requestRename}
onPatchHelp={this.onPatchHelpClicked}
/>
<PatchTypeSelector
options={[
{ key: PATCH_TYPE.ALL, name: 'All' },
{ key: PATCH_TYPE.LIBRARY, name: 'Libraries' },
{ key: PATCH_TYPE.MY, name: 'My Patches' },
]}
onChange={this.props.actions.removeSelection}
>
{this.renderPatches}
</PatchTypeSelector>
</HotKeys>
);
}
@@ -380,6 +335,8 @@ ProjectBrowser.propTypes = {
startDraggingPatch: PropTypes.func.isRequired,
renameProject: PropTypes.func.isRequired,
closeAllPopups: PropTypes.func.isRequired,
showLibSuggester: PropTypes.func.isRequired,
showHelpbar: PropTypes.func.isRequired,
}),
};
@@ -416,6 +373,8 @@ const mapDispatchToProps = dispatch => ({
closeAllPopups: PopupActions.hideAllPopups,
addNotification: MessagesActions.addNotification,
showLibSuggester: EditorActions.showLibSuggester,
showHelpbar: EditorActions.showHelpbar,
}, dispatch),
});

View File

@@ -1,14 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const Workarea = ({ children }) => (
<div className="Workarea">
{children}
</div>
);
Workarea.propTypes = {
children: PropTypes.arrayOf(PropTypes.element),
};
export default Workarea;

View File

@@ -1,45 +0,0 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import '../src/core/styles/main.scss';
import PatchTypeSelector from '../src/projectBrowser/components/PatchTypeSelector';
const selectedOptionsRenderer = selectedKey => `selected ${selectedKey}`;
storiesOf('PatchTypeSelector', module)
.addDecorator(story => (
<div style={{ width: 300 }}>
<p>inside a 300px wide div:</p>
{story()}
</div>
))
.add('default', () => (
<PatchTypeSelector
options={[
{ key: 'foo', name: 'Foo' },
{ key: 'bar', name: 'Bar' },
]}
>
{selectedOptionsRenderer}
</PatchTypeSelector>
))
.add('with initial selection', () => (
<PatchTypeSelector
options={[
{ key: 'foo', name: 'Foo' },
{ key: 'bar', name: 'Bar' },
{ key: 'baz', name: 'Baz' },
]}
initialSelectedKey="bar"
>
{selectedOptionsRenderer}
</PatchTypeSelector>
))
.add('with no options', () => (
<PatchTypeSelector
options={[]}
>
{selectedOptionsRenderer}
</PatchTypeSelector>
));

View File

@@ -8842,6 +8842,13 @@ react-collapsible@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/react-collapsible/-/react-collapsible-2.0.3.tgz#b7228b002428a6e0a9f41646ea61ba28aacfcb00"
react-contextmenu@^2.9.1:
version "2.9.1"
resolved "https://registry.yarnpkg.com/react-contextmenu/-/react-contextmenu-2.9.1.tgz#391c7001f73e49772e37e98d154736d50dac11de"
dependencies:
classnames "^2.2.5"
object-assign "^4.1.0"
react-custom-scroll@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/react-custom-scroll/-/react-custom-scroll-2.0.1.tgz#4775761a48d66c4b1e576c0705fc6a59fc0d89b6"