Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build/plotcss.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ 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-message--hostname": "font-weight:bold;",
"X .plotly-cloud-dialog .plotly-cloud-dialog-message--hostname": "font-weight:bold;text-decoration:underline;",
"X .plotly-cloud-dialog .plotly-cloud-dialog-message--account": "margin-top:16px;padding:8px;border-radius:3px;font-size:.9em;background-color:#edf1f8;",
"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;",
Expand Down
1 change: 1 addition & 0 deletions draftlogs/7928_change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Update "Share chart" dialog with more informative wording [[#7928](https://github.com/plotly/plotly.js/pull/7928)]
19 changes: 12 additions & 7 deletions src/components/modebar/buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var Plots = require('../../plots/plots');
var axisIds = require('../../plots/cartesian/axis_ids');
var Icons = require('../../fonts/ploticon');
var eraseActiveShape = require('../shapes/draw').eraseActiveShape;
var confirmCloudDialog = require('./cloud_confirm');
var confirmCloudDialog = require('./share_chart/dialog');
var Lib = require('../../lib');
var _ = Lib._;

Expand Down Expand Up @@ -72,25 +72,30 @@ modeBarButtons.toImage = {
modeBarButtons.sendChartToCloud = {
name: 'sendChartToCloud',
title: function (gd) {
return _(gd, 'Share Chart');
return _(gd, 'Share chart...');
},
icon: Icons.cloudupload,
click: function (gd) {
var baseUrl = (window.PLOTLYENV || {}).BASE_URL || gd._context.plotlyServerURL;
if (!baseUrl) {
console.error('No destination URL provided (plotlyServerURL is not set)');
console.error('No destination URL provided (plotlyServerURL is empty)');
return;
}

// Plotly Cloud origin, used to validate incoming messages and to target outgoing ones.
// `baseUrl` (plotlyServerURL) is the upload page that handles login and signals
// back when authentication succeeds.
// Validate that the provided plotlyServerURL is a valid URL
// with an http or https protocol
var baseUrlObj;
try {
new URL(baseUrl);
baseUrlObj = new URL(baseUrl);
} catch (e) {
console.error('Invalid plotlyServerURL: ' + baseUrl);
return;
}
const supportedProtocols = ['http:', 'https:'];
if (!supportedProtocols.includes(baseUrlObj.protocol)) {
console.error(`Invalid protocol '${baseUrlObj.protocol}' in plotlyServerURL '${baseUrl}'. Must be one of: ${supportedProtocols.join(', ')}`);
return;
}

confirmCloudDialog(gd, baseUrl, function () {
Plots.sendDataToCloud(gd, baseUrl);
Expand Down
75 changes: 0 additions & 75 deletions src/components/modebar/cloud_confirm.js

This file was deleted.

148 changes: 148 additions & 0 deletions src/components/modebar/share_chart/dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
'use strict';

const d3 = require('@plotly/d3');

const { dfltConfig } = require('../../../plot_api/plot_config');
const getDialogStrings = require('./strings');

/**
* Build the chart-sharing confirmation dialog box and add it to DOM
* inside the overlay element.
*
* The message wording depends on the destination: when serverUrl is the
* default Plotly Cloud URL we show wording specific to Plotly Cloud,
* otherwise we show a generic message naming the server's hostname.
*
* @param {DOM node} gd - the graph div (used for localizing strings)
* @param {d3 selection} overlay - the dialog backdrop element to append the box to
* @param {string} serverUrl - destination URL (must be a valid URL)
* @param {function} onClickConfirm - called when the confirm button is clicked
* @param {function} onClickCancel - called when the cancel button is clicked
*/
const buildDialogBox = (gd, overlay, serverUrl, onClickConfirm, onClickCancel) => {

const strings = getDialogStrings(gd);

const dialog = overlay.append('div')
.classed('plotly-cloud-dialog-box', true);

dialog.append('div')
.classed('plotly-cloud-dialog-title', true)
.text(strings.DIALOG_TITLE);

if (serverUrl === dfltConfig.plotlyServerURL) {
// If serverUrl matches the default Plotly Cloud URL,
// show a custom message designed for Plotly Cloud
const description = dialog.append('div')
.classed('plotly-cloud-dialog-message', true);

// Link to the base domain only, leaving the endpoint path
const serverUrlHref = new URL(serverUrl).origin;

// Split description into three parts: Before {, between, and after }
const descriptionParts = strings.DIALOG_MESSAGE_CLOUD.split(/(\{|\})/);
const beforePart = descriptionParts[0];
const betweenPart = descriptionParts[2];
const afterPart = descriptionParts[4];

// Append the parts to the description div
description.append('span').text(beforePart);
description.append('a')
.classed('plotly-cloud-dialog-message--hostname', true)
.attr('href', serverUrlHref)
.attr('target', '_blank')
.text(betweenPart);
description.append('span').text(afterPart);

description.append('div')
.classed('plotly-cloud-dialog-message--account', true)
.text(strings.DIALOG_MESSAGE_CLOUD_ACCOUNT);
} else {
// Otherwise, show a generic message with the server URL
// We can trust that serverUrl is a valid URL because it was validated in buttons.js
const serverUrlObj = new URL(serverUrl);
const serverUrlHostname = serverUrlObj.hostname;
// Link to the base domain only, leaving off any endpoint path
const serverUrlHref = serverUrlObj.origin;
const descriptionParts = strings.DIALOG_MESSAGE_OTHER.split(/(\{|\})/);
const beforePart = descriptionParts[0];
const afterPart = descriptionParts[4];

const description = dialog.append('div')
.classed('plotly-cloud-dialog-message', true);

description.append('span').text(beforePart);
description.append('a')
.classed('plotly-cloud-dialog-message--hostname', true)
.attr('href', serverUrlHref)
.attr('target', '_blank')
.text(serverUrlHostname);
description.append('span').text(afterPart);
}

const buttons = dialog.append('div')
.classed('plotly-cloud-dialog-buttons', true);

buttons.append('button')
.classed('plotly-cloud-dialog-btn', true)
.classed('plotly-cloud-dialog-btn--cancel', true)
.text(strings.DIALOG_CANCEL)
.on('click', onClickCancel);

buttons.append('button')
.classed('plotly-cloud-dialog-btn', true)
.classed('plotly-cloud-dialog-btn--confirm', true)
.text(strings.DIALOG_CONFIRM)
.on('click', onClickConfirm);
};

/**
* Show a styled confirmation dialog before sharing a chart with Plotly Cloud.
*
* The dialog is appended to the plot's positioning container (.svg-container)
* 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.
*
* @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
*/
const confirmCloudDialog = (gd, serverUrl, onConfirm) => {
const container = d3.select(gd._fullLayout._paperdiv.node());

// Never stack dialogs - drop any that is already open.
container.selectAll('.plotly-cloud-dialog').remove();

const overlay = container
.append('div')
.classed('plotly-cloud-dialog', true);

const close = () => {
overlay.remove();
document.removeEventListener('keydown', onKeydown);
};

const onKeydown = (e) => {
if(e.key === 'Escape' || e.keyCode === 27) close();
};
document.addEventListener('keydown', onKeydown);

// Clicking the backdrop (but not the dialog box) cancels.
overlay.on('click', () => {
if(d3.event.target === overlay.node()) close();
});

// Build the dialog box and append it to the overlay
buildDialogBox(
gd,
overlay,
serverUrl,
() => {
close();
onConfirm();
},
close
);
};

module.exports = confirmCloudDialog;
32 changes: 32 additions & 0 deletions src/components/modebar/share_chart/strings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const _ = require('../../../lib')._;

/**
* Get the localized wording for the share chart dialog box.
*
* The strings must be built inside this function rather than defined
* as constants because localization requires a reference to the graph div.
*
* Braces in the message strings mark the span of text that dialog.js turns
* into a link to the destination server, so translations must keep them.
*
* @param {DOM node} gd - the graph div, used for localization
* @returns {object} object containing localized strings
*/
const getDialogStrings = function(gd) {
return {
DIALOG_TITLE: _(gd, 'Share chart'),

// Messages to be shown when serverUrl matches the default (Plotly Cloud) URL
DIALOG_MESSAGE_CLOUD: _(gd, 'This chart will be uploaded to {Plotly Cloud} to create a sharing link. Only you can see it until you change its visibility.'),
DIALOG_MESSAGE_CLOUD_ACCOUNT: _(gd, "If you don't have a Plotly Cloud account yet, you'll have a chance to create one."),

// Message to be shown when serverUrl is not the default URL
DIALOG_MESSAGE_OTHER: _(gd, 'This chart will be sent to {serverUrl}.'),

// Labels for buttons
DIALOG_CANCEL: _(gd, 'Cancel'),
DIALOG_CONFIRM: _(gd, 'Share'),
}
}

module.exports = getDialogStrings;
9 changes: 9 additions & 0 deletions src/css/_cloud_dialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@

&--hostname {
font-weight: bold;
text-decoration: underline;
}

&--account {
margin-top: 16px;
padding: 8px;
border-radius: 3px;
font-size: 0.9em;
background-color: vars.$color-bg-hint;
}
}

Expand Down
Loading
Loading