diff --git a/.dockerignore b/.dockerignore
index ec502fa3c..84703a3f8 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -17,6 +17,7 @@ frontend/static/frontend/*.js
frontend/static/frontend/*.css
frontend/static/frontend/*.map
frontend/static/frontend/report.html
+frontend/safe_mode/safe-mode.min.js
staticfiles/
frontend/lib/queries/__generated__/
querybuilder.js
diff --git a/.gitignore b/.gitignore
index ee5fd5386..0c3252a50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ frontend/static/frontend/*.js
frontend/static/frontend/*.css
frontend/static/frontend/*.map
frontend/static/frontend/report.html
+frontend/safe_mode/safe-mode.min.js
staticfiles/
frontend/lib/queries/__generated__/
querybuilder.js
diff --git a/frontend/context_processors.py b/frontend/context_processors.py
new file mode 100644
index 000000000..79d64fa53
--- /dev/null
+++ b/frontend/context_processors.py
@@ -0,0 +1,19 @@
+from typing import Dict, Any
+from project.util.js_snippet import JsSnippetContextProcessor
+import frontend.safe_mode
+
+
+class SafeModeJsSnippet(JsSnippetContextProcessor):
+ @property
+ def template(self) -> str:
+ return frontend.safe_mode.SAFE_MODE_JS.read_text()
+
+ var_name = 'SAFE_MODE_SNIPPET'
+
+
+def safe_mode(request):
+ is_enabled = frontend.safe_mode.is_enabled(request)
+ ctx: Dict[str, Any] = {'is_safe_mode_enabled': is_enabled}
+ if not is_enabled:
+ ctx.update(SafeModeJsSnippet()(request))
+ return ctx
diff --git a/frontend/lib/app.tsx b/frontend/lib/app.tsx
index ee60e950e..0fbeca707 100644
--- a/frontend/lib/app.tsx
+++ b/frontend/lib/app.tsx
@@ -194,10 +194,6 @@ export class AppWithoutRouter extends React.Component
-
- {/* This is a no-JS fallback. */}
-
-
diff --git a/frontend/lib/main.ts b/frontend/lib/main.ts
index 7997992ca..8b3bf584a 100644
--- a/frontend/lib/main.ts
+++ b/frontend/lib/main.ts
@@ -21,7 +21,5 @@ window.addEventListener('load', () => {
// Since JS is now loaded, let's remove that restriction.
div.removeAttribute('hidden');
- document.documentElement.classList.remove('jf-no-js');
-
startApp(div, initialProps);
});
diff --git a/frontend/lib/navbar.tsx b/frontend/lib/navbar.tsx
index ef946e944..b2f4b1673 100644
--- a/frontend/lib/navbar.tsx
+++ b/frontend/lib/navbar.tsx
@@ -4,25 +4,28 @@ import { Link } from 'react-router-dom';
import autobind from 'autobind-decorator';
import { AriaExpandableButton } from './aria';
import { bulmaClasses } from './bulma';
-import { AppContext, AppContextType } from './app-context';
+import { AppContextType, withAppContext } from './app-context';
import Routes from './routes';
-type Dropdown = 'developer';
+type Dropdown = 'developer'|'all';
-export interface NavbarProps {
-}
+export type NavbarProps = AppContextType;
interface NavbarState {
currentDropdown: Dropdown|null;
isHamburgerOpen: boolean;
}
-export default class Navbar extends React.Component {
+class NavbarWithoutAppContext extends React.Component {
navbarRef: React.RefObject;
constructor(props: NavbarProps) {
super(props);
- this.state = { currentDropdown: null, isHamburgerOpen: false };
+ if (props.session.isSafeModeEnabled) {
+ this.state = { currentDropdown: 'all', isHamburgerOpen: true };
+ } else {
+ this.state = { currentDropdown: null, isHamburgerOpen: false };
+ }
this.navbarRef = React.createRef();
}
@@ -56,13 +59,33 @@ export default class Navbar extends React.Component {
componentDidMount() {
window.addEventListener('focus', this.handleFocus, true);
+ window.addEventListener('resize', this.handleResize, false);
}
componentWillUnmount() {
window.removeEventListener('focus', this.handleFocus, true);
+ window.removeEventListener('resize', this.handleResize, false);
+ }
+
+ isDropdownActive(dropdown: Dropdown) {
+ return this.state.currentDropdown === dropdown || this.state.currentDropdown === 'all';
+ }
+
+ @autobind
+ handleResize() {
+ this.setState({
+ currentDropdown: null,
+ isHamburgerOpen: false
+ });
}
- renderDevMenu({ server }: AppContextType): JSX.Element|null {
+ @autobind
+ handleShowSafeModeUI() {
+ window.SafeMode.showUI();
+ }
+
+ renderDevMenu(): JSX.Element|null {
+ const { server, session } = this.props;
const { state } = this;
if (!server.debug) return null;
@@ -71,18 +94,21 @@ export default class Navbar extends React.Component {
this.toggleDropdown('developer')}
>
Webpack analysis
GraphiQL
Example PDF
GitHub
+ {!session.isSafeModeEnabled &&
+ Show safe mode UI}
);
}
- renderNavbarBrand({ server }: AppContextType): JSX.Element {
+ renderNavbarBrand(): JSX.Element {
+ const { server } = this.props;
const { state } = this;
return (
@@ -106,30 +132,31 @@ export default class Navbar extends React.Component {
render() {
const { state } = this;
+ const { session, server } = this.props;
return (
-
- {appContext => (
-