Skip to content

Commit a1467c1

Browse files
committed
feat(routes): add fade transition effect
1 parent 2c7ae00 commit a1467c1

File tree

8 files changed

+166
-115
lines changed

8 files changed

+166
-115
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"react-dom": "^17.0.2",
2020
"react-router-dom": "^5.3.0",
2121
"react-scripts": "^4.0.3",
22+
"react-transition-group": "^4.4.2",
2223
"sass": "^1.49.8",
2324
"web-vitals": "^2.1.4"
2425
},

src/App.jsx

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { Component } from 'react';
22
import { HashRouter as Router, Switch, Route } from 'react-router-dom';
3+
import { TransitionGroup, CSSTransition } from 'react-transition-group';
34
import { storage } from './helpers/webext';
45
import { isDevEnv } from './helpers/debug';
56
import { Panel, Settings, Logs, Background, Blocked, PasswordPrompt, AddWebsitePrompt } from './components';
@@ -29,17 +30,27 @@ export default class App extends Component {
2930
render() {
3031
return (
3132
<Router>
32-
<Switch>
33-
<PasswordProtectedRoute exact path="/" component={Panel} accessAllowed={this.state.accessAllowed} showPromptHeader={true} showPromptFooter={true} />
34-
<PasswordProtectedRoute path="/settings" component={Settings} accessAllowed={this.state.accessAllowed} />
35-
<PasswordProtectedRoute path="/logs" component={Logs} accessAllowed={this.state.accessAllowed} />
36-
<Route path="/background" component={Background} />
37-
<Route path="/blocked" component={Blocked} />
38-
<Route path="/addWebsitePrompt" component={AddWebsitePrompt} />
39-
{isDevEnv && (
40-
<Route path="/pwd" component={PasswordPrompt} />
41-
)}
42-
</Switch>
33+
<Route render={({ location }) => (
34+
<TransitionGroup className="page">
35+
<CSSTransition
36+
key={location.pathname}
37+
classNames="fade"
38+
timeout={300}
39+
>
40+
<Switch location={location}>
41+
<PasswordProtectedRoute exact path="/" component={Panel} accessAllowed={this.state.accessAllowed} showPromptHeader={true} showPromptFooter={true} />
42+
<PasswordProtectedRoute path="/settings" component={Settings} accessAllowed={this.state.accessAllowed} />
43+
<PasswordProtectedRoute path="/logs" component={Logs} accessAllowed={this.state.accessAllowed} />
44+
<Route path="/background" component={Background} />
45+
<Route path="/blocked" component={Blocked} />
46+
<Route path="/addWebsitePrompt" component={AddWebsitePrompt} />
47+
{isDevEnv || !this.state.accessAllowed ? (
48+
<Route path="/pwd" component={PasswordPrompt} />
49+
) : null}
50+
</Switch>
51+
</CSSTransition>
52+
</TransitionGroup>
53+
)} />
4354
</Router>
4455
);
4556
}

src/components/Panel/index.jsx

Lines changed: 69 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -119,81 +119,81 @@ export class Panel extends Component {
119119
}
120120

121121
render() {
122-
if (!this.state.ready) {
123-
return null;
124-
}
125-
126122
return (
127123
<Pane minWidth={350}>
128124
<Header />
129-
{this.state.isEnabled && this.state.schedule.isEnabled ? (
130-
<Pane display="flex" paddingX={16} paddingY={20}>
131-
<Pane display="flex" alignItems="center" flex={1}>
132-
<Text className="cursor-default">{translate('status')}</Text>
133-
</Pane>
134-
<Pane display="flex" alignItems="center" justifyContent="center">
135-
{this.renderScheduleStatus()}
136-
</Pane>
137-
</Pane>
138-
) : (
139-
<SwitchField
140-
label={translate('status')}
141-
labelClassName="cursor-default"
142-
checked={this.state.isEnabled}
143-
onChange={event => this.toggleStatus(event.target.checked)}
144-
height={20}
145-
paddingX={16}
146-
paddingY={20}
147-
/>
148-
)}
149-
<SegmentedControlField
150-
name="mode"
151-
label={translate('mode')}
152-
labelClassName="cursor-default"
153-
options={modes}
154-
value={this.state.mode}
155-
onChange={this.changeMode}
156-
maxWidth={260}
157-
paddingX={16}
158-
paddingBottom={20}
159-
/>
160-
<Pane display="flex" paddingX={16} paddingY={10} alignItems="center" justifyContent="space-between" borderTop>
161-
<Pane display="flex" gap={10}>
162-
<SettingsButton history={this.props.history} />
163-
{this.state.enableLogs && (
164-
<LinkIconButton
165-
icon={HistoryIcon}
166-
link="/logs"
167-
tooltip={translate('logs')}
168-
history={this.props.history}
169-
/>
170-
)}
171-
{!this.state.hideReportIssueButton && (
172-
<LinkIconButton
173-
icon={IssueNewIcon}
174-
link="https://github.com/AXeL-dev/distract-me-not/issues"
175-
external
176-
tooltip={translate('reportIssue')}
177-
history={this.props.history}
125+
{!this.state.ready ? null : (
126+
<>
127+
{this.state.isEnabled && this.state.schedule.isEnabled ? (
128+
<Pane display="flex" paddingX={16} paddingY={20}>
129+
<Pane display="flex" alignItems="center" flex={1}>
130+
<Text className="cursor-default">{translate('status')}</Text>
131+
</Pane>
132+
<Pane display="flex" alignItems="center" justifyContent="center">
133+
{this.renderScheduleStatus()}
134+
</Pane>
135+
</Pane>
136+
) : (
137+
<SwitchField
138+
label={translate('status')}
139+
labelClassName="cursor-default"
140+
checked={this.state.isEnabled}
141+
onChange={event => this.toggleStatus(event.target.checked)}
142+
height={20}
143+
paddingX={16}
144+
paddingY={20}
178145
/>
179146
)}
180-
</Pane>
181-
<Pane>
182-
<AnimatedIconButton
183-
appearance="minimal"
184-
tooltip={this.state.mode === Mode.whitelist ? translate('addToWhitelist') : translate('addToBlacklist')}
185-
tooltipPosition={Position.LEFT}
186-
icon={PlusIcon}
187-
iconSize={22}
188-
iconColor="#47b881"
189-
onClick={() => addCurrentWebsite(this.state.mode, this.state.showAddWebsitePrompt)}
190-
hideOnClick={true}
191-
hideAnimationIcon={TickIcon}
192-
isVisible={this.state.isAddButtonVisible}
193-
onVisibilityChange={this.setAddButtonVisibility}
147+
<SegmentedControlField
148+
name="mode"
149+
label={translate('mode')}
150+
labelClassName="cursor-default"
151+
options={modes}
152+
value={this.state.mode}
153+
onChange={this.changeMode}
154+
maxWidth={260}
155+
paddingX={16}
156+
paddingBottom={20}
194157
/>
195-
</Pane>
196-
</Pane>
158+
<Pane display="flex" paddingX={16} paddingY={10} alignItems="center" justifyContent="space-between" borderTop>
159+
<Pane display="flex" gap={10}>
160+
<SettingsButton history={this.props.history} />
161+
{this.state.enableLogs && (
162+
<LinkIconButton
163+
icon={HistoryIcon}
164+
link="/logs"
165+
tooltip={translate('logs')}
166+
history={this.props.history}
167+
/>
168+
)}
169+
{!this.state.hideReportIssueButton && (
170+
<LinkIconButton
171+
icon={IssueNewIcon}
172+
link="https://github.com/AXeL-dev/distract-me-not/issues"
173+
external
174+
tooltip={translate('reportIssue')}
175+
history={this.props.history}
176+
/>
177+
)}
178+
</Pane>
179+
<Pane>
180+
<AnimatedIconButton
181+
appearance="minimal"
182+
tooltip={this.state.mode === Mode.whitelist ? translate('addToWhitelist') : translate('addToBlacklist')}
183+
tooltipPosition={Position.LEFT}
184+
icon={PlusIcon}
185+
iconSize={22}
186+
iconColor="#47b881"
187+
onClick={() => addCurrentWebsite(this.state.mode, this.state.showAddWebsitePrompt)}
188+
hideOnClick={true}
189+
hideAnimationIcon={TickIcon}
190+
isVisible={this.state.isAddButtonVisible}
191+
onVisibilityChange={this.setAddButtonVisibility}
192+
/>
193+
</Pane>
194+
</Pane>
195+
</>
196+
)}
197197
</Pane>
198198
);
199199
}

src/components/PasswordPrompt/index.jsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,36 @@ export class PasswordPrompt extends Component {
1616
super(props);
1717
this.mode = defaultMode;
1818
this.hash = defaultHash || null;
19+
this.hasHeader = this.props.hasHeader || this.getLocationProp('hasHeader');
20+
this.hasFooter = this.props.hasFooter || this.getLocationProp('hasFooter');
1921
this.showAddWebsitePrompt = false;
20-
this.isWideScreen = ['/settings', '/logs'].some((route) => this.props.path.startsWith(route));
21-
debug.log({ hash: this.hash, path: this.props.path });
22+
this.isWideScreen = this.getIsWideScreen();
23+
debug.log({ hash: this.hash, props });
2224
this.state = {
2325
password: '',
24-
isQuickActivationButtonVisible: false,
25-
isAddButtonVisible: false,
2626
enableLogs: false,
27+
isAddButtonVisible: false,
28+
isQuickActivationButtonVisible: false,
2729
};
2830
}
2931

32+
getLocationProp(prop, defaultValue = undefined) {
33+
return this.props.location.state ? this.props.location.state[prop] : defaultValue;
34+
}
35+
36+
getRedirectPath() {
37+
return this.props.path || this.getLocationProp('path') || '/';
38+
}
39+
40+
getQueryParams() {
41+
return this.getLocationProp('search') || '';
42+
}
43+
44+
getIsWideScreen() {
45+
const redirectPath = this.getRedirectPath();
46+
return ['/settings', '/logs'].includes(redirectPath);
47+
}
48+
3049
componentDidMount() {
3150
sendMessage('getLogsSettings').then(logs => this.setState({ enableLogs: (logs || defaultLogsSettings).isEnabled }));
3251
storage.get({
@@ -76,12 +95,6 @@ export class PasswordPrompt extends Component {
7695
}));
7796
}
7897

79-
redirectTo = (path, state = null) => {
80-
debug.log('redirecting to:', path, state);
81-
this.props.history.location.state = state;
82-
this.props.history.push(path || '/');//, state); // passing state to history.push() doesn't work with hash router
83-
}
84-
8598
checkPassword = () => {
8699
if (!compare(this.state.password, this.hash)) {
87100
toaster.danger(translate('passwordIsWrong'), { id: 'pwd-toaster' });
@@ -90,7 +103,14 @@ export class PasswordPrompt extends Component {
90103
if (this.props.onSuccess) {
91104
this.props.onSuccess();
92105
} else {
93-
this.redirectTo(this.props.path, { accessAllowed: true });
106+
const pathname = this.getRedirectPath();
107+
const search = this.getQueryParams();
108+
debug.log(`redirecting to: ${[pathname, search].join()}`);
109+
this.props.history.push({
110+
pathname,
111+
search,
112+
state: { accessAllowed: true },
113+
});
94114
}
95115
}
96116
}
@@ -143,7 +163,7 @@ export class PasswordPrompt extends Component {
143163
minWidth={this.getMinWidth()}
144164
minHeight={this.getMinHeight()}
145165
>
146-
{this.props.hasHeader && (
166+
{this.hasHeader && (
147167
<Header />
148168
)}
149169
<Pane
@@ -183,7 +203,7 @@ export class PasswordPrompt extends Component {
183203
</Pane>
184204
</Pane>
185205
</Pane>
186-
{this.props.hasFooter && (
206+
{this.hasFooter && (
187207
<Pane display="flex" paddingX={16} paddingY={10} alignItems="start" justifyContent="space-between" borderTop>
188208
<Pane display="flex" gap={10}>
189209
<SettingsButton history={this.props.history} />

src/components/Settings/index.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,14 +203,13 @@ export class Settings extends Component {
203203
}
204204

205205
getSelectedTab = () => {
206-
const search = window.location.hash.replace(/^#\/settings/, '');
207-
const urlParams = new URLSearchParams(search);
206+
const urlParams = new URLSearchParams(this.props.location.search);
208207
return urlParams.get('tab');
209208
}
210209

211210
selectTab = (id) => {
212211
this.setState({ selectedTab: id });
213-
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}#/settings?tab=${id}`;
212+
const url = `#${this.props.location.pathname}?tab=${id}`;
214213
window.history.pushState({ path: url }, document.title, url);
215214
}
216215

src/index.css

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,31 @@ body {
1717
#root {
1818
height: 100%;
1919
}
20+
21+
.page {
22+
width: 100%;
23+
height: 100%;
24+
}
25+
26+
.fade-enter {
27+
opacity: 0;
28+
z-index: 1;
29+
}
30+
31+
.fade-enter-active {
32+
opacity: 1;
33+
transition: opacity 250ms ease-in;
34+
}
35+
36+
.fade-exit {
37+
opacity: 1;
38+
position: absolute;
39+
top: 0;
40+
left: 0;
41+
width: 100%;
42+
}
43+
44+
.fade-exit-active {
45+
opacity: 0;
46+
transition: opacity 250ms ease-out;
47+
}

src/routes/PasswordProtectedRoute.jsx

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,9 @@
11
import React from 'react';
2-
import { Route } from 'react-router-dom';
2+
import { Route, Redirect } from 'react-router-dom';
33
import { debug } from 'helpers/debug';
4-
import { PasswordPrompt } from 'components';
54

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

8-
function getFullRoute(path) {
9-
if (path) {
10-
// return path + hash parameters
11-
const regex = new RegExp(`^#${path}`);
12-
const params = window.location.hash.replace(regex, '');
13-
return `${path}${params}`;
14-
} else {
15-
return '/';
16-
}
17-
}
18-
197
export const PasswordProtectedRoute = ({
208
path,
219
component: Component,
@@ -35,12 +23,15 @@ export const PasswordProtectedRoute = ({
3523
) : accessAllowed === true || (props.location.state && props.location.state.accessAllowed === true) ? (
3624
<Component {...props} />
3725
) : (
38-
<PasswordPrompt
39-
path={getFullRoute(path)}
40-
hasHeader={showPromptHeader}
41-
hasFooter={showPromptFooter}
42-
{...props}
43-
/>
26+
<Redirect to={{
27+
pathname: '/pwd',
28+
state: {
29+
path,
30+
search: props.location.search,
31+
hasHeader: showPromptHeader,
32+
hasFooter: showPromptFooter,
33+
}
34+
}} />
4435
);
4536
}}
4637
/>

0 commit comments

Comments
 (0)