Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add save as menu option#3289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
takluyver merged 7 commits intojupyter:masterfromMadhu94:add-save-as-menu-option
Jun 20, 2018

Conversation

@Madhu94
Copy link
Contributor

@Madhu94Madhu94 commentedFeb 1, 2018
edited
Loading

Trying toclose#2816

I'd like some quick, initial feedback on this.

I am checking the tests. The UI needs some work too (maybe a directory picker instead of a text box?)

Thanks.

@takluyver
Copy link
Member

I would expect save-as to work without needing any changes to the REST API or the server. It should update the path for the notebook file in the frontend, and then do a regular save (i.e. a PUT request to the URL of the new file).

This is in line with how 'save as' works in most applications: it changes the file path you're working on, so if you open A and then 'save as' B, subsequent saves will write B until you use 'save as' again.

Some applications have a separate 'save a copy' action, which lets you write file B but keep working on A. I don't think that's a priority to add to the notebook - not many other apps have it.

@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedFeb 2, 2018
edited
Loading

That's how I started but I thinkthis line stopped me.

I'll make the changes, thanks

@takluyver
Copy link
Member

Yup, I'd just send the whole contents again (for now, every save does that). The notebook may have changed since it was last saved, so copying the file we saved before may not be right.

@Madhu94Madhu94force-pushed theadd-save-as-menu-option branch fromf97e26e to50d6297CompareFebruary 6, 2018 20:15
@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedFeb 13, 2018
edited
Loading

@takluyver Don't you think that if "Save As" behaves almost like "Save", the user could accidentally overwrite a notebook, if they are not careful?

I guess "save as" should do an additional check and warn the user that the file exists..

@takluyver
Copy link
Member

Yes, ideally if the user tries to 'save as' over an existing file, they should be warned about that.

@Madhu94Madhu94force-pushed theadd-save-as-menu-option branch from0610bd5 to21b7710CompareMarch 11, 2018 17:40
@Madhu94Madhu94 changed the title[WIP] Add save as menu optionAdd save as menu optionMar 11, 2018
@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedMar 11, 2018
edited
Loading

@takluyver Now the user is asked to confirm if they want to overwrite, if a notebook exists with the same name. Let me know if these changes are okay.

@takluyver
Copy link
Member

I think the frontend stuff (the dialog, updating the URL, etc.) should be able to mostly use the code we already have instatic/notebook/js/savewidget.js to rename a notebook. On this side, I would do what you started out doing on the server side: have an option likecopy_file: true to switch the behaviour from moving to copying.

@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedMar 13, 2018
edited
Loading

On this side, I would do what you started out doing on the server side: have an option like copy_file: true to switch the behaviour from moving to copying.

Did you mean for reusing the dialog, I should use a flag ?

@takluyver
Copy link
Member

Yep, so it would be called like this:

notebook.save_widget.rename_notebook({notebook:notebook,copy_file:true});

@Madhu94Madhu94force-pushed theadd-save-as-menu-option branch from21b7710 to5fdf8c2CompareMarch 17, 2018 18:57
@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedMar 17, 2018 via email

I've factored out the common portions for "save as" and rename. I know thisisn't what you had in mind, but using a flag brought a lot of "if -else"clauses within the click handler of rename. I'll do that if you feelstrongly about it though.Re the feature itself, do you think it should take paths relative tocurrent directory and not the notebook root ? That's how creating a newnotebook works..
On 13-Mar-2018 10:23 PM, "Thomas Kluyver" ***@***.***> wrote: Yep, so it would be called like this: notebook.save_widget.rename_notebook({notebook: notebook, copy_file: true}); — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub <#3289 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AImSDAaCdVPTHyAa7-e6hvdtyyMv3dtWks5td_lzgaJpZM4R2G_h> .

@takluyver
Copy link
Member

From a user perspective, I think the starting point should be the directory the notebook is currently saved in. The REST API should always work with paths from the notebook root, though.

};


Notebook.prototype.save_notebook_as=function(){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'd probably put this method intosavewidget.js because of the symmetry with rename.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Will do!

Notebook.prototype.save_notebook_as=function(){
// If current notebook has some changes, save them
if(this.writable&&this.dirty){
this.save_notebook();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't think I'd expect 'save as' to save over the old name as well, though it's a bit complicated by autosave.@minrk@gnestor@Carreau ?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This is to save the current notebook before navigating away. We did agree that "Save as" would open the newly created notebook (Right? Did I misunderstand?), so I thought we should try to save the current notebook before moving away. I think "make a copy" and "trust" do this too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This is to save the current notebook before navigating away. We did agree that "Save as" would open the newly created notebook (Right? Did I misunderstand?), so I thought we should try to save the current notebook before moving away. I think "make a copy" and "trust" do this too.

If you do open the new notebook, then this will likely be almost identical to "Make a Copy", Should we have "Make a copy" take a parameter which is the new name/path ?

dialog_title:i18n.msg._('Save As'),
message:i18n.msg._('Enter a notebook path relative to notebook dir'),
message_classname:'save-message',
button_name:'Save',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This should probably be translatable.

returnthat.contents.save(nb_path,model)
.then(function(data){
vardir_path=utils.get_body_data("baseUrl");
window.open(utils.url_path_join(dir_path,"notebooks",data.path),'_self');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

There's already code to deal with updating the address bar and other details when the notebook is renamed, based on thenotebook_renamed.Notebook event. Instead of calling window.open(), we should make sure that event fires, as on this line:

that.events.trigger('notebook_renamed.Notebook',json);

We should also ensure that the session is updated - that links a kernel to a filename. See the line above the one I just linked to.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm not sure if triggering event will cause the new notebook to open up...I'll check and get back.

*/
SaveWidget.prototype._nb_name_dialog=function(options){
varthat=this;
vardialog_template=_.template(`<p class="<%= classname %>"><%= msg %></p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Neat! I assume the_.template() makes it translatable?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

No, I'm sorry. Underscore templates are just plain html templates. This was just an attempt to share some of the ui code for "save as" and "rename".

@Madhu94
Copy link
ContributorAuthor

From a user perspective, I think the starting point should be the directory the notebook is currently saved in. The REST API should always work with paths from the notebook root, though.

I.e. "save as" will be restricted to the current dir + all subdirectories therein ?

@Madhu94
Copy link
ContributorAuthor

@takluyver I'm sorry, this PR "fell through the cracks". I will try to wind this up in the next few days

@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedApr 19, 2018
edited
Loading

I'm really, really sorry, but I'm not able to understand why the above changes are requested, hence a lot of delay. I'll come out and place my concerns here -
I don't get how save as and rename are symmetrical.
Rename

  • patches an existing notebook
  • Updates the url and
  • changes the kernel session

Save As

  • creates a new notebook
  • checks that a notebook of the same name does not exist
  • Opens the newly created notebook (hence no need to change the session in the kernel ??)
    The only shared code I could find is that we need a very similar dialog box - we could use underscore templates here.

Please let me know why you think "save as" and "rename" should be implemented the same.

Thanks for your patience.

@takluyver
Copy link
Member

Let me try to put those as I see them:

Rename

  • Pick a new name that doesn't already exist
  • Ask the server to move the notebook file to the new name
  • Update the session so that our kernel is associated with the new name
  • Update the frontend (URL, notebook name in the page, possibly other things)

Save as

  • Pick a new name that doesn't already exist
  • Ask the server to copy the notebook file to the new name
  • Update the session so that our kernel is associated with the new name
  • Update the frontend (URL, notebook name in the page, possibly other things)

Does that make it clearer?

I think maybe the disconnect is that you're talking about opening the new notebook after 'save as' - are you thinking it would open a second tab, so you would have both notebooks open? It's an interesting idea, but what I describe is the way lots of familiar programs do 'save as' - you have just one editor open afterwards, and it's associated with the new file.

@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedApr 20, 2018
edited
Loading

I see. The disconnect was because I implemented "save as" to work with any path relative to the notebook's root_dir, and I don't think that can be implemented with a rename.

I guess that isn't relevant now as we later agreed to implement save asonly in the current directory.

Thanks a lot for the expanation, I will implement "Save As" as rename + save.

data-ws-url="{{ws_url | urlencode}}"
data-notebook-name="{{notebook_name | urlencode}}"
data-notebook-path="{{notebook_path | urlencode}}"
data-server-root="{{server_root}}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I would like to try that without exposingserver_root it is potentially sensitive informations, and we tried out best to not expose it anywhere in the UI. The root is usually represented as the Home icon.

As far as I can tell having this information is only for display purpose, so I would just stay with the home icon.

Copy link
Member

@takluyvertakluyverApr 21, 2018
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I actually disagree. I don't thinkserver_root is really that sensitive, and it's probably not hard to work out from the list of files, which is already accessible. The last time we thought about this was before we enabled authentication by default; now that we've done that, I think we can be slightly more relaxed about showing this kind of information.

We've been using the home icon for lack of anything better, but I don't think it's great UI:

  • It's easy to confuse with your home directory, especially as the two are sometimes but not always the same. File managers frequently use a very similar icon for your home directory, e.g. in Gnome:
    screenshot from 2018-04-21 08-39-43
  • It obscures where the files are actually stored, if you want to go and work with them outside the notebook. At one point Anaconda's GUI launcher would create a directory like 'Notebooks' to run the server in, which is hard to discover from inside.

We should make sure it comes from the contents manager and works if it's empty, to allow for non-filesystem contents managers. But I think the benefits of showing it outweigh the disadvantages.

@Carreau
Copy link
Member

I agree with your plan, and think that with this approach we have something really close to what "Make a copy" does, and it would be nice to get both functionality closer. I think that "Save as..." may be more familiar than "Make a copy", and we could try to see if "Make a copy" could be removed in the longer term.

@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedApr 20, 2018 via email

I'm a little confused nowI think it would be easier to decide what to do if we fix on how "Save As"should behave - save a new notebook in the current directory or anywhererelative to root_dir ?If we decide that it is the current directory, I agree with@takluyver thatthis feature is a lot like renameIf not, rename may not be identical to Save As.
On Sat 21 Apr, 2018, 2:33 AM Matthias Bussonnier, ***@***.***> wrote: I agree with your plan, and think that with this approach we have something really close to what "Make a copy" does, and it would be nice to get both functionality closer. I think that "Save as..." may be more familiar than "Make a copy", and we could try to see if "Make a copy" could be removed in the longer term. — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub <#3289 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AImSDGShpd_A3tnmSeExIbTeQ3cPmMz2ks5tqk0ggaJpZM4R2G_h> .

@takluyver
Copy link
Member

I implemented "save as" to work with any path relative to the notebook's root_dir, and I don't think that can be implemented with a rename.

Ah, my bad. Rename and move are the same operation in the REST API, and I thought we'd made the GUI so that rename could move it to a different folder, but I was wrong about that.

I think it would be easier to decide what to do if we fix on how "Save As" should behave - save a new notebook in the current directory or anywhere relative to root_dir ?

I think it should be able to save anywhere under the server root directory, but in terms of the UI, it should work relative to the directory the notebook is already in. So if I start the notebook server at/home/takluyver and open a notebook/home/takluyver/foo/ThisNotebook.ipynb, then 'Save as' should assume that I want to stay in thefoo folder unless I tell it otherwise.

@Madhu94
Copy link
ContributorAuthor

unless I tell it otherwise.

So, if the user typesbar/copynotebook.ipynb insidefoo, we save under/home/takluyver/foo/bar/copynotebook.ipynb?

@Madhu94Madhu94force-pushed theadd-save-as-menu-option branch from5fdf8c2 tod3b29edCompareApril 29, 2018 15:58
@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedApr 29, 2018
edited
Loading

I've changed it back to the original implementation which behaved a little like "Make a copy". Even deciding to make it relative to current dir should be as easy as appending$('body').attr('data-notebook-path') to the notebook path, let me know.

@takluyver
Copy link
Member

So, if the user types bar/copynotebook.ipynb inside foo, we save under /home/takluyver/foo/bar/copynotebook.ipynb?

Yes... but if possible, could we make it so that when the dialog appears,foo/ is already filled in, and the cursor is at the end? So you could type a name to save in the same folder, or backspace to take it to another folder.

};
returnthat.contents.save(nb_path,model)
.then(function(data){
window.open(data.path,'_self');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Here I think we should use the same functions that rename uses to update the session and the page:

that.session.rename_notebook(json.path);
that.events.trigger('notebook_renamed.Notebook',json);

@Madhu94
Copy link
ContributorAuthor

I think I got the behavior you wanted now. (Although the save as method has become a callback+promise hell...)

Copy link
Member

@takluyvertakluyver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The code and the functionality are looking good now, but I found a few minor UI issues.

I'd also like to have a basic Selenium test for this - see this folder:https://github.com/jupyter/notebook/tree/master/notebook/tests/selenium



Notebook.prototype.save_notebook_as=function(){
// If current notebook has some changes, save them, or the copied notebook won't have them.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is this still needed? We're sending a new copy of the notebook to the new name, so I don't think we need to save it at the old name again first.

).append($('<label />').attr('for','save-as-dialog')
.html('<i class="fa fa-home"></i>')
).append(
$('<input/>').attr('type','text').attr('size','25')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This input area doesn't show up well for me. I think you're missing theform-control class that the rename dialog uses:

$('<input/>').attr('type','text').attr('size','25').addClass('form-control')

Also, I hope we can get rid of the inline styling a couple of lines below.

returnfalse;
}
});
d.find('input[type="text"]').focus().select();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This currently selects the path pre-filled in the input, which is not ideal. Can we make it put the cursor at the end of the input already there?

<ahref="#">{% trans %}Make a Copy...{% endtrans %}</a></li>
<liid="save_notebook_as"
title="{% trans %}Save a copy of the notebook's contents and start a new kernel{% endtrans %}">
<ahref="#">{% trans %}Save as{% endtrans %}</a></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This label should end... to indicate that it brings up a dialog rather than acting immediately.

@Madhu94Madhu94force-pushed theadd-save-as-menu-option branch from576d3ea to84e565cCompareMay 12, 2018 18:36
@Madhu94
Copy link
ContributorAuthor

I'd also like to have a basic Selenium test for this - see this folder:https://github.com/jupyter/notebook/tree/master/notebook/tests/selenium

I added one. Also took care of the css issues.

@Madhu94
Copy link
ContributorAuthor

Hi@takluyver@Carreau, any updates here ?

$('.save-message').html(`<span style='color:red;'>${msg}</span>`);
});
}
that.contents.get(nb_path,{type:'notebook'}).then(function(data){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This should at least usecontent: false to avoid getting the full content of the other notebook if it does exist. It's also not great in that it assumes any error getting a model means that the file is not there.

The right way to do this is probably to add a parameter when saving, to ask the server to throw an error if the filename already exists. Maybe this way of doing it is good enough for now, though. I'd like to get some other opinions on it.

Copy link
ContributorAuthor

@Madhu94Madhu94Jun 17, 2018
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This should at least use content: false to avoid getting the full content of the other notebook if it does exist.

This is done, thank you.

It's also not great in that it assumes any error getting a model means that the file is not there.

I will try to think of a better way. Maybe we go back to something like my original implementation which used a flag to toggle between copy/save on the server..

if it existsDon't get entire contents of the notebooks, while checkingif it exists
@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedJun 18, 2018
edited
Loading

What do you think of adding a HEAD HTTP method to the contents handler? This would simply call contents_manager.file_exists or contents_manager.dir_exists. (I'm not sure if things like this are "done")

It might be better than adding a flag while saving, I don't like how that looks -

if exists:    if model.get('overwrite', True):        yield gen.maybe_future(self._save(model.get("model"), path))     else:         raise web.HTTPError(400, "File with that name exists")

@takluyver
Copy link
Member

That's roughly whatcontent: false already does, but with the metadata in the JSON model rather than HTTP headers.

@minrk@Carreau@mpacer I'd like to get another pair of eyes on this PR if possible. Thanks!

@takluyver
Copy link
Member

@Madhu94 It seems to be failing the tests, if you could take a look.

@Madhu94
Copy link
ContributorAuthor

@Madhu94 It seems to be failing the tests, if you could take a look.

".CodeMirror-code" still did not exist" => I thought this was some transient Travis bug, will check

@takluyver
Copy link
Member

I think that error can mean it's failing to load the notebook page - it waits for a codemirror block to exist to check that the notebook is loading correctly. The fact that it's doing that on all frontend tests suggests that there's a real problem somewhere (though it might be that something else changed at the same time).

ES6 syntax is not available to us
@minrk
Copy link
Member

I tracked down the test failure to some ES6 syntax in the new javascript. Removing that should let the tests pass.

@takluyver
Copy link
Member

Indeed it does :-). Thanks Min, and thanks@Madhu94 for your patience with this. Merging now.

@takluyvertakluyver merged commit5766341 intojupyter:masterJun 20, 2018
@takluyver
Copy link
Member

Heads up@mpacer that this is another change for 5.6.

@Madhu94
Copy link
ContributorAuthor

Madhu94 commentedJun 20, 2018 via email

Thanks@minrk, I was having a lot of trouble with that. Thanks for allyour help, @taklyuver
On Wed 20 Jun, 2018, 4:21 PM Thomas Kluyver, ***@***.***> wrote: Indeed it does :-). Thanks Min, and thanks@Madhu94 <https://github.com/Madhu94> for your patience with this. Merging now. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#3289 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AImSDP-dMzwgQozXqYk9ynoy6eIxXVhoks5t-ik1gaJpZM4R2G_h> .

@minrkminrk added this to the5.6 milestoneJun 20, 2018
@rachelgshaffer
Copy link

@Madhu94@takluyver quick question... I am curious about the implementation here. The tooltip indicates that it starts a new session but seems the intent was to associate the new name with the same kernel session. I wanted to double check on the actual behavior: does it keep the same kernel or start a new one?

@takluyver
Copy link
Member

@rachelgshaffer it should be keeping the same session and changing the filename associated with it. The 'start a new kernel' language in the tooltip is misleading copy-pasta, I think. Do you want to make a PR to fix that?

@github-actionsgithub-actionsbot locked asresolvedand limited conversation to collaboratorsMar 30, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.

Reviewers

@takluyvertakluyvertakluyver left review comments

@CarreauCarreauCarreau left review comments

Assignees

No one assigned

Projects

None yet

Milestone

5.6

Development

Successfully merging this pull request may close these issues.

feature request: "save as..." under the file menu

5 participants

@Madhu94@takluyver@Carreau@minrk@rachelgshaffer

[8]ページ先頭

©2009-2025 Movatter.jp