Skip to content

Commit edeb443

Browse files
committed
Functional pin repository
1 parent eac6a5d commit edeb443

File tree

12 files changed

+428
-148
lines changed

12 files changed

+428
-148
lines changed

LICENSE

+26
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,29 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
2727
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2828
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2929
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+
---
32+
33+
This software makes use of octicons (https://github.com/primer/octicons) licensed under
34+
35+
MIT License
36+
37+
Copyright (c) 2019 GitHub Inc.
38+
39+
Permission is hereby granted, free of charge, to any person obtaining a copy
40+
of this software and associated documentation files (the "Software"), to deal
41+
in the Software without restriction, including without limitation the rights
42+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
43+
copies of the Software, and to permit persons to whom the Software is
44+
furnished to do so, subject to the following conditions:
45+
46+
The above copyright notice and this permission notice shall be included in all
47+
copies or substantial portions of the Software.
48+
49+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
50+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
51+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
52+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
53+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
54+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
55+
SOFTWARE.

src/components/GitPanel.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { BranchHeader } from './BranchHeader';
1313
import { FileList } from './FileList';
1414
import { HistorySideBar } from './HistorySideBar';
1515
import { PathHeader } from './PathHeader';
16+
import { FileBrowserModel } from '@jupyterlab/filebrowser';
1617

1718
/** Interface for GitPanel component state */
1819
export interface IGitSessionNodeState {
@@ -36,6 +37,7 @@ export interface IGitSessionNodeProps {
3637
model: GitExtension;
3738
renderMime: IRenderMimeRegistry;
3839
settings: ISettingRegistry.ISettings;
40+
fileBrowserModel: FileBrowserModel;
3941
}
4042

4143
/** A React component for the git extension's main display */
@@ -230,6 +232,7 @@ export class GitPanel extends React.Component<
230232
<div className={panelContainerStyle}>
231233
<PathHeader
232234
model={this.props.model}
235+
fileBrowserModel={this.props.fileBrowserModel}
233236
refresh={async () => {
234237
await this.refreshBranch();
235238
if (this.state.isHistoryVisible) {

src/components/PathHeader.tsx

+160-77
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,200 @@
11
import { Dialog, showDialog, UseSignal } from '@jupyterlab/apputils';
22
import { PathExt } from '@jupyterlab/coreutils';
3+
import { FileDialog, FileBrowserModel } from '@jupyterlab/filebrowser';
4+
import { DefaultIconReact } from '@jupyterlab/ui-components';
35
import * as React from 'react';
46
import { classes } from 'typestyle';
57
import {
68
gitPullStyle,
79
gitPushStyle,
10+
pinIconStyle,
811
repoPathStyle,
912
repoRefreshStyle,
10-
repoStyle
13+
repoStyle,
14+
repoPinStyle,
15+
repoPinnedStyle
1116
} from '../style/PathHeaderStyle';
1217
import { GitCredentialsForm } from '../widgets/CredentialsBox';
1318
import { GitPullPushDialog, Operation } from '../widgets/gitPushPull';
1419
import { IGitExtension } from '../tokens';
1520

1621
export interface IPathHeaderProps {
1722
model: IGitExtension;
23+
fileBrowserModel: FileBrowserModel;
1824
refresh: () => Promise<void>;
1925
}
2026

21-
export class PathHeader extends React.Component<IPathHeaderProps> {
22-
constructor(props: IPathHeaderProps) {
23-
super(props);
27+
/**
28+
* Displays the error dialog when the Git Push/Pull operation fails.
29+
* @param title the title of the error dialog
30+
* @param body the message to be shown in the body of the modal.
31+
*/
32+
async function showGitPushPullDialog(
33+
model: IGitExtension,
34+
operation: Operation
35+
): Promise<void> {
36+
let result = await showDialog({
37+
title: `Git ${operation}`,
38+
body: new GitPullPushDialog(model, operation),
39+
buttons: [Dialog.okButton({ label: 'DISMISS' })]
40+
});
41+
let retry = false;
42+
while (!result.button.accept) {
43+
retry = true;
44+
45+
let response = await showDialog({
46+
title: 'Git credentials required',
47+
body: new GitCredentialsForm(
48+
'Enter credentials for remote repository',
49+
retry ? 'Incorrect username or password.' : ''
50+
),
51+
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'OK' })]
52+
});
53+
54+
if (response.button.accept) {
55+
// user accepted attempt to login
56+
result = await showDialog({
57+
title: `Git ${operation}`,
58+
body: new GitPullPushDialog(model, operation, response.value),
59+
buttons: [Dialog.okButton({ label: 'DISMISS' })]
60+
});
61+
} else {
62+
break;
63+
}
64+
}
65+
}
66+
67+
/**
68+
* Select a Git repository folder
69+
*
70+
* @param model Git extension model
71+
* @param fileModel file browser model
72+
*/
73+
async function selectGitRepository(
74+
model: IGitExtension,
75+
fileModel: FileBrowserModel
76+
) {
77+
const result = await FileDialog.getExistingDirectory({
78+
iconRegistry: fileModel.iconRegistry,
79+
manager: fileModel.manager,
80+
title: 'Select a Git repository folder'
81+
});
82+
83+
if (result.button.accept) {
84+
const folder = result.value[0];
85+
if (model.repositoryPinned) {
86+
model.pathRepository = folder.path;
87+
} else if (fileModel.path !== folder.path) {
88+
// Change current filebrowser path
89+
fileModel.cd('/' + folder.path);
90+
}
91+
}
92+
}
93+
94+
/**
95+
* React function component to render the toolbar and path header component
96+
*
97+
* @param props Properties for the path header component
98+
*/
99+
export const PathHeader: React.FunctionComponent<IPathHeaderProps> = (
100+
props: IPathHeaderProps
101+
) => {
102+
const [pin, setPin] = React.useState(props.model.repositoryPinned);
103+
104+
React.useEffect(() => {
105+
props.model.restored.then(() => {
106+
setPin(props.model.repositoryPinned);
107+
});
108+
});
109+
110+
const pinStyles = [repoPinStyle, 'jp-Icon-16'];
111+
if (pin) {
112+
pinStyles.push(repoPinnedStyle);
24113
}
25114

26-
render() {
27-
return (
115+
return (
116+
<div>
28117
<div className={repoStyle}>
29-
<UseSignal
30-
signal={this.props.model.repositoryChanged}
31-
initialArgs={{
32-
name: 'pathRepository',
33-
oldValue: null,
34-
newValue: this.props.model.pathRepository
35-
}}
36-
>
37-
{(_, change) => (
38-
<span className={repoPathStyle} title={change.newValue}>
39-
{PathExt.basename(change.newValue || '')}
40-
</span>
41-
)}
42-
</UseSignal>
43118
<button
44119
className={classes(gitPullStyle, 'jp-Icon-16')}
45120
title={'Pull latest changes'}
46121
onClick={() =>
47-
this.showGitPushPullDialog(this.props.model, Operation.Pull).catch(
48-
reason => {
49-
console.error(
50-
`An error occurs when pulling the changes.\n${reason}`
51-
);
52-
}
53-
)
122+
showGitPushPullDialog(props.model, Operation.Pull).catch(reason => {
123+
console.error(
124+
`An error occurs when pulling the changes.\n${reason}`
125+
);
126+
})
54127
}
55128
/>
56129
<button
57130
className={classes(gitPushStyle, 'jp-Icon-16')}
58131
title={'Push committed changes'}
59132
onClick={() =>
60-
this.showGitPushPullDialog(this.props.model, Operation.Push).catch(
61-
reason => {
62-
console.error(
63-
`An error occurs when pulling the changes.\n${reason}`
64-
);
65-
}
66-
)
133+
showGitPushPullDialog(props.model, Operation.Push).catch(reason => {
134+
console.error(
135+
`An error occurs when pulling the changes.\n${reason}`
136+
);
137+
})
67138
}
68139
/>
69140
<button
70141
className={classes(repoRefreshStyle, 'jp-Icon-16')}
71142
title={'Refresh the repository to detect local and remote changes'}
72-
onClick={() => this.props.refresh()}
143+
onClick={() => props.refresh()}
73144
/>
74145
</div>
75-
);
76-
}
77-
78-
/**
79-
* Displays the error dialog when the Git Push/Pull operation fails.
80-
* @param title the title of the error dialog
81-
* @param body the message to be shown in the body of the modal.
82-
*/
83-
private async showGitPushPullDialog(
84-
model: IGitExtension,
85-
operation: Operation
86-
): Promise<void> {
87-
let result = await showDialog({
88-
title: `Git ${operation}`,
89-
body: new GitPullPushDialog(model, operation),
90-
buttons: [Dialog.okButton({ label: 'DISMISS' })]
91-
});
92-
let retry = false;
93-
while (!result.button.accept) {
94-
retry = true;
95-
96-
let response = await showDialog({
97-
title: 'Git credentials required',
98-
body: new GitCredentialsForm(
99-
'Enter credentials for remote repository',
100-
retry ? 'Incorrect username or password.' : ''
101-
),
102-
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'OK' })]
103-
});
146+
<div>
147+
<UseSignal
148+
signal={props.model.repositoryChanged}
149+
initialArgs={{
150+
name: 'pathRepository',
151+
oldValue: null,
152+
newValue: props.model.pathRepository
153+
}}
154+
>
155+
{(_, change) => {
156+
const pathStyles = [repoPathStyle];
157+
let pinTitle = 'the repository path';
158+
if (props.model.repositoryPinned) {
159+
pinTitle = 'Unpin ' + pinTitle;
160+
} else {
161+
pinTitle = 'Pin ' + pinTitle;
162+
}
104163

105-
if (response.button.accept) {
106-
// user accepted attempt to login
107-
result = await showDialog({
108-
title: `Git ${operation}`,
109-
body: new GitPullPushDialog(model, operation, response.value),
110-
buttons: [Dialog.okButton({ label: 'DISMISS' })]
111-
});
112-
} else {
113-
break;
114-
}
115-
}
116-
}
117-
}
164+
return (
165+
<React.Fragment>
166+
<label className={repoPinStyle} title={pinTitle}>
167+
<input
168+
type="checkbox"
169+
checked={props.model.repositoryPinned}
170+
onChange={() => {
171+
props.model.repositoryPinned = !props.model
172+
.repositoryPinned;
173+
setPin(props.model.repositoryPinned);
174+
}}
175+
/>
176+
<DefaultIconReact
177+
className={pinIconStyle}
178+
tag="span"
179+
name="git-pin"
180+
/>
181+
</label>
182+
<span
183+
className={classes(...pathStyles)}
184+
title={change.newValue}
185+
onClick={() => {
186+
selectGitRepository(props.model, props.fileBrowserModel);
187+
}}
188+
>
189+
{PathExt.basename(
190+
change.newValue || 'Click to select a Git repository.'
191+
)}
192+
</span>
193+
</React.Fragment>
194+
);
195+
}}
196+
</UseSignal>
197+
</div>
198+
</div>
199+
);
200+
};

0 commit comments

Comments
 (0)