mirror of
https://github.com/chinchang/web-maker.git
synced 2025-07-13 10:06:23 +02:00
Firebase is here people!
This commit is contained in:
@ -28,6 +28,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"codemirror": "^5.37.0",
|
"codemirror": "^5.37.0",
|
||||||
|
"firebase": "^5.0.4",
|
||||||
"preact": "^8.2.6",
|
"preact": "^8.2.6",
|
||||||
"preact-compat": "^3.17.0",
|
"preact-compat": "^3.17.0",
|
||||||
"preact-router": "^2.5.7"
|
"preact-router": "^2.5.7"
|
||||||
|
49
webmaker/src/analytics.js
Normal file
49
webmaker/src/analytics.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
log
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
|
/* global ga */
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-params
|
||||||
|
export function trackEvent(category, action, label, value) {
|
||||||
|
if (window.DEBUG) {
|
||||||
|
log('trackevent', category, action, label, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (window.ga) {
|
||||||
|
ga('send', 'event', category, action, label, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// if online, load after sometime
|
||||||
|
if (navigator.onLine && !window.DEBUG) {
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
setTimeout(function () {
|
||||||
|
(function (i, s, o, g, r, a, m) {
|
||||||
|
i['GoogleAnalyticsObject'] = r;
|
||||||
|
i[r] = i[r] || function () {
|
||||||
|
(i[r].q = i[r].q || []).push(arguments)
|
||||||
|
}, i[r].l = 1 * new Date();
|
||||||
|
a = s.createElement(o),
|
||||||
|
m = s.getElementsByTagName(o)[0];
|
||||||
|
a.async = 1;
|
||||||
|
a.src = g;
|
||||||
|
m.parentNode.insertBefore(a, m)
|
||||||
|
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||||
|
|
||||||
|
if (location.href.indexOf('chrome-extension://') === -1) {
|
||||||
|
ga('create', 'UA-87786708-1');
|
||||||
|
} else {
|
||||||
|
ga('create', 'UA-87786708-1', {
|
||||||
|
'cookieDomain': 'none'
|
||||||
|
});
|
||||||
|
// required for chrome extension protocol
|
||||||
|
ga('set', 'checkProtocolTask', function () { /* nothing */ });
|
||||||
|
}
|
||||||
|
ga('send', 'pageview');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
/* eslint-enable */
|
||||||
|
}
|
43
webmaker/src/auth.js
Normal file
43
webmaker/src/auth.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
trackEvent
|
||||||
|
} from './analytics';
|
||||||
|
|
||||||
|
import firebase from 'firebase/app'
|
||||||
|
|
||||||
|
export const auth = {
|
||||||
|
logout() {
|
||||||
|
firebase.auth().signOut();
|
||||||
|
},
|
||||||
|
login(providerName) {
|
||||||
|
var provider;
|
||||||
|
if (providerName === 'facebook') {
|
||||||
|
provider = new firebase.auth.FacebookAuthProvider();
|
||||||
|
} else if (providerName === 'twitter') {
|
||||||
|
provider = new firebase.auth.TwitterAuthProvider();
|
||||||
|
} else if (providerName === 'google') {
|
||||||
|
provider = new firebase.auth.GoogleAuthProvider();
|
||||||
|
provider.addScope('https://www.googleapis.com/auth/userinfo.profile');
|
||||||
|
} else {
|
||||||
|
provider = new firebase.auth.GithubAuthProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
return firebase
|
||||||
|
.auth()
|
||||||
|
.signInWithPopup(provider)
|
||||||
|
.then(function () {
|
||||||
|
trackEvent('fn', 'loggedIn', providerName);
|
||||||
|
// Save to recommend next time
|
||||||
|
window.db.local.set({
|
||||||
|
lastAuthProvider: providerName
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
utils.log(error);
|
||||||
|
if (error.code === 'auth/account-exists-with-different-credential') {
|
||||||
|
alert(
|
||||||
|
'You have already signed up with the same email using different social login'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
74
webmaker/src/components/Login.jsx
Normal file
74
webmaker/src/components/Login.jsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { h, Component } from 'preact';
|
||||||
|
import { jsLibs, cssLibs } from '../libraryList';
|
||||||
|
import { trackEvent } from '../analytics';
|
||||||
|
import { auth } from '../auth';
|
||||||
|
|
||||||
|
export default class Login extends Component {
|
||||||
|
login(e) {
|
||||||
|
const provider = e.target.dataset.authProvider;
|
||||||
|
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>
|
||||||
|
<h2>Login / Signup</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={this.login.bind(this)}
|
||||||
|
class="social-login-btn social-login-btn--github btn btn-icon btn--big full-width hint--right hint--always"
|
||||||
|
data-auth-provider="github"
|
||||||
|
data-hint="You logged in with Github last time"
|
||||||
|
>
|
||||||
|
<svg>
|
||||||
|
<use xlinkHref="#github-icon" />
|
||||||
|
</svg>Login with Github
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={this.login.bind(this)}
|
||||||
|
class="social-login-btn social-login-btn--google btn btn-icon btn--big full-width hint--right hint--always"
|
||||||
|
data-auth-provider="google"
|
||||||
|
data-hint="You logged in with Google last time"
|
||||||
|
>
|
||||||
|
<svg>
|
||||||
|
<use xlinkHref="#google-icon" />
|
||||||
|
</svg>Login with Google
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p class="mb-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={this.login.bind(this)}
|
||||||
|
class="social-login-btn social-login-btn--facebook btn btn-icon btn--big full-width hint--right hint--always"
|
||||||
|
data-auth-provider="facebook"
|
||||||
|
data-hint="You logged in with Facebook last time"
|
||||||
|
>
|
||||||
|
<svg>
|
||||||
|
<use xlinkHref="#fb-icon" />
|
||||||
|
</svg>Login with Facebook
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p>Join a community of 50,000+ Developers</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -94,7 +94,7 @@ export default class Header extends Component {
|
|||||||
Open
|
Open
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
d-open-modal="loginModal"
|
onClick={this.props.loginBtnHandler}
|
||||||
data-event-category="ui"
|
data-event-category="ui"
|
||||||
data-event-action="loginButtonClick"
|
data-event-action="loginButtonClick"
|
||||||
class="hide-on-login flex flex-v-center hint--rounded hint--bottom-left"
|
class="hide-on-login flex flex-v-center hint--rounded hint--bottom-left"
|
||||||
@ -103,7 +103,7 @@ export default class Header extends Component {
|
|||||||
Login/Signup
|
Login/Signup
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
d-open-modal="profileModal"
|
onClick={this.props.profileBtnHandler}
|
||||||
data-event-category="ui"
|
data-event-category="ui"
|
||||||
data-event-action="headerAvatarClick"
|
data-event-action="headerAvatarClick"
|
||||||
aria-label="See profile or Logout"
|
aria-label="See profile or Logout"
|
||||||
@ -112,7 +112,11 @@ export default class Header extends Component {
|
|||||||
<img
|
<img
|
||||||
id="headerAvatarImg"
|
id="headerAvatarImg"
|
||||||
width="20"
|
width="20"
|
||||||
src=""
|
src={
|
||||||
|
this.props.user
|
||||||
|
? this.props.user.photoURL || DEFAULT_PROFILE_IMG
|
||||||
|
: ''
|
||||||
|
}
|
||||||
class="main-header__avatar-img"
|
class="main-header__avatar-img"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
35
webmaker/src/components/Profile.jsx
Normal file
35
webmaker/src/components/Profile.jsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
|
export default class Profile extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="tac">
|
||||||
|
<img
|
||||||
|
height="80"
|
||||||
|
class="profile-modal__avatar-img"
|
||||||
|
src={
|
||||||
|
this.props.user
|
||||||
|
? this.props.user.photoURL || DEFAULT_PROFILE_IMG
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
id="profileAvatarImg"
|
||||||
|
alt="Profile image"
|
||||||
|
/>
|
||||||
|
<h3 id="profileUserName" class="mb-2">
|
||||||
|
{this.props.user && this.props.user.displayName
|
||||||
|
? this.props.user.displayName
|
||||||
|
: 'Anonymous Creator'}
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
aria-label="Logout from your account"
|
||||||
|
onClick={this.props.logoutBtnHandler}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ 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 Login from './Login.jsx';
|
||||||
import { log, generateRandomId } from '../utils';
|
import { log, generateRandomId } from '../utils';
|
||||||
import { itemService } from '../itemService';
|
import { itemService } from '../itemService';
|
||||||
import '../db';
|
import '../db';
|
||||||
@ -17,6 +18,9 @@ import { modes, cssModes } from '../codeModes';
|
|||||||
import { trackEvent } from '../analytics';
|
import { trackEvent } from '../analytics';
|
||||||
import { deferred } from '../deferred';
|
import { deferred } from '../deferred';
|
||||||
import { alertsService } from '../notifications';
|
import { alertsService } from '../notifications';
|
||||||
|
import firebase from 'firebase/app';
|
||||||
|
import 'firebase/auth';
|
||||||
|
import Profile from './Profile';
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
require('preact/debug');
|
require('preact/debug');
|
||||||
@ -38,6 +42,8 @@ export default class App extends Component {
|
|||||||
isAddLibraryModalOpen: false,
|
isAddLibraryModalOpen: false,
|
||||||
isHelpModalOpen: false,
|
isHelpModalOpen: false,
|
||||||
isNotificationsModalOpen: false,
|
isNotificationsModalOpen: false,
|
||||||
|
isLoginModalOpen: false,
|
||||||
|
isProfileModalOpen: false,
|
||||||
prefs: {},
|
prefs: {},
|
||||||
currentItem: {
|
currentItem: {
|
||||||
title: '',
|
title: '',
|
||||||
@ -68,6 +74,45 @@ export default class App extends Component {
|
|||||||
infiniteLoopTimeout: 1000
|
infiniteLoopTimeout: 1000
|
||||||
};
|
};
|
||||||
this.prefs = {};
|
this.prefs = {};
|
||||||
|
|
||||||
|
firebase.auth().onAuthStateChanged(user => {
|
||||||
|
this.setState({ isLoginModalOpen: false });
|
||||||
|
if (user) {
|
||||||
|
log('You are -> ', user);
|
||||||
|
alertsService.add('You are now logged in!');
|
||||||
|
this.setState({ user });
|
||||||
|
window.user = user;
|
||||||
|
if (
|
||||||
|
!window.localStorage[LocalStorageKeys.ASKED_TO_IMPORT_CREATIONS] &&
|
||||||
|
window.oldSavedCreationsCountEl
|
||||||
|
) {
|
||||||
|
this.fetchItems(false, true).then(items => {
|
||||||
|
if (!items.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.oldSavedItems = items;
|
||||||
|
// window.oldSavedCreationsCountEl.textContent = items.length;
|
||||||
|
this.setState({
|
||||||
|
isAskToImportModalOpen: true
|
||||||
|
});
|
||||||
|
trackEvent('ui', 'askToImportModalSeen');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.db.getUser(user.uid).then(customUser => {
|
||||||
|
if (customUser) {
|
||||||
|
const prefs = { ...this.state.prefs };
|
||||||
|
Object.assign(prefs, user.settings);
|
||||||
|
this.setState({ prefs: prefs });
|
||||||
|
this.updateSetting();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({ user: undefined });
|
||||||
|
delete window.user;
|
||||||
|
// User is signed out.
|
||||||
|
}
|
||||||
|
this.updateProfileUi();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
@ -121,6 +166,14 @@ export default class App extends Component {
|
|||||||
this.updateSetting();
|
this.updateSetting();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
updateProfileUi() {
|
||||||
|
if (this.state.user) {
|
||||||
|
document.body.classList.add('is-logged-in');
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('is-logged-in');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
refreshEditor() {}
|
refreshEditor() {}
|
||||||
createNewItem() {
|
createNewItem() {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
@ -561,6 +614,27 @@ export default class App extends Component {
|
|||||||
}
|
}
|
||||||
autoSaveLoop() {}
|
autoSaveLoop() {}
|
||||||
|
|
||||||
|
loginBtnClickHandler() {
|
||||||
|
this.setState({ isLoginModalOpen: true });
|
||||||
|
}
|
||||||
|
profileBtnClickHandler() {
|
||||||
|
this.setState({ isProfileModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (unsavedEditCount) {
|
||||||
|
var shouldDiscard = confirm(
|
||||||
|
'You have unsaved changes. Do you still want to logout?'
|
||||||
|
);
|
||||||
|
if (!shouldDiscard) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trackEvent('fn', 'loggedOut');
|
||||||
|
window.logout();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -569,10 +643,13 @@ export default class App extends Component {
|
|||||||
externalLibCount={this.state.externalLibCount}
|
externalLibCount={this.state.externalLibCount}
|
||||||
openBtnHandler={this.openSavedItemsPane.bind(this)}
|
openBtnHandler={this.openSavedItemsPane.bind(this)}
|
||||||
saveBtnHandler={this.saveBtnClickHandler.bind(this)}
|
saveBtnHandler={this.saveBtnClickHandler.bind(this)}
|
||||||
|
loginBtnHandler={this.loginBtnClickHandler.bind(this)}
|
||||||
|
profileBtnHandler={this.profileBtnClickHandler.bind(this)}
|
||||||
addLibraryBtnHandler={this.openAddLibrary.bind(this)}
|
addLibraryBtnHandler={this.openAddLibrary.bind(this)}
|
||||||
isFetchingItems={this.state.isFetchingItems}
|
isFetchingItems={this.state.isFetchingItems}
|
||||||
isSaving={this.state.isSaving}
|
isSaving={this.state.isSaving}
|
||||||
titleInputBlurHandler={this.titleInputBlurHandler.bind(this)}
|
titleInputBlurHandler={this.titleInputBlurHandler.bind(this)}
|
||||||
|
user={this.state.user}
|
||||||
/>
|
/>
|
||||||
<ContentWrap
|
<ContentWrap
|
||||||
currentItem={this.state.currentItem}
|
currentItem={this.state.currentItem}
|
||||||
@ -647,6 +724,18 @@ export default class App extends Component {
|
|||||||
onChange={this.updateSetting.bind(this)}
|
onChange={this.updateSetting.bind(this)}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
show={this.state.isLoginModalOpen}
|
||||||
|
closeHandler={() => this.setState({ isLoginModalOpen: false })}
|
||||||
|
>
|
||||||
|
<Login />
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
show={this.state.isProfileModalOpen}
|
||||||
|
closeHandler={() => this.setState({ isProfileModalOpen: false })}
|
||||||
|
>
|
||||||
|
<Profile user={this.state.user} />
|
||||||
|
</Modal>
|
||||||
<HelpModal
|
<HelpModal
|
||||||
show={this.state.isHelpModalOpen}
|
show={this.state.isHelpModalOpen}
|
||||||
closeHandler={() => this.setState({ isHelpModalOpen: false })}
|
closeHandler={() => this.setState({ isHelpModalOpen: false })}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
|
import './firebaseInit';
|
||||||
|
import firebase from 'firebase/app';
|
||||||
|
import 'firebase/firestore';
|
||||||
import {
|
import {
|
||||||
deferred
|
deferred
|
||||||
} from './deferred'
|
} from './deferred';
|
||||||
|
import {
|
||||||
|
trackEvent
|
||||||
|
} from './analytics';
|
||||||
|
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const FAUX_DELAY = 1;
|
const FAUX_DELAY = 1;
|
||||||
|
|
||||||
@ -53,6 +61,10 @@ import {
|
|||||||
.then(function () {
|
.then(function () {
|
||||||
// Initialize Cloud Firestore through firebase
|
// Initialize Cloud Firestore through firebase
|
||||||
db = firebase.firestore();
|
db = firebase.firestore();
|
||||||
|
// const settings = {
|
||||||
|
// timestampsInSnapshots: true
|
||||||
|
// };
|
||||||
|
// db.settings(settings);
|
||||||
utils.log('firebase db ready', db);
|
utils.log('firebase db ready', db);
|
||||||
resolve(db);
|
resolve(db);
|
||||||
})
|
})
|
||||||
@ -64,7 +76,7 @@ import {
|
|||||||
alert(
|
alert(
|
||||||
"Opening Web Maker web app in multiple tabs isn't supported at present and it seems like you already have it opened in another tab. Please use in one tab."
|
"Opening Web Maker web app in multiple tabs isn't supported at present and it seems like you already have it opened in another tab. Please use in one tab."
|
||||||
);
|
);
|
||||||
window.trackEvent('fn', 'multiTabError');
|
trackEvent('fn', 'multiTabError');
|
||||||
} else if (err.code === 'unimplemented') {
|
} else if (err.code === 'unimplemented') {
|
||||||
// The current browser does not support all of the
|
// The current browser does not support all of the
|
||||||
// features required to enable persistence
|
// features required to enable persistence
|
||||||
|
11
webmaker/src/firebaseInit.js
Normal file
11
webmaker/src/firebaseInit.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import firebase from 'firebase/app';
|
||||||
|
// import 'firebase/firestore';
|
||||||
|
const config = {
|
||||||
|
apiKey: 'AIzaSyBl8Dz7ZOE7aP75mipYl2zKdLSRzBU2fFc',
|
||||||
|
authDomain: 'web-maker-app.firebaseapp.com',
|
||||||
|
databaseURL: 'https://web-maker-app.firebaseio.com',
|
||||||
|
projectId: 'web-maker-app',
|
||||||
|
storageBucket: 'web-maker-app.appspot.com',
|
||||||
|
messagingSenderId: '560473480645'
|
||||||
|
};
|
||||||
|
firebase.initializeApp(config);
|
20
webmaker/src/notifications.js
Normal file
20
webmaker/src/notifications.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
var hideTimeout;
|
||||||
|
|
||||||
|
function addNotification(msg) {
|
||||||
|
const noticationContainerEL = $('#js-alerts-container');
|
||||||
|
|
||||||
|
// var n = document.createElement('div');
|
||||||
|
// div.textContent = msg;
|
||||||
|
// noticationContainerEL.appendChild(n);
|
||||||
|
noticationContainerEL.textContent = msg;
|
||||||
|
noticationContainerEL.classList.add('is-active');
|
||||||
|
|
||||||
|
clearTimeout(hideTimeout);
|
||||||
|
hideTimeout = setTimeout(function () {
|
||||||
|
noticationContainerEL.classList.remove('is-active');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const alertsService = {
|
||||||
|
add: addNotification
|
||||||
|
};
|
Reference in New Issue
Block a user