Skip to content

Commit

Permalink
Add possibility to open tutorial template
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandreSi committed Sep 13, 2024
1 parent d37f5e4 commit eef463a
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 12 deletions.
1 change: 1 addition & 0 deletions newIDE/app/src/MainFrame/EditorContainers/BaseEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export type RenderEditorContainerProps = {|
newProjectSetup: NewProjectSetup,
i18n: I18nType
) => Promise<void>,
onOpenTemplateFromTutorial: (tutorialId: string) => Promise<void>,

// Project save
onSave: () => Promise<void>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type Props = {|
tutorial: Tutorial,
onSelectTutorial: (tutorial: Tutorial) => void,
index: number,
onOpenTemplateFromTutorial: ?(string) => void,
|};

const EducationCurriculumLesson = ({
Expand All @@ -107,6 +108,7 @@ const EducationCurriculumLesson = ({
limits,
onSelectTutorial,
index,
onOpenTemplateFromTutorial,
}: Props) => {
const { isMobile } = useResponsiveWindowSize();
const [isImageLoaded, setIsImageLoaded] = React.useState<boolean>(false);
Expand Down Expand Up @@ -168,6 +170,14 @@ const EducationCurriculumLesson = ({
<ColumnStackLayout justifyContent="space-between" noMargin expand>
<ColumnStackLayout noMargin expand>
{!isMobile && title}
{gameLink && isMobile && !isUpcomingMessage && !isLocked && (
<FlatButton
primary
leftIcon={<Play fontSize="small" />}
label={<Trans>Play game</Trans>}
onClick={() => Window.openExternalURL(gameLink)}
/>
)}
<div style={styles.tagsContainer}>
{tutorial.tagsByLocale &&
tutorial.tagsByLocale.map(tagByLocale => {
Expand All @@ -192,21 +202,33 @@ const EducationCurriculumLesson = ({
noMargin
alignItems="center"
justifyContent={gameLink ? 'space-between' : 'flex-end'}
expand={isMobile}
>
{gameLink && (
{gameLink && !isMobile && (
<FlatButton
primary
leftIcon={<Play fontSize="small" />}
label={<Trans>Play game</Trans>}
onClick={() => Window.openExternalURL(gameLink)}
/>
)}
<RaisedButton
primary
disabled={isLocked}
label={<Trans>Open lesson</Trans>}
onClick={() => onSelectTutorial(tutorial)}
/>
<LineStackLayout noMargin alignItems="center" expand={isMobile}>
{onOpenTemplateFromTutorial && (
<FlatButton
primary
fullWidth={isMobile}
label={<Trans>Open project</Trans>}
onClick={onOpenTemplateFromTutorial}
/>
)}
<RaisedButton
primary
fullWidth={isMobile}
disabled={isLocked}
label={<Trans>Open lesson</Trans>}
onClick={() => onSelectTutorial(tutorial)}
/>
</LineStackLayout>
</LineStackLayout>
)}
</ColumnStackLayout>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ type EducationCurriculumProps = {|
limits: ?Limits,
tutorials: Tutorial[],
onSelectTutorial: Tutorial => void,
onOpenTemplateFromTutorial: string => Promise<void>,
|};

const EducationCurriculum = ({
i18n,
limits,
tutorials,
onSelectTutorial,
onOpenTemplateFromTutorial,
}: EducationCurriculumProps) => {
const listItems: React.Node[] = React.useMemo(
() => {
Expand Down Expand Up @@ -67,13 +69,20 @@ const EducationCurriculum = ({
tutorial={tutorial}
onSelectTutorial={onSelectTutorial}
index={sectionIndex}
onOpenTemplateFromTutorial={
tutorial.templateUrl
? () => {
onOpenTemplateFromTutorial(tutorial.id);
}
: null
}
/>
);
sectionIndex += 1;
});
return items;
},
[tutorials, i18n, limits, onSelectTutorial]
[tutorials, i18n, limits, onSelectTutorial, onOpenTemplateFromTutorial]
);

return (
Expand All @@ -100,9 +109,15 @@ type Props = {|
onBack: () => void,
tutorials: Array<Tutorial>,
category: TutorialCategory,
onOpenTemplateFromTutorial: string => Promise<void>,
|};

const TutorialsCategoryPage = ({ category, tutorials, onBack }: Props) => {
const TutorialsCategoryPage = ({
category,
tutorials,
onBack,
onOpenTemplateFromTutorial,
}: Props) => {
const { limits } = React.useContext(AuthenticatedUserContext);
const texts = TUTORIAL_CATEGORY_TEXTS[category];
const filteredTutorials = tutorials.filter(
Expand All @@ -129,6 +144,7 @@ const TutorialsCategoryPage = ({ category, tutorials, onBack }: Props) => {
onSelectTutorial={setSelectedTutorial}
i18n={i18n}
limits={limits}
onOpenTemplateFromTutorial={onOpenTemplateFromTutorial}
/>
) : (
<ImageTileGrid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,15 @@ type Props = {|
onTabChange: (tab: HomeTab) => void,
selectInAppTutorial: (tutorialId: string) => void,
initialCategory: TutorialCategory | null,
onOpenTemplateFromTutorial: string => Promise<void>,
|};

const LearnSection = ({
onOpenExampleStore,
onTabChange,
selectInAppTutorial,
initialCategory,
onOpenTemplateFromTutorial,
}: Props) => {
const {
tutorials,
Expand Down Expand Up @@ -181,6 +183,7 @@ const LearnSection = ({
onBack={() => setSelectedCategory(null)}
category={selectedCategory}
tutorials={tutorials}
onOpenTemplateFromTutorial={onOpenTemplateFromTutorial}
/>
);
};
Expand Down
4 changes: 4 additions & 0 deletions newIDE/app/src/MainFrame/EditorContainers/HomePage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ type Props = {|
newProjectSetup: NewProjectSetup,
i18n: I18nType
) => Promise<void>,
onOpenTemplateFromTutorial: (tutorialId: string) => Promise<void>,

// Project save
onSave: () => Promise<void>,
Expand Down Expand Up @@ -174,6 +175,7 @@ export const HomePage = React.memo<Props>(
canSave,
resourceManagementProps,
askToCloseProject,
onOpenTemplateFromTutorial,
}: Props,
ref
) => {
Expand Down Expand Up @@ -528,6 +530,7 @@ export const HomePage = React.memo<Props>(
onOpenExampleStore={onOpenExampleStore}
onTabChange={setActiveTab}
selectInAppTutorial={selectInAppTutorial}
onOpenTemplateFromTutorial={onOpenTemplateFromTutorial}
initialCategory={learnInitialCategory}
/>
)}
Expand Down Expand Up @@ -638,6 +641,7 @@ export const renderHomePageContainer = (
}
onOpenNewProjectSetupDialog={props.onOpenNewProjectSetupDialog}
onOpenProjectManager={props.onOpenProjectManager}
onOpenTemplateFromTutorial={props.onOpenTemplateFromTutorial}
onOpenLanguageDialog={props.onOpenLanguageDialog}
onOpenProfile={props.onOpenProfile}
onCreateProjectFromExample={props.onCreateProjectFromExample}
Expand Down
30 changes: 30 additions & 0 deletions newIDE/app/src/MainFrame/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ const MainFrame = (props: Props) => {
createProjectFromExample,
createProjectFromPrivateGameTemplate,
createProjectFromInAppTutorial,
createProjectFromTutorial,
createProjectWithLogin,
createProjectFromAIGeneration,
} = useCreateProject({
Expand Down Expand Up @@ -2984,6 +2985,34 @@ const MainFrame = (props: Props) => {
}
};

const openTemplateFromTutorial = React.useCallback(
async (tutorialId: string) => {
const projectIsClosed = await askToCloseProject();
if (!projectIsClosed) {
return;
}
try {
await createProjectFromTutorial(tutorialId, {
storageProvider: emptyStorageProvider,
saveAsLocation: null,
// Remaining will be set by the template.
});
} catch (error) {
showErrorBox({
message: i18n._(
t`Unable to create a new project for the tutorial. Try again later.`
),
rawError: new Error(
`Can't create project from template of tutorial "${tutorialId}"`
),
errorId: 'cannot-create-project-from-tutorial-template',
});
return;
}
},
[askToCloseProject, createProjectFromTutorial, i18n]
);

const startSelectedTutorial = React.useCallback(
async (scenario: 'resume' | 'startOver' | 'start') => {
if (!selectedInAppTutorialInfo) return;
Expand Down Expand Up @@ -3474,6 +3503,7 @@ const MainFrame = (props: Props) => {
openSceneEditor: false,
});
},
onOpenTemplateFromTutorial: openTemplateFromTutorial,
previewDebuggerServer,
hotReloadPreviewButtonProps,
onOpenLayout: name => {
Expand Down
5 changes: 3 additions & 2 deletions newIDE/app/src/Utils/GDevelopServices/Tutorial.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ export type Tutorial = {|
isPrivateTutorial?: boolean,
redeemHintByLocale?: MessageByLocale,
redeemLinkByLocale?: MessageByLocale,
sectionByLocale?: MessageByLocale;
tagsByLocale?: MessageByLocale[];
sectionByLocale?: MessageByLocale,
tagsByLocale?: MessageByLocale[],
availableAt?: string,
gameLink?: string,
templateUrl?: string,
|};

export const canAccessTutorial = (
Expand Down
30 changes: 30 additions & 0 deletions newIDE/app/src/Utils/UseCreateProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from './GDevelopServices/Shop';
import { createPrivateGameTemplateUrl } from './GDevelopServices/Asset';
import { getDefaultRegisterGamePropertiesFromProject } from './UseGameAndBuildsManager';
import { TutorialContext } from '../Tutorial/TutorialContext';

type Props = {|
beforeCreatingProject: () => void,
Expand Down Expand Up @@ -80,6 +81,7 @@ const useCreateProject = ({
const { getInAppTutorialShortHeader } = React.useContext(
InAppTutorialContext
);
const { tutorials } = React.useContext(TutorialContext);

const initialiseProjectProperties = (
project: gdProject,
Expand Down Expand Up @@ -346,6 +348,33 @@ const useCreateProject = ({
[beforeCreatingProject, createProject, getInAppTutorialShortHeader]
);

const createProjectFromTutorial = React.useCallback(
async (tutorialId: string, newProjectSetup: NewProjectSetup) => {
beforeCreatingProject();
if (!tutorials) {
throw new Error(`Tutorials could not be loaded`);
}
const selectedTutorial = tutorials.find(
tutorial => tutorial.id === tutorialId
);
if (!selectedTutorial) {
throw new Error(`No tutorial found for id "${tutorialId}"`);
}
const { templateUrl } = selectedTutorial;
if (!templateUrl) {
throw new Error(`No template URL for the tutorial "${tutorialId}"`);
}
const newProjectSource = await createNewProjectFromTutorialTemplate(
templateUrl,
tutorialId
);
await createProject(newProjectSource, newProjectSetup, {
openAllScenes: true,
});
},
[beforeCreatingProject, createProject, tutorials]
);

const createProjectWithLogin = React.useCallback(
async (newProjectSetup: NewProjectSetup) => {
beforeCreatingProject();
Expand All @@ -371,6 +400,7 @@ const useCreateProject = ({
createProjectFromExample,
createProjectFromPrivateGameTemplate,
createProjectFromInAppTutorial,
createProjectFromTutorial,
createProjectWithLogin,
createProjectFromAIGeneration,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export const fakeTutorials: Array<Tutorial> = [
tagsByLocale: [{ en: 'Single player' }, { en: 'Beginner' }],
sectionByLocale: { en: 'Practical lessons' },
gameLink: 'https://gd.games/gdevelop/flappy-cat',
templateUrl:
'https://resources.gdevelop-app.com/tutorials/templates/flappy-cat/game.json',
},
{
id: 'education-curriculum-angry-pigs',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ const WrappedHomePage = ({
onOpenNewProjectSetupDialog={() =>
action('onOpenNewProjectSetupDialog')()
}
onOpenTemplateFromTutorial={() =>
action('onOpenTemplateFromTutorial')()
}
canSave={true}
onSave={() => action('onSave')()}
selectInAppTutorial={() => action('select in app tutorial')()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export default {
};

export const Default = () => (
<AuthenticatedUserContext.Provider value={fakeAuthenticatedUserWithNoSubscription}>
<AuthenticatedUserContext.Provider
value={fakeAuthenticatedUserWithNoSubscription}
>
<PreferencesContext.Provider value={initialPreferences}>
<TutorialContext.Provider
value={{
Expand All @@ -38,6 +40,7 @@ export const Default = () => (
onOpenExampleStore={action('onOpenExampleStore')}
onTabChange={() => {}}
selectInAppTutorial={action('selectInAppTutorial')}
onOpenTemplateFromTutorial={action('onOpenTemplateFromTutorial')}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>
Expand All @@ -61,6 +64,7 @@ export const EducationSubscriber = () => (
onOpenExampleStore={action('onOpenExampleStore')}
onTabChange={() => {}}
selectInAppTutorial={action('selectInAppTutorial')}
onOpenTemplateFromTutorial={action('onOpenTemplateFromTutorial')}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>
Expand All @@ -84,6 +88,7 @@ export const EducationTeacher = () => (
onOpenExampleStore={action('onOpenExampleStore')}
onTabChange={() => {}}
selectInAppTutorial={action('selectInAppTutorial')}
onOpenTemplateFromTutorial={action('onOpenTemplateFromTutorial')}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>
Expand All @@ -104,6 +109,7 @@ export const Loading = () => (
onOpenExampleStore={action('onOpenExampleStore')}
onTabChange={() => {}}
selectInAppTutorial={action('selectInAppTutorial')}
onOpenTemplateFromTutorial={action('onOpenTemplateFromTutorial')}
/>
</TutorialContext.Provider>
</PreferencesContext.Provider>
Expand Down

0 comments on commit eef463a

Please sign in to comment.