From ea44ed392b3cea624beab56864652edb534190ff Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Thu, 9 Jul 2026 17:24:05 -0700 Subject: [PATCH 01/12] Add ability to use custom URL in cloud upload dialog --- src/components/modebar/buttons.js | 6 +- src/components/modebar/cloud_confirm.js | 88 ++++++++++++++++++++++--- src/css/_cloud_dialog.scss | 55 ++++++++++++++++ src/plot_api/plot_config.js | 4 +- 4 files changed, 140 insertions(+), 13 deletions(-) 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..62660e624a0 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, pre-filled into the URL input + * @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?', From 9b9c0d1b90ee533c53bc3860ea2c3fe38e717004 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Thu, 9 Jul 2026 17:29:16 -0700 Subject: [PATCH 02/12] Update docstring and draftlogs --- draftlogs/7896_add.md | 1 + src/components/modebar/cloud_confirm.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 draftlogs/7896_add.md 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/cloud_confirm.js b/src/components/modebar/cloud_confirm.js index 62660e624a0..f2ff0d868ac 100644 --- a/src/components/modebar/cloud_confirm.js +++ b/src/components/modebar/cloud_confirm.js @@ -18,7 +18,7 @@ var _ = require('../../lib')._; * plotlyServerURL config for that upload. * * @param {DOM node} gd - the graph div, used to scope the dialog to the plot - * @param {string} serverUrl - default destination, pre-filled into the URL input + * @param {string} serverUrl - default destination * @param {function} onConfirm - called with the (possibly edited) server URL when confirmed */ module.exports = function confirmCloudDialog(gd, serverUrl, onConfirm) { From 4fb6bf998252cc5f582f14c1a0fe1206dac183a6 Mon Sep 17 00:00:00 2001 From: Emily KL <4672118+emilykl@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:50:54 -0400 Subject: [PATCH 03/12] update schema --- test/plot-schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plot-schema.json b/test/plot-schema.json index 78614b86693..ff7681f0213 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -280,7 +280,7 @@ }, "plotlyServerURL": { "description": "Sets the URL for the `sendChartToCloud` modebar button. When clicked, the button will send the chart data to this URL.", - "dflt": "", + "dflt": "https://cloud.plotly.com/newchart", "valType": "string" }, "queueLength": { @@ -342,7 +342,7 @@ }, "showSendToCloud": { "description": "Should we include a modebar button that sends this chart to a URL specified by `plotlyServerURL`, for sharing the chart with others? Note that this button will (after a confirmation step) send chart data to an external server.", - "dflt": false, + "dflt": true, "valType": "boolean" }, "showSources": { From f051fe216693bbe26a634529adef662f645db6f3 Mon Sep 17 00:00:00 2001 From: Emily KL <4672118+emilykl@users.noreply.github.com> Date: Tue, 14 Jul 2026 11:51:51 -0400 Subject: [PATCH 04/12] format draftlog --- draftlogs/7896_add.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draftlogs/7896_add.md b/draftlogs/7896_add.md index b8e757b9ceb..ee80904db81 100644 --- a/draftlogs/7896_add.md +++ b/draftlogs/7896_add.md @@ -1 +1 @@ -- Add ability to use custom URL for chart upload from the dialog UI [#7896](https://github.com/plotly/plotly.js/pull/7896) +- Add ability to use custom URL for chart upload from the dialog UI [[#7896](https://github.com/plotly/plotly.js/pull/7896)] From b03fb13866cdec81642f6a3baa1c58d1f7666d7d Mon Sep 17 00:00:00 2001 From: Emily KL <4672118+emilykl@users.noreply.github.com> Date: Tue, 14 Jul 2026 12:34:36 -0400 Subject: [PATCH 05/12] add https:// protocol if no protocol specified --- src/components/modebar/cloud_confirm.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/modebar/cloud_confirm.js b/src/components/modebar/cloud_confirm.js index f2ff0d868ac..33d39a54044 100644 --- a/src/components/modebar/cloud_confirm.js +++ b/src/components/modebar/cloud_confirm.js @@ -92,6 +92,10 @@ module.exports = function confirmCloudDialog(gd, serverUrl, onConfirm) { return; } + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = 'https://' + url; + } + try { new URL(url); } catch(e) { From 5ced48c4039e6ce98a650078d12f71bec9f7eb7b Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 15 Jul 2026 13:40:16 -0700 Subject: [PATCH 06/12] Reset defaults in plot config --- src/plot_api/plot_config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 0ccd526aed1..55237deb55c 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: 'https://cloud.plotly.com/newchart', + dflt: '', 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: true, + dflt: false, description: [ 'Should we include a modebar button that sends this chart to a URL', 'specified by `plotlyServerURL`, for sharing the chart with others?', From 7c6d279a363df50582a15943a5950ec7c634b38b Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 15 Jul 2026 13:45:09 -0700 Subject: [PATCH 07/12] Remove incorrect CSS from cloud upload dialog --- src/css/_cloud_dialog.scss | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/css/_cloud_dialog.scss b/src/css/_cloud_dialog.scss index fd4ef0a3ba6..717aebabcef 100644 --- a/src/css/_cloud_dialog.scss +++ b/src/css/_cloud_dialog.scss @@ -96,30 +96,6 @@ } } - .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; From 11cc8e165eb6a4e418ed98a48322d2b79b21d302 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 15 Jul 2026 19:41:02 -0700 Subject: [PATCH 08/12] Update dist plot-schema.json --- dist/plot-schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/plot-schema.json b/dist/plot-schema.json index 78614b86693..21610a9e96b 100644 --- a/dist/plot-schema.json +++ b/dist/plot-schema.json @@ -280,7 +280,7 @@ }, "plotlyServerURL": { "description": "Sets the URL for the `sendChartToCloud` modebar button. When clicked, the button will send the chart data to this URL.", - "dflt": "", + "dflt": "https://cloud.plotly.com/newchart", "valType": "string" }, "queueLength": { @@ -98668,4 +98668,4 @@ "type": "waterfall" } } -} \ No newline at end of file +} From dd0a79ab6a5a79edf19f7b8414d4076aca3239e7 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 15 Jul 2026 19:44:26 -0700 Subject: [PATCH 09/12] Update dist plot-schema.json --- dist/plot-schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/plot-schema.json b/dist/plot-schema.json index 21610a9e96b..78614b86693 100644 --- a/dist/plot-schema.json +++ b/dist/plot-schema.json @@ -280,7 +280,7 @@ }, "plotlyServerURL": { "description": "Sets the URL for the `sendChartToCloud` modebar button. When clicked, the button will send the chart data to this URL.", - "dflt": "https://cloud.plotly.com/newchart", + "dflt": "", "valType": "string" }, "queueLength": { @@ -98668,4 +98668,4 @@ "type": "waterfall" } } -} +} \ No newline at end of file From f34bc9c05531d74aeba4e391d9fe49a1f8de343b Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 15 Jul 2026 19:53:38 -0700 Subject: [PATCH 10/12] Update test plot-schema.json --- test/plot-schema.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/plot-schema.json b/test/plot-schema.json index ff7681f0213..2b889ce1422 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -280,7 +280,7 @@ }, "plotlyServerURL": { "description": "Sets the URL for the `sendChartToCloud` modebar button. When clicked, the button will send the chart data to this URL.", - "dflt": "https://cloud.plotly.com/newchart", + "dflt": "", "valType": "string" }, "queueLength": { @@ -342,7 +342,7 @@ }, "showSendToCloud": { "description": "Should we include a modebar button that sends this chart to a URL specified by `plotlyServerURL`, for sharing the chart with others? Note that this button will (after a confirmation step) send chart data to an external server.", - "dflt": true, + "dflt": false, "valType": "boolean" }, "showSources": { @@ -98668,4 +98668,4 @@ "type": "waterfall" } } -} \ No newline at end of file +} From 1543debaebf7ca182de6e1908a9f742d89e61d24 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 15 Jul 2026 20:02:40 -0700 Subject: [PATCH 11/12] Remove newline in test plot-schema.json --- test/plot-schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plot-schema.json b/test/plot-schema.json index 2b889ce1422..78614b86693 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -98668,4 +98668,4 @@ "type": "waterfall" } } -} +} \ No newline at end of file From 28b2af3d85b65f9137ef03fcdd6de1f4d062eeda Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Wed, 15 Jul 2026 20:08:03 -0700 Subject: [PATCH 12/12] Update plotcss --- build/plotcss.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/plotcss.js b/build/plotcss.js index b1e2798597f..503adc9afe1 100644 --- a/build/plotcss.js +++ b/build/plotcss.js @@ -54,6 +54,10 @@ var rules = { "X .plotly-cloud-dialog .plotly-cloud-dialog-box": "box-sizing:border-box;min-width:300px;max-width:420px;padding:20px 24px;background-color:#fff;border:1px solid #e0e2e5;border-radius:4px;box-shadow:0 4px 16px rgba(0,0,0,.25);font-size:13px;color:#2a3f5f;", "X .plotly-cloud-dialog .plotly-cloud-dialog-title": "font-size:16px;font-weight:bold;margin-bottom:12px;", "X .plotly-cloud-dialog .plotly-cloud-dialog-message": "line-height:1.5;overflow-wrap:break-word;word-wrap:break-word;", + "X .plotly-cloud-dialog .plotly-cloud-dialog-label": "display:block;margin-top:16px;margin-bottom:6px;font-weight:bold;", + "X .plotly-cloud-dialog .plotly-cloud-dialog-input": "box-sizing:border-box;width:100%;padding:7px 10px;font-family:inherit;font-size:13px;color:#2a3f5f;background-color:#fff;border:1px solid #e0e2e5;border-radius:3px;", + "X .plotly-cloud-dialog .plotly-cloud-dialog-input:focus": "outline:none;border-color:#447adb;", + "X .plotly-cloud-dialog .plotly-cloud-dialog-error": "margin-top:6px;color:#db44a5;", "X .plotly-cloud-dialog .plotly-cloud-dialog-buttons": "display:flex;justify-content:flex-end;margin-top:20px;", "X .plotly-cloud-dialog .plotly-cloud-dialog-btn": "font-family:inherit;font-size:13px;padding:7px 16px;margin-left:8px;border-radius:3px;border:1px solid rgba(0,0,0,0);cursor:pointer;", "X .plotly-cloud-dialog .plotly-cloud-dialog-btn:focus-visible": "outline:2px solid #447adb;outline-offset:1px;",