From f28f837fda9babb035da29a2bbfe5c437df20a31 Mon Sep 17 00:00:00 2001 From: Kushagra Gour <chinchang457@gmail.com> Date: Sun, 28 Apr 2024 17:29:46 +0530 Subject: [PATCH] add fork in header --- src/components/Icons.jsx | 3 ++ src/components/MainHeader.jsx | 90 +++++++++++++++++++++-------------- src/components/app.jsx | 14 ++++++ src/db.js | 2 +- 4 files changed, 72 insertions(+), 37 deletions(-) diff --git a/src/components/Icons.jsx b/src/components/Icons.jsx index 4267e09..1519d27 100644 --- a/src/components/Icons.jsx +++ b/src/components/Icons.jsx @@ -128,6 +128,9 @@ export function Icons() { <symbol id="check-circle" viewBox="0 0 24 24"> <path d="M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z" /> </symbol> + <symbol id="fork" viewBox="0 0 24 24"> + <path d="M13 14c-3.36 0-4.46 1.35-4.82 2.24C9.25 16.7 10 17.76 10 19a3 3 0 0 1-3 3 3 3 0 0 1-3-3c0-1.31.83-2.42 2-2.83V7.83A2.99 2.99 0 0 1 4 5a3 3 0 0 1 3-3 3 3 0 0 1 3 3c0 1.31-.83 2.42-2 2.83v5.29c.88-.65 2.16-1.12 4-1.12 2.67 0 3.56-1.34 3.85-2.23A3.006 3.006 0 0 1 14 7a3 3 0 0 1 3-3 3 3 0 0 1 3 3c0 1.34-.88 2.5-2.09 2.86C17.65 11.29 16.68 14 13 14m-6 4a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1M7 4a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1m10 2a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1Z" /> + </symbol> <symbol id="loader-icon" viewBox="0 0 44 44"> {/* By Sam Herbert (@sherb), for everyone. More http://goo.gl/7AJzbL */} <g fill="none" fillRule="evenodd" strokeWidth={10}> diff --git a/src/components/MainHeader.jsx b/src/components/MainHeader.jsx index d6d37f8..6517c07 100644 --- a/src/components/MainHeader.jsx +++ b/src/components/MainHeader.jsx @@ -4,16 +4,30 @@ import { Trans, NumberFormat, t } from '@lingui/macro'; import { I18n } from '@lingui/react'; import { ProBadge } from './ProBadge'; import { HStack, Stack } from './Stack'; +import { Icon } from './Icons'; const DEFAULT_PROFILE_IMG = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ccc' d='M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z'/%3E%3C/svg%3E"; -export function MainHeader(props) { +export function MainHeader({ + user, + currentItem, + titleInputBlurHandler, + runBtnClickHandler, + assetsBtnHandler, + isFileMode, + onItemFork, + ...props +}) { const isAutoPreviewOn = window.forcedSettings.autoPreview !== undefined ? window.forcedSettings : props.isAutoPreviewOn; + const isNotMine = + currentItem.createdBy && user?.uid !== currentItem.createdBy; + + // console.log(33, currentItem, user?.uid); return ( <I18n> {({ i18n }) => ( @@ -23,15 +37,15 @@ export function MainHeader(props) { id="titleInput" title="Click to edit" class="item-title-input" - value={props.currentItem.title} - onBlur={props.titleInputBlurHandler} + value={currentItem.title} + onBlur={titleInputBlurHandler} /> <div class="main-header__btn-wrap flex flex-v-center"> {!isAutoPreviewOn && ( <button class="btn btn btn--dark flex flex-v-center hint--rounded hint--bottom-left" aria-label={i18n._(t`Run preview (Ctrl/⌘ + Shift + 5)`)} - onClick={props.runBtnClickHandler} + onClick={runBtnClickHandler} > <svg> <use xlinkHref="#play-icon" /> @@ -39,9 +53,8 @@ export function MainHeader(props) { <Trans>Run</Trans> </button> )} - <Button - onClick={props.assetsBtnHandler} + onClick={assetsBtnHandler} data-event-category="ui" data-event-action="addLibraryButtonClick" data-testid="addLibraryButton" @@ -50,8 +63,7 @@ export function MainHeader(props) { > <Trans>Assets</Trans> </Button> - - {!props.isFileMode && ( + {!isFileMode && ( <Button onClick={props.addLibraryBtnHandler} data-event-category="ui" @@ -72,7 +84,6 @@ export function MainHeader(props) { </span> </Button> )} - <button class="btn btn--dark hint--bottom-left" aria-label={i18n._(t`Share this creation publicly`)} @@ -82,9 +93,7 @@ export function MainHeader(props) { <svg viewBox="0 0 24 24" style={{ - fill: props.currentItem.isPublic - ? 'limegreen' - : 'currentColor' + fill: currentItem.isPublic ? 'limegreen' : 'currentColor' }} strokeWidth="2" strokeLinecap="round" @@ -92,7 +101,17 @@ export function MainHeader(props) { > <path d="M18 16.08C17.24 16.08 16.56 16.38 16.04 16.85L8.91 12.7C8.96 12.47 9 12.24 9 12S8.96 11.53 8.91 11.3L15.96 7.19C16.5 7.69 17.21 8 18 8C19.66 8 21 6.66 21 5S19.66 2 18 2 15 3.34 15 5C15 5.24 15.04 5.47 15.09 5.7L8.04 9.81C7.5 9.31 6.79 9 6 9C4.34 9 3 10.34 3 12S4.34 15 6 15C6.79 15 7.5 14.69 8.04 14.19L15.16 18.34C15.11 18.55 15.08 18.77 15.08 19C15.08 20.61 16.39 21.91 18 21.91S20.92 20.61 20.92 19C20.92 17.39 19.61 16.08 18 16.08M18 4C18.55 4 19 4.45 19 5S18.55 6 18 6 17 5.55 17 5 17.45 4 18 4M6 13C5.45 13 5 12.55 5 12S5.45 11 6 11 7 11.45 7 12 6.55 13 6 13M18 20C17.45 20 17 19.55 17 19S17.45 18 18 18 19 18.45 19 19 18.55 20 18 20Z" /> </svg> - {props.currentItem.isPublic ? null : <Trans>Share</Trans>} + {currentItem.isPublic ? null : <Trans>Share</Trans>} + </button> + + <button + class="btn btn--dark hint--bottom-left" + aria-label={i18n._(t`Fork this creation`)} + data-testid="headerForkButton" + onClick={onItemFork} + > + <Icon name="fork" /> + <Trans>Fork</Trans> </button> <button @@ -106,22 +125,25 @@ export function MainHeader(props) { </svg> <Trans>New</Trans> </button> - <button - id="saveBtn" - class={`btn btn--dark hint--rounded hint--bottom-left ${ - props.isSaving ? 'is-loading' : '' - } ${props.unsavedEditCount ? 'is-marked' : 0}`} - aria-label={i18n._(t`Save current creation (Ctrl/⌘ + S)`)} - onClick={props.saveBtnHandler} - > - <svg viewBox="0 0 24 24"> - <path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" /> - </svg> - <svg class="btn-loader" width="15" height="15" stroke="#fff"> - <use xlinkHref="#loader-icon" /> - </svg> - <Trans>Save</Trans> - </button> + + {!isNotMine && ( + <button + id="saveBtn" + class={`btn btn--dark hint--rounded hint--bottom-left ${ + props.isSaving ? 'is-loading' : '' + } ${props.unsavedEditCount ? 'is-marked' : 0}`} + aria-label={i18n._(t`Save current creation (Ctrl/⌘ + S)`)} + onClick={props.saveBtnHandler} + > + <svg viewBox="0 0 24 24"> + <path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" /> + </svg> + <svg class="btn-loader" width="15" height="15" stroke="#fff"> + <use xlinkHref="#loader-icon" /> + </svg> + <Trans>Save</Trans> + </button> + )} <button id="openItemsBtn" class={`btn btn--dark hint--rounded hint--bottom-left ${ @@ -138,7 +160,7 @@ export function MainHeader(props) { </svg> <Trans>Open</Trans> </button> - {!props.user ? ( + {!user ? ( <Button onClick={props.loginBtnHandler} data-event-category="ui" @@ -160,14 +182,10 @@ export function MainHeader(props) { <img id="headerAvatarImg" width="20" - src={ - props.user - ? props.user.photoURL || DEFAULT_PROFILE_IMG - : '' - } + src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''} class="main-header__avatar-img" /> - {props.user?.isPro ? <ProBadge /> : null} + {user?.isPro ? <ProBadge /> : null} </HStack> </Button> )} diff --git a/src/components/app.jsx b/src/components/app.jsx index dd37f83..3640c9f 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -383,9 +383,11 @@ export default class App extends Component { } const fork = JSON.parse(JSON.stringify(sourceItem)); delete fork.id; + delete fork.createdBy; fork.title = '(Forked) ' + sourceItem.title; fork.updatedOn = Date.now(); this.setCurrentItem(fork).then(() => this.refreshEditor()); + route('/create'); alertsService.add(`"${sourceItem.title}" was forked`); trackEvent('fn', 'itemForked'); } @@ -936,6 +938,15 @@ export default class App extends Component { var isNewItem = !this.state.currentItem.id; this.state.currentItem.id = this.state.currentItem.id || 'item-' + generateRandomId(); + if ( + this.state.currentItem.createdBy && + this.state.currentItem.createdBy !== this.state.user.uid + ) { + alertsService.add( + 'You cannot save this item as it was created by someone else. Fork it to save it as your own.' + ); + return; + } this.setState({ isSaving: true }); @@ -1697,6 +1708,9 @@ export default class App extends Component { isFileMode={ this.state.currentItem && this.state.currentItem.files } + onItemFork={() => { + this.forkItem(this.state.currentItem); + }} /> {this.state.currentItem && this.state.currentItem.files ? ( <ContentWrapFiles diff --git a/src/db.js b/src/db.js index 1f56485..c9b8381 100644 --- a/src/db.js +++ b/src/db.js @@ -157,7 +157,7 @@ function getArrayFromQuerySnapshot(querySnapshot) { return {}; } const user = doc.data(); - Object.assign(window.user, user); + window.user = { ...window.user, ...user }; return user; }); }