mirror of
https://github.com/chinchang/web-maker.git
synced 2025-08-02 19:37:29 +02:00
start saving and fix savedItemspane
This commit is contained in:
@@ -6,10 +6,11 @@ export default class Header extends Component {
|
|||||||
<div class="main-header">
|
<div class="main-header">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="js-title-input"
|
id="titleInput"
|
||||||
title="Click to edit"
|
title="Click to edit"
|
||||||
class="item-title-input"
|
class="item-title-input"
|
||||||
value="Untitled Work"
|
value="Untitled Work"
|
||||||
|
onBlur={this.props.titleInputBlurHandler}
|
||||||
/>
|
/>
|
||||||
<div class="main-header__btn-wrap flex flex-v-center">
|
<div class="main-header__btn-wrap flex flex-v-center">
|
||||||
<a
|
<a
|
||||||
@@ -56,9 +57,11 @@ export default class Header extends Component {
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
id="saveBtn"
|
id="saveBtn"
|
||||||
class="flex flex-v-center hint--rounded hint--bottom-left"
|
class={`flex flex-v-center hint--rounded hint--bottom-left ${
|
||||||
|
this.props.isSaving ? 'is-loading' : ''
|
||||||
|
}`}
|
||||||
aria-label="Save current creation (Ctrl/⌘ + S)"
|
aria-label="Save current creation (Ctrl/⌘ + S)"
|
||||||
d-click="onSaveBtnClick"
|
onClick={this.props.saveBtnHandler}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
style="vertical-align:middle;width:14px;height:14px"
|
style="vertical-align:middle;width:14px;height:14px"
|
||||||
@@ -73,7 +76,9 @@ export default class Header extends Component {
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
id="openItemsBtn"
|
id="openItemsBtn"
|
||||||
class="flex flex-v-center hint--rounded hint--bottom-left"
|
class={`flex flex-v-center hint--rounded hint--bottom-left ${
|
||||||
|
this.props.isFetchingItems ? 'is-loading' : ''
|
||||||
|
}`}
|
||||||
aria-label="Open a saved creation (Ctrl/⌘ + O)"
|
aria-label="Open a saved creation (Ctrl/⌘ + O)"
|
||||||
onClick={this.props.openBtnHandler}
|
onClick={this.props.openBtnHandler}
|
||||||
>
|
>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
|
import { getHumanDate } from '../utils';
|
||||||
|
|
||||||
export default class SavedItemPane extends Component {
|
export default class SavedItemPane extends Component {
|
||||||
onCloseIntent() {
|
onCloseIntent() {
|
||||||
@@ -19,7 +20,10 @@ export default class SavedItemPane extends Component {
|
|||||||
</button>
|
</button>
|
||||||
<div class="flex flex-v-center" style="justify-content: space-between;">
|
<div class="flex flex-v-center" style="justify-content: space-between;">
|
||||||
<h3>
|
<h3>
|
||||||
My Library <span id="savedItemCountEl" />
|
My Library{' '}
|
||||||
|
<span id="savedItemCountEl">
|
||||||
|
{this.props.items ? this.props.items.length : 0}
|
||||||
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="main-header__btn-wrap">
|
<div class="main-header__btn-wrap">
|
||||||
@@ -49,7 +53,38 @@ export default class SavedItemPane extends Component {
|
|||||||
placeholder="Search your creations here..."
|
placeholder="Search your creations here..."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div id="js-saved-items-wrap" class="saved-items-pane__container" />
|
<div id="js-saved-items-wrap" class="saved-items-pane__container">
|
||||||
|
{this.props.items &&
|
||||||
|
this.props.items.length &&
|
||||||
|
this.props.items.map(item => (
|
||||||
|
<div
|
||||||
|
class="js-saved-item-tile saved-item-tile"
|
||||||
|
data-item-id={item.id}
|
||||||
|
>
|
||||||
|
<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)"
|
||||||
|
>
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h3 class="saved-item-tile__title">{item.title}</h3>
|
||||||
|
<span class="saved-item-tile__meta">
|
||||||
|
Last updated: {getHumanDate(item.updatedOn)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{!(this.props.items && this.props.items.length) && (
|
||||||
|
<h2 class="opacity--30">Nothing saved here.</h2>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -8,12 +8,15 @@ import SavedItemPane from './SavedItemPane.jsx';
|
|||||||
import AddLibrary from './AddLibrary.jsx';
|
import AddLibrary from './AddLibrary.jsx';
|
||||||
import Modal from './Modal.jsx';
|
import Modal from './Modal.jsx';
|
||||||
import HelpModal from './HelpModal.jsx';
|
import HelpModal from './HelpModal.jsx';
|
||||||
import { log } from '../utils';
|
import { log, generateRandomId } from '../utils';
|
||||||
import { itemService } from '../itemService';
|
import { itemService } from '../itemService';
|
||||||
import '../db';
|
import '../db';
|
||||||
import Notifications from './Notifications';
|
import Notifications from './Notifications';
|
||||||
import Settings from './Settings.jsx';
|
import Settings from './Settings.jsx';
|
||||||
import { modes, cssModes } from '../codeModes';
|
import { modes, cssModes } from '../codeModes';
|
||||||
|
import { trackEvent } from '../analytics';
|
||||||
|
import { deferred } from '../deferred';
|
||||||
|
import { alertsService } from '../notifications';
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
require('preact/debug');
|
require('preact/debug');
|
||||||
@@ -150,8 +153,103 @@ export default class App extends Component {
|
|||||||
this.setState({ unsavedEditCount: 0 });
|
this.setState({ unsavedEditCount: 0 });
|
||||||
// saveBtn.classList.remove('is-marked');
|
// saveBtn.classList.remove('is-marked');
|
||||||
}
|
}
|
||||||
|
saveBtnClickHandler() {
|
||||||
|
trackEvent(
|
||||||
|
'ui',
|
||||||
|
'saveBtnClick',
|
||||||
|
this.state.currentItem.id ? 'saved' : 'new'
|
||||||
|
);
|
||||||
|
this.saveItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
populateItemsInSavedPane(items) {
|
||||||
|
const savedItemsPane = $('#js-saved-items-pane');
|
||||||
|
// TODO: sort desc. by updation date
|
||||||
|
this.setState({
|
||||||
|
savedItems: items.sort(function(a, b) {
|
||||||
|
return b.updatedOn - a.updatedOn;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
this.toggleSavedItemsPane();
|
||||||
|
// HACK: Set overflow after sometime so that the items can animate without getting cropped.
|
||||||
|
// setTimeout(() => $('#js-saved-items-wrap').style.overflowY = 'auto', 1000);
|
||||||
|
}
|
||||||
|
toggleSavedItemsPane(shouldOpen) {
|
||||||
|
// const savedItemsPane = $('#js-saved-items-pane');
|
||||||
|
this.setState({ isSavedItemPaneOpen: !this.state.isSavedItemPaneOpen });
|
||||||
|
|
||||||
|
if (this.state.isSavedItemPaneOpen) {
|
||||||
|
window.searchInput.focus();
|
||||||
|
} else {
|
||||||
|
window.searchInput.value = '';
|
||||||
|
// Give last focused editor, focus again
|
||||||
|
// if (editorWithFocus) {
|
||||||
|
// editorWithFocus.focus();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
document.body.classList[this.state.isSavedItemPaneOpen ? 'add' : 'remove'](
|
||||||
|
'overlay-visible'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all items from storage
|
||||||
|
* @param {boolean} shouldSaveGlobally Whether to store the fetched items in global arr for later use.
|
||||||
|
* @return {promise} Promise.
|
||||||
|
*/
|
||||||
|
async fetchItems(shouldSaveGlobally, shouldFetchLocally) {
|
||||||
|
var d = deferred();
|
||||||
|
this.state.savedItems = {};
|
||||||
|
var items = [];
|
||||||
|
if (window.user && !shouldFetchLocally) {
|
||||||
|
items = await itemService.getAllItems();
|
||||||
|
utils.log('got items');
|
||||||
|
if (shouldSaveGlobally) {
|
||||||
|
items.forEach(item => {
|
||||||
|
this.state.savedItems[item.id] = item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
d.resolve(items);
|
||||||
|
return d.promise;
|
||||||
|
}
|
||||||
|
db.local.get('items', result => {
|
||||||
|
var itemIds = Object.getOwnPropertyNames(result.items || {});
|
||||||
|
if (!itemIds.length) {
|
||||||
|
d.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackEvent('fn', 'fetchItems', itemIds.length);
|
||||||
|
for (let i = 0; i < itemIds.length; i++) {
|
||||||
|
/* eslint-disable no-loop-func */
|
||||||
|
db.local.get(itemIds[i], itemResult => {
|
||||||
|
if (shouldSaveGlobally) {
|
||||||
|
this.state.savedItems[itemIds[i]] = itemResult[itemIds[i]];
|
||||||
|
}
|
||||||
|
items.push(itemResult[itemIds[i]]);
|
||||||
|
// Check if we have all items now.
|
||||||
|
if (itemIds.length === items.length) {
|
||||||
|
d.resolve(items);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* eslint-enable no-loop-func */
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return d.promise;
|
||||||
|
}
|
||||||
|
|
||||||
openSavedItemsPane() {
|
openSavedItemsPane() {
|
||||||
this.setState({ isSavedItemPaneOpen: true });
|
this.setState({
|
||||||
|
isFetchingItems: true
|
||||||
|
});
|
||||||
|
this.fetchItems(true).then(items => {
|
||||||
|
this.setState({
|
||||||
|
isFetchingItems: false
|
||||||
|
});
|
||||||
|
this.populateItemsInSavedPane(items);
|
||||||
|
});
|
||||||
|
// this.setState({ isSavedItemPaneOpen: true });
|
||||||
}
|
}
|
||||||
openAddLibrary() {
|
openAddLibrary() {
|
||||||
this.setState({ isAddLibraryModalOpen: true });
|
this.setState({ isAddLibraryModalOpen: true });
|
||||||
@@ -168,8 +266,8 @@ export default class App extends Component {
|
|||||||
// Ctrl/⌘ + S
|
// Ctrl/⌘ + S
|
||||||
if ((event.ctrlKey || event.metaKey) && event.keyCode === 83) {
|
if ((event.ctrlKey || event.metaKey) && event.keyCode === 83) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// saveItem();
|
this.saveItem();
|
||||||
// trackEvent('ui', 'saveItemKeyboardShortcut');
|
trackEvent('ui', 'saveItemKeyboardShortcut');
|
||||||
}
|
}
|
||||||
// Ctrl/⌘ + Shift + 5
|
// Ctrl/⌘ + Shift + 5
|
||||||
if (
|
if (
|
||||||
@@ -179,12 +277,12 @@ export default class App extends Component {
|
|||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// scope.setPreviewContent(true, true);
|
// scope.setPreviewContent(true, true);
|
||||||
// trackEvent('ui', 'previewKeyboardShortcut');
|
trackEvent('ui', 'previewKeyboardShortcut');
|
||||||
} else if ((event.ctrlKey || event.metaKey) && event.keyCode === 79) {
|
} else if ((event.ctrlKey || event.metaKey) && event.keyCode === 79) {
|
||||||
// Ctrl/⌘ + O
|
// Ctrl/⌘ + O
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.openSavedItemsPane();
|
this.openSavedItemsPane();
|
||||||
// trackEvent('ui', 'openCreationKeyboardShortcut');
|
trackEvent('ui', 'openCreationKeyboardShortcut');
|
||||||
} else if (
|
} else if (
|
||||||
(event.ctrlKey || event.metaKey) &&
|
(event.ctrlKey || event.metaKey) &&
|
||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
@@ -193,7 +291,7 @@ export default class App extends Component {
|
|||||||
// Ctrl/⌘ + Shift + ?
|
// Ctrl/⌘ + Shift + ?
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// scope.toggleModal(keyboardShortcutsModal);
|
// scope.toggleModal(keyboardShortcutsModal);
|
||||||
// trackEvent('ui', 'showKeyboardShortcutsShortcut');
|
trackEvent('ui', 'showKeyboardShortcutsShortcut');
|
||||||
} else if (event.keyCode === 27) {
|
} else if (event.keyCode === 27) {
|
||||||
this.closeAllOverlays();
|
this.closeAllOverlays();
|
||||||
}
|
}
|
||||||
@@ -302,10 +400,8 @@ export default class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveCode(key) {
|
saveCode(key) {
|
||||||
// this.currentItem.title = titleInput.value;
|
this.state.currentItem.title = window.titleInput.value;
|
||||||
// currentItem.html = scope.cm.html.getValue();
|
|
||||||
// currentItem.css = scope.cm.css.getValue();
|
|
||||||
// currentItem.js = scope.cm.js.getValue();
|
|
||||||
// currentItem.htmlMode = htmlMode;
|
// currentItem.htmlMode = htmlMode;
|
||||||
// currentItem.cssMode = cssMode;
|
// currentItem.cssMode = cssMode;
|
||||||
// currentItem.jsMode = jsMode;
|
// currentItem.jsMode = jsMode;
|
||||||
@@ -328,11 +424,11 @@ export default class App extends Component {
|
|||||||
|
|
||||||
function onSaveComplete() {
|
function onSaveComplete() {
|
||||||
if (window.user && !navigator.onLine) {
|
if (window.user && !navigator.onLine) {
|
||||||
// alertsService.add(
|
alertsService.add(
|
||||||
// 'Item saved locally. Will save to account when you are online.'
|
'Item saved locally. Will save to account when you are online.'
|
||||||
// );
|
);
|
||||||
} else {
|
} else {
|
||||||
// alertsService.add('Item saved.');
|
alertsService.add('Item saved.');
|
||||||
}
|
}
|
||||||
this.state.unsavedEditCount = 0;
|
this.state.unsavedEditCount = 0;
|
||||||
// saveBtn.classList.remove('is-marked');
|
// saveBtn.classList.remove('is-marked');
|
||||||
@@ -340,7 +436,7 @@ export default class App extends Component {
|
|||||||
|
|
||||||
return itemService
|
return itemService
|
||||||
.setItem(key || this.state.currentItem.id, this.state.currentItem)
|
.setItem(key || this.state.currentItem.id, this.state.currentItem)
|
||||||
.then(onSaveComplete);
|
.then(onSaveComplete.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save current item to storage
|
// Save current item to storage
|
||||||
@@ -361,29 +457,43 @@ export default class App extends Component {
|
|||||||
}
|
}
|
||||||
trackEvent('ui', LocalStorageKeys.LOGIN_AND_SAVE_MESSAGE_SEEN, 'local');
|
trackEvent('ui', LocalStorageKeys.LOGIN_AND_SAVE_MESSAGE_SEEN, 'local');
|
||||||
}
|
}
|
||||||
var isNewItem = !currentItem.id;
|
var isNewItem = !this.state.currentItem.id;
|
||||||
currentItem.id = currentItem.id || 'item-' + utils.generateRandomId();
|
this.state.currentItem.id =
|
||||||
saveBtn.classList.add('is-loading');
|
this.state.currentItem.id || 'item-' + generateRandomId();
|
||||||
|
this.setState({
|
||||||
|
isSaving: true
|
||||||
|
});
|
||||||
this.saveCode().then(() => {
|
this.saveCode().then(() => {
|
||||||
saveBtn.classList.remove('is-loading');
|
this.setState({
|
||||||
|
isSaving: false
|
||||||
|
});
|
||||||
|
// TODO: May be setState with currentItem
|
||||||
|
|
||||||
// If this is the first save, and auto-saving settings is enabled,
|
// If this is the first save, and auto-saving settings is enabled,
|
||||||
// then start auto-saving from now on.
|
// then start auto-saving from now on.
|
||||||
// This is done in `saveCode()` completion so that the
|
// This is done in `saveCode()` completion so that the
|
||||||
// auto-save notification overrides the `saveCode` function's notification.
|
// auto-save notification overrides the `saveCode` function's notification.
|
||||||
if (!isAutoSavingEnabled && prefs.autoSave) {
|
if (!this.isAutoSavingEnabled && this.state.prefs.autoSave) {
|
||||||
isAutoSavingEnabled = true;
|
this.isAutoSavingEnabled = true;
|
||||||
alertsService.add('Auto-save enabled.');
|
alertsService.add('Auto-save enabled.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Push into the items hash if its a new item being saved
|
// Push into the items hash if its a new item being saved
|
||||||
if (isNewItem) {
|
if (isNewItem) {
|
||||||
itemService.setItemForUser(currentItem.id);
|
itemService.setItemForUser(this.state.currentItem.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onCodeChange(type, code) {
|
onCodeChange(type, code) {
|
||||||
this.state.currentItem[type] = code;
|
this.state.currentItem[type] = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
titleInputBlurHandler() {
|
||||||
|
if (this.state.currentItem.id) {
|
||||||
|
this.saveItem();
|
||||||
|
trackEvent('ui', 'titleChanged');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles all user triggered preference changes in the UI.
|
* Handles all user triggered preference changes in the UI.
|
||||||
*/
|
*/
|
||||||
@@ -458,7 +568,11 @@ export default class App extends Component {
|
|||||||
<MainHeader
|
<MainHeader
|
||||||
externalLibCount={this.state.externalLibCount}
|
externalLibCount={this.state.externalLibCount}
|
||||||
openBtnHandler={this.openSavedItemsPane.bind(this)}
|
openBtnHandler={this.openSavedItemsPane.bind(this)}
|
||||||
|
saveBtnHandler={this.saveBtnClickHandler.bind(this)}
|
||||||
addLibraryBtnHandler={this.openAddLibrary.bind(this)}
|
addLibraryBtnHandler={this.openAddLibrary.bind(this)}
|
||||||
|
isFetchingItems={this.state.isFetchingItems}
|
||||||
|
isSaving={this.state.isSaving}
|
||||||
|
titleInputBlurHandler={this.titleInputBlurHandler.bind(this)}
|
||||||
/>
|
/>
|
||||||
<ContentWrap
|
<ContentWrap
|
||||||
currentItem={this.state.currentItem}
|
currentItem={this.state.currentItem}
|
||||||
@@ -479,6 +593,7 @@ export default class App extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SavedItemPane
|
<SavedItemPane
|
||||||
|
items={this.state.savedItems}
|
||||||
isOpen={this.state.isSavedItemPaneOpen}
|
isOpen={this.state.isSavedItemPaneOpen}
|
||||||
closeHandler={this.closeSavedItemsPane.bind(this)}
|
closeHandler={this.closeSavedItemsPane.bind(this)}
|
||||||
/>
|
/>
|
||||||
@@ -537,6 +652,8 @@ export default class App extends Component {
|
|||||||
closeHandler={() => this.setState({ isHelpModalOpen: false })}
|
closeHandler={() => this.setState({ isHelpModalOpen: false })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div class="modal-overlay" />
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
version="1.1"
|
version="1.1"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
Reference in New Issue
Block a user