Skip to content

Commit

Permalink
Merge pull request #147 from Program-AR/url-share
Browse files Browse the repository at this point in the history
Url share
  • Loading branch information
tfloxolodeiro authored Dec 11, 2023
2 parents 12addc9 + 56d65be commit 6df07ff
Show file tree
Hide file tree
Showing 15 changed files with 390 additions and 20 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
runs-on: ubuntu-latest
env:
REACT_APP_API_URL: ${{ secrets.API_URL }}
REACT_APP_PB_APP_URL: ${{ secrets.APP_URL }}
REACT_APP_GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }}
REACT_APP_VERSION: ${{github.ref_name}}
if: startsWith(github.ref, 'refs/tags')
Expand All @@ -55,6 +56,7 @@ jobs:
runs-on: macos-latest
env:
REACT_APP_API_URL: ${{ secrets.API_URL }}
REACT_APP_PB_APP_URL: ${{ secrets.APP_URL }}
REACT_APP_GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }}
REACT_APP_VERSION: ${{github.ref_name}}
if: startsWith(github.ref, 'refs/tags')
Expand All @@ -77,6 +79,7 @@ jobs:
runs-on: windows-latest
env:
REACT_APP_API_URL: ${{ secrets.API_URL }}
REACT_APP_PB_APP_URL: ${{ secrets.APP_URL }}
REACT_APP_GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }}
REACT_APP_VERSION: ${{github.ref_name}}
if: startsWith(github.ref, 'refs/tags')
Expand All @@ -99,6 +102,7 @@ jobs:
runs-on: ubuntu-latest
env:
REACT_APP_API_URL: ${{ secrets.API_URL }}
REACT_APP_PB_APP_URL: ${{ secrets.APP_URL }}
REACT_APP_GOOGLE_ANALYTICS_KEY: ${{ secrets.GOOGLE_ANALYTICS_KEY }}
REACT_APP_VERSION: ${{github.ref_name}}
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
Expand Down
15 changes: 13 additions & 2 deletions locales/en-us/creator.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,16 @@
"buttons": {
"discardChallenge": "Discard challenge",
"discardChallengeShort": "Discard",
"download": "Download",
"download": "Download challenge",
"downloadShort": "Download",
"share": "Share challenge",
"shareUrl": "Share via url",
"shareUrlShort": "Share",
"save": "Save challenge",
"saveShort": "Save",
"savedCorrectly": "Challenge was saved correctly",
"copyToClipboard": "Copy to clipboard",
"copiedToClipboard": "Copied to clipboard",
"preview": "Challenge preview",
"previewShort": "Preview",
"keepEditing": "Keep editing",
Expand All @@ -108,7 +117,9 @@

},
"editorHeader": "Edit challenge",
"previewModeHeader": "Challenge preview"
"previewModeHeader": "Challenge preview",
"serverError": "There's a problem with the server, try again later",
"loginWarning": "You need to be logged in to share a challenge via url."
},
"title": {
"title": "Write the challenge's title for ",
Expand Down
15 changes: 13 additions & 2 deletions locales/es-ar/creator.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,16 @@
"buttons": {
"discardChallenge": "Descartar desafío",
"discardChallengeShort": "Descartar",
"download": "Descargar",
"download": "Descargar desafío",
"downloadShort": "Descargar",
"share": "Compartir desafío",
"shareUrl": "Compartir por url",
"shareUrlShort": "Compartir",
"copyToClipboard": "Copiar al portapapeles",
"copiedToClipboard": "Copiado al portapapeles",
"save": "Guardar desafío",
"saveShort": "Guardar",
"savedCorrectly": "El desafío fue guardado correctamente",
"preview": "Ver desafío",
"previewShort": "Ver",
"keepEditing": "Seguir editando",
Expand All @@ -106,7 +115,9 @@
"loadChallengeShort": "Abrir"
},
"editorHeader": "Editar desafío",
"previewModeHeader": "Ver desafío"
"previewModeHeader": "Ver desafío",
"serverError": "Problema con el servidor, intente más tarde",
"loginWarning": "Para compartir un desafío por URL es necesario estar loggeado."
},
"title": {
"title": "Escribí el título del desafío para ",
Expand Down
1 change: 1 addition & 0 deletions sample.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
REACT_APP_API_URL=http://localhost:3001
REACT_APP_PB_APP_URL=http://localhost:3000
REACT_APP_VERSION=$npm_package_version
REACT_APP_GOOGLE_ANALYTICS_KEY=G-xxxxxx #Not necessary, can be omitted for development
16 changes: 14 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { useLocation } from 'react-router-dom';
import ReactGA from "react-ga4";
import { CreatorViewMode } from './components/creator/Editor/CreatorViewMode';
import { useThemeContext } from './theme/ThemeContext';
import { SharedChallengeView } from './components/creator/SharedChallengeView';
import { PilasBloquesApi } from './pbApi';
import { Ember } from './emberCommunication';

const AnalyticsComponent = () => {
const location = useLocation();
Expand All @@ -38,6 +41,16 @@ const router = createHashRouter([{
element: <Home/>,
errorElement: <PBError />
},
{
path: "/desafio/guardado/:id",
element: <SharedChallengeView/>,
errorElement: <PBError />,
loader: async ({ params }) => {
const challenge = await PilasBloquesApi.getSharedChallenge(params.id!);
Ember.importChallenge(challenge)
return challenge
},
},
{
path: "/libros/:id",
element: <BookView/>,
Expand Down Expand Up @@ -103,5 +116,4 @@ function App() {
);
}

export default App;

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { CreatorActionButton } from "../CreatorActionButton";
import DownloadIcon from '@mui/icons-material/Download';
import { useContext, useState } from "react";
import { Dialog, DialogContent, DialogTitle, InputAdornment, Stack, TextField } from "@mui/material";
import { CreatorContext } from "../../CreatorContext";
import { ShareButtons, CopyToClipboardButton } from "./ShareModalButtons";
import { useTranslation } from "react-i18next";

export const ShareButton = () => {

const [dialogOpen, setDialogOpen] = useState<boolean>(false)

return <>
<ShareDialog open={dialogOpen} setDialogOpen={setDialogOpen} />
<CreatorActionButton onClick={() => { setDialogOpen(true) }} startIcon={<DownloadIcon />} nametag='share' isshortversion={true} />
</>

}

const ShareDialog = ({ open, setDialogOpen }: { open: boolean, setDialogOpen: (open: boolean) => void }) => {

const { t } = useTranslation('creator');

return <>
<Dialog open={open} onClose={() => { setDialogOpen(false) }}>
<DialogTitle>{t('editor.buttons.share')}</DialogTitle>
<DialogContent >
<ShareModal />
</DialogContent>
</Dialog >
</>
}

export const ShareModal = () => {
const { sharedId } = useContext(CreatorContext)

const sharedLink = process.env.REACT_APP_PB_APP_URL + `/#/desafio/guardado/${sharedId}`

return <Stack>
{sharedId ?
<Stack direction='row'>
<TextField
sx={{ width: '100%', margin: 1}}
defaultValue={sharedLink}
InputProps={{
readOnly: true,
endAdornment: (
<InputAdornment position="end">
<CopyToClipboardButton textToCopy={sharedLink} />
</InputAdornment>
)
}}
/>
</Stack>
: <></>
}
<ShareButtons />
</Stack>
}




Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import ShareIcon from '@mui/icons-material/Share';
import SaveIcon from '@mui/icons-material/Save';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import { ReactNode, useContext, useState } from "react"
import { IconButtonTooltip } from "../../SceneEdition/IconButtonTooltip"
import { Snackbar, Stack, Tooltip } from "@mui/material"
import { CreatorContext } from '../../CreatorContext';
import { LocalStorage } from '../../../../../localStorage';
import { PilasBloquesApi } from '../../../../../pbApi';
import { CreatorActionButton } from '../CreatorActionButton';
import { DialogSnackbar } from '../../../../dialogSnackbar/DialogSnackbar';
import { useTranslation } from 'react-i18next';
import { SerializedChallenge } from '../../../../serializedChallenge';
import { DownloadButton } from '../DownloadButton';

export const CopyToClipboardButton = ({ textToCopy }: { textToCopy: string }) => {

const [openSnackbar, setOpenSnackbar] = useState(false)

const { t } = useTranslation('creator');

const handleClick = () => {
setOpenSnackbar(true)
navigator.clipboard.writeText(textToCopy)
}

return <>
<IconButtonTooltip icon={<ContentCopyIcon />} onClick={handleClick} tooltip={t('editor.buttons.copyToClipboard')} />
<Snackbar
open={openSnackbar}
onClose={() => setOpenSnackbar(false)}
autoHideDuration={2000}
message={t('editor.buttons.copiedToClipboard')}
/>
</>
}

export const ShareButtons = () => {
const { sharedId } = useContext(CreatorContext)

return <>
<Stack direction="row" justifyContent="space-between" alignItems='center'>
{// If the challenge has already been saved, show Save, else show Share, which saves for the first time.
sharedId ? <SaveButton /> : <ShareUrlButton />
}
<DownloadButton />
</Stack>
</>
}

const ShareUrlButton = () =>
<ChallengeUpsertButton Icon={<ShareIcon />} nametag="shareUrl" challengeUpsert={PilasBloquesApi.shareChallenge} />

const SaveButton = () =>
<ChallengeUpsertButton Icon={<SaveIcon />} nametag="save" challengeUpsert={PilasBloquesApi.saveChallenge} />

export const ChallengeUpsertButton = ({ Icon, challengeUpsert, nametag }: { Icon: ReactNode, nametag: string, challengeUpsert: (challenge: SerializedChallenge) => Promise<SerializedChallenge> }) => {

const { setSharedId } = useContext(CreatorContext)
const userLoggedIn = !!LocalStorage.getUser()
const [serverError, setServerError] = useState<boolean>(false)
const { t } = useTranslation('creator');
const [savedSnackbar, setSavedSnackbarOpen] = useState(false)

const handleClick = async () => {
try {
const savedChallenge = await challengeUpsert(LocalStorage.getCreatorChallenge()!)
setSharedId(savedChallenge.sharedId!)
setSavedSnackbarOpen(true)
}
catch (error) {
setServerError(true)
}
}

return <>
<Snackbar
open={savedSnackbar}
onClose={() => setSavedSnackbarOpen(false)}
autoHideDuration={2000}
message={t('editor.buttons.savedCorrectly')}
/>
<Tooltip title={!userLoggedIn ? t('editor.loginWarning') : ''} followCursor>
<div>
<CreatorActionButton data-testid="upsertButton" onClick={handleClick} disabled={!userLoggedIn} startIcon={Icon} variant='contained' nametag={nametag} />
</div>
</Tooltip>
<DialogSnackbar open={serverError} onClose={() => setServerError(false)} message={t('editor.serverError')} />
</>
}
14 changes: 10 additions & 4 deletions src/components/creator/Editor/CreatorContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export type CreatorContextType = {
index: number;
setIndex: (index: number) => void;
setMaps: (maps: any) => void;
maps: SceneMap[]
maps: SceneMap[];
sharedId: string
setSharedId: (id: string) => void
};

const defaultCreatorContext = {
Expand All @@ -22,7 +24,9 @@ const defaultCreatorContext = {
index: 0,
setIndex: () => { },
setMaps: () => { },
maps: defaultChallenge('Duba').scene.maps
maps: defaultChallenge('Duba').scene.maps,
sharedId: "",
setSharedId: (id: string) => {}
}

export const CreatorContext = React.createContext<CreatorContextType>(defaultCreatorContext);
Expand All @@ -39,6 +43,7 @@ export const CreatorContextProvider: React.FC<CreatorProviderProps> = ({ childre
const challenge = LocalStorage.getCreatorChallenge() || defaultChallenge("Duba")
const [maps, setMaps] = useState(challenge.scene.maps)
const [index, setIndex] = useState(defaultIndex)
const [sharedId, setSharedId] = useState(challenge.sharedId || "")

const currentMap = maps[index] || challenge.scene.maps[index]

Expand All @@ -49,11 +54,12 @@ export const CreatorContextProvider: React.FC<CreatorProviderProps> = ({ childre

useEffect(() => {
challenge.scene.maps = maps
challenge.sharedId = sharedId
LocalStorage.saveCreatorChallenge(challenge)
}, [maps, challenge])
}, [maps, challenge, sharedId])

return (
<CreatorContext.Provider value={{ selectedTool, setSelectedTool, currentMap, setCurrentMap, index, setIndex, setMaps, maps}}>
<CreatorContext.Provider value={{ selectedTool, setSelectedTool, currentMap, setCurrentMap, sharedId: sharedId, setSharedId: setSharedId, index, setIndex, setMaps, maps}}>
{children}
</CreatorContext.Provider>
);
Expand Down
6 changes: 3 additions & 3 deletions src/components/creator/Editor/CreatorViewMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ export const CreatorViewMode = () => {
return (<>
{challengeExists ? (
<>
<Header CenterComponent={<CreatorViewHeader challenge={challengeBeingEdited} />} SubHeader={<EditorSubHeader viewButton={<ReturnToEditionButton />} />} />
<Header CenterComponent={<CreatorViewHeader title={challengeBeingEdited.title} />} SubHeader={<EditorSubHeader viewButton={<ReturnToEditionButton />} />} />
<EmberView height='calc(100% - var(--creator-subheader-height))' path={EMBER_IMPORTED_CHALLENGE_PATH} />
</>
) : <></>}
</>)
}

const CreatorViewHeader = ({ challenge }: { challenge: SerializedChallenge }) => {
export const CreatorViewHeader = ({ title }: { title: string }) => {
const { t } = useTranslation('creator')

return <BetaBadge smaller={true}>
<PBreadcrumbs>
<HeaderText text={t("editor.previewModeHeader")} />
<Typography>{challenge.title}</Typography>
<Typography>{title}</Typography>
</PBreadcrumbs>
</BetaBadge>

Expand Down
4 changes: 2 additions & 2 deletions src/components/creator/Editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { SceneEdition } from "./SceneEdition/SceneEdition";
import { CreatorContextProvider } from "./CreatorContext";
import { CreatorSubHeader } from "./EditorSubHeader/CreatorSubHeader";
import { useTranslation } from "react-i18next";
import { DownloadButton } from "./ActionButtons/DownloadButton";
import { DiscardChallengeButton } from "./ActionButtons/DiscardChallengeButton";
import { PreviewButton } from "./ActionButtons/PreviewButton";
import { BetaBadge } from "../BetaBadge";
import { useThemeContext } from "../../../theme/ThemeContext";
import { useNavigate } from "react-router-dom";
import { LocalStorage } from "../../../localStorage";
import { useEffect } from "react";
import { ShareButton } from "./ActionButtons/ShareChallenge/ShareButton";

export const CreatorEditor = () => {
const { theme } = useThemeContext()
Expand Down Expand Up @@ -51,5 +51,5 @@ export const EditorSubHeader = (props: EditorSubHeaderProps) =>
<CreatorSubHeader>
<DiscardChallengeButton />
{props.viewButton}
<DownloadButton />
<ShareButton/>
</CreatorSubHeader>
16 changes: 16 additions & 0 deletions src/components/creator/SharedChallengeView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { LocalStorage } from "../../localStorage"
import { EmberView } from "../emberView/EmberView"
import { Header } from "../header/Header"
import { EMBER_IMPORTED_CHALLENGE_PATH } from "../ImportedChallengeView"
import { CreatorViewHeader } from "./Editor/CreatorViewMode"

export const SharedChallengeView = () => {

const challenge = LocalStorage.getImportedChallenge()

return <>
<Header CenterComponent={<CreatorViewHeader title={challenge.titulo} />} />
<EmberView height='100%' path={EMBER_IMPORTED_CHALLENGE_PATH} />
</>
}

Loading

0 comments on commit 6df07ff

Please sign in to comment.