diff --git a/src/app.jsx b/src/app.jsx index ebbce5dd..2091994d 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -26,6 +26,7 @@ import Shortcuts from './components/shortcuts'; import ShortcutsSettings from './components/shortcuts-settings'; import NotFound from './pages/404'; import AccountStatuses from './pages/account-statuses'; +import Accounts from './pages/accounts'; import Bookmarks from './pages/bookmarks'; import Favourites from './pages/favourites'; import FollowedHashtags from './pages/followed-hashtags'; @@ -163,6 +164,7 @@ function App() { const showModal = snapStates.showCompose || snapStates.showSettings || + snapStates.showAccounts || snapStates.showAccount || snapStates.showDrafts || snapStates.showMediaModal || @@ -374,6 +376,21 @@ function App() { /> </Modal> )} + {!!snapStates.showAccounts && ( + <Modal + onClick={(e) => { + if (e.target === e.currentTarget) { + states.showAccounts = false; + } + }} + > + <Accounts + onClose={() => { + states.showAccounts = false; + }} + /> + </Modal> + )} {!!snapStates.showAccount && ( <Modal class="light" diff --git a/src/components/menu.jsx b/src/components/menu.jsx index c7ca39f0..92d67db5 100644 --- a/src/components/menu.jsx +++ b/src/components/menu.jsx @@ -102,6 +102,13 @@ function NavMenu(props) { {authenticated && ( <> <MenuDivider /> + <MenuItem + onClick={() => { + states.showAccounts = true; + }} + > + <Icon icon="group" size="l" /> <span>Accounts…</span> + </MenuItem> <MenuItem onClick={() => { states.showShortcutsSettings = true; diff --git a/src/pages/accounts.jsx b/src/pages/accounts.jsx new file mode 100644 index 00000000..0e617d9a --- /dev/null +++ b/src/pages/accounts.jsx @@ -0,0 +1,152 @@ +import './settings.css'; + +import { Menu, MenuItem } from '@szhsin/react-menu'; +import { useReducer, useState } from 'preact/hooks'; + +import Avatar from '../components/avatar'; +import Icon from '../components/icon'; +import Link from '../components/link'; +import NameText from '../components/name-text'; +import { api } from '../utils/api'; +import states from '../utils/states'; +import store from '../utils/store'; + +function Accounts({ onClose }) { + const { masto } = api(); + // Accounts + const accounts = store.local.getJSON('accounts'); + const currentAccount = store.session.get('currentAccount'); + const moreThanOneAccount = accounts.length > 1; + const [currentDefault, setCurrentDefault] = useState(0); + + const [_, reload] = useReducer((x) => x + 1, 0); + + return ( + <div id="settings-container" class="sheet" tabIndex="-1"> + <header class="header-grid"> + <h2>Accounts</h2> + <div class="header-side"> + <Link to="/login" class="button plain" onClick={onClose}> + <Icon icon="plus" /> <span>Account</span> + </Link> + </div> + </header> + <main> + <section> + <ul class="accounts-list"> + {accounts.map((account, i) => { + const isCurrent = account.info.id === currentAccount; + const isDefault = i === (currentDefault || 0); + return ( + <li key={i + account.id}> + <div> + {moreThanOneAccount && ( + <span class={`current ${isCurrent ? 'is-current' : ''}`}> + <Icon icon="check-circle" alt="Current" /> + </span> + )} + <Avatar + url={account.info.avatarStatic} + size="xxl" + onDblClick={async () => { + if (isCurrent) { + try { + const info = await masto.v1.accounts.fetch( + account.info.id, + ); + console.log('fetched account info', info); + account.info = info; + store.local.setJSON('accounts', accounts); + reload(); + } catch (e) {} + } + }} + /> + <NameText + account={account.info} + showAcct + onClick={() => { + states.showAccount = `${account.info.username}@${account.instanceURL}`; + }} + /> + </div> + <div class="actions"> + {isDefault && moreThanOneAccount && ( + <> + <span class="tag">Default</span>{' '} + </> + )} + {!isCurrent && ( + <button + type="button" + class="light" + onClick={() => { + store.session.set('currentAccount', account.info.id); + location.reload(); + }} + > + <Icon icon="transfer" /> Switch + </button> + )} + <Menu + align="end" + menuButton={ + <button + type="button" + title="More" + class="plain more-button" + > + <Icon icon="more" size="l" alt="More" /> + </button> + } + > + {moreThanOneAccount && ( + <MenuItem + disabled={isDefault} + onClick={() => { + // Move account to the top of the list + accounts.splice(i, 1); + accounts.unshift(account); + store.local.setJSON('accounts', accounts); + setCurrentDefault(i); + }} + > + <Icon icon="check-circle" /> + <span>Set as default</span> + </MenuItem> + )} + <MenuItem + disabled={!isCurrent} + onClick={() => { + const yes = confirm('Log out?'); + if (!yes) return; + accounts.splice(i, 1); + store.local.setJSON('accounts', accounts); + // location.reload(); + location.href = '/'; + }} + > + <Icon icon="exit" /> + <span>Log out</span> + </MenuItem> + </Menu> + </div> + </li> + ); + })} + </ul> + {moreThanOneAccount && ( + <p> + <small> + Note: <i>Default</i> account will always be used for first load. + Switched accounts will persist during the session. + </small> + </p> + )} + </section> + </main> + </div> + ); +} + +export default Accounts; diff --git a/src/pages/settings.css b/src/pages/settings.css index 9b1cf279..c2b52f1f 100644 --- a/src/pages/settings.css +++ b/src/pages/settings.css @@ -2,19 +2,16 @@ background-color: var(--bg-faded-color); } -#settings-container h2 { +#settings-container main h3 { font-size: 85%; text-transform: uppercase; color: var(--text-insignificant-color); font-weight: normal; } -#settings-container h2 ~ h2 { - margin-top: 2em; -} #settings-container section { background-color: var(--bg-color); - margin: 0; + margin: 8px 0 0; padding: 8px 16px; border-top: var(--hairline-width) solid var(--outline-color); border-bottom: var(--hairline-width) solid var(--outline-color); @@ -30,7 +27,7 @@ list-style: none; } #settings-container section > ul > li { - padding: 8px 0 16px; + padding: 8px 0; display: flex; justify-content: space-between; align-items: center; @@ -71,6 +68,10 @@ margin-right: 8px; } +#settings-container section select { + padding: 4px; +} + #settings-container .radio-group { display: inline-flex; align-items: center; diff --git a/src/pages/settings.jsx b/src/pages/settings.jsx index 2f06cbfa..decb90e7 100644 --- a/src/pages/settings.jsx +++ b/src/pages/settings.jsx @@ -1,41 +1,20 @@ import './settings.css'; -import { Menu, MenuItem } from '@szhsin/react-menu'; -import { useReducer, useRef, useState } from 'preact/hooks'; +import { useRef } from 'preact/hooks'; import { useSnapshot } from 'valtio'; import logo from '../assets/logo.svg'; -import Avatar from '../components/avatar'; -import Icon from '../components/icon'; -import Link from '../components/link'; -import NameText from '../components/name-text'; import RelativeTime from '../components/relative-time'; import targetLanguages from '../data/lingva-target-languages'; -import { api } from '../utils/api'; import getTranslateTargetLanguage from '../utils/get-translate-target-language'; import localeCode2Text from '../utils/localeCode2Text'; import states from '../utils/states'; import store from '../utils/store'; -/* - Settings component that shows these settings: - - Accounts list for switching - - Dark/light/auto theme switch (done with adding/removing 'is-light' or 'is-dark' class on the body) -*/ - function Settings({ onClose }) { - const { masto } = api(); const snapStates = useSnapshot(states); - // Accounts - const accounts = store.local.getJSON('accounts'); - const currentAccount = store.session.get('currentAccount'); const currentTheme = store.local.get('theme') || 'auto'; const themeFormRef = useRef(); - const moreThanOneAccount = accounts.length > 1; - const [currentDefault, setCurrentDefault] = useState(0); - - const [_, reload] = useReducer((x) => x + 1, 0); - const targetLanguage = snapStates.settings.contentTranslationTargetLanguage || null; const systemTargetLanguage = getTranslateTargetLanguage(); @@ -43,129 +22,10 @@ function Settings({ onClose }) { return ( <div id="settings-container" class="sheet" tabIndex="-1"> - <main> - {/* <button type="button" class="close-button plain large" onClick={onClose}> - <Icon icon="x" alt="Close" /> - </button> */} - <h2>Accounts</h2> - <section> - <ul class="accounts-list"> - {accounts.map((account, i) => { - const isCurrent = account.info.id === currentAccount; - const isDefault = i === (currentDefault || 0); - return ( - <li key={i + account.id}> - <div> - {moreThanOneAccount && ( - <span class={`current ${isCurrent ? 'is-current' : ''}`}> - <Icon icon="check-circle" alt="Current" /> - </span> - )} - <Avatar - url={account.info.avatarStatic} - size="xxl" - onDblClick={async () => { - if (isCurrent) { - try { - const info = await masto.v1.accounts.fetch( - account.info.id, - ); - console.log('fetched account info', info); - account.info = info; - store.local.setJSON('accounts', accounts); - reload(); - } catch (e) {} - } - }} - /> - <NameText - account={account.info} - showAcct - onClick={() => { - states.showAccount = `${account.info.username}@${account.instanceURL}`; - }} - /> - </div> - <div class="actions"> - {isDefault && moreThanOneAccount && ( - <> - <span class="tag">Default</span>{' '} - </> - )} - {!isCurrent && ( - <button - type="button" - class="light" - onClick={() => { - store.session.set('currentAccount', account.info.id); - location.reload(); - }} - > - <Icon icon="transfer" /> Switch - </button> - )} - <Menu - align="end" - menuButton={ - <button - type="button" - title="More" - class="plain more-button" - > - <Icon icon="more" size="l" alt="More" /> - </button> - } - > - {moreThanOneAccount && ( - <MenuItem - disabled={isDefault} - onClick={() => { - // Move account to the top of the list - accounts.splice(i, 1); - accounts.unshift(account); - store.local.setJSON('accounts', accounts); - setCurrentDefault(i); - }} - > - <Icon icon="check-circle" /> - <span>Set as default</span> - </MenuItem> - )} - <MenuItem - disabled={!isCurrent} - onClick={() => { - const yes = confirm('Log out?'); - if (!yes) return; - accounts.splice(i, 1); - store.local.setJSON('accounts', accounts); - // location.reload(); - location.href = '/'; - }} - > - <Icon icon="exit" /> - <span>Log out</span> - </MenuItem> - </Menu> - </div> - </li> - ); - })} - </ul> - {moreThanOneAccount && ( - <p> - <small> - Note: <i>Default</i> account will always be used for first load. - Switched accounts will persist during the session. - </small> - </p> - )} - <p style={{ textAlign: 'end' }}> - <Link to="/login" class="button" onClick={onClose}> - Add new account - </Link> - </p> - </section> + <header> <h2>Settings</h2> + </header> + <main> <section> <ul> <li> @@ -236,6 +96,11 @@ function Settings({ onClose }) { </form> </div> </li> + </ul> + </section> + <h3>Experiments</h3> + <section> + <ul> <li> <label> <input @@ -245,7 +110,7 @@ function Settings({ onClose }) { states.settings.boostsCarousel = e.target.checked; }} />{' '} - Boosts carousel (experimental) + Boosts carousel </label> </li> <li> @@ -257,7 +122,7 @@ function Settings({ onClose }) { states.settings.contentTranslation = e.target.checked; }} />{' '} - Post translation (experimental) + Post translation </label> {snapStates.settings.contentTranslation && ( <div class="sub-section"> @@ -295,24 +160,21 @@ function Settings({ onClose }) { </div> )} </li> + <li> + <button + type="button" + class="light" + onClick={() => { + states.showDrafts = true; + states.showSettings = false; + }} + > + Unsent drafts + </button> + </li> </ul> </section> - <h2>Hidden features</h2> - <section> - <div> - <button - type="button" - class="light" - onClick={() => { - states.showDrafts = true; - states.showSettings = false; - }} - > - Unsent drafts - </button> - </div> - </section> - <h2>About</h2> + <h3>About</h3> <section> <p> <img diff --git a/src/utils/states.js b/src/utils/states.js index 1355114b..c1bf0006 100644 --- a/src/utils/states.js +++ b/src/utils/states.js @@ -31,6 +31,7 @@ const states = proxy({ showCompose: false, showSettings: false, showAccount: false, + showAccounts: false, showDrafts: false, showMediaModal: false, showShortcutsSettings: false, @@ -82,6 +83,7 @@ export function hideAllModals() { states.showCompose = false; states.showSettings = false; states.showAccount = false; + states.showAccounts = false; states.showDrafts = false; states.showMediaModal = false; states.showShortcutsSettings = false;