Feature: mobile support

This commit is contained in:
Dreamacro 2019-06-12 17:55:57 +08:00
parent 64d612dd53
commit 293e484f1d
13 changed files with 286 additions and 79 deletions

View File

@ -23,3 +23,9 @@
display: flex;
align-items: center;
}
@media (max-width: 768px) {
.header > h1 {
font-size: 20px;
}
}

View File

@ -3,6 +3,9 @@
$width: 400px;
$bigWidth: 600px;
$mobileWidth: 280px;
$mobileBigWidth: 480px;
.modal-mask {
position: fixed;
top: 0;
@ -76,3 +79,15 @@ $bigWidth: 600px;
transform: scale(1);
}
}
@media (max-width: 768px) {
.modal-mask {
.modal-small {
width: $mobileWidth;
}
.modal-big {
width: $mobileBigWidth;
}
}
}

View File

@ -28,12 +28,12 @@ export default class App extends React.Component<AppProps, {}> {
// { path: '/', name: 'Overview', component: Overview, exact: true },
{ path: '/proxies', name: 'Proxies', component: Proxies },
{ path: '/logs', name: 'Logs', component: Logs },
{ path: '/rules', name: 'Rules', component: Rules },
{ path: '/rules', name: 'Rules', component: Rules, noMobile: true },
{ path: '/settings', name: 'Settings', component: Settings }
]
return (
<div className={classnames('app', { 'clash-x': !isClashX() })}>
<div className={classnames('app', { 'not-clashx': !isClashX() })}>
<SlideBar routes={routes} />
<div className="page-container">
<Route exact path="/" component={() => <Redirect to="/proxies"/>}/>

View File

@ -20,3 +20,31 @@
font-weight: bold;
}
}
@media (max-width: 768px) {
.external-controller {
// hack <Row />
.row {
flex-direction: column;
align-items: flex-start;
}
.title {
margin: 5px 0;
}
.form {
margin: 5px 0;
}
// hack <Col />
.column {
width: 100%;
}
// hack <Alert />
.alert {
display: none;
}
}
}

View File

@ -16,6 +16,7 @@
.proxy-group-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 20px;
width: 120px;
}
@ -51,3 +52,32 @@
flex: 1;
margin-left: 30px;
}
@media (max-width: 768px) {
.proxy-group {
flex-direction: column;
}
.proxy-group-name {
width: 140px;
}
.proxy-group-part {
width: 100%;
height: 42px;
margin-top: 6px;
justify-content: space-between;
}
.proxy-group-type {
margin-right: 20px;
}
.proxy-group-tags-container {
padding: 5px 0 10px;
}
.proxy-group-tags {
margin-left: 20px;
}
}

View File

@ -64,3 +64,20 @@
background-color: $color-gray-darken;
}
}
@media (max-width: 768px) {
.proxy-item {
$height: 70px;
height: $height;
.proxy-delay {
left: unset;
bottom: unset;
top: 0;
right: 20px;
height: $height;
line-height: $height;
}
}
}

View File

@ -37,10 +37,6 @@
}
@include response(xxs) {
--columns: 4;
}
@include response(tiny) {
--columns: 3;
}
@ -68,3 +64,22 @@
text-shadow: 0 2px 6px rgba($color: $color-primary-dark, $alpha: 0.4);
cursor: pointer;
}
@media (max-width: 768px) {
.proxies-group-card {
margin: 12px 0;
}
.proxies-list {
margin-right: 0;
padding-bottom: 20px;
flex-wrap: unset;
flex-direction: column;
> li {
width: 100%;
margin-right: 0;
margin-bottom: 10px;
}
}
}

View File

@ -112,85 +112,101 @@ class Settings extends React.Component<SettingProps, {}> {
<Header title={t('title')} />
<Card className="settings-card">
<Row gutter={24} align="middle">
<Col span={6} offset={1}>
<span className="label">{t('labels.startAtLogin')}</span>
<Col span={12}>
<Col span={14} offset={1}>
<span className="label">{t('labels.startAtLogin')}</span>
</Col>
<Col span={8} className="value-column">
<Switch disabled={!isClashX} checked={startAtLogin} onChange={this.handleStartAtLoginChange} />
</Col>
</Col>
<Col span={4} className="value-column">
<Switch disabled={!isClashX} checked={startAtLogin} onChange={this.handleStartAtLoginChange} />
</Col>
<Col span={4} offset={1}>
<span className="label">{t('labels.language')}</span>
</Col>
<Col span={7} className="value-column">
<ButtonSelect options={this.languageOptions} value={lng.replace(/-.+$/, '')} onSelect={this.changeLanguage} />
<Col span={12}>
<Col span={8} offset={1}>
<span className="label">{t('labels.language')}</span>
</Col>
<Col span={14} className="value-column">
<ButtonSelect options={this.languageOptions} value={lng.replace(/-.+$/, '')} onSelect={this.changeLanguage} />
</Col>
</Col>
</Row>
<Row gutter={24} align="middle">
<Col span={6} offset={1}>
<span className="label">{t('labels.setAsSystemProxy')}</span>
<Col span={12}>
<Col span={14} offset={1}>
<span className="label">{t('labels.setAsSystemProxy')}</span>
</Col>
<Col span={8} className="value-column">
<Switch
disabled={!isClashX}
checked={systemProxy}
onChange={this.handleSetSystemProxy}
/>
</Col>
</Col>
<Col span={4} className="value-column">
<Switch
disabled={!isClashX}
checked={systemProxy}
onChange={this.handleSetSystemProxy}
/>
</Col>
<Col span={7} offset={1}>
<span className="label">{t('labels.allowConnectFromLan')}</span>
</Col>
<Col span={4} className="value-column">
<Switch
checked={allowLan}
onChange={this.handleAllowLanChange}
/>
<Col span={12}>
<Col span={8} offset={1}>
<span className="label">{t('labels.allowConnectFromLan')}</span>
</Col>
<Col span={14} className="value-column">
<Switch
checked={allowLan}
onChange={this.handleAllowLanChange}
/>
</Col>
</Col>
</Row>
</Card>
<Card className="settings-card">
<Row gutter={24} align="middle">
<Col span={3} offset={1}>
<span className="label">{t('labels.proxyMode')}</span>
<Col span={12}>
<Col span={8} offset={1}>
<span className="label">{t('labels.proxyMode')}</span>
</Col>
<Col span={14} className="value-column">
<ButtonSelect
options={proxyModeOptions}
value={mode}
onSelect={this.handleProxyModeChange}
/>
</Col>
</Col>
<Col span={7} className="value-column">
<ButtonSelect
options={proxyModeOptions}
value={mode}
onSelect={this.handleProxyModeChange}
/>
</Col>
<Col span={5} offset={1}>
<span className="label">{t('labels.socks5ProxyPort')}</span>
</Col>
<Col span={3} offset={3}>
<Input
value={socks5ProxyPort}
onChange={socks5ProxyPort => this.setState({ socks5ProxyPort: parseInt(socks5ProxyPort, 10) })}
onBlur={this.handleSocksPortSave}
/>
<Col span={12}>
<Col span={14} offset={1}>
<span className="label">{t('labels.socks5ProxyPort')}</span>
</Col>
<Col span={8}>
<Input
value={socks5ProxyPort}
onChange={socks5ProxyPort => this.setState({ socks5ProxyPort: parseInt(socks5ProxyPort, 10) })}
onBlur={this.handleSocksPortSave}
/>
</Col>
</Col>
</Row>
<Row gutter={24} align="middle">
<Col span={5} offset={1}>
<span className="label">{t('labels.httpProxyPort')}</span>
<Col span={12}>
<Col span={14} offset={1}>
<span className="label">{t('labels.httpProxyPort')}</span>
</Col>
<Col span={8}>
<Input
type="number"
value={httpProxyPort}
onChange={httpProxyPort => this.setState({ httpProxyPort: parseInt(httpProxyPort, 10) })}
onBlur={this.handleHttpPortSave}
/>
</Col>
</Col>
<Col span={3} offset={2}>
<Input
type="number"
value={httpProxyPort}
onChange={httpProxyPort => this.setState({ httpProxyPort: parseInt(httpProxyPort, 10) })}
onBlur={this.handleHttpPortSave}
/>
</Col>
<Col span={4} offset={1}>
<span className="label">{t('labels.externalController')}</span>
</Col>
<Col className="external-controller" span={6} offset={1}>
<span>{`${externalControllerHost}:${externalControllerPort}`}</span>
<span className="modify-btn" onClick={() => this.props.store.setShowAPIModal(true)}>
</span>
<Col span={12}>
<Col span={8} offset={1}>
<span className="label">{t('labels.externalController')}</span>
</Col>
<Col className="external-controller" span={14}>
<span>{`${externalControllerHost}:${externalControllerPort}`}</span>
<span className="modify-btn" onClick={() => this.props.store.setShowAPIModal(true)}>
</span>
</Col>
</Col>
</Row>
</Card>

View File

@ -78,3 +78,19 @@
}
}
}
@media (max-width: 768px) {
.settings-card {
// hack <Row />
.row {
flex-direction: column;
padding-top: 0;
padding-bottom: 0;
> .column {
width: 100%;
margin: 10px 0;
}
}
}
}

View File

@ -1,6 +1,7 @@
import * as React from 'react'
import { NavLink } from 'react-router-dom'
import { translate } from 'react-i18next'
import classnames from 'classnames'
import { I18nProps } from '@models'
import './style.scss'
@ -10,6 +11,7 @@ interface SidebarProps extends I18nProps {
routes: {
path: string
name: string
noMobile?: boolean
exact?: boolean
}[]
}
@ -23,8 +25,8 @@ class Sidebar extends React.Component<SidebarProps, {}> {
<ul className="sidebar-menu">
{
routes.map(
({ path, name, exact }) => (
<li className="item" key={name}>
({ path, name, exact, noMobile }) => (
<li className={classnames('item', { 'no-mobile': noMobile })} key={name}>
<NavLink to={path} activeClassName="active" exact={!!exact}>{ t(name) }</NavLink>
</li>
)

View File

@ -51,3 +51,46 @@
}
}
}
@media (max-width: 768px) {
.sidebar {
width: 100%;
height: 60px;
flex-direction: row;
background: $background;
z-index: 10;
}
.sidebar-logo {
margin: 0 15px;
width: 36px;
height: 36px;
}
.sidebar-menu {
flex: 1;
flex-direction: row;
justify-content: center;
margin-top: 0;
overflow-x: scroll;
padding: 10px;
&::-webkit-scrollbar {
display: none;
}
.item {
margin-top: 0;
> a {
width: 80px;
height: 32px;
line-height: 32px;
}
}
.item.no-mobile {
display: none;
}
}
}

View File

@ -37,8 +37,8 @@ body {
padding-left: 150px;
}
.app.clash-x {
background: #f4f5f6;
.app.not-clashx {
background: $background;
}
.page-container {
@ -49,9 +49,7 @@ body {
}
.page {
padding: 20px 35px;
padding-left: 0;
padding-bottom: 30px;
padding: 20px 35px 30px 0;
width: 100%;
height: 100vh;
margin: 0 auto;
@ -62,3 +60,25 @@ body {
.container {
margin: 20px 0;
}
@media (max-width: 768px) {
.app {
padding-left: 0;
padding-top: 60px;
}
.page-container {
width: 100%;
padding: 0 10px;
height: calc(100vh - 60px);
&::-webkit-scrollbar {
display: none;
}
}
.page {
padding: 0 0 20px;
height: 100%;
}
}

View File

@ -3,6 +3,8 @@
* Style Common Variables
*/
$background: #f4f5f6;
// primary colors
$color-primary: #57befc;
$color-primary-dark: #2c8af8;
@ -31,9 +33,6 @@ $color-black: #000;
}
$breakpoints: (
'tiny': (
max-width: 680px
),
'xxs': (
max-width: 760px
),