1 |
|
2 |
|
3 |
|
4 | import { Dialog, showDialog, showErrorMessage } from '@jupyterlab/apputils';
|
5 | import { DocumentRegistry } from '@jupyterlab/docregistry';
|
6 | import { PathExt } from '@jupyterlab/coreutils';
|
7 | import { Contents } from '@jupyterlab/services';
|
8 | import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
9 | import { JSONObject } from '@lumino/coreutils';
|
10 | import { Widget } from '@lumino/widgets';
|
11 | import { IDocumentManager } from './';
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | const FILE_DIALOG_CLASS = 'jp-FileDialog';
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | const RENAME_NEW_NAME_TITLE_CLASS = 'jp-new-name-title';
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | export interface IFileContainer extends JSONObject {
|
27 | |
28 |
|
29 |
|
30 | items: string[];
|
31 | |
32 |
|
33 |
|
34 | path: string;
|
35 | }
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | export function renameDialog(
|
41 | manager: IDocumentManager,
|
42 | context: DocumentRegistry.Context,
|
43 | translator?: ITranslator
|
44 | ): Promise<void | null> {
|
45 | translator = translator || nullTranslator;
|
46 | const trans = translator.load('jupyterlab');
|
47 |
|
48 | const localPath = context.localPath.split('/');
|
49 | const fileName = localPath.pop() || context.localPath;
|
50 |
|
51 | return showDialog({
|
52 | title: trans.__('Rename File'),
|
53 | body: new RenameHandler(fileName),
|
54 | focusNodeSelector: 'input',
|
55 | buttons: [
|
56 | Dialog.cancelButton(),
|
57 | Dialog.okButton({
|
58 | label: trans.__('Rename'),
|
59 | ariaLabel: trans.__('Rename File')
|
60 | })
|
61 | ]
|
62 | }).then(result => {
|
63 | if (!result.value) {
|
64 | return null;
|
65 | }
|
66 | if (!isValidFileName(result.value)) {
|
67 | void showErrorMessage(
|
68 | trans.__('Rename Error'),
|
69 | Error(
|
70 | trans.__(
|
71 | '"%1" is not a valid name for a file. Names must have nonzero length, and cannot include "/", "\\", or ":"',
|
72 | result.value
|
73 | )
|
74 | )
|
75 | );
|
76 | return null;
|
77 | }
|
78 | return context.rename(result.value);
|
79 | });
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | export function renameFile(
|
86 | manager: IDocumentManager,
|
87 | oldPath: string,
|
88 | newPath: string
|
89 | ): Promise<Contents.IModel | null> {
|
90 | return manager.rename(oldPath, newPath).catch(error => {
|
91 | if (error.response.status !== 409) {
|
92 |
|
93 | throw error;
|
94 | }
|
95 |
|
96 |
|
97 | return shouldOverwrite(newPath).then((value: boolean) => {
|
98 | if (value) {
|
99 | return manager.overwrite(oldPath, newPath);
|
100 | }
|
101 | return Promise.reject('File not renamed');
|
102 | });
|
103 | });
|
104 | }
|
105 |
|
106 |
|
107 |
|
108 |
|
109 | export function shouldOverwrite(
|
110 | path: string,
|
111 | translator?: ITranslator
|
112 | ): Promise<boolean> {
|
113 | translator = translator || nullTranslator;
|
114 | const trans = translator.load('jupyterlab');
|
115 |
|
116 | const options = {
|
117 | title: trans.__('Overwrite file?'),
|
118 | body: trans.__('"%1" already exists, overwrite?', path),
|
119 | buttons: [
|
120 | Dialog.cancelButton(),
|
121 | Dialog.warnButton({
|
122 | label: trans.__('Overwrite'),
|
123 | ariaLabel: trans.__('Overwrite Existing File')
|
124 | })
|
125 | ]
|
126 | };
|
127 | return showDialog(options).then(result => {
|
128 | return Promise.resolve(result.button.accept);
|
129 | });
|
130 | }
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | export function isValidFileName(name: string): boolean {
|
138 | const validNameExp = /[\/\\:]/;
|
139 | return name.length > 0 && !validNameExp.test(name);
|
140 | }
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | class RenameHandler extends Widget {
|
146 | |
147 |
|
148 |
|
149 | constructor(oldPath: string) {
|
150 | super({ node: Private.createRenameNode(oldPath) });
|
151 | this.addClass(FILE_DIALOG_CLASS);
|
152 | const ext = PathExt.extname(oldPath);
|
153 | const value = (this.inputNode.value = PathExt.basename(oldPath));
|
154 | this.inputNode.setSelectionRange(0, value.length - ext.length);
|
155 | }
|
156 |
|
157 | |
158 |
|
159 |
|
160 | get inputNode(): HTMLInputElement {
|
161 | return this.node.getElementsByTagName('input')[0] as HTMLInputElement;
|
162 | }
|
163 |
|
164 | |
165 |
|
166 |
|
167 | getValue(): string {
|
168 | return this.inputNode.value;
|
169 | }
|
170 | }
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | namespace Private {
|
176 | |
177 |
|
178 |
|
179 | export function createRenameNode(
|
180 | oldPath: string,
|
181 | translator?: ITranslator
|
182 | ): HTMLElement {
|
183 | translator = translator || nullTranslator;
|
184 | const trans = translator.load('jupyterlab');
|
185 |
|
186 | const body = document.createElement('div');
|
187 | const existingLabel = document.createElement('label');
|
188 | existingLabel.textContent = trans.__('File Path');
|
189 | const existingPath = document.createElement('span');
|
190 | existingPath.textContent = oldPath;
|
191 |
|
192 | const nameTitle = document.createElement('label');
|
193 | nameTitle.textContent = trans.__('New Name');
|
194 | nameTitle.className = RENAME_NEW_NAME_TITLE_CLASS;
|
195 | const name = document.createElement('input');
|
196 |
|
197 | body.appendChild(existingLabel);
|
198 | body.appendChild(existingPath);
|
199 | body.appendChild(nameTitle);
|
200 | body.appendChild(name);
|
201 | return body;
|
202 | }
|
203 | }
|