From 959fef487f33b3824b9370f6904c7b948e363793 Mon Sep 17 00:00:00 2001 From: Kushagra Gour <chinchang457@gmail.com> Date: Sat, 2 Jun 2018 22:55:44 +0530 Subject: [PATCH] complete functionality for header --- webmaker/src/components/ContentWrap.jsx | 59 +++++---- webmaker/src/components/Login.jsx | 12 -- webmaker/src/components/MainHeader.jsx | 6 +- webmaker/src/components/SavedItemPane.jsx | 14 ++ webmaker/src/components/app.jsx | 154 +++++++++++++++++++--- 5 files changed, 183 insertions(+), 62 deletions(-) diff --git a/webmaker/src/components/ContentWrap.jsx b/webmaker/src/components/ContentWrap.jsx index 6a9694a..ff3ddbe 100644 --- a/webmaker/src/components/ContentWrap.jsx +++ b/webmaker/src/components/ContentWrap.jsx @@ -25,17 +25,29 @@ export default class ContentWrap extends Component { onHtmlCodeChange(editor, change) { this.cmCodes.html = editor.getValue(); - this.props.onCodeChange('html', this.cmCodes.html); + this.props.onCodeChange( + 'html', + this.cmCodes.html, + change.origin !== 'setValue' + ); this.onCodeChange(editor, change); } onCssCodeChange(editor, change) { this.cmCodes.css = editor.getValue(); - this.props.onCodeChange('css', this.cmCodes.css); + this.props.onCodeChange( + 'css', + this.cmCodes.css, + change.origin !== 'setValue' + ); this.onCodeChange(editor, change); } onJsCodeChange(editor, change) { this.cmCodes.js = editor.getValue(); - this.props.onCodeChange('js', this.cmCodes.js); + this.props.onCodeChange( + 'js', + this.cmCodes.js, + change.origin !== 'setValue' + ); this.onCodeChange(editor, change); } onCodeChange(editor, change) { @@ -51,25 +63,11 @@ export default class ContentWrap extends Component { this.setPreviewContent(); } - /* saveBtn.classList.add('is-marked'); - this.unsavedEditCount += 1; - if ( - this.unsavedEditCount % this.unsavedEditWarningCount === 0 && - this.unsavedEditCount >= this.unsavedEditWarningCount - ) { - saveBtn.classList.add('animated'); - saveBtn.classList.add('wobble'); - saveBtn.addEventListener('animationend', () => { - saveBtn.classList.remove('animated'); - saveBtn.classList.remove('wobble'); - }); - } */ - // Track when people actually are working. - // trackEvent.previewCount = (trackEvent.previewCount || 0) + 1; - // if (trackEvent.previewCount === 4) { - // trackEvent('fn', 'usingPreview'); - // } + trackEvent.previewCount = (trackEvent.previewCount || 0) + 1; + if (trackEvent.previewCount === 4) { + trackEvent('fn', 'usingPreview'); + } } }, this.updateDelay); } @@ -292,17 +290,26 @@ export default class ContentWrap extends Component { this.codeInPreview.js = currentCode.js; } componentDidUpdate() { + this.refreshEditor(); + // this.setPreviewContent(true); + // console.log('componentdidupdate', this.props.currentItem); + } + componentDidMount() { + this.props.onRef(this); + } + refreshEditor() { + console.log('ContentWrap refresh editor'); this.cmCodes.html = this.props.currentItem.html; this.cmCodes.css = this.props.currentItem.css; this.cmCodes.js = this.props.currentItem.js; this.cm.html.setValue(this.cmCodes.html || ''); this.cm.css.setValue(this.cmCodes.css || ''); this.cm.js.setValue(this.cmCodes.js || ''); - // this.setPreviewContent(true); - // console.log('componentdidupdate', this.props.currentItem); - } - componentDidMount() { - this.props.onRef(this); + this.cm.html.refresh(); + this.cm.css.refresh(); + this.cm.js.refresh(); + + this.setPreviewContent(true); } applyCodemirrorSettings(prefs) { if (!this.cm) { diff --git a/webmaker/src/components/Login.jsx b/webmaker/src/components/Login.jsx index 5b73786..fe46299 100644 --- a/webmaker/src/components/Login.jsx +++ b/webmaker/src/components/Login.jsx @@ -9,18 +9,6 @@ export default class Login extends Component { trackEvent('ui', 'loginProviderClick', provider); auth.login(provider); } - logout(e) { - if (this.unsavedEditCount) { - var shouldDiscard = confirm( - 'You have unsaved changes. Do you still want to logout?' - ); - if (!shouldDiscard) { - return; - } - } - trackEvent('fn', 'loggedOut'); - auth.logout(); - } render() { return ( <div> diff --git a/webmaker/src/components/MainHeader.jsx b/webmaker/src/components/MainHeader.jsx index 8dae8cb..9cf420a 100644 --- a/webmaker/src/components/MainHeader.jsx +++ b/webmaker/src/components/MainHeader.jsx @@ -9,7 +9,7 @@ export default class Header extends Component { id="titleInput" title="Click to edit" class="item-title-input" - value="Untitled Work" + value={this.props.title} onBlur={this.props.titleInputBlurHandler} /> <div class="main-header__btn-wrap flex flex-v-center"> @@ -46,7 +46,7 @@ export default class Header extends Component { <a class="flex flex-v-center hint--rounded hint--bottom-left" aria-label="Start a new creation" - d-click="onNewBtnClick" + onClick={this.props.newBtnHandler} > <svg style="vertical-align:middle;width:14px;height:14px" @@ -59,7 +59,7 @@ export default class Header extends Component { id="saveBtn" class={`flex flex-v-center hint--rounded hint--bottom-left ${ this.props.isSaving ? 'is-loading' : '' - }`} + } ${this.props.unsavedEditCount ? 'is-marked' : 0}`} aria-label="Save current creation (Ctrl/⌘ + S)" onClick={this.props.saveBtnHandler} > diff --git a/webmaker/src/components/SavedItemPane.jsx b/webmaker/src/components/SavedItemPane.jsx index a666516..e65bd82 100644 --- a/webmaker/src/components/SavedItemPane.jsx +++ b/webmaker/src/components/SavedItemPane.jsx @@ -5,6 +5,17 @@ export default class SavedItemPane extends Component { onCloseIntent() { this.props.closeHandler(); } + itemClickHandler(item) { + this.props.itemClickHandler(item); + } + itemRemoveBtnClickHandler(item, e) { + e.stopPropagation(); + this.props.itemRemoveBtnClickHandler(item); + } + itemForkBtnClickHandler(item, e) { + e.stopPropagation(); + this.props.itemForkBtnClickHandler(item); + } render() { return ( <div @@ -60,17 +71,20 @@ export default class SavedItemPane extends Component { <div class="js-saved-item-tile saved-item-tile" data-item-id={item.id} + onClick={this.itemClickHandler.bind(this, item)} > <div class="saved-item-tile__btns"> <a class="js-saved-item-tile__fork-btn saved-item-tile__btn hint--left hint--medium" aria-label="Creates a duplicate of this creation (Ctrl/⌘ + F)" + onClick={this.itemForkBtnClickHandler.bind(this, item)} > Fork<span class="show-when-selected">(Ctrl/⌘ + F)</span> </a> <a class="js-saved-item-tile__remove-btn saved-item-tile__btn hint--left" aria-label="Remove" + onClick={this.itemRemoveBtnClickHandler.bind(this, item)} > X </a> diff --git a/webmaker/src/components/app.jsx b/webmaker/src/components/app.jsx index 31d5fc8..525ba24 100644 --- a/webmaker/src/components/app.jsx +++ b/webmaker/src/components/app.jsx @@ -21,6 +21,7 @@ import { alertsService } from '../notifications'; import firebase from 'firebase/app'; import 'firebase/auth'; import Profile from './Profile'; +import { auth } from '../auth'; if (module.hot) { require('preact/debug'); @@ -30,6 +31,7 @@ const LocalStorageKeys = { LOGIN_AND_SAVE_MESSAGE_SEEN: 'loginAndsaveMessageSeen', ASKED_TO_IMPORT_CREATIONS: 'askedToImportCreations' }; +const UNSAVED_WARNING_COUNT = 15; export default class App extends Component { constructor() { @@ -71,7 +73,8 @@ export default class App extends Component { preserveConsoleLogs: true, lightVersion: false, lineWrap: true, - infiniteLoopTimeout: 1000 + infiniteLoopTimeout: 1000, + layoutMode: 2 }; this.prefs = {}; @@ -131,7 +134,7 @@ export default class App extends Component { }, result => { // this.toggleLayout(result.layoutMode); - this.prefs.layoutMode = result.layoutMode; + this.state.prefs.layoutMode = result.layoutMode; if (result.code) { lastCode = result.code; } @@ -174,7 +177,31 @@ export default class App extends Component { } } - refreshEditor() {} + refreshEditor() { + this.toggleLayout( + this.state.currentItem.layoutMode || this.state.prefs.layoutMode + ); + // this.contentWrap.refreshEditor(); + } + // Creates a new item with passed item's contents + forkItem(sourceItem) { + if (this.state.unsavedEditCount) { + var shouldDiscard = confirm( + 'You have unsaved changes in your current work. Do you want to discard unsaved changes and continue?' + ); + if (!shouldDiscard) { + return; + } + } + const fork = JSON.parse(JSON.stringify(sourceItem)); + delete fork.id; + fork.title = '(Forked) ' + sourceItem.title; + fork.updatedOn = Date.now(); + this.setCurrentItem(fork); + this.refreshEditor(); + alertsService.add(`"${sourceItem.title}" was forked`); + trackEvent('fn', 'itemForked'); + } createNewItem() { var d = new Date(); this.setCurrentItem({ @@ -196,8 +223,43 @@ export default class App extends Component { this.refreshEditor(); alertsService.add('New item created'); } + openItem(item) { + // console.log(itemId, this.state.savedItems) + + this.setCurrentItem(item); + this.refreshEditor(); + alertsService.add('Saved item loaded'); + } + removeItem(itemId) { + var answer = confirm( + `Are you sure you want to delete "${savedItems[itemId].title}"?` + ); + if (!answer) { + return; + } + + // Remove from items list + itemService.unsetItemForUser(itemId); + + // Remove individual item too. + itemService.removeItem(itemId).then(() => { + alertsService.add('Item removed.'); + // This item is open in the editor. Lets open a new one. + if (this.state.currentItem.id === itemId) { + this.createNewItem(); + } + }); + + // Remove from cached list + delete this.state.savedItems[itemId]; + this.setState({ + savedItems: { ...this.state.savedItems } + }); + + trackEvent('fn', 'itemRemoved'); + } setCurrentItem(item) { - this.state.currentItem = item; + this.setState({ currentItem: item }); log('Current Item set', item); // Reset auto-saving flag @@ -373,8 +435,8 @@ export default class App extends Component { d.getSeconds() ].join('-'); - if (currentItem.title) { - fileName = currentItem.title; + if (this.state.currentItem.title) { + fileName = this.state.currentItem.title; } fileName += '.html'; @@ -440,8 +502,8 @@ export default class App extends Component { } layoutBtnClickHandler(layoutId) { - // saveSetting('layoutMode', mode); - trackEvent('ui', 'toggleLayoutClick', mode); + this.saveSetting('layoutMode', layoutId); + trackEvent('ui', 'toggleLayoutClick', layoutId); this.toggleLayout(layoutId); } saveSetting(setting, value) { @@ -454,8 +516,6 @@ export default class App extends Component { } saveCode(key) { - this.state.currentItem.title = window.titleInput.value; - // currentItem.htmlMode = htmlMode; // currentItem.cssMode = cssMode; // currentItem.jsMode = jsMode; @@ -466,10 +526,6 @@ export default class App extends Component { } this.state.currentItem.updatedOn = Date.now(); this.state.currentItem.layoutMode = this.state.currentLayoutMode; - // this.state.currentItem.externalLibs = { - // js: externalJsTextarea.value, - // css: externalCssTextarea.value - // }; // currentItem.sizes = getCodePaneSizes(); // currentItem.mainSizes = getMainPaneSizes(); @@ -537,11 +593,28 @@ export default class App extends Component { itemService.setItemForUser(this.state.currentItem.id); } } - onCodeChange(type, code) { + onCodeChange(type, code, isUserChange) { this.state.currentItem[type] = code; + if (isUserChange) { + this.setState({ unsavedEditCount: this.state.unsavedEditCount + 1 }); + + if ( + this.state.unsavedEditCount % UNSAVED_WARNING_COUNT === 0 && + this.state.unsavedEditCount >= UNSAVED_WARNING_COUNT + ) { + window.saveBtn.classList.add('animated'); + window.saveBtn.classList.add('wobble'); + window.saveBtn.addEventListener('animationend', () => { + window.saveBtn.classList.remove('animated'); + window.saveBtn.classList.remove('wobble'); + }); + } + } } - titleInputBlurHandler() { + titleInputBlurHandler(e) { + this.state.currentItem.title = e.target.value; + if (this.state.currentItem.id) { this.saveItem(); trackEvent('ui', 'titleChanged'); @@ -622,9 +695,8 @@ export default class App extends Component { this.setState({ isProfileModalOpen: true }); } - logout(e) { - e.preventDefault(); - if (unsavedEditCount) { + logout() { + if (this.state.unsavedEditCount) { var shouldDiscard = confirm( 'You have unsaved changes. Do you still want to logout?' ); @@ -633,7 +705,36 @@ export default class App extends Component { } } trackEvent('fn', 'loggedOut'); - window.logout(); + auth.logout(); + } + + itemClickHandler(item) { + setTimeout(() => { + this.openItem(item); + }, 350); + this.toggleSavedItemsPane(); + } + itemRemoveBtnClickHandler(itemId) { + this.removeItem(itemId); + } + itemForkBtnClickHandler(item) { + this.toggleSavedItemsPane(); + setTimeout(() => { + this.forkItem(item); + }, 350); + } + newBtnClickHandler() { + trackEvent('ui', 'newBtnClick'); + if (this.state.unsavedEditCount) { + var shouldDiscard = confirm( + 'You have unsaved changes. Do you still want to create something new?' + ); + if (shouldDiscard) { + this.createNewItem(); + } + } else { + this.createNewItem(); + } } render() { @@ -643,19 +744,24 @@ export default class App extends Component { <MainHeader externalLibCount={this.state.externalLibCount} openBtnHandler={this.openSavedItemsPane.bind(this)} + newBtnHandler={this.newBtnClickHandler.bind(this)} saveBtnHandler={this.saveBtnClickHandler.bind(this)} loginBtnHandler={this.loginBtnClickHandler.bind(this)} profileBtnHandler={this.profileBtnClickHandler.bind(this)} addLibraryBtnHandler={this.openAddLibrary.bind(this)} isFetchingItems={this.state.isFetchingItems} isSaving={this.state.isSaving} + title={this.state.currentItem.title} titleInputBlurHandler={this.titleInputBlurHandler.bind(this)} user={this.state.user} + unsavedEditCount={this.state.unsavedEditCount} /> <ContentWrap + currentLayoutMode={this.state.currentLayoutMode} currentItem={this.state.currentItem} onCodeChange={this.onCodeChange.bind(this)} onRef={comp => (this.contentWrap = comp)} + prefs={this.state.prefs} /> <div class="global-console-container" id="globalConsoleContainerEl" /> <Footer @@ -674,6 +780,9 @@ export default class App extends Component { items={this.state.savedItems} isOpen={this.state.isSavedItemPaneOpen} closeHandler={this.closeSavedItemsPane.bind(this)} + itemClickHandler={this.itemClickHandler.bind(this)} + itemRemoveBtnClickHandler={this.itemRemoveBtnClickHandler.bind(this)} + itemForkBtnClickHandler={this.itemForkBtnClickHandler.bind(this)} /> <div class="alerts-container" id="js-alerts-container" /> <form @@ -735,7 +844,10 @@ export default class App extends Component { show={this.state.isProfileModalOpen} closeHandler={() => this.setState({ isProfileModalOpen: false })} > - <Profile user={this.state.user} /> + <Profile + user={this.state.user} + logoutBtnHandler={this.logout.bind(this)} + /> </Modal> <HelpModal show={this.state.isHelpModalOpen}