Skip to content

Commit

Permalink
feat(routes): add fade transition effect
Browse files Browse the repository at this point in the history
  • Loading branch information
AXeL-dev committed Feb 25, 2022
1 parent 2c7ae00 commit a1467c1
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 115 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"react-dom": "^17.0.2",
"react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3",
"react-transition-group": "^4.4.2",
"sass": "^1.49.8",
"web-vitals": "^2.1.4"
},
Expand Down
33 changes: 22 additions & 11 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { HashRouter as Router, Switch, Route } from 'react-router-dom';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { storage } from './helpers/webext';
import { isDevEnv } from './helpers/debug';
import { Panel, Settings, Logs, Background, Blocked, PasswordPrompt, AddWebsitePrompt } from './components';
Expand Down Expand Up @@ -29,17 +30,27 @@ export default class App extends Component {
render() {
return (
<Router>
<Switch>
<PasswordProtectedRoute exact path="/" component={Panel} accessAllowed={this.state.accessAllowed} showPromptHeader={true} showPromptFooter={true} />
<PasswordProtectedRoute path="/settings" component={Settings} accessAllowed={this.state.accessAllowed} />
<PasswordProtectedRoute path="/logs" component={Logs} accessAllowed={this.state.accessAllowed} />
<Route path="/background" component={Background} />
<Route path="/blocked" component={Blocked} />
<Route path="/addWebsitePrompt" component={AddWebsitePrompt} />
{isDevEnv && (
<Route path="/pwd" component={PasswordPrompt} />
)}
</Switch>
<Route render={({ location }) => (
<TransitionGroup className="page">
<CSSTransition
key={location.pathname}
classNames="fade"
timeout={300}
>
<Switch location={location}>
<PasswordProtectedRoute exact path="/" component={Panel} accessAllowed={this.state.accessAllowed} showPromptHeader={true} showPromptFooter={true} />
<PasswordProtectedRoute path="/settings" component={Settings} accessAllowed={this.state.accessAllowed} />
<PasswordProtectedRoute path="/logs" component={Logs} accessAllowed={this.state.accessAllowed} />
<Route path="/background" component={Background} />
<Route path="/blocked" component={Blocked} />
<Route path="/addWebsitePrompt" component={AddWebsitePrompt} />
{isDevEnv || !this.state.accessAllowed ? (
<Route path="/pwd" component={PasswordPrompt} />
) : null}
</Switch>
</CSSTransition>
</TransitionGroup>
)} />
</Router>
);
}
Expand Down
138 changes: 69 additions & 69 deletions src/components/Panel/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,81 +119,81 @@ export class Panel extends Component {
}

render() {
if (!this.state.ready) {
return null;
}

return (
<Pane minWidth={350}>
<Header />
{this.state.isEnabled && this.state.schedule.isEnabled ? (
<Pane display="flex" paddingX={16} paddingY={20}>
<Pane display="flex" alignItems="center" flex={1}>
<Text className="cursor-default">{translate('status')}</Text>
</Pane>
<Pane display="flex" alignItems="center" justifyContent="center">
{this.renderScheduleStatus()}
</Pane>
</Pane>
) : (
<SwitchField
label={translate('status')}
labelClassName="cursor-default"
checked={this.state.isEnabled}
onChange={event => this.toggleStatus(event.target.checked)}
height={20}
paddingX={16}
paddingY={20}
/>
)}
<SegmentedControlField
name="mode"
label={translate('mode')}
labelClassName="cursor-default"
options={modes}
value={this.state.mode}
onChange={this.changeMode}
maxWidth={260}
paddingX={16}
paddingBottom={20}
/>
<Pane display="flex" paddingX={16} paddingY={10} alignItems="center" justifyContent="space-between" borderTop>
<Pane display="flex" gap={10}>
<SettingsButton history={this.props.history} />
{this.state.enableLogs && (
<LinkIconButton
icon={HistoryIcon}
link="/logs"
tooltip={translate('logs')}
history={this.props.history}
/>
)}
{!this.state.hideReportIssueButton && (
<LinkIconButton
icon={IssueNewIcon}
link="https://github.com/AXeL-dev/distract-me-not/issues"
external
tooltip={translate('reportIssue')}
history={this.props.history}
{!this.state.ready ? null : (
<>
{this.state.isEnabled && this.state.schedule.isEnabled ? (
<Pane display="flex" paddingX={16} paddingY={20}>
<Pane display="flex" alignItems="center" flex={1}>
<Text className="cursor-default">{translate('status')}</Text>
</Pane>
<Pane display="flex" alignItems="center" justifyContent="center">
{this.renderScheduleStatus()}
</Pane>
</Pane>
) : (
<SwitchField
label={translate('status')}
labelClassName="cursor-default"
checked={this.state.isEnabled}
onChange={event => this.toggleStatus(event.target.checked)}
height={20}
paddingX={16}
paddingY={20}
/>
)}
</Pane>
<Pane>
<AnimatedIconButton
appearance="minimal"
tooltip={this.state.mode === Mode.whitelist ? translate('addToWhitelist') : translate('addToBlacklist')}
tooltipPosition={Position.LEFT}
icon={PlusIcon}
iconSize={22}
iconColor="#47b881"
onClick={() => addCurrentWebsite(this.state.mode, this.state.showAddWebsitePrompt)}
hideOnClick={true}
hideAnimationIcon={TickIcon}
isVisible={this.state.isAddButtonVisible}
onVisibilityChange={this.setAddButtonVisibility}
<SegmentedControlField
name="mode"
label={translate('mode')}
labelClassName="cursor-default"
options={modes}
value={this.state.mode}
onChange={this.changeMode}
maxWidth={260}
paddingX={16}
paddingBottom={20}
/>
</Pane>
</Pane>
<Pane display="flex" paddingX={16} paddingY={10} alignItems="center" justifyContent="space-between" borderTop>
<Pane display="flex" gap={10}>
<SettingsButton history={this.props.history} />
{this.state.enableLogs && (
<LinkIconButton
icon={HistoryIcon}
link="/logs"
tooltip={translate('logs')}
history={this.props.history}
/>
)}
{!this.state.hideReportIssueButton && (
<LinkIconButton
icon={IssueNewIcon}
link="https://github.com/AXeL-dev/distract-me-not/issues"
external
tooltip={translate('reportIssue')}
history={this.props.history}
/>
)}
</Pane>
<Pane>
<AnimatedIconButton
appearance="minimal"
tooltip={this.state.mode === Mode.whitelist ? translate('addToWhitelist') : translate('addToBlacklist')}
tooltipPosition={Position.LEFT}
icon={PlusIcon}
iconSize={22}
iconColor="#47b881"
onClick={() => addCurrentWebsite(this.state.mode, this.state.showAddWebsitePrompt)}
hideOnClick={true}
hideAnimationIcon={TickIcon}
isVisible={this.state.isAddButtonVisible}
onVisibilityChange={this.setAddButtonVisibility}
/>
</Pane>
</Pane>
</>
)}
</Pane>
);
}
Expand Down
46 changes: 33 additions & 13 deletions src/components/PasswordPrompt/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,36 @@ export class PasswordPrompt extends Component {
super(props);
this.mode = defaultMode;
this.hash = defaultHash || null;
this.hasHeader = this.props.hasHeader || this.getLocationProp('hasHeader');
this.hasFooter = this.props.hasFooter || this.getLocationProp('hasFooter');
this.showAddWebsitePrompt = false;
this.isWideScreen = ['/settings', '/logs'].some((route) => this.props.path.startsWith(route));
debug.log({ hash: this.hash, path: this.props.path });
this.isWideScreen = this.getIsWideScreen();
debug.log({ hash: this.hash, props });
this.state = {
password: '',
isQuickActivationButtonVisible: false,
isAddButtonVisible: false,
enableLogs: false,
isAddButtonVisible: false,
isQuickActivationButtonVisible: false,
};
}

getLocationProp(prop, defaultValue = undefined) {
return this.props.location.state ? this.props.location.state[prop] : defaultValue;
}

getRedirectPath() {
return this.props.path || this.getLocationProp('path') || '/';
}

getQueryParams() {
return this.getLocationProp('search') || '';
}

getIsWideScreen() {
const redirectPath = this.getRedirectPath();
return ['/settings', '/logs'].includes(redirectPath);
}

componentDidMount() {
sendMessage('getLogsSettings').then(logs => this.setState({ enableLogs: (logs || defaultLogsSettings).isEnabled }));
storage.get({
Expand Down Expand Up @@ -76,12 +95,6 @@ export class PasswordPrompt extends Component {
}));
}

redirectTo = (path, state = null) => {
debug.log('redirecting to:', path, state);
this.props.history.location.state = state;
this.props.history.push(path || '/');//, state); // passing state to history.push() doesn't work with hash router
}

checkPassword = () => {
if (!compare(this.state.password, this.hash)) {
toaster.danger(translate('passwordIsWrong'), { id: 'pwd-toaster' });
Expand All @@ -90,7 +103,14 @@ export class PasswordPrompt extends Component {
if (this.props.onSuccess) {
this.props.onSuccess();
} else {
this.redirectTo(this.props.path, { accessAllowed: true });
const pathname = this.getRedirectPath();
const search = this.getQueryParams();
debug.log(`redirecting to: ${[pathname, search].join()}`);
this.props.history.push({
pathname,
search,
state: { accessAllowed: true },
});
}
}
}
Expand Down Expand Up @@ -143,7 +163,7 @@ export class PasswordPrompt extends Component {
minWidth={this.getMinWidth()}
minHeight={this.getMinHeight()}
>
{this.props.hasHeader && (
{this.hasHeader && (
<Header />
)}
<Pane
Expand Down Expand Up @@ -183,7 +203,7 @@ export class PasswordPrompt extends Component {
</Pane>
</Pane>
</Pane>
{this.props.hasFooter && (
{this.hasFooter && (
<Pane display="flex" paddingX={16} paddingY={10} alignItems="start" justifyContent="space-between" borderTop>
<Pane display="flex" gap={10}>
<SettingsButton history={this.props.history} />
Expand Down
5 changes: 2 additions & 3 deletions src/components/Settings/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,13 @@ export class Settings extends Component {
}

getSelectedTab = () => {
const search = window.location.hash.replace(/^#\/settings/, '');
const urlParams = new URLSearchParams(search);
const urlParams = new URLSearchParams(this.props.location.search);
return urlParams.get('tab');
}

selectTab = (id) => {
this.setState({ selectedTab: id });
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}#/settings?tab=${id}`;
const url = `#${this.props.location.pathname}?tab=${id}`;
window.history.pushState({ path: url }, document.title, url);
}

Expand Down
28 changes: 28 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,31 @@ body {
#root {
height: 100%;
}

.page {
width: 100%;
height: 100%;
}

.fade-enter {
opacity: 0;
z-index: 1;
}

.fade-enter-active {
opacity: 1;
transition: opacity 250ms ease-in;
}

.fade-exit {
opacity: 1;
position: absolute;
top: 0;
left: 0;
width: 100%;
}

.fade-exit-active {
opacity: 0;
transition: opacity 250ms ease-out;
}
29 changes: 10 additions & 19 deletions src/routes/PasswordProtectedRoute.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
import React from 'react';
import { Route } from 'react-router-dom';
import { Route, Redirect } from 'react-router-dom';
import { debug } from 'helpers/debug';
import { PasswordPrompt } from 'components';

// Inspired from: https://blog.netcetera.com/how-to-create-guarded-routes-for-your-react-app-d2fe7c7b6122

function getFullRoute(path) {
if (path) {
// return path + hash parameters
const regex = new RegExp(`^#${path}`);
const params = window.location.hash.replace(regex, '');
return `${path}${params}`;
} else {
return '/';
}
}

export const PasswordProtectedRoute = ({
path,
component: Component,
Expand All @@ -35,12 +23,15 @@ export const PasswordProtectedRoute = ({
) : accessAllowed === true || (props.location.state && props.location.state.accessAllowed === true) ? (
<Component {...props} />
) : (
<PasswordPrompt
path={getFullRoute(path)}
hasHeader={showPromptHeader}
hasFooter={showPromptFooter}
{...props}
/>
<Redirect to={{
pathname: '/pwd',
state: {
path,
search: props.location.search,
hasHeader: showPromptHeader,
hasFooter: showPromptFooter,
}
}} />
);
}}
/>
Expand Down

0 comments on commit a1467c1

Please sign in to comment.