mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
14 Commits
v5.1.148
...
add-public
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6efb481505 | ||
![]() |
24116fd3f3 | ||
![]() |
9b5633bfee | ||
![]() |
8808d6c7f0 | ||
![]() |
7cf166e7d4 | ||
![]() |
951e98c525 | ||
![]() |
ca4b517f6d | ||
![]() |
502876d130 | ||
![]() |
f9df0cb02a | ||
![]() |
187be83575 | ||
![]() |
160753c605 | ||
![]() |
aaf9e9c8e0 | ||
![]() |
9bbbed71f0 | ||
![]() |
91fd83dd7e |
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
stories: ['../src/stories/index.js'],
|
||||
stories: ['../src/stories/index.js', '../src/stories/**/*.stories.js'],
|
||||
addons: [{
|
||||
name: '@storybook/addon-essentials',
|
||||
options: {
|
||||
|
@@ -139,7 +139,11 @@ export function ExampleDialog({
|
||||
{exampleShortHeader.authors && (
|
||||
<Line>
|
||||
{exampleShortHeader.authors.map(author => (
|
||||
<UserPublicProfileChip user={author} key={author.id} />
|
||||
<UserPublicProfileChip
|
||||
user={author}
|
||||
key={author.id}
|
||||
isClickable
|
||||
/>
|
||||
))}
|
||||
</Line>
|
||||
)}
|
||||
|
@@ -149,7 +149,11 @@ export default class ExtensionInstallDialog extends Component<Props, State> {
|
||||
<Line>
|
||||
{extensionShortHeader.authors &&
|
||||
extensionShortHeader.authors.map(author => (
|
||||
<UserPublicProfileChip user={author} key={author.id} />
|
||||
<UserPublicProfileChip
|
||||
user={author}
|
||||
key={author.id}
|
||||
isClickable
|
||||
/>
|
||||
))}
|
||||
</Line>
|
||||
</Column>
|
||||
|
@@ -5,6 +5,7 @@ import { ThemeProvider } from '@material-ui/styles';
|
||||
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
|
||||
import { getTheme } from '../UI/Theme';
|
||||
import AuthenticatedUserProvider from '../Profile/AuthenticatedUserProvider';
|
||||
import PublicProfileProvider from '../Profile/PublicProfileProvider';
|
||||
import Authentication from '../Utils/GDevelopServices/Authentication';
|
||||
import PreferencesProvider from './Preferences/PreferencesProvider';
|
||||
import PreferencesContext from './Preferences/PreferencesContext';
|
||||
@@ -85,40 +86,42 @@ export default class Providers extends React.Component<Props, {||}> {
|
||||
<AuthenticatedUserProvider
|
||||
authentication={authentication}
|
||||
>
|
||||
<I18n update>
|
||||
{({ i18n }) => (
|
||||
<EventsFunctionsExtensionsProvider
|
||||
i18n={i18n}
|
||||
makeEventsFunctionCodeWriter={
|
||||
makeEventsFunctionCodeWriter
|
||||
}
|
||||
eventsFunctionsExtensionWriter={
|
||||
eventsFunctionsExtensionWriter
|
||||
}
|
||||
eventsFunctionsExtensionOpener={
|
||||
eventsFunctionsExtensionOpener
|
||||
}
|
||||
>
|
||||
<CommandsContextProvider>
|
||||
<AssetStoreStateProvider>
|
||||
<ResourceStoreStateProvider>
|
||||
<ExampleStoreStateProvider>
|
||||
<ExtensionStoreStateProvider>
|
||||
<GamesShowcaseStateProvider>
|
||||
<ResourceFetcherContext.Provider
|
||||
value={resourceFetcher}
|
||||
>
|
||||
{children({ i18n })}
|
||||
</ResourceFetcherContext.Provider>
|
||||
</GamesShowcaseStateProvider>
|
||||
</ExtensionStoreStateProvider>
|
||||
</ExampleStoreStateProvider>
|
||||
</ResourceStoreStateProvider>
|
||||
</AssetStoreStateProvider>
|
||||
</CommandsContextProvider>
|
||||
</EventsFunctionsExtensionsProvider>
|
||||
)}
|
||||
</I18n>
|
||||
<PublicProfileProvider>
|
||||
<I18n update>
|
||||
{({ i18n }) => (
|
||||
<EventsFunctionsExtensionsProvider
|
||||
i18n={i18n}
|
||||
makeEventsFunctionCodeWriter={
|
||||
makeEventsFunctionCodeWriter
|
||||
}
|
||||
eventsFunctionsExtensionWriter={
|
||||
eventsFunctionsExtensionWriter
|
||||
}
|
||||
eventsFunctionsExtensionOpener={
|
||||
eventsFunctionsExtensionOpener
|
||||
}
|
||||
>
|
||||
<CommandsContextProvider>
|
||||
<AssetStoreStateProvider>
|
||||
<ResourceStoreStateProvider>
|
||||
<ExampleStoreStateProvider>
|
||||
<ExtensionStoreStateProvider>
|
||||
<GamesShowcaseStateProvider>
|
||||
<ResourceFetcherContext.Provider
|
||||
value={resourceFetcher}
|
||||
>
|
||||
{children({ i18n })}
|
||||
</ResourceFetcherContext.Provider>
|
||||
</GamesShowcaseStateProvider>
|
||||
</ExtensionStoreStateProvider>
|
||||
</ExampleStoreStateProvider>
|
||||
</ResourceStoreStateProvider>
|
||||
</AssetStoreStateProvider>
|
||||
</CommandsContextProvider>
|
||||
</EventsFunctionsExtensionsProvider>
|
||||
)}
|
||||
</I18n>
|
||||
</PublicProfileProvider>
|
||||
</AuthenticatedUserProvider>
|
||||
</ThemeProvider>
|
||||
</StylesProvider>
|
||||
|
@@ -4,7 +4,6 @@ import { Trans, t } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import { Column, Line, Spacer } from '../UI/Grid';
|
||||
import { type Profile } from '../Utils/GDevelopServices/Authentication';
|
||||
import PlaceholderLoader from '../UI/PlaceholderLoader';
|
||||
import { getGravatarUrl } from '../UI/GravatarUrl';
|
||||
import Text from '../UI/Text';
|
||||
@@ -12,12 +11,19 @@ import RaisedButton from '../UI/RaisedButton';
|
||||
import TextField from '../UI/TextField';
|
||||
import { I18n } from '@lingui/react';
|
||||
|
||||
type Props = {|
|
||||
profile: ?Profile,
|
||||
onEditProfile: Function,
|
||||
|};
|
||||
type DisplayedProfile = {
|
||||
+email?: string,
|
||||
description: ?string,
|
||||
username: ?string,
|
||||
};
|
||||
|
||||
export default ({ profile, onEditProfile }: Props) => {
|
||||
type Props = {
|
||||
profile: ?DisplayedProfile,
|
||||
onEditProfile?: Function,
|
||||
isPrivate?: boolean,
|
||||
};
|
||||
|
||||
export default ({ profile, onEditProfile, isPrivate }: Props) => {
|
||||
return profile ? (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
@@ -32,18 +38,22 @@ export default ({ profile, onEditProfile }: Props) => {
|
||||
}}
|
||||
>
|
||||
{profile.username ||
|
||||
i18n._(t`Edit your profile to pick a username!`)}
|
||||
(isPrivate
|
||||
? i18n._(t`Edit your profile to pick a username!`)
|
||||
: i18n._(t`No username`))}
|
||||
</Text>
|
||||
</Line>
|
||||
<Line>
|
||||
<TextField
|
||||
value={profile.email}
|
||||
readOnly
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Email</Trans>}
|
||||
floatingLabelFixed={true}
|
||||
/>
|
||||
</Line>
|
||||
{isPrivate && profile.email && (
|
||||
<Line>
|
||||
<TextField
|
||||
value={profile.email}
|
||||
readOnly
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Email</Trans>}
|
||||
floatingLabelFixed={true}
|
||||
/>
|
||||
</Line>
|
||||
)}
|
||||
<Line>
|
||||
<TextField
|
||||
value={profile.description || ''}
|
||||
@@ -52,18 +62,24 @@ export default ({ profile, onEditProfile }: Props) => {
|
||||
multiline
|
||||
floatingLabelText={<Trans>Bio</Trans>}
|
||||
floatingLabelFixed={true}
|
||||
hintText={t`No bio defined. Edit your profile to tell us what you are using GDevelop for!`}
|
||||
hintText={
|
||||
isPrivate
|
||||
? t`No bio defined. Edit your profile to tell us what you are using GDevelop for!`
|
||||
: t`No bio defined`
|
||||
}
|
||||
rows={3}
|
||||
rowsMax={5}
|
||||
/>
|
||||
</Line>
|
||||
<Line justifyContent="flex-end">
|
||||
<RaisedButton
|
||||
label={<Trans>Edit my profile</Trans>}
|
||||
primary
|
||||
onClick={onEditProfile}
|
||||
/>
|
||||
</Line>
|
||||
{isPrivate && (
|
||||
<Line justifyContent="flex-end">
|
||||
<RaisedButton
|
||||
label={<Trans>Edit my profile</Trans>}
|
||||
primary
|
||||
onClick={onEditProfile}
|
||||
/>
|
||||
</Line>
|
||||
)}
|
||||
</Column>
|
||||
)}
|
||||
</I18n>
|
||||
|
40
newIDE/app/src/Profile/PublicProfile.js
Normal file
40
newIDE/app/src/Profile/PublicProfile.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import React from 'react';
|
||||
|
||||
import Dialog from '../UI/Dialog';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import { getUserPublicProfile } from '../Utils/GDevelopServices/User';
|
||||
import ProfileDetails from './ProfileDetails';
|
||||
|
||||
type Props = {|
|
||||
userId: string,
|
||||
onClose: () => void,
|
||||
|};
|
||||
|
||||
export default ({ userId, onClose }: Props) => {
|
||||
const [profile, setProfile] = React.useState(null);
|
||||
React.useEffect(
|
||||
() => {
|
||||
setProfile(null);
|
||||
getUserPublicProfile(userId).then(profile => setProfile(profile));
|
||||
},
|
||||
[userId]
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={true}
|
||||
actions={[
|
||||
<FlatButton
|
||||
key="close"
|
||||
label={<Trans>Close</Trans>}
|
||||
primary={false}
|
||||
onClick={onClose}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<ProfileDetails profile={profile} />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
12
newIDE/app/src/Profile/PublicProfileContext.js
Normal file
12
newIDE/app/src/Profile/PublicProfileContext.js
Normal file
@@ -0,0 +1,12 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
|
||||
export type PublicProfileOpenerType = string => void;
|
||||
|
||||
export const initialPublicProfileOpener = (userId: string) => {};
|
||||
|
||||
const PublicProfileContext = React.createContext<PublicProfileOpenerType>(
|
||||
initialPublicProfileOpener
|
||||
);
|
||||
|
||||
export default PublicProfileContext;
|
36
newIDE/app/src/Profile/PublicProfileProvider.js
Normal file
36
newIDE/app/src/Profile/PublicProfileProvider.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import PublicProfile from './PublicProfile';
|
||||
import PublicProfileDataContext from './PublicProfileContext';
|
||||
|
||||
type Props = {|
|
||||
children: React.Node,
|
||||
|};
|
||||
|
||||
export default ({ children }: Props) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [userId, setUserId] = React.useState(null);
|
||||
|
||||
const setUserAndOpenProfile = (userId: string): void => {
|
||||
setUserId(userId);
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const closeProfile = (): void => {
|
||||
setUserId(null);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PublicProfileDataContext.Provider value={setUserAndOpenProfile}>
|
||||
{children}
|
||||
</PublicProfileDataContext.Provider>
|
||||
{open && userId && (
|
||||
<PublicProfile userId={userId} onClose={closeProfile} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
@@ -2,7 +2,10 @@
|
||||
import * as React from 'react';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import FaceIcon from '@material-ui/icons/Face';
|
||||
import { type UserPublicProfile } from '../Utils/GDevelopServices/User';
|
||||
import { type UserPublicProfileSearch } from '../Utils/GDevelopServices/User';
|
||||
import PublicProfileContext, {
|
||||
type PublicProfileOpenerType,
|
||||
} from '../Profile/PublicProfileContext';
|
||||
|
||||
const styles = {
|
||||
chip: {
|
||||
@@ -11,17 +14,23 @@ const styles = {
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
user: UserPublicProfile,
|
||||
user: UserPublicProfileSearch,
|
||||
isClickable?: boolean,
|
||||
|};
|
||||
|
||||
export const UserPublicProfileChip = ({ user }: Props) => {
|
||||
export const UserPublicProfileChip = ({ user, isClickable = false }: Props) => {
|
||||
return (
|
||||
<Chip
|
||||
icon={<FaceIcon />}
|
||||
size="small"
|
||||
style={styles.chip}
|
||||
label={user.username}
|
||||
key={user.username}
|
||||
/>
|
||||
<PublicProfileContext.Consumer>
|
||||
{(openPublicProfile: PublicProfileOpenerType) => (
|
||||
<Chip
|
||||
icon={<FaceIcon />}
|
||||
size="small"
|
||||
style={styles.chip}
|
||||
label={user.username}
|
||||
key={user.username}
|
||||
onClick={isClickable ? () => openPublicProfile(user.id) : null}
|
||||
/>
|
||||
)}
|
||||
</PublicProfileContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import axios from 'axios';
|
||||
import { GDevelopAssetApi } from './ApiConfigs';
|
||||
import { type Filters } from './Filters';
|
||||
import { type UserPublicProfile } from './User';
|
||||
import { type UserPublicProfileSearch } from './User';
|
||||
|
||||
export type ExampleShortHeader = {|
|
||||
id: string,
|
||||
@@ -10,8 +10,8 @@ export type ExampleShortHeader = {|
|
||||
shortDescription: string,
|
||||
license: string,
|
||||
tags: Array<string>,
|
||||
authors?: Array<UserPublicProfile>,
|
||||
authorIds?: Array<UserPublicProfile>,
|
||||
authors?: Array<UserPublicProfileSearch>,
|
||||
authorIds?: Array<UserPublicProfileSearch>,
|
||||
previewImageUrls: Array<string>,
|
||||
gdevelopVersion: string,
|
||||
|};
|
||||
|
@@ -2,11 +2,11 @@
|
||||
import axios from 'axios';
|
||||
import { GDevelopAssetApi } from './ApiConfigs';
|
||||
import semverSatisfies from 'semver/functions/satisfies';
|
||||
import { type UserPublicProfile } from './User';
|
||||
import { type UserPublicProfileSearch } from './User';
|
||||
|
||||
export type ExtensionShortHeader = {|
|
||||
shortDescription: string,
|
||||
authors?: Array<UserPublicProfile>,
|
||||
authors?: Array<UserPublicProfileSearch>,
|
||||
extensionNamespace: string,
|
||||
fullName: string,
|
||||
name: string,
|
||||
|
@@ -4,7 +4,13 @@ import { GDevelopUserApi } from './ApiConfigs';
|
||||
|
||||
export type UserPublicProfile = {|
|
||||
id: string,
|
||||
username?: string,
|
||||
username: ?string,
|
||||
description: ?string,
|
||||
|};
|
||||
|
||||
export type UserPublicProfileSearch = {|
|
||||
id: string,
|
||||
username: ?string,
|
||||
|};
|
||||
|
||||
export type UserPublicProfileByIds = {|
|
||||
@@ -13,7 +19,7 @@ export type UserPublicProfileByIds = {|
|
||||
|
||||
export const searchUserPublicProfilesByUsername = (
|
||||
searchString: string
|
||||
): Promise<Array<UserPublicProfile>> => {
|
||||
): Promise<Array<UserPublicProfileSearch>> => {
|
||||
return axios
|
||||
.get(`${GDevelopUserApi.baseUrl}/user-public-profile/search`, {
|
||||
params: {
|
||||
|
@@ -7,6 +7,7 @@ import SemiControlledMultiAutoComplete from '../UI/SemiControlledMultiAutoComple
|
||||
import {
|
||||
searchUserPublicProfilesByUsername,
|
||||
type UserPublicProfile,
|
||||
type UserPublicProfileSearch,
|
||||
getUserPublicProfilesByIds,
|
||||
} from './GDevelopServices/User';
|
||||
|
||||
@@ -35,7 +36,7 @@ export const UsersAutocomplete = (props: Props) => {
|
||||
const [
|
||||
completionUserPublicProfiles,
|
||||
setCompletionUserPublicProfiles,
|
||||
] = React.useState<Array<UserPublicProfile>>([]);
|
||||
] = React.useState<Array<UserPublicProfileSearch>>([]);
|
||||
const [error, setError] = React.useState(null);
|
||||
|
||||
// Recalculate if the userInput has changed.
|
||||
@@ -132,7 +133,7 @@ export const UsersAutocomplete = (props: Props) => {
|
||||
setUserInput(value);
|
||||
}}
|
||||
dataSource={completionUserPublicProfiles
|
||||
.map((userPublicProfile: UserPublicProfile) => {
|
||||
.map((userPublicProfile: UserPublicProfileSearch) => {
|
||||
if (userPublicProfile.username && userPublicProfile.id) {
|
||||
return {
|
||||
text: userPublicProfile.username,
|
||||
|
@@ -0,0 +1,52 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import muiDecorator from '../ThemeDecorator';
|
||||
import paperDecorator from '../PaperDecorator';
|
||||
|
||||
import ProfileDetails from '../../Profile/ProfileDetails';
|
||||
import { indieUserProfile } from '../../fixtures/GDevelopServicesTestData';
|
||||
import { type Profile as ProfileType } from '../../Utils/GDevelopServices/Authentication';
|
||||
|
||||
const indieUserWithoutUsernameNorDescriptionProfile: ProfileType = {
|
||||
...indieUserProfile,
|
||||
username: null,
|
||||
description: null,
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'ProfileDetails',
|
||||
component: ProfileDetails,
|
||||
decorators: [paperDecorator, muiDecorator],
|
||||
argTypes: {
|
||||
profile: {
|
||||
control: { type: 'radio' },
|
||||
options: ['Complete profile', 'Without username nor bio'],
|
||||
defaultValue: 'Complete profile',
|
||||
mapping: {
|
||||
'Complete profile': indieUserProfile,
|
||||
'Without username nor bio': indieUserWithoutUsernameNorDescriptionProfile,
|
||||
},
|
||||
},
|
||||
onEditProfile: { action: 'edit profile' },
|
||||
},
|
||||
};
|
||||
|
||||
type ArgsTypes = {|
|
||||
profile: ProfileType,
|
||||
onEditProfile: () => void,
|
||||
|};
|
||||
|
||||
export const MyProfile = (args: ArgsTypes) => (
|
||||
<ProfileDetails {...args} isPrivate={true} />
|
||||
);
|
||||
export const OtherUserProfile = (args: ArgsTypes) => (
|
||||
<ProfileDetails {...args} />
|
||||
);
|
||||
export const Loading = (args: ArgsTypes) => (
|
||||
<ProfileDetails {...args} profile={null} />
|
||||
);
|
||||
Loading.argTypes = {
|
||||
profile: { control: { disable: true } },
|
||||
};
|
@@ -86,7 +86,6 @@ import {
|
||||
noSubscription,
|
||||
usagesForIndieUser,
|
||||
indieFirebaseUser,
|
||||
indieUserProfile,
|
||||
fakeNoSubscriptionAuthenticatedUser,
|
||||
fakeIndieAuthenticatedUser,
|
||||
fakeNotAuthenticatedAuthenticatedUser,
|
||||
@@ -3871,19 +3870,6 @@ storiesOf('LimitDisplayer', module)
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('ProfileDetails', module)
|
||||
.addDecorator(paperDecorator)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('profile', () => (
|
||||
<ProfileDetails
|
||||
profile={indieUserProfile}
|
||||
onEditProfile={action('edit profile')}
|
||||
/>
|
||||
))
|
||||
.add('loading', () => (
|
||||
<ProfileDetails profile={null} onEditProfile={action('edit profile')} />
|
||||
));
|
||||
|
||||
storiesOf('SubscriptionDetails', module)
|
||||
.addDecorator(paperDecorator)
|
||||
.addDecorator(muiDecorator)
|
||||
|
Reference in New Issue
Block a user