aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBen Woodward <ben@bdw.to>2021-08-05 12:12:17 -0700
committerGitHub <noreply@github.com>2021-08-05 12:12:17 -0700
commit093f025de3e97339750dc3df5be626a0315507dc (patch)
tree9b740790ae1f4fb6f5ad2b675c01f37acd2f0914 /src
parent5a03a61de7ac4e8f58060ae19a309dd40eea13bc (diff)
Style and code refactor (#166)
* Move all settings below card * refactor components; lifting state up * background color * Evergreen components for everything * password error * Tighten card size * Simply hide password to basic toggle, never disable * Hide password label, too * Maximize mobile portrait width * Make wifi tip smaller * Small style tweaks * Copy: update password length error text to include helpful instructions This will need a backfill for other translations * Remove unused css * Use empty string for EncryptionMode=None value * Remove light.min.css * Include logo on wificard * Cleanup after rebase * Clean-up comments on state * Padding for mobile
Diffstat (limited to 'src')
-rw-r--r--src/App.js173
-rw-r--r--src/components/Card.js225
-rw-r--r--src/components/Settings.js77
-rw-r--r--src/components/WifiCard.js148
-rw-r--r--src/components/style.css46
-rw-r--r--src/i18n.js33
-rw-r--r--src/style.css7
7 files changed, 401 insertions, 308 deletions
diff --git a/src/App.js b/src/App.js
index 87b88da..3d93bd6 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,7 +1,9 @@
1import React from 'react'; 1import { Button, Heading, Link, Pane, Paragraph } from 'evergreen-ui';
2import React, { useEffect, useRef, useState } from 'react';
2import { useTranslation } from 'react-i18next'; 3import { useTranslation } from 'react-i18next';
3import logo from '../src/images/wifi.png'; 4import logo from '../src/images/wifi.png';
4import { Card } from './components/Card'; 5import { Settings } from './components/Settings';
6import { WifiCard } from './components/WifiCard';
5import './style.css'; 7import './style.css';
6 8
7/* List of languages that require RTL direction (alphabetic order). */ 9/* List of languages that require RTL direction (alphabetic order). */
@@ -10,65 +12,138 @@ const RTL_LANGUAGES = ['ar', 'fa-IR'];
10function App() { 12function App() {
11 const html = document.querySelector('html'); 13 const html = document.querySelector('html');
12 const { t, i18n } = useTranslation(); 14 const { t, i18n } = useTranslation();
15 const firstLoad = useRef(true);
16 const [settings, setSettings] = useState({
17 // Network SSID name
18 ssid: '',
19 // Network password
20 password: '',
21 // Settings: Network encryption mode
22 encryptionMode: 'WPA',
23 // Settings: Hide password on the printed card
24 hidePassword: false,
25 // Settings: Portrait orientation
26 portrait: false,
27 });
28 const [errors, setErrors] = useState({
29 ssidError: '',
30 passwordError: '',
31 });
13 32
14 const changeLanguage = (language) => { 33 const onChangeLanguage = (language) => {
15 html.style.direction = RTL_LANGUAGES.includes(language) ? 'rtl' : 'ltr'; 34 html.style.direction = RTL_LANGUAGES.includes(language) ? 'rtl' : 'ltr';
16 i18n.changeLanguage(language); 35 i18n.changeLanguage(language);
17 }; 36 };
18 37
19 /* handle the edge case of the initial render requiring RTL direction */ 38 const onPrint = () => {
20 if (RTL_LANGUAGES.includes(i18n.language)) { 39 if (!settings.ssid.length) {
21 html.style.direction = 'rtl'; 40 setErrors({
22 } 41 ...errors,
42 ssidError: t('wifi.alert.name'),
43 });
44 return;
45 }
46
47 if (settings.ssid.length > 0) {
48 if (settings.password.length < 8 && settings.encryptionMode === 'WPA') {
49 setErrors({
50 ...errors,
51 passwordError: t('wifi.alert.password.length.8'),
52 });
53 } else if (
54 settings.password.length < 5 &&
55 settings.encryptionMode === 'WEP'
56 ) {
57 setErrors({
58 ...errors,
59 passwordError: t('wifi.alert.password.length.5'),
60 });
61 } else {
62 document.title = 'WiFi Card - ' + settings.ssid;
63 window.print();
64 }
65 }
66 };
67
68 const onSSIDChange = (ssid) => {
69 setErrors({ ...errors, ssidError: '' });
70 setSettings({ ...settings, ssid });
71 };
72 const onPasswordChange = (password) => {
73 setErrors({ ...errors, passwordError: '' });
74 setSettings({ ...settings, password });
75 };
76 const onEncryptionModeChange = (encryptionMode) => {
77 setErrors({ ...errors, passwordError: '' });
78 setSettings({ ...settings, encryptionMode });
79 };
80 const onOrientationChange = (portrait) => {
81 setSettings({ ...settings, portrait });
82 };
83 const onHidePasswordChange = (hidePassword) => {
84 setSettings({ ...settings, hidePassword });
85 };
86 const onFirstLoad = () => {
87 firstLoad.current = false;
88 };
89
90 useEffect(() => {
91 /* handle the edge case of the initial render requiring RTL direction */
92 if (RTL_LANGUAGES.includes(i18n.language)) {
93 html.style.direction = 'rtl';
94 }
95 });
23 96
24 return ( 97 return (
25 <div className="App"> 98 <Pane>
26 <h1> 99 <Pane display="flex">
27 <img alt="icon" src={logo} width="32" height="32" /> 100 <img alt="icon" src={logo} width="32" height="32" />
28 &nbsp; {t('title')} 101 <Heading size={900} paddingLeft={16}>
29 </h1> 102 {t('title')}
103 </Heading>
104 </Pane>
105
106 <Pane>
107 <Paragraph marginTop={12}>{t('desc.use')}</Paragraph>
30 108
31 <div> 109 <Paragraph marginTop={12}>
32 <label>{t('select')}</label> 110 {t('desc.privacy')}{' '}
33 <select 111 <Link href="https://github.com/bndw/wifi-card">
34 value={i18n.language} 112 {t('desc.source')}
35 onChange={(e) => changeLanguage(e.target.value)} 113 </Link>
36 > 114 .
37 <option value="en-US">English</option> 115 </Paragraph>
38 <option value="ar">Arabic - العربية</option> 116 </Pane>
39 <option value="ca">Catalan - Català</option>
40 <option value="zh-HK">Chinese Hong Kong - 简体中文</option>
41 <option value="zh-CN">Chinese Simplified - 简体中文</option>
42 <option value="nl-NL">Dutch - Nederlands</option>
43 <option value="fr-FR">French - Français</option>
44 <option value="de-DE">German - Deutsch</option>
45 <option value="hi-IN">Hindi - हिन्दी</option>
46 <option value="id-ID">Indonesian</option>
47 <option value="it-IT">Italian</option>
48 <option value="ja">Japanese - 日本語</option>
49 <option value="ko">Korean - 한국어</option>
50 <option value="no-NB">Norwegian - Norsk</option>
51 <option value="oc">Occitan</option>
52 <option value="fa-IR">Persian Iran - فارسی</option>
53 <option value="pl-PL">Polish - Polski</option>
54 <option value="pt">Portuguese - Português</option>
55 <option value="pt-BR">Portuguese - Português brasileiro</option>
56 <option value="ru-RU">Russian - Русский</option>
57 <option value="es">Spanish - Español</option>
58 <option value="tr-TR">Turkish - Türkçe</option>
59 <option value="uk-UA">Ukrainian - Українська</option>
60 </select>
61 </div>
62 117
63 <p className="tag">{t('desc.use')}</p> 118 <WifiCard
119 direction={RTL_LANGUAGES.includes(i18n.language) ? 'rtl' : 'ltr'}
120 settings={settings}
121 ssidError={errors.ssidError}
122 passwordError={errors.passwordError}
123 onSSIDChange={onSSIDChange}
124 onPasswordChange={onPasswordChange}
125 />
64 126
65 <p className="tag"> 127 <Settings
66 {t('desc.privacy')}{' '} 128 settings={settings}
67 <a href="https://github.com/bndw/wifi-card">{t('desc.source')}</a>. 129 firstLoad={firstLoad}
68 </p> 130 onFirstLoad={onFirstLoad}
131 onLanguageChange={onChangeLanguage}
132 onEncryptionModeChange={onEncryptionModeChange}
133 onOrientationChange={onOrientationChange}
134 onHidePasswordChange={onHidePasswordChange}
135 />
69 136
70 <Card direction={RTL_LANGUAGES.includes(i18n.language) ? 'rtl' : 'ltr'} /> 137 <Button
71 </div> 138 id="print"
139 appearance="primary"
140 height={40}
141 marginRight={16}
142 onClick={onPrint}
143 >
144 {t('button.print')}
145 </Button>
146 </Pane>
72 ); 147 );
73} 148}
74 149
diff --git a/src/components/Card.js b/src/components/Card.js
deleted file mode 100644
index 601b760..0000000
--- a/src/components/Card.js
+++ /dev/null
@@ -1,225 +0,0 @@
1import QRCode from 'qrcode.react';
2import { useEffect, useRef, useState } from 'react';
3import { useTranslation } from 'react-i18next';
4import './style.css';
5
6export const Card = ({ direction = 'ltr' }) => {
7 const firstLoad = useRef(true);
8 const [qrvalue, setQrvalue] = useState('');
9 const [network, setNetwork] = useState({
10 ssid: '',
11 encryptionMode: 'WPA',
12 password: '',
13 hidePassword: false,
14 });
15 const [portrait, setPortrait] = useState(false);
16 const { t } = useTranslation();
17 const escape = (v) => {
18 const needsEscape = ['"', ';', ',', ':', '\\'];
19
20 let escaped = '';
21 for (const c of v) {
22 if (needsEscape.includes(c)) {
23 escaped += `\\${c}`;
24 } else {
25 escaped += c;
26 }
27 }
28 return escaped;
29 };
30
31 const onPrint = () => {
32 if (network.ssid.length > 0) {
33 if (network.password.length < 8 && network.encryptionMode === 'WPA') {
34 alert(t('wifi.alert.password.length.8'));
35 } else if (
36 network.password.length < 5 &&
37 network.encryptionMode === 'WEP'
38 ) {
39 alert(t('wifi.alert.password.length.5'));
40 } else {
41 document.title = 'WiFi Card - ' + network.ssid;
42 window.print();
43 }
44 } else {
45 alert(t('wifi.alert.name'));
46 }
47 };
48
49 const disableHidePassword = () => {
50 const isWEPWithPasswordLengthShorterThat5Characters = () => {
51 return network.encryptionMode === 'WEP' && network.password.length < 5
52 ? true
53 : false;
54 };
55
56 return network.encryptionMode === 'WPA' && network.password.length < 8
57 ? true
58 : isWEPWithPasswordLengthShorterThat5Characters();
59 };
60
61 useEffect(() => {
62 if (firstLoad.current && window.innerWidth < 500) {
63 firstLoad.current = false;
64 setPortrait(true);
65 }
66
67 const ssid = escape(network.ssid);
68 const password =
69 network.encryptionMode === 'nopass' ? '' : escape(network.password);
70 setQrvalue(`WIFI:T:${network.encryptionMode};S:${ssid};P:${password};;`);
71 }, [network]);
72
73 const setEncryptionMode = (e) =>
74 setNetwork({
75 ...network,
76 encryptionMode: e.target.value,
77 });
78
79 const checkDirectionAndSetPadding =
80 direction === 'ltr' ? { paddingRight: '1em' } : { paddingLeft: '1em' };
81
82 return (
83 <div>
84 <fieldset
85 id="print-area"
86 style={{ maxWidth: portrait ? '350px' : '100%' }}
87 >
88 <h1 style={{ textAlign: portrait ? 'center' : 'unset' }}>
89 {t('wifi.login')}
90 </h1>
91
92 <div
93 className="details"
94 style={{ flexDirection: portrait ? 'column' : 'row' }}
95 >
96 <QRCode
97 className="qrcode"
98 style={!portrait && checkDirectionAndSetPadding}
99 value={qrvalue}
100 size={175}
101 />
102
103 <div className="inputs">
104 <label>{t('wifi.name')}</label>
105 <textarea
106 id="ssid"
107 type="text"
108 maxLength="32"
109 placeholder={t('wifi.name.placeholder')}
110 autoComplete="off"
111 autoCorrect="off"
112 autoCapitalize="none"
113 spellCheck="false"
114 value={network.ssid}
115 onChange={(e) => setNetwork({ ...network, ssid: e.target.value })}
116 />
117 <label
118 className={`
119 ${network.hidePassword && 'no-print hidden'}
120 ${network.encryptionMode === 'nopass' && 'hidden'}
121 `}
122 >
123 {t('wifi.password')}
124 </label>
125 <textarea
126 id="password"
127 type="text"
128 className={`
129 ${network.hidePassword && 'no-print hidden'}
130 ${network.encryptionMode === 'nopass' && 'hidden'}
131 `}
132 style={{
133 height:
134 portrait && network.password.length > 40 ? '5em' : 'auto',
135 }}
136 maxLength="63"
137 placeholder={t('wifi.password.placeholder')}
138 autoComplete="off"
139 autoCorrect="off"
140 autoCapitalize="none"
141 spellCheck="false"
142 value={network.password}
143 onChange={(e) => {
144 setNetwork({ ...network, password: e.target.value });
145 }}
146 />
147
148 <div className="no-print">
149 <input
150 type="checkbox"
151 id="hide-password-checkbox"
152 checked={network.hidePassword}
153 disabled={disableHidePassword()}
154 className={network.encryptionMode === 'nopass' ? 'hidden' : ''}
155 onChange={() =>
156 setNetwork({
157 ...network,
158 hidePassword: !network.hidePassword,
159 })
160 }
161 />
162 <label
163 htmlFor="hide-password-checkbox"
164 className={network.encryptionMode === 'nopass' ? 'hidden' : ''}
165 >
166 {t('wifi.password.hide')}
167 </label>
168 </div>
169
170 <div className="no-print">
171 <label>
172 {t('wifi.password.encryption')}:{direction === 'rtl' ? ' ' : ''}
173 </label>
174 <span dir="ltr">
175 <input
176 type="radio"
177 name="encrypt-select"
178 id="encrypt-none"
179 value="nopass"
180 onChange={setEncryptionMode}
181 />
182 <label htmlFor="encrypt-none">
183 {t('wifi.password.encryption.none')}
184 </label>
185 <input
186 type="radio"
187 name="encrypt-select"
188 id="encrypt-wpa-wpa2-wpa3"
189 value="WPA"
190 onChange={setEncryptionMode}
191 defaultChecked
192 />
193 <label htmlFor="encrypt-wpa-wpa2-wpa3">WPA/WPA2/WPA3</label>
194 <input
195 type="radio"
196 name="encrypt-select"
197 id="encrypt-wep"
198 value="WEP"
199 onChange={setEncryptionMode}
200 />
201 <label htmlFor="encrypt-wep">WEP</label>
202 </span>
203 </div>
204 </div>
205 </div>
206 <hr />
207 <p>
208 <span role="img" aria-label="mobile-phone">
209 📸📱
210 </span>
211 {t('wifi.tip')}
212 </p>
213 </fieldset>
214
215 <div className="buttons">
216 <button id="rotate" onClick={() => setPortrait(!portrait)}>
217 {t('button.rotate')}
218 </button>
219 <button id="print" onClick={onPrint}>
220 {t('button.print')}
221 </button>
222 </div>
223 </div>
224 );
225};
diff --git a/src/components/Settings.js b/src/components/Settings.js
new file mode 100644
index 0000000..1c28dab
--- /dev/null
+++ b/src/components/Settings.js
@@ -0,0 +1,77 @@
1import { Checkbox, Pane, RadioGroup, SelectField } from 'evergreen-ui';
2import { useEffect, useState } from 'react';
3import { useTranslation } from 'react-i18next';
4import i18n from '../i18n';
5import './style.css';
6
7export const Settings = (props) => {
8 const { t } = useTranslation();
9 const [encryptionModes] = useState([
10 { label: 'None', value: '' },
11 { label: 'WPA/WPA2/WPA3', value: 'WPA' },
12 { label: 'WEP', value: 'WEP' },
13 ]);
14
15 useEffect(() => {
16 if (props.firstLoad.current && window.innerWidth < 500) {
17 props.onFirstLoad();
18 props.onOrientationChange(true);
19 }
20 });
21
22 return (
23 <Pane id="settings" maxWidth={props.settings.portrait ? '350px' : '100%'}>
24 <SelectField
25 width={300}
26 inputHeight={38}
27 label={t('select')}
28 selected={i18n.language}
29 onChange={(e) => props.onLanguageChange(e.target.value)}
30 >
31 <option value="en-US">English</option>
32 <option value="ar">Arabic - العربية</option>
33 <option value="ca">Catalan - Català</option>
34 <option value="zh-HK">Chinese Hong Kong - 简体中文</option>
35 <option value="zh-CN">Chinese Simplified - 简体中文</option>
36 <option value="nl-NL">Dutch - Nederlands</option>
37 <option value="fr-FR">French - Français</option>
38 <option value="de-DE">German - Deutsch</option>
39 <option value="hi-IN">Hindi - हिन्दी</option>
40 <option value="id-ID">Indonesian</option>
41 <option value="it-IT">Italian</option>
42 <option value="ja">Japanese - 日本語</option>
43 <option value="ko">Korean - 한국어</option>
44 <option value="no-NB">Norwegian - Norsk</option>
45 <option value="oc">Occitan</option>
46 <option value="fa-IR">Persian Iran - فارسی</option>
47 <option value="pl-PL">Polish - Polski</option>
48 <option value="pt">Portuguese - Português</option>
49 <option value="pt-BR">Portuguese - Português brasileiro</option>
50 <option value="ru-RU">Russian - Русский</option>
51 <option value="es">Spanish - Español</option>
52 <option value="tr-TR">Turkish - Türkçe</option>
53 <option value="uk-UA">Ukrainian - Українська</option>
54 </SelectField>
55
56 <Checkbox
57 label={t('button.rotate')}
58 checked={props.settings.portrait}
59 onChange={() => props.onOrientationChange(!props.settings.portrait)}
60 />
61 <Checkbox
62 label={t('wifi.password.hide')}
63 checked={props.settings.hidePassword}
64 onChange={() =>
65 props.onHidePasswordChange(!props.settings.hidePassword)
66 }
67 />
68 <RadioGroup
69 label={t('wifi.password.encryption')}
70 size={16}
71 value={props.settings.encryptionMode}
72 options={encryptionModes}
73 onChange={(e) => props.onEncryptionModeChange(e.target.value)}
74 />
75 </Pane>
76 );
77};
diff --git a/src/components/WifiCard.js b/src/components/WifiCard.js
new file mode 100644
index 0000000..645dfa1
--- /dev/null
+++ b/src/components/WifiCard.js
@@ -0,0 +1,148 @@
1import {
2 CameraIcon,
3 Card,
4 Heading,
5 MobilePhoneIcon,
6 Pane,
7 Paragraph,
8 Text,
9 TextareaField,
10} from 'evergreen-ui';
11import QRCode from 'qrcode.react';
12import { useEffect, useState } from 'react';
13import { useTranslation } from 'react-i18next';
14import logo from '../../src/images/wifi.png';
15import './style.css';
16
17export const WifiCard = (props) => {
18 const { t } = useTranslation();
19 const [qrvalue, setQrvalue] = useState('');
20
21 const escape = (v) => {
22 const needsEscape = ['"', ';', ',', ':', '\\'];
23
24 let escaped = '';
25 for (const c of v) {
26 if (needsEscape.includes(c)) {
27 escaped += `\\${c}`;
28 } else {
29 escaped += c;
30 }
31 }
32 return escaped;
33 };
34
35 useEffect(() => {
36 const ssid = escape(props.settings.ssid);
37 const password = !props.settings.encryptionMode
38 ? ''
39 : escape(props.settings.password);
40 setQrvalue(
41 `WIFI:T:${props.settings.encryptionMode};S:${ssid};P:${password};;`
42 );
43 }, [props.settings]);
44
45 const portraitWidth = () => {
46 const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
47 return isMobile ? '100%' : '280px';
48 };
49
50 const passwordFieldLabel = () => {
51 const hiddenPassword =
52 props.settings.hidePassword || !props.settings.encryptionMode;
53 return hiddenPassword ? '' : t('wifi.password');
54 };
55
56 return (
57 <Pane>
58 <Card
59 id="print-area"
60 elevation={3}
61 style={{ maxWidth: props.settings.portrait ? portraitWidth() : '100%' }}
62 >
63 <Pane display="flex" paddingBottom={12}>
64 <img alt="icon" src={logo} width="24" height="24" />
65 <Heading
66 paddingLeft={10}
67 size={700}
68 textAlign={props.settings.portrait ? 'center' : 'unset'}
69 >
70 {t('wifi.login')}
71 </Heading>
72 </Pane>
73
74 <Pane
75 className="details"
76 style={{ flexDirection: props.settings.portrait ? 'column' : 'row' }}
77 >
78 <QRCode
79 className="qrcode"
80 style={
81 !props.settings.portrait
82 ? props.direction === 'ltr'
83 ? { paddingRight: '1em' }
84 : { paddingLeft: '1em' }
85 : {}
86 }
87 value={qrvalue}
88 size={150}
89 />
90
91 <Pane width={'100%'}>
92 <TextareaField
93 id="ssid"
94 type="text"
95 marginBottom={5}
96 autoComplete="off"
97 autoCorrect="off"
98 autoCapitalize="none"
99 spellCheck={false}
100 maxLength="32"
101 label={t('wifi.name')}
102 placeholder={t('wifi.name.placeholder')}
103 value={props.settings.ssid}
104 onChange={(e) => props.onSSIDChange(e.target.value)}
105 isInvalid={!!props.ssidError}
106 validationMessage={!!props.ssidError && props.ssidError}
107 />
108 <TextareaField
109 id="password"
110 type="text"
111 maxLength="63"
112 autoComplete="off"
113 autoCorrect="off"
114 autoCapitalize="none"
115 spellCheck={false}
116 className={`
117 ${
118 (props.settings.hidePassword ||
119 !props.settings.encryptionMode) &&
120 'hidden'
121 }
122 `}
123 height={
124 props.settings.portrait && props.settings.password.length > 40
125 ? '5em'
126 : 'auto'
127 }
128 label={passwordFieldLabel()}
129 placeholder={t('wifi.password.placeholder')}
130 value={props.settings.password}
131 onChange={(e) => props.onPasswordChange(e.target.value)}
132 isInvalid={!!props.passwordError}
133 validationMessage={!!props.passwordError && props.passwordError}
134 />
135 </Pane>
136 </Pane>
137 <hr />
138 <Paragraph>
139 <CameraIcon />
140 <MobilePhoneIcon />
141 <Text size={300} paddingLeft={8}>
142 {t('wifi.tip')}
143 </Text>
144 </Paragraph>
145 </Card>
146 </Pane>
147 );
148};
diff --git a/src/components/style.css b/src/components/style.css
index c9dbb54..fa931bb 100644
--- a/src/components/style.css
+++ b/src/components/style.css
@@ -2,8 +2,7 @@
2 2
3#print-area { 3#print-area {
4 border-color: #aaa; 4 border-color: #aaa;
5 border-style: dashed; 5 margin-bottom: 1em;
6 margin-bottom: 2em;
7 margin-top: 2em; 6 margin-top: 2em;
8 padding: 1em; 7 padding: 1em;
9} 8}
@@ -16,39 +15,39 @@
16.qrcode { 15.qrcode {
17 margin-bottom: 1em; 16 margin-bottom: 1em;
18 max-width: 175px; 17 max-width: 175px;
19 min-width: 175px;
20} 18}
21 19
22.hidden { 20.hidden {
23 display: none; 21 display: none;
24} 22}
25
26textarea { 23textarea {
27 background-color: #fff; 24 background-color: #fff;
28 border: solid 1px #ddd; 25 border: solid 1px #ddd;
29 font-family: 'Source Code Pro', serif; 26 font-family: 'Source Code Pro', serif !important;
30 font-size: 1.3em; 27 font-size: 1em !important;
31 font-weight: bold; 28 font-weight: bold !important;
32 height: 3em; 29 margin-bottom: 0;
30 height: 40px !important;
31 min-height: 0px !important;
33 overflow: hidden; 32 overflow: hidden;
34 resize: none; 33 resize: none;
35} 34}
35textarea#password {
36 height: 60px !important;
37}
38
39hr {
40 margin-top: 0;
41}
36 42
37button { 43button {
38 height: 50px; 44 height: 50px;
39 width: 180px; 45 width: 180px;
40} 46}
41 47
42button#print { 48#settings {
43 color: #fff; 49 margin-bottom: 1em;
44 background-color: #0074d9; 50 padding: 1em;
45 border-color: #0074d9;
46}
47
48button#rotate {
49 color: #fff;
50 background-color: #6c757d;
51 border-color: #6c757d;
52} 51}
53 52
54@media print { 53@media print {
@@ -59,19 +58,16 @@ button#rotate {
59 #print-area * { 58 #print-area * {
60 visibility: visible; 59 visibility: visible;
61 } 60 }
61 #print-area {
62 border-style: dashed;
63 box-shadow: none;
64 }
62 #print-area textarea { 65 #print-area textarea {
63 border: none; 66 border: none;
64 } 67 }
65 .no-print {
66 display: none;
67 }
68 #print-area { 68 #print-area {
69 position: absolute; 69 position: absolute;
70 left: 0; 70 left: 0;
71 top: 0; 71 top: 0;
72 } 72 }
73} 73}
74
75hr {
76 margin-top: 25px;
77}
diff --git a/src/i18n.js b/src/i18n.js
index 452bf23..891e291 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -16,16 +16,19 @@ const resources = {
16 'wifi.name.placeholder': 'WiFi Network name', 16 'wifi.name.placeholder': 'WiFi Network name',
17 'wifi.password': 'Password', 17 'wifi.password': 'Password',
18 'wifi.password.placeholder': 'Password', 18 'wifi.password.placeholder': 'Password',
19 'wifi.password.hide': 'Hide password field before printing', 19 'wifi.password.hide': 'Hide password',
20 'wifi.password.encryption': 'Encryption', 20 'wifi.password.encryption': 'Encryption',
21 'wifi.password.encryption.none': 'None', 21 'wifi.password.encryption.none': 'None',
22 'wifi.tip': 22 'wifi.tip':
23 "Point your phone's camera at the QR Code to connect automatically", 23 "Point your phone's camera at the QR Code to connect automatically",
24 'wifi.alert.name': 'Network name cannot be empty', 24 'wifi.alert.name': 'Network name cannot be empty',
25 'wifi.alert.password.length.5': 'Password must be at least 5 characters', 25 'wifi.alert.password.length.5':
26 'wifi.alert.password.length.8': 'Password must be at least 8 characters', 26 'Password must be at least 5 characters, or change the encryption to "None"',
27 'wifi.alert.password.length.8':
28 'Password must be at least 8 characters, or change the encryption to "None"',
27 'button.rotate': 'Rotate', 29 'button.rotate': 'Rotate',
28 'button.print': 'Print', 30 'button.print': 'Print',
31 'button.settings': 'Settings',
29 select: 'Select Language', 32 select: 'Select Language',
30 }, 33 },
31 }, 34 },
@@ -80,6 +83,7 @@ const resources = {
80 'Wachtwoord moet ten minste 8 tekens bevatten', 83 'Wachtwoord moet ten minste 8 tekens bevatten',
81 'button.rotate': 'Draai', 84 'button.rotate': 'Draai',
82 'button.print': 'Print', 85 'button.print': 'Print',
86 'button.settings': 'Instellingen',
83 select: 'Selecteer Taal', 87 select: 'Selecteer Taal',
84 }, 88 },
85 }, 89 },
@@ -105,6 +109,7 @@ const resources = {
105 'wifi.alert.password.length.8': '密码至少 8 个字符', 109 'wifi.alert.password.length.8': '密码至少 8 个字符',
106 'button.rotate': '翻转', 110 'button.rotate': '翻转',
107 'button.print': '打印', 111 'button.print': '打印',
112 'button.settings': '设置',
108 select: '选择语言', 113 select: '选择语言',
109 }, 114 },
110 }, 115 },
@@ -130,6 +135,7 @@ const resources = {
130 'wifi.alert.password.8': '密碼至少要有 8 個字符 ', 135 'wifi.alert.password.8': '密碼至少要有 8 個字符 ',
131 'button.rotate': '翻轉', 136 'button.rotate': '翻轉',
132 'button.print': '打印', 137 'button.print': '打印',
138 'button.settings': '设置',
133 select: '選擇語言', 139 select: '選擇語言',
134 }, 140 },
135 }, 141 },
@@ -159,6 +165,7 @@ const resources = {
159 'La contraseña debe tener al menos 8 caracteres', 165 'La contraseña debe tener al menos 8 caracteres',
160 'button.rotate': 'Girar', 166 'button.rotate': 'Girar',
161 'button.print': 'Imprimir', 167 'button.print': 'Imprimir',
168 'button.settings': 'Ajustes',
162 select: 'Seleccionar idioma', 169 select: 'Seleccionar idioma',
163 }, 170 },
164 }, 171 },
@@ -185,6 +192,7 @@ const resources = {
185 'wifi.alert.password.length.8': 'A senha precisa ter no mínimo 8 dígtos', 192 'wifi.alert.password.length.8': 'A senha precisa ter no mínimo 8 dígtos',
186 'button.rotate': 'Girar', 193 'button.rotate': 'Girar',
187 'button.print': 'Imprimir', 194 'button.print': 'Imprimir',
195 'button.settings': 'Definições',
188 select: 'Selecionar Idioma', 196 select: 'Selecionar Idioma',
189 }, 197 },
190 }, 198 },
@@ -212,6 +220,7 @@ const resources = {
212 'パスワードは8文字以上でなければなりません', 220 'パスワードは8文字以上でなければなりません',
213 'button.rotate': '回転する', 221 'button.rotate': '回転する',
214 'button.print': '印刷する', 222 'button.print': '印刷する',
223 'button.settings': '設定',
215 select: '言語を選択', 224 select: '言語を選択',
216 }, 225 },
217 }, 226 },
@@ -240,6 +249,7 @@ const resources = {
240 'Пароль должен состоять не менее чем из 8 символов', 249 'Пароль должен состоять не менее чем из 8 символов',
241 'button.rotate': 'Повернуть', 250 'button.rotate': 'Повернуть',
242 'button.print': 'Распечатать', 251 'button.print': 'Распечатать',
252 'button.settings': 'настройки',
243 select: 'Выбор языка', 253 select: 'Выбор языка',
244 }, 254 },
245 }, 255 },
@@ -266,6 +276,7 @@ const resources = {
266 'wifi.alert.password.8': 'رمز‌عبور باید حداقل ۸ حرف داشته باشد.', 276 'wifi.alert.password.8': 'رمز‌عبور باید حداقل ۸ حرف داشته باشد.',
267 'button.rotate': 'چرخاندن', 277 'button.rotate': 'چرخاندن',
268 'button.print': 'چاپ', 278 'button.print': 'چاپ',
279 'button.settings': 'تنظیمات',
269 select: 'انتخاب زبان', 280 select: 'انتخاب زبان',
270 }, 281 },
271 }, 282 },
@@ -294,6 +305,7 @@ const resources = {
294 'Пароль повинен містити принаймні 8 символів', 305 'Пароль повинен містити принаймні 8 символів',
295 'button.rotate': 'Повернути', 306 'button.rotate': 'Повернути',
296 'button.print': 'Друкувати', 307 'button.print': 'Друкувати',
308 'button.settings': 'налаштування',
297 select: 'Вибір мови', 309 select: 'Вибір мови',
298 }, 310 },
299 }, 311 },
@@ -322,6 +334,7 @@ const resources = {
322 'Das Passwort muss mindestends 8 Zeichen lang sein', 334 'Das Passwort muss mindestends 8 Zeichen lang sein',
323 'button.rotate': 'Drehen', 335 'button.rotate': 'Drehen',
324 'button.print': 'Drucken', 336 'button.print': 'Drucken',
337 'button.settings': 'Einstellungen',
325 select: 'Sprache auswählen', 338 select: 'Sprache auswählen',
326 }, 339 },
327 }, 340 },
@@ -352,6 +365,7 @@ const resources = {
352 'wifi.alert.password.8': 'Hasło musi mieć co najmniej 8 znaków', 365 'wifi.alert.password.8': 'Hasło musi mieć co najmniej 8 znaków',
353 'button.rotate': 'Obróć', 366 'button.rotate': 'Obróć',
354 'button.print': 'Drukuj', 367 'button.print': 'Drukuj',
368 'button.settings': 'Ustawienia',
355 select: 'Wybierz język', 369 select: 'Wybierz język',
356 }, 370 },
357 }, 371 },
@@ -380,6 +394,7 @@ const resources = {
380 'Le mot de passe doit au moins faire 8 caractères', 394 'Le mot de passe doit au moins faire 8 caractères',
381 'button.rotate': 'Pivoter', 395 'button.rotate': 'Pivoter',
382 'button.print': 'Imprimer', 396 'button.print': 'Imprimer',
397 'button.settings': 'Paramètres',
383 select: 'Choisir la langue', 398 select: 'Choisir la langue',
384 }, 399 },
385 }, 400 },
@@ -407,6 +422,7 @@ const resources = {
407 'wifi.alert.password.8': 'Lo senhal deu conténer almens 8 caractèrs', 422 'wifi.alert.password.8': 'Lo senhal deu conténer almens 8 caractèrs',
408 'button.rotate': 'Pivotar', 423 'button.rotate': 'Pivotar',
409 'button.print': 'Imprimir', 424 'button.print': 'Imprimir',
425 'button.settings': 'Paramètres',
410 select: 'Causir la lenga', 426 select: 'Causir la lenga',
411 }, 427 },
412 }, 428 },
@@ -434,6 +450,7 @@ const resources = {
434 'wifi.alert.password.8': 'A Senha deve ter pelo menos 8 caracteres', 450 'wifi.alert.password.8': 'A Senha deve ter pelo menos 8 caracteres',
435 'button.rotate': 'Rotacionar', 451 'button.rotate': 'Rotacionar',
436 'button.print': 'Imprimir', 452 'button.print': 'Imprimir',
453 'button.settings': 'Definições',
437 select: 'Escolha o idioma', 454 select: 'Escolha o idioma',
438 }, 455 },
439 }, 456 },
@@ -462,6 +479,7 @@ const resources = {
462 'La password deve contenere almeno 8 caratteri', 479 'La password deve contenere almeno 8 caratteri',
463 'button.rotate': 'Ruota', 480 'button.rotate': 'Ruota',
464 'button.print': 'Stampa', 481 'button.print': 'Stampa',
482 'button.settings': 'Impostazioni',
465 select: 'Seleziona una lingua', 483 select: 'Seleziona una lingua',
466 }, 484 },
467 }, 485 },
@@ -488,6 +506,7 @@ const resources = {
488 'wifi.alert.password.length.8': 'Parola en az 8 karakter olmalıdır', 506 'wifi.alert.password.length.8': 'Parola en az 8 karakter olmalıdır',
489 'button.rotate': 'Döndür', 507 'button.rotate': 'Döndür',
490 'button.print': 'Yazdır', 508 'button.print': 'Yazdır',
509 'button.settings': 'Ayarlar',
491 select: 'Dil Seçin', 510 select: 'Dil Seçin',
492 }, 511 },
493 }, 512 },
@@ -516,6 +535,7 @@ const resources = {
516 'يجب أن تكون كلمة المرور مكونة من ٨ أحرف على الأقل', 535 'يجب أن تكون كلمة المرور مكونة من ٨ أحرف على الأقل',
517 'button.rotate': 'تدوير', 536 'button.rotate': 'تدوير',
518 'button.print': 'طباعة', 537 'button.print': 'طباعة',
538 'button.settings': 'إعدادات',
519 select: 'اختر لغة', 539 select: 'اختر لغة',
520 }, 540 },
521 }, 541 },
@@ -544,6 +564,7 @@ const resources = {
544 'पासवर्ड कम से कम 8 अक्षरों का होना चाहिए', 564 'पासवर्ड कम से कम 8 अक्षरों का होना चाहिए',
545 'button.rotate': 'घुमाएँ', 565 'button.rotate': 'घुमाएँ',
546 'button.print': 'प्रिंट करे', 566 'button.print': 'प्रिंट करे',
567 'button.settings': 'समायोजन',
547 select: 'भाषा चुने', 568 select: 'भाषा चुने',
548 }, 569 },
549 }, 570 },
@@ -568,10 +589,11 @@ const resources = {
568 'wifi.alert.name': 'El nom de la xarxa no pot estar buit', 589 'wifi.alert.name': 'El nom de la xarxa no pot estar buit',
569 'wifi.alert.password.length.5': 590 'wifi.alert.password.length.5':
570 'La contrasenya ha de tenir com a mínim 5 caràcters', 591 'La contrasenya ha de tenir com a mínim 5 caràcters',
571 'wifi.alert.password.8': 592 'wifi.alert.password.length.8':
572 'La contrasenya ha de tenir com a mínim 8 caràcters', 593 'La contrasenya ha de tenir com a mínim 8 caràcters',
573 'button.rotate': 'Gira', 594 'button.rotate': 'Gira',
574 'button.print': 'Imprimeix', 595 'button.print': 'Imprimeix',
596 'button.settings': 'Configuració',
575 select: 'Escolliu l’idioma', 597 select: 'Escolliu l’idioma',
576 }, 598 },
577 }, 599 },
@@ -595,9 +617,10 @@ const resources = {
595 'Arahkan kamera ponsel anda ke Kode QR untuk terhubung ke WiFi secara otomatis', 617 'Arahkan kamera ponsel anda ke Kode QR untuk terhubung ke WiFi secara otomatis',
596 'wifi.alert.name': 'Nama jaringan tidak boleh kosong', 618 'wifi.alert.name': 'Nama jaringan tidak boleh kosong',
597 'wifi.alert.password.length.5': 'Kata sandi minimal harus 5 karakter', 619 'wifi.alert.password.length.5': 'Kata sandi minimal harus 5 karakter',
598 'wifi.alert.password.8': 'Kata sandi minimal harus 8 karakter', 620 'wifi.alert.password.length.8': 'Kata sandi minimal harus 8 karakter',
599 'button.rotate': 'Putar', 621 'button.rotate': 'Putar',
600 'button.print': 'Cetak', 622 'button.print': 'Cetak',
623 'button.settings': 'Pengaturan',
601 select: 'Pilih Bahasa', 624 select: 'Pilih Bahasa',
602 }, 625 },
603 }, 626 },
diff --git a/src/style.css b/src/style.css
index f29d097..cc43310 100644
--- a/src/style.css
+++ b/src/style.css
@@ -1,6 +1,5 @@
1body { 1body {
2 max-width: 680px; 2 margin: 0 auto;
3} 3 max-width: 500px;
4.tag { 4 padding: 1em;
5 line-height: 1.6;
6} 5}