1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-07-26 08:11:17 +02:00

Merge remote-tracking branch 'origin/master' into manifestv3

This commit is contained in:
Kushagra Gour
2024-05-06 17:54:20 +05:30
83 changed files with 10240 additions and 16784 deletions

View File

@@ -2,11 +2,12 @@
*/
import { h, Component } from 'preact';
import { route } from 'preact-router';
// import '../service-worker-registration';
import { MainHeader } from './MainHeader.jsx';
import ContentWrap from './ContentWrap.jsx';
import ContentWrapFiles from './ContentWrapFiles.jsx';
import Footer from './Footer.jsx';
import { Footer } from './Footer.jsx';
import SavedItemPane from './SavedItemPane.jsx';
import AddLibrary from './AddLibrary.jsx';
import Modal from './Modal.jsx';
@@ -22,7 +23,8 @@ import {
getCompleteHtml,
getFilenameFromUrl,
prettify,
sanitizeSplitSizes
sanitizeSplitSizes,
persistAuthUserLocally
} from '../utils';
import {
linearizeFiles,
@@ -35,7 +37,7 @@ import {
import { itemService } from '../itemService';
import '../db';
import { Notifications } from './Notifications';
import { Changelog } from './Changelog';
import Settings from './Settings.jsx';
import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
import { trackEvent } from '../analytics';
@@ -68,14 +70,21 @@ import {
import { commandPaletteService } from '../commandPaletteService';
import { I18nProvider } from '@lingui/react';
import { Assets } from './Assets.jsx';
import { LocalStorageKeys } from '../constants.js';
import { Share } from './Share.jsx';
import { Pro } from './Pro.jsx';
import { VStack } from './Stack.jsx';
import { ProBadge } from './ProBadge.jsx';
import { Text } from './Text.jsx';
import { ProOnAppModal } from './ProOnAppModal.js';
if (module.hot) {
require('preact/debug');
}
const UNSAVED_WARNING_COUNT = 15;
const version = '5.2.0';
const version = '6.1.0';
// Read forced settings as query parameters
window.forcedSettings = {};
@@ -98,10 +107,19 @@ if (location.search) {
window.codeCss = params.get('css') || '';
}
function customRoute(path) {
// we don't /create redirections on extension since SPA paths don't work there
if (window.IS_EXTENSION) return;
else {
route(path);
}
}
export default class App extends Component {
constructor() {
super();
this.AUTO_SAVE_INTERVAL = 15000; // 15 seconds
const savedUser = window.localStorage.getItem('user');
this.modalDefaultStates = {
isModalOpen: false,
isAddLibraryModalOpen: false,
@@ -116,7 +134,12 @@ export default class App extends Component {
isOnboardModalOpen: false,
isJs13KModalOpen: false,
isCreateNewModalOpen: false,
isCommandPaletteOpen: false
isCommandPaletteOpen: false,
isAssetsOpen: false,
isShareModalOpen: false,
isProModalOpen: false,
isFilesLimitModalOpen: false,
isProOnAppModalOpen: false
};
this.state = {
isSavedItemPaneOpen: false,
@@ -128,7 +151,8 @@ export default class App extends Component {
html: window.codeHtml,
css: window.codeCss
},
catalogs: {}
catalogs: {},
user: savedUser
};
this.defaultSettings = {
preserveLastCode: true,
@@ -161,13 +185,35 @@ export default class App extends Component {
};
this.prefs = {};
onAuthStateChanged(auth, user => {
if (savedUser) {
window.user = savedUser;
}
onAuthStateChanged(auth, authUser => {
this.setState({ isLoginModalOpen: false });
if (user) {
log('You are -> ', user);
if (authUser) {
log('You are -> ', authUser);
alertsService.add('You are now logged in!');
this.setState({ user });
window.user = user;
let newUser = {
uid: authUser.uid,
photoURL: authUser.photoURL
};
// port some keys from localstorage user to new auth user
const keysToPort = ['isPro', 'displayName', 'settings'];
keysToPort.forEach(key => {
if (this.state.user && this.state.user[key] !== undefined) {
newUser[key] = this.state.user[key];
}
});
// storing actual firebase user object for accessing functions like updateProfile
newUser.firebaseUser = authUser;
this.setState({ user: newUser });
window.user = newUser;
// window.localStorage.setItem('user', authUser);
trackEvent('fn', 'loggedIn', window.IS_EXTENSION ? 'extension' : 'web');
if (!window.localStorage[LocalStorageKeys.ASKED_TO_IMPORT_CREATIONS]) {
this.fetchItems(false, true).then(items => {
if (!items.length) {
@@ -181,12 +227,23 @@ export default class App extends Component {
trackEvent('ui', 'askToImportModalSeen');
});
}
window.db.getUser(user.uid).then(customUser => {
window.db.getUser(authUser.uid).then(customUser => {
if (customUser) {
const prefs = { ...this.state.prefs };
Object.assign(prefs, user.settings);
this.setState({ prefs }, this.updateSetting);
Object.assign(prefs, customUser.settings);
// spreading authUser doesn't work below anymore because the required properties are
// not enumerable anymore
newUser = {
...newUser,
isPro: false,
...customUser
};
window.user = newUser;
this.setState({ user: newUser, prefs }, this.updateSetting);
}
persistAuthUserLocally(newUser);
});
} else {
// User is signed out.
@@ -234,6 +291,18 @@ export default class App extends Component {
this.setCurrentItem(this.state.currentItem).then(() => {
this.refreshEditor();
});
} else if (this.props.itemId) {
window.db
.fetchItem(this.props.itemId)
.then(item => {
this.setCurrentItem(item).then(() => this.refreshEditor());
})
.catch(err => {
alert('No such creation found!');
this.createNewItem();
// route('/');
});
} else if (result.preserveLastCode && lastCode) {
this.setState({ unsavedEditCount: 0 });
log('Load last unsaved item', lastCode);
@@ -267,8 +336,8 @@ export default class App extends Component {
semverCompare(lastSeenVersion, version) === -1 &&
!window.localStorage.pledgeModalSeen
) {
this.openSupportDeveloperModal();
window.localStorage.pledgeModalSeen = true;
// this.openSupportDeveloperModal();
// window.localStorage.pledgeModalSeen = true;
}
if (!lastSeenVersion || semverCompare(lastSeenVersion, version) === -1) {
@@ -346,9 +415,12 @@ export default class App extends Component {
}
const fork = JSON.parse(JSON.stringify(sourceItem));
delete fork.id;
delete fork.createdBy;
delete fork.isPublic;
fork.title = '(Forked) ' + sourceItem.title;
fork.updatedOn = Date.now();
this.setCurrentItem(fork).then(() => this.refreshEditor());
customRoute('/create');
alertsService.add(`"${sourceItem.title}" was forked`);
trackEvent('fn', 'itemForked');
}
@@ -408,10 +480,12 @@ export default class App extends Component {
};
}
this.setCurrentItem(item).then(() => this.refreshEditor());
customRoute('/create');
alertsService.add('New item created');
}
openItem(item) {
this.setCurrentItem(item).then(() => this.refreshEditor());
customRoute(`/create/${item.id}`);
alertsService.add('Saved item loaded');
}
removeItem(item) {
@@ -538,6 +612,9 @@ export default class App extends Component {
openAddLibrary() {
this.setState({ isAddLibraryModalOpen: true });
}
assetsBtnClickHandler() {
this.setState({ isAssetsOpen: true });
}
closeSavedItemsPane() {
this.setState({
isSavedItemPaneOpen: false
@@ -556,6 +633,7 @@ export default class App extends Component {
}
componentDidMount() {
console.log('itemId', this.props.itemId);
function setBodySize() {
document.body.style.height = `${window.innerHeight}px`;
}
@@ -891,8 +969,16 @@ export default class App extends Component {
trackEvent('ui', LocalStorageKeys.LOGIN_AND_SAVE_MESSAGE_SEEN, 'local');
}
var isNewItem = !this.state.currentItem.id;
this.state.currentItem.id =
this.state.currentItem.id || 'item-' + generateRandomId();
this.state.currentItem.id = this.state.currentItem.id || 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
});
@@ -952,14 +1038,17 @@ export default class App extends Component {
}
titleInputBlurHandler(e) {
this.setState({
currentItem: { ...this.state.currentItem, title: e.target.value }
});
if (this.state.currentItem.id) {
this.saveItem();
trackEvent('ui', 'titleChanged');
}
this.setState(
{
currentItem: { ...this.state.currentItem, title: e.target.value }
},
() => {
if (this.state.currentItem.id) {
this.saveItem();
trackEvent('ui', 'titleChanged');
}
}
);
}
/**
@@ -1045,6 +1134,7 @@ export default class App extends Component {
trackEvent('fn', 'loggedOut');
authh.logout();
this.setState({ isProfileModalOpen: false });
this.createNewItem();
alertsService.add('Log out successfull');
}
@@ -1087,6 +1177,14 @@ export default class App extends Component {
trackEvent('ui', 'openBtnClick');
this.openSavedItemsPane();
}
shareBtnClickHandler() {
trackEvent('ui', 'shareBtnClick');
if (!window.user || this.state.currentItem.id) {
this.setState({ isShareModalOpen: true });
} else {
alertsService.add('Please save your creation before sharing.');
}
}
detachedPreviewBtnHandler() {
trackEvent('ui', 'detachPreviewBtnClick');
@@ -1095,7 +1193,7 @@ export default class App extends Component {
notificationsBtnClickHandler() {
this.setState({ isNotificationsModalOpen: true });
if (this.state.isNotificationsModalOpen && !this.hasSeenNotifications) {
if (!this.state.isNotificationsModalOpen && !this.hasSeenNotifications) {
this.hasSeenNotifications = true;
this.setState({ hasUnseenChangelog: false });
window.db.setUserLastSeenVersion(version);
@@ -1103,6 +1201,15 @@ export default class App extends Component {
trackEvent('ui', 'notificationButtonClick', version);
return false;
}
proBtnClickHandler() {
if (this.state.user?.isPro) {
this.setState({ isProfileModalOpen: true });
trackEvent('ui', 'manageProBtnClick');
} else {
this.setState({ isProModalOpen: true });
trackEvent('ui', 'proBtnClick');
}
}
codepenBtnClickHandler(e) {
if (this.state.currentItem.cssMode === CssModes.ACSS) {
alert(
@@ -1443,9 +1550,11 @@ export default class App extends Component {
this.setState({ isCreateNewModalOpen: false });
} else {
trackEvent('ui', 'FileModeCreationLimitMessageSeen');
return alert(
'"Files mode" is currently in beta and is limited to only 2 creations per user. You have already made 2 creations in Files mode.\n\nNote: You can choose to delete old ones to create new.'
);
// this.closeAllOverlays();
this.setState({ isFilesLimitModalOpen: true });
// return alert(
// '"Files mode" is currently in beta and is limited to only 2 creations per user. You have already made 2 creations in Files mode.\n\nNote: You can choose to delete old ones to create new.'
// );
}
});
}
@@ -1578,7 +1687,7 @@ export default class App extends Component {
// 3 pane mode
if (typeof what === 'string') {
prettify({
content: this.state.currentItem[what],
content: this.state.currentItem[what] || '',
type: { html: 'html', js: 'js', css: 'css' }[what]
}).then(formattedContent => {
if (this.state.currentItem[what] === formattedContent) {
@@ -1599,6 +1708,7 @@ export default class App extends Component {
...this.state.currentItem,
files: [...this.state.currentItem.files]
};
prettify({ file: selectedFile }).then(formattedContent => {
if (formattedContent !== selectedFile.content) {
selectedFile.content = formattedContent;
@@ -1621,10 +1731,12 @@ export default class App extends Component {
loginBtnHandler={this.loginBtnClickHandler.bind(this)}
profileBtnHandler={this.profileBtnClickHandler.bind(this)}
addLibraryBtnHandler={this.openAddLibrary.bind(this)}
assetsBtnHandler={this.assetsBtnClickHandler.bind(this)}
shareBtnHandler={this.shareBtnClickHandler.bind(this)}
runBtnClickHandler={this.runBtnClickHandler.bind(this)}
isFetchingItems={this.state.isFetchingItems}
isSaving={this.state.isSaving}
title={this.state.currentItem.title}
currentItem={this.state.currentItem}
titleInputBlurHandler={this.titleInputBlurHandler.bind(this)}
user={this.state.user}
isAutoPreviewOn={this.state.prefs.autoPreview}
@@ -1632,6 +1744,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
@@ -1667,6 +1782,7 @@ export default class App extends Component {
<Footer
prefs={this.state.prefs}
user={this.state.user}
layoutBtnClickHandler={this.layoutBtnClickHandler.bind(this)}
helpBtnClickHandler={() =>
this.setState({ isHelpModalOpen: true })
@@ -1693,11 +1809,11 @@ export default class App extends Component {
onJs13KDownloadBtnClick={this.js13KDownloadBtnClickHandler.bind(
this
)}
proBtnClickHandler={this.proBtnClickHandler.bind(this)}
hasUnseenChangelog={this.state.hasUnseenChangelog}
codeSize={this.state.codeSize}
/>
</div>
<SavedItemPane
itemsMap={this.state.savedItems}
isOpen={this.state.isSavedItemPaneOpen}
@@ -1708,9 +1824,7 @@ export default class App extends Component {
onExport={this.exportBtnClickHandler.bind(this)}
mergeImportedItems={this.mergeImportedItems.bind(this)}
/>
<Alerts />
<Modal
show={this.state.isAddLibraryModalOpen}
closeHandler={() => this.setState({ isAddLibraryModalOpen: false })}
@@ -1735,7 +1849,7 @@ export default class App extends Component {
this.setState({ isNotificationsModalOpen: false })
}
>
<Notifications
<Changelog
onSupportBtnClick={this.openSupportDeveloperModal.bind(this)}
/>
</Modal>
@@ -1749,13 +1863,6 @@ export default class App extends Component {
onChange={this.updateSetting.bind(this)}
/>
</Modal>
<Modal
extraClasses="login-modal"
show={this.state.isLoginModalOpen}
closeHandler={() => this.setState({ isLoginModalOpen: false })}
>
<Login />
</Modal>
<Modal
show={this.state.isProfileModalOpen}
closeHandler={() => this.setState({ isProfileModalOpen: false })}
@@ -1765,6 +1872,73 @@ export default class App extends Component {
logoutBtnHandler={this.logout.bind(this)}
/>
</Modal>
<Modal
show={this.state.isAssetsOpen}
closeHandler={() => this.setState({ isAssetsOpen: false })}
>
<Assets
onProBtnClick={() => {
this.setState({ isAssetsOpen: false });
this.proBtnClickHandler();
}}
onLoginBtnClick={() => {
this.closeAllOverlays();
this.loginBtnClickHandler();
}}
/>
</Modal>
<Modal
show={this.state.isShareModalOpen}
closeHandler={() => this.setState({ isShareModalOpen: false })}
>
<Share
user={this.state.user}
item={this.state.currentItem}
onVisibilityChange={visibility => {
const item = {
...this.state.currentItem,
isPublic: visibility
};
this.setState({ currentItem: item });
}}
onLoginBtnClick={() => {
this.closeAllOverlays();
this.loginBtnClickHandler();
}}
onProBtnClick={() => {
this.closeAllOverlays();
this.proBtnClickHandler();
}}
/>
</Modal>
<Modal
show={this.state.isProModalOpen}
closeHandler={() => this.setState({ isProModalOpen: false })}
extraClasses="pro-modal"
>
<Pro
user={this.state.user}
onLoginClick={() => {
this.closeAllOverlays();
this.loginBtnClickHandler();
}}
onBuyFromExtensionClick={() => {
console.log('open modal');
this.closeAllOverlays();
this.setState({ isProOnAppModalOpen: true });
}}
/>
</Modal>
{/* Login modal is intentionally kept here after assets & share modal because
they trigger this modal and if order isn't maintainer, the modal overlay doesn't
show properly */}
<Modal
extraClasses="login-modal"
show={this.state.isLoginModalOpen}
closeHandler={() => this.setState({ isLoginModalOpen: false })}
>
<Login />
</Modal>
<HelpModal
show={this.state.isHelpModalOpen}
closeHandler={() => this.setState({ isHelpModalOpen: false })}
@@ -1794,17 +1968,14 @@ export default class App extends Component {
)}
dontAskBtnClickHandler={this.dontAskToImportAnymore.bind(this)}
/>
<OnboardingModal
show={this.state.isOnboardModalOpen}
closeHandler={() => this.setState({ isOnboardModalOpen: false })}
/>
<Js13KModal
show={this.state.isJs13KModalOpen}
closeHandler={() => this.setState({ isJs13KModalOpen: false })}
/>
<CreateNewModal
show={this.state.isCreateNewModalOpen}
closeHandler={() => this.setState({ isCreateNewModalOpen: false })}
@@ -1817,21 +1988,38 @@ export default class App extends Component {
this
)}
/>
<ProOnAppModal
show={this.state.isProOnAppModalOpen}
closeHandler={() => this.setState({ isProOnAppModalOpen: false })}
/>
<Modal
extraClasses=""
show={this.state.isFilesLimitModalOpen}
closeHandler={() => this.setState({ isFilesLimitModalOpen: false })}
>
<VStack align="stretch" gap={2}>
<Text tag="p">
You have used your quota of 2 'Files mode' creations in Free
plan.
</Text>
<Text tag="p">
You can choose to delete old ones to free quota or upgrade to{' '}
<ProBadge />.{' '}
</Text>
</VStack>
</Modal>
<CommandPalette
show={this.state.isCommandPaletteOpen}
files={linearizeFiles(this.state.currentItem.files || [])}
isCommandMode={this.state.isCommandPaletteInCommandMode}
closeHandler={() => this.setState({ isCommandPaletteOpen: false })}
/>
<Portal into="#portal">
<div
class="modal-overlay"
onClick={this.modalOverlayClickHandler.bind(this)}
/>
</Portal>
<Icons />
<form
style="display:none;"