mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Improve web export with multiple sharing capabilities
This commit is contained in:
51180
newIDE/app/package-lock.json
generated
51180
newIDE/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -70,6 +70,7 @@
|
||||
"react-measure": "2.3.0",
|
||||
"react-monaco-editor": "^0.18.0",
|
||||
"react-mosaic-component": "git://github.com/4ian/react-mosaic#v3.1.0",
|
||||
"react-share": "^4.4.0",
|
||||
"react-sortable-hoc": "1.5.0",
|
||||
"react-sortable-tree": "2.6.2",
|
||||
"react-test-renderer": "16.8.6",
|
||||
@@ -109,13 +110,20 @@
|
||||
"eslintConfig": {
|
||||
"extends": "react-app",
|
||||
"rules": {
|
||||
"no-restricted-imports": ["error", {
|
||||
"paths": [{
|
||||
"name": "@lingui/react",
|
||||
"importNames": ["Trans"],
|
||||
"message": "Please import Trans from @lingui/macro"
|
||||
}]
|
||||
}]
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "@lingui/react",
|
||||
"importNames": [
|
||||
"Trans"
|
||||
],
|
||||
"message": "Please import Trans from @lingui/macro"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"flow-coverage-report": {
|
||||
|
@@ -9,7 +9,7 @@ export default () => {
|
||||
const theme = React.useContext(GDevelopThemeContext);
|
||||
return (
|
||||
windowWidth !== 'small' && (
|
||||
<Column justifyContent="center">
|
||||
<Column justifyContent="center" noMargin>
|
||||
<span
|
||||
style={{
|
||||
height: 'calc(100% - 30px)',
|
||||
|
@@ -411,7 +411,7 @@ export default class ExportLauncher extends Component<Props, State> {
|
||||
})}
|
||||
{doneFooterOpen && (
|
||||
<Line justifyContent="center">
|
||||
<GameRegistration project={project} />
|
||||
<GameRegistration project={project} hideIfSubscribed hideLoader />
|
||||
</Line>
|
||||
)}
|
||||
</Column>
|
||||
|
@@ -3,7 +3,7 @@ import { Trans } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import Text from '../../UI/Text';
|
||||
import { Column, Line } from '../../UI/Grid';
|
||||
import { Column, Line, Spacer } from '../../UI/Grid';
|
||||
import TextField from '../../UI/TextField';
|
||||
import {
|
||||
getBuildArtifactUrl,
|
||||
@@ -12,9 +12,30 @@ import {
|
||||
import RaisedButton from '../../UI/RaisedButton';
|
||||
import Window from '../../Utils/Window';
|
||||
import Copy from '../../UI/CustomSvgIcons/Copy';
|
||||
import Share from '@material-ui/icons/Share';
|
||||
import InfoBar from '../../UI/Messages/InfoBar';
|
||||
import IconButton from '../../UI/IconButton';
|
||||
import { TextFieldWithButtonLayout } from '../../UI/Layout';
|
||||
import { LinearProgress } from '@material-ui/core';
|
||||
import FlatButton from '../../UI/FlatButton';
|
||||
import Dialog from '../../UI/Dialog';
|
||||
import {
|
||||
EmailShareButton,
|
||||
FacebookShareButton,
|
||||
RedditShareButton,
|
||||
TwitterShareButton,
|
||||
WhatsappShareButton,
|
||||
EmailIcon,
|
||||
FacebookIcon,
|
||||
RedditIcon,
|
||||
TwitterIcon,
|
||||
WhatsappIcon,
|
||||
} from 'react-share';
|
||||
|
||||
const styles = {
|
||||
icon: {
|
||||
padding: 5,
|
||||
},
|
||||
};
|
||||
|
||||
export const ExplanationHeader = () => (
|
||||
<Column noMargin alignItems="center" justifyContent="center">
|
||||
@@ -38,62 +59,134 @@ export const WebProjectLink = ({ build, loading }: WebProjectLinkProps) => {
|
||||
const [showCopiedInfoBar, setShowCopiedInfoBar] = React.useState<boolean>(
|
||||
false
|
||||
);
|
||||
const [isShareDialogOpen, setIsShareDialogOpen] = React.useState<boolean>(
|
||||
false
|
||||
);
|
||||
|
||||
if (!build && !loading) return null;
|
||||
const buildPending = loading || (build && build.status !== 'complete');
|
||||
|
||||
const value = buildPending
|
||||
? 'Just a few seconds while we generate the link...'
|
||||
: getBuildArtifactUrl(build, 's3Key') || '';
|
||||
const buildUrl = buildPending ? null : getBuildArtifactUrl(build, 's3Key');
|
||||
|
||||
const onOpen = () => {
|
||||
if (buildPending) return;
|
||||
Window.openExternalURL(value);
|
||||
if (buildPending || !buildUrl) return;
|
||||
Window.openExternalURL(buildUrl);
|
||||
};
|
||||
|
||||
const onCopy = () => {
|
||||
if (buildPending) return;
|
||||
if (buildPending || !buildUrl) return;
|
||||
// TODO: use Clipboard.js, after it's been reworked to use this API and handle text.
|
||||
navigator.clipboard.writeText(value);
|
||||
navigator.clipboard.writeText(buildUrl);
|
||||
setShowCopiedInfoBar(true);
|
||||
};
|
||||
|
||||
const onShare = async () => {
|
||||
if (buildPending || !buildUrl) return;
|
||||
|
||||
if (!navigator.share) {
|
||||
// We are on desktop (or do not support sharing using the system dialog).
|
||||
setIsShareDialogOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// We are on mobile (or on browsers supporting sharing using the system dialog).
|
||||
const shareData = {
|
||||
title: 'My GDevelop game',
|
||||
text: 'Try the game I just created with #gdevelop',
|
||||
url: buildUrl,
|
||||
};
|
||||
|
||||
try {
|
||||
await navigator.share(shareData);
|
||||
} catch (err) {
|
||||
console.error("Couldn't share the game", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextFieldWithButtonLayout
|
||||
noFloatingLabelText
|
||||
renderTextField={() => (
|
||||
<TextField
|
||||
value={value}
|
||||
readOnly
|
||||
fullWidth
|
||||
endAdornment={
|
||||
<IconButton
|
||||
disabled={!!buildPending}
|
||||
onClick={onCopy}
|
||||
tooltip={t`Copy`}
|
||||
edge="end"
|
||||
{buildPending ? (
|
||||
<>
|
||||
<Text>
|
||||
<Trans>Just a few seconds while we generate the link...</Trans>
|
||||
</Text>
|
||||
<LinearProgress />
|
||||
</>
|
||||
) : (
|
||||
<Line justifyContent="center">
|
||||
<FlatButton
|
||||
label={<Trans>Share</Trans>}
|
||||
onClick={onShare}
|
||||
icon={<Share />}
|
||||
/>
|
||||
<Spacer />
|
||||
<RaisedButton label={<Trans>Open</Trans>} onClick={onOpen} primary />
|
||||
</Line>
|
||||
)}
|
||||
<Dialog
|
||||
title={<Trans>Share your game</Trans>}
|
||||
actions={[
|
||||
<FlatButton
|
||||
key="close"
|
||||
label={<Trans>Back</Trans>}
|
||||
primary={false}
|
||||
onClick={() => setIsShareDialogOpen(false)}
|
||||
/>,
|
||||
]}
|
||||
open={isShareDialogOpen}
|
||||
onRequestClose={() => setIsShareDialogOpen(false)}
|
||||
>
|
||||
{buildUrl && (
|
||||
<Column>
|
||||
<TextField
|
||||
value={buildUrl}
|
||||
readOnly
|
||||
fullWidth
|
||||
endAdornment={
|
||||
<IconButton onClick={onCopy} tooltip={t`Copy`} edge="end">
|
||||
<Copy />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<Line justifyContent="flex-end">
|
||||
<FacebookShareButton
|
||||
url={`Try the game I just created with #gdevelop: ${buildUrl}`}
|
||||
style={styles.icon}
|
||||
>
|
||||
<Copy />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<FacebookIcon size={32} round />
|
||||
</FacebookShareButton>
|
||||
<RedditShareButton
|
||||
url={`Try the game I just created with r/gdevelop: ${buildUrl}`}
|
||||
style={styles.icon}
|
||||
>
|
||||
<RedditIcon size={32} round />
|
||||
</RedditShareButton>
|
||||
<TwitterShareButton
|
||||
url={`Try the game I just created with #gdevelop: ${buildUrl}`}
|
||||
style={styles.icon}
|
||||
>
|
||||
<TwitterIcon size={32} round />
|
||||
</TwitterShareButton>
|
||||
<WhatsappShareButton
|
||||
url={`Try the game I just created with gdevelop.io: ${buildUrl}`}
|
||||
style={styles.icon}
|
||||
>
|
||||
<WhatsappIcon size={32} round />
|
||||
</WhatsappShareButton>
|
||||
<EmailShareButton
|
||||
url={`Try the game I just created with gdevelop.io: ${buildUrl}`}
|
||||
style={styles.icon}
|
||||
>
|
||||
<EmailIcon size={32} round />
|
||||
</EmailShareButton>
|
||||
</Line>
|
||||
</Column>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton
|
||||
disabled={!!buildPending}
|
||||
primary
|
||||
label={<Trans>Open</Trans>}
|
||||
onClick={onOpen}
|
||||
style={style}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<InfoBar
|
||||
message={<Trans>Copied to clipboard!</Trans>}
|
||||
visible={showCopiedInfoBar}
|
||||
hide={() => setShowCopiedInfoBar(false)}
|
||||
/>
|
||||
<InfoBar
|
||||
message={<Trans>Copied to clipboard!</Trans>}
|
||||
visible={showCopiedInfoBar}
|
||||
hide={() => setShowCopiedInfoBar(false)}
|
||||
/>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -23,6 +23,7 @@ import { GameDetailsDialog } from './GameDetailsDialog';
|
||||
type Props = {|
|
||||
project: ?gdProject,
|
||||
hideIfRegistered?: boolean,
|
||||
hideIfSubscribed?: boolean,
|
||||
hideLoader?: boolean,
|
||||
onGameRegistered?: () => void,
|
||||
|};
|
||||
@@ -33,6 +34,7 @@ type UnavailableReason = 'unauthorized' | 'not-existing' | null;
|
||||
export const GameRegistration = ({
|
||||
project,
|
||||
hideIfRegistered,
|
||||
hideIfSubscribed,
|
||||
hideLoader,
|
||||
onGameRegistered,
|
||||
}: Props) => {
|
||||
@@ -168,6 +170,7 @@ export const GameRegistration = ({
|
||||
onRegisterGame={onRegisterGame}
|
||||
registrationInProgress={registrationInProgress}
|
||||
hideIfRegistered={hideIfRegistered}
|
||||
hideIfSubscribed={hideIfSubscribed}
|
||||
unavailableReason={unavailableReason}
|
||||
acceptGameStatsEmailInProgress={acceptGameStatsEmailInProgress}
|
||||
onAcceptGameStatsEmail={_onAcceptGameStatsEmail}
|
||||
@@ -193,6 +196,7 @@ export type GameRegistrationWidgetProps = {|
|
||||
onRegisterGame: () => Promise<void>,
|
||||
registrationInProgress: boolean,
|
||||
hideIfRegistered?: boolean,
|
||||
hideIfSubscribed?: boolean,
|
||||
unavailableReason: ?UnavailableReason,
|
||||
acceptGameStatsEmailInProgress: boolean,
|
||||
onAcceptGameStatsEmail: () => Promise<void>,
|
||||
@@ -216,6 +220,7 @@ export const GameRegistrationWidget = ({
|
||||
onRegisterGame,
|
||||
registrationInProgress,
|
||||
hideIfRegistered,
|
||||
hideIfSubscribed,
|
||||
unavailableReason,
|
||||
acceptGameStatsEmailInProgress,
|
||||
onAcceptGameStatsEmail,
|
||||
@@ -302,6 +307,7 @@ export const GameRegistrationWidget = ({
|
||||
</AlertMessage>
|
||||
);
|
||||
}
|
||||
if (hideIfSubscribed) return null;
|
||||
return (
|
||||
<ColumnStackLayout noMargin>
|
||||
<Line justifyContent="center">
|
||||
|
@@ -9,7 +9,7 @@ import { tooltipEnterDelay } from './Tooltip';
|
||||
// They should be self descriptive - refer to Material UI docs otherwise.
|
||||
type Props = {|
|
||||
label: React.Node,
|
||||
onClick: ?(ev: any) => void,
|
||||
onClick: ?(ev: any) => void | Promise<void>,
|
||||
primary?: boolean,
|
||||
disabled?: boolean,
|
||||
keyboardFocused?: boolean,
|
||||
|
@@ -11,6 +11,9 @@ import {
|
||||
GameRegistrationWidget,
|
||||
type GameRegistrationWidgetProps,
|
||||
} from '../../GameDashboard/GameRegistration';
|
||||
import GDevelopJsInitializerDecorator, {
|
||||
testProject,
|
||||
} from '../GDevelopJsInitializerDecorator';
|
||||
import {
|
||||
indieUserProfile,
|
||||
game1,
|
||||
@@ -22,13 +25,10 @@ const indieUserProfileWithGameStatsEmail: Profile = {
|
||||
getGameStatsEmail: true,
|
||||
};
|
||||
|
||||
// $FlowFixMe - Understand why the testProject imported from fixtures is always undefined.
|
||||
const testProject: TestProject = { project: 'fake' };
|
||||
|
||||
export default {
|
||||
title: 'GameDashboard/GameRegistrationWidget',
|
||||
component: GameRegistrationWidget,
|
||||
decorators: [paperDecorator, muiDecorator],
|
||||
decorators: [paperDecorator, muiDecorator, GDevelopJsInitializerDecorator],
|
||||
};
|
||||
|
||||
const defaultProps: GameRegistrationWidgetProps = {
|
||||
@@ -94,6 +94,13 @@ export const EmailAccepted = () => (
|
||||
profile={indieUserProfileWithGameStatsEmail}
|
||||
/>
|
||||
);
|
||||
export const EmailAcceptedButHidingIfSubscribed = () => (
|
||||
<GameRegistrationWidget
|
||||
{...defaultProps}
|
||||
profile={indieUserProfileWithGameStatsEmail}
|
||||
hideIfSubscribed
|
||||
/>
|
||||
);
|
||||
export const DetailsOpened = () => (
|
||||
<GameRegistrationWidget
|
||||
{...defaultProps}
|
||||
|
Reference in New Issue
Block a user