Skip to content

Commit 9a353ff

Browse files
authored
Merge pull request #19 from ian-r-rose/add_mybinder_badge
Add mybinder badge
2 parents dbee1d2 + 98c60b3 commit 9a353ff

File tree

4 files changed

+215
-27
lines changed

4 files changed

+215
-27
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
"@jupyterlab/docregistry": "^0.12.0",
3939
"@jupyterlab/filebrowser": "^0.12.0",
4040
"@jupyterlab/services": "^0.51.0",
41-
"@phosphor/widgets": "^1.3.0"
41+
"@phosphor/algorithm": "^1.1.2",
42+
"@phosphor/widgets": "^1.5.0"
4243
},
4344
"devDependencies": {
4445
"rimraf": "^2.5.2",

src/browser.ts

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
// Copyright (c) Jupyter Development Team.
22
// Distributed under the terms of the Modified BSD License.
33

4+
import {
5+
find
6+
} from '@phosphor/algorithm';
7+
48
import {
59
PanelLayout, Widget
610
} from '@phosphor/widgets';
711

812
import {
9-
ObservableValue
13+
ToolbarButton
14+
} from '@jupyterlab/apputils';
15+
16+
import {
17+
ObservableValue, URLExt
1018
} from '@jupyterlab/coreutils';
1119

1220
import {
@@ -17,6 +25,22 @@ import {
1725
GitHubDrive
1826
} from './contents';
1927

28+
29+
/**
30+
* The base url for a mybinder deployment.
31+
*/
32+
const MY_BINDER_BASE_URL = 'https://mybinder.org/v2/gh';
33+
34+
/**
35+
* The GitHub base url.
36+
*/
37+
const GITHUB_BASE_URL = 'https://github.com';
38+
39+
/**
40+
* The className for disabling the mybinder button.
41+
*/
42+
const MY_BINDER_DISABLED = 'jp-MyBinderButton-disabled';
43+
2044
/**
2145
* Widget for hosting the GitHub filebrowser.
2246
*/
@@ -30,17 +54,60 @@ class GitHubFileBrowser extends Widget {
3054
this._browser = browser;
3155
this._drive = drive;
3256

33-
const userLabel = new Widget();
34-
userLabel.addClass('jp-GitHubUserLabel');
35-
userLabel.node.textContent = 'User:';
36-
this._browser.toolbar.addItem('label', userLabel);
37-
57+
// Create an editable name for the user/org name.
3858
this.userName = new GitHubEditableName(drive.user, '<Edit User>');
3959
this.userName.addClass('jp-GitHubEditableUserName');
40-
this.userName.node.title = 'User';
60+
this.userName.node.title = 'Click to edit user/organization';
4161
this._browser.toolbar.addItem('user', this.userName);
4262
this.userName.name.changed.connect(this._onUserChanged, this);
4363

64+
// Create a button that opens GitHub at the appropriate
65+
// repo+directory.
66+
this._openGitHubButton = new ToolbarButton({
67+
onClick: () => {
68+
let url = GITHUB_BASE_URL;
69+
// If there is no valid user, do nothing.
70+
if (!this._drive.validUserState.get()) {
71+
window.open(url);
72+
return;
73+
}
74+
const user = this._drive.user;
75+
const path = this._browser.model.path;
76+
const repo = path.split('/')[0].split(':')[1];
77+
url = URLExt.join(url, user);
78+
if (repo) {
79+
const dirPath = URLExt.join(repo, ...path.split('/').slice(1));
80+
url = URLExt.join(url, repo, 'tree', 'master', dirPath);
81+
}
82+
window.open(url);
83+
},
84+
className: 'jp-GitHubIcon',
85+
tooltip: 'Open this repository on GitHub'
86+
});
87+
this._browser.toolbar.addItem('GitHub', this._openGitHubButton);
88+
89+
// Create a button the opens MyBinder to the appropriate repo.
90+
this._launchBinderButton = new ToolbarButton({
91+
onClick: () => {
92+
// If binder is not active for this directory, do nothing.
93+
if (!this._binderActive) {
94+
return;
95+
}
96+
const user = this._drive.user;
97+
const repo = this._browser.model.path.split('/')[0].split(':')[1];
98+
const url = URLExt.join(MY_BINDER_BASE_URL, user, repo, 'master');
99+
window.open(url+'?urlpath=lab');
100+
},
101+
tooltip: 'Launch this repository on mybinder.org',
102+
className: 'jp-MyBinderButton'
103+
});
104+
this._browser.toolbar.addItem('binder', this._launchBinderButton);
105+
106+
// Set up a listener to check if we can launch mybinder.
107+
this._browser.model.pathChanged.connect(this._onPathChanged, this);
108+
// Trigger an initial pathChanged to check for binder state.
109+
this._onPathChanged();
110+
44111
this._drive.rateLimitedState.changed.connect(this._updateErrorPanel, this);
45112
this._drive.validUserState.changed.connect(this._updateErrorPanel, this);
46113
}
@@ -70,6 +137,40 @@ class GitHubFileBrowser extends Widget {
70137
});
71138
}
72139

140+
/**
141+
* React to the path changing for the browser.
142+
*/
143+
private _onPathChanged(): void {
144+
const path = this._browser.model.path;
145+
// Check for a valid user.
146+
if(!this._drive.validUserState.get()) {
147+
this._launchBinderButton.addClass(MY_BINDER_DISABLED);
148+
this._binderActive = false;
149+
return;
150+
}
151+
// Check for a valid repo.
152+
const repo = path.split('/')[0].split(':')[1];
153+
if (!repo) {
154+
this._launchBinderButton.addClass(MY_BINDER_DISABLED);
155+
this._binderActive = false;
156+
return;
157+
}
158+
// Check for one of the special values indicating we can
159+
// launch the repository.
160+
const item = find(this._browser.model.items(), i => {
161+
return i.name === 'requirements.txt' || i.name === 'environment.yml' ||
162+
i.name === 'apt.txt' || i.name === 'REQUIRE' ||
163+
i.name === 'Dockerfile';
164+
});
165+
if (item) {
166+
this._launchBinderButton.removeClass(MY_BINDER_DISABLED);
167+
this._binderActive = true;
168+
return;
169+
}
170+
this._launchBinderButton.addClass(MY_BINDER_DISABLED);
171+
this._binderActive = false;
172+
}
173+
73174
/**
74175
* React to a change in the validity of the drive.
75176
*/
@@ -108,6 +209,9 @@ class GitHubFileBrowser extends Widget {
108209
private _browser: FileBrowser;
109210
private _drive: GitHubDrive;
110211
private _errorPanel: GitHubErrorPanel | null;
212+
private _openGitHubButton: ToolbarButton;
213+
private _launchBinderButton: ToolbarButton;
214+
private _binderActive = false;
111215
}
112216

113217
/**
@@ -121,8 +225,9 @@ class GitHubEditableName extends Widget {
121225
super();
122226
this.addClass('jp-GitHubEditableName');
123227
this._nameNode = document.createElement('div');
228+
this._nameNode.className = 'jp-GitHubEditableName-display';
124229
this._editNode = document.createElement('input');
125-
this._editNode.className = 'jp-GitHubEditableNameInput';
230+
this._editNode.className = 'jp-GitHubEditableName-input';
126231

127232
this._placeholder = placeholder || '<Edit Name>'
128233

@@ -137,7 +242,9 @@ class GitHubEditableName extends Widget {
137242
this._pending = true;
138243
Private.changeField(this._nameNode, this._editNode).then(value => {
139244
this._pending = false;
140-
this.name.set(value);
245+
if (this.name.get() !== value) {
246+
this.name.set(value);
247+
}
141248
});
142249
};
143250

@@ -182,8 +289,6 @@ class GitHubErrorPanel extends Widget {
182289
}
183290

184291

185-
186-
187292
/**
188293
* A module-Private namespace.
189294
*/
@@ -202,13 +307,10 @@ namespace Private {
202307
* or has been canceled.
203308
*/
204309
function changeField(text: HTMLElement, edit: HTMLInputElement): Promise<string> {
205-
// Replace the text node with an the input element,
206-
// setting the value and width of the input element to
207-
// the same as the text node.
310+
// Replace the text node with an the input element.
208311
let parent = text.parentElement as HTMLElement;
209312
let initialValue = text.textContent;
210313
edit.value = initialValue;
211-
parent.style.width = String(parent.offsetWidth+3)+'px';
212314
parent.replaceChild(edit, text);
213315
edit.focus();
214316

@@ -222,10 +324,8 @@ namespace Private {
222324

223325
return new Promise<string>((resolve, reject) => {
224326
edit.onblur = () => {
225-
// Restore the correct width and set the
226-
// text content of the original node, then
327+
// Set the text content of the original node, then
227328
// replace the node.
228-
parent.style.width = '';
229329
parent.replaceChild(text, edit);
230330
text.textContent = edit.value || initialValue;
231331
resolve(edit.value);

style/binder.svg

Lines changed: 68 additions & 0 deletions
Loading

style/index.css

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,25 @@
2020
height: 100%;
2121
}
2222

23-
.jp-GitHubBrowser .jp-GitHubUserLabel {
24-
width: auto;
25-
text-align: left;
26-
font-size: large;
27-
}
28-
2923
.jp-GitHubBrowser .jp-GitHubEditableName {
30-
width: auto;
3124
overflow: hidden;
3225
white-space: nowrap;
3326
text-align: center;
3427
font-size: large;
28+
padding: 0px;
29+
flex: 8 8;
30+
}
31+
32+
.jp-GitHubBrowser .jp-GitHubEditableName-display {
33+
border: 1px solid transparent;
34+
}
35+
36+
.jp-GitHubBrowser .jp-GitHubEditableName-display:hover {
37+
border: 1px solid var(--jp-border-color1);
38+
box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.24);
3539
}
3640

37-
.jp-GitHubBrowser .jp-GitHubEditableNameInput {
41+
.jp-GitHubBrowser .jp-GitHubEditableName-input {
3842
background-color: var(--jp-layout-color2);
3943
color: var(--jp-ui-font-color1);
4044
font-size: large;
@@ -93,3 +97,18 @@
9397
text-align: center;
9498
padding: 12px;
9599
}
100+
101+
.jp-GitHubBrowser .jp-MyBinderButton {
102+
flex: 1 1;
103+
background-image: url(binder.svg);
104+
}
105+
106+
.jp-GitHubBrowser .jp-MyBinderButton-disabled {
107+
opacity: 0.3;
108+
}
109+
110+
111+
.jp-GitHubBrowser .jp-GitHubIcon {
112+
flex: 1 1;
113+
background-image: url(octocat.png);
114+
}

0 commit comments

Comments
 (0)