diff --git a/draftlogs/7896_add.md b/draftlogs/7896_add.md new file mode 100644 index 00000000000..b8e757b9ceb --- /dev/null +++ b/draftlogs/7896_add.md @@ -0,0 +1 @@ +- Add ability to use custom URL for chart upload from the dialog UI [#7896](https://github.com/plotly/plotly.js/pull/7896) diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 267d109d5c9..aa02c4db45a 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -89,8 +89,10 @@ modeBarButtons.sendChartToCloud = { return; } - confirmCloudDialog(gd, baseUrl, function() { - Plots.sendDataToCloud(gd, baseUrl); + // The dialog pre-fills baseUrl but lets the user override it with a + // custom server URL, which is passed back here as `chosenUrl`. + confirmCloudDialog(gd, baseUrl, function(chosenUrl) { + Plots.sendDataToCloud(gd, chosenUrl); }); } }; diff --git a/src/components/modebar/cloud_confirm.js b/src/components/modebar/cloud_confirm.js index 4f4ed1b6bf6..f2ff0d868ac 100644 --- a/src/components/modebar/cloud_confirm.js +++ b/src/components/modebar/cloud_confirm.js @@ -11,9 +11,15 @@ var _ = require('../../lib')._; * so it is centered over the plot rather than the whole viewport. It can be * dismissed by clicking Cancel, clicking the backdrop, or pressing Escape. * + * By default the chart is uploaded to the configured plotlyServerURL. A + * "Use a custom server URL" link reveals an editable "Server URL" field + * pre-filled with that default; whatever the user leaves in the field when they + * confirm becomes the destination, so a custom URL entered here overrides the + * plotlyServerURL config for that upload. + * * @param {DOM node} gd - the graph div, used to scope the dialog to the plot - * @param {string} serverUrl - destination shown in the dialog message - * @param {function} onConfirm - called when the user confirms the upload + * @param {string} serverUrl - default destination + * @param {function} onConfirm - called with the (possibly edited) server URL when confirmed */ module.exports = function confirmCloudDialog(gd, serverUrl, onConfirm) { var container = d3.select(gd._fullLayout._paperdiv.node()); @@ -30,11 +36,34 @@ module.exports = function confirmCloudDialog(gd, serverUrl, onConfirm) { dialog.append('div') .classed('plotly-cloud-dialog-title', true) - .text(_(gd, 'Share with Plotly Cloud')); + .text(_(gd, 'Share Chart')); - dialog.append('div') + var description = dialog.append('div') .classed('plotly-cloud-dialog-message', true) - .text(_(gd, 'This chart and its data will be sent to') + ' ' + serverUrl + '.'); + .text(_(gd, 'This chart and its data will be sent to') + ' ' + serverUrl + '. '); + + // The custom-URL field stays hidden until the user opts in. By default the + // chart is shared to the configured serverUrl; a button in the button row + // reveals this input for anyone who wants to override that destination. + var urlField = dialog.append('div') + .classed('plotly-cloud-dialog-url-field', true) + .style('display', 'none'); + + urlField.append('label') + .classed('plotly-cloud-dialog-label', true) + .attr('for', 'plotly-cloud-dialog-url') + .text(_(gd, 'Dash Enterprise URL')); + + var input = urlField.append('input') + .classed('plotly-cloud-dialog-input', true) + .attr('id', 'plotly-cloud-dialog-url') + .attr('type', 'text') + .attr('spellcheck', false) + .attr('placeholder', 'https:///newchart'); + + var error = dialog.append('div') + .classed('plotly-cloud-dialog-error', true) + .style('display', 'none'); var buttons = dialog.append('div') .classed('plotly-cloud-dialog-buttons', true); @@ -54,6 +83,50 @@ module.exports = function confirmCloudDialog(gd, serverUrl, onConfirm) { if(d3.event.target === overlay.node()) close(); }); + function confirm() { + var url = (input.property('value') || serverUrl).trim(); + + if(!url) { + error.text(_(gd, 'Please enter a server URL.')).style('display', ''); + input.node().focus(); + return; + } + + try { + new URL(url); + } catch(e) { + error.text(_(gd, 'Please enter a valid server URL.')).style('display', ''); + input.node().focus(); + return; + } + + close(); + onConfirm(url); + } + + // Hide the error and allow Enter to confirm from the input. + input.on('input', function() { + error.style('display', 'none'); + }); + input.on('keydown', function() { + if(d3.event.key === 'Enter' || d3.event.keyCode === 13) confirm(); + }); + + // Sits at the left of the button row, in line with Cancel/Share. Reveals + // the Server URL field and hides itself once a custom URL is requested. + var customBtn = buttons.append('button') + .classed('plotly-cloud-dialog-btn', true) + .classed('plotly-cloud-dialog-btn--custom', true) + .attr('type', 'button') + .text(_(gd, 'Share with Dash Enterprise')); + + customBtn.on('click', function() { + customBtn.style('display', 'none'); + urlField.style('display', ''); + description.text(_(gd, 'This chart and its data will be sent to the server URL you provide.')); + input.node().focus(); + }); + buttons.append('button') .classed('plotly-cloud-dialog-btn', true) .classed('plotly-cloud-dialog-btn--cancel', true) @@ -64,8 +137,5 @@ module.exports = function confirmCloudDialog(gd, serverUrl, onConfirm) { .classed('plotly-cloud-dialog-btn', true) .classed('plotly-cloud-dialog-btn--confirm', true) .text(_(gd, 'Share')) - .on('click', function() { - close(); - onConfirm(); - }); + .on('click', confirm); }; diff --git a/src/css/_cloud_dialog.scss b/src/css/_cloud_dialog.scss index aca353f1978..fd4ef0a3ba6 100644 --- a/src/css/_cloud_dialog.scss +++ b/src/css/_cloud_dialog.scss @@ -43,6 +43,37 @@ word-wrap: break-word; } + .plotly-cloud-dialog-label { + display: block; + margin-top: 16px; + margin-bottom: 6px; + font-weight: bold; + } + + .plotly-cloud-dialog-input { + box-sizing: border-box; + width: 100%; + padding: 7px 10px; + + font-family: inherit; + font-size: 13px; + color: #2a3f5f; + + background-color: vars.$color-bg-light; + border: 1px solid vars.$color-bg-darker; + border-radius: 3px; + + &:focus { + outline: none; + border-color: vars.$color-brand-primary; + } + } + + .plotly-cloud-dialog-error { + margin-top: 6px; + color: vars.$color-inline-code; + } + .plotly-cloud-dialog-buttons { display: flex; justify-content: flex-end; @@ -65,6 +96,30 @@ } } + .plotly-cloud-dialog-btn--custom { + // Push this button to the left so Cancel/Share stay right-aligned, + // while all three share the same row (vertical) baseline. + margin-left: 0; + margin-right: auto; + + // Zero the padding and any UA button chrome so the button text + // lines up on its left edge with the message/label text above it. + padding-left: 0; + padding-right: 0; + text-align: left; + appearance: none; + -webkit-appearance: none; + + background: none; + border: none; + color: vars.$color-brand-primary; + text-decoration: underline; + + &:hover { + color: vars.$color-brand-accent; + } + } + .plotly-cloud-dialog-btn--cancel { background-color: vars.$color-bg-light; border-color: vars.$color-bg-darker; diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 55237deb55c..0ccd526aed1 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -33,7 +33,7 @@ var configAttributes = { plotlyServerURL: { valType: 'string', - dflt: '', + dflt: 'https://cloud.plotly.com/newchart', description: [ 'Sets the URL for the `sendChartToCloud` modebar button.', 'When clicked, the button will send the chart data to this URL.', @@ -271,7 +271,7 @@ var configAttributes = { }, showSendToCloud: { valType: 'boolean', - dflt: false, + dflt: true, description: [ 'Should we include a modebar button that sends this chart to a URL', 'specified by `plotlyServerURL`, for sharing the chart with others?',