mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 14:01:56 +08:00
Feature: mobile support
This commit is contained in:
parent
64d612dd53
commit
293e484f1d
@ -23,3 +23,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header > h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
$width: 400px;
|
$width: 400px;
|
||||||
$bigWidth: 600px;
|
$bigWidth: 600px;
|
||||||
|
|
||||||
|
$mobileWidth: 280px;
|
||||||
|
$mobileBigWidth: 480px;
|
||||||
|
|
||||||
.modal-mask {
|
.modal-mask {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -76,3 +79,15 @@ $bigWidth: 600px;
|
|||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.modal-mask {
|
||||||
|
.modal-small {
|
||||||
|
width: $mobileWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-big {
|
||||||
|
width: $mobileBigWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,12 +28,12 @@ export default class App extends React.Component<AppProps, {}> {
|
|||||||
// { path: '/', name: 'Overview', component: Overview, exact: true },
|
// { path: '/', name: 'Overview', component: Overview, exact: true },
|
||||||
{ path: '/proxies', name: 'Proxies', component: Proxies },
|
{ path: '/proxies', name: 'Proxies', component: Proxies },
|
||||||
{ path: '/logs', name: 'Logs', component: Logs },
|
{ 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 }
|
{ path: '/settings', name: 'Settings', component: Settings }
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('app', { 'clash-x': !isClashX() })}>
|
<div className={classnames('app', { 'not-clashx': !isClashX() })}>
|
||||||
<SlideBar routes={routes} />
|
<SlideBar routes={routes} />
|
||||||
<div className="page-container">
|
<div className="page-container">
|
||||||
<Route exact path="/" component={() => <Redirect to="/proxies"/>}/>
|
<Route exact path="/" component={() => <Redirect to="/proxies"/>}/>
|
||||||
|
@ -20,3 +20,31 @@
|
|||||||
font-weight: bold;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
.proxy-group-name {
|
.proxy-group-name {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
}
|
}
|
||||||
@ -51,3 +52,32 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
margin-left: 30px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -64,3 +64,20 @@
|
|||||||
background-color: $color-gray-darken;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -37,10 +37,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include response(xxs) {
|
@include response(xxs) {
|
||||||
--columns: 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include response(tiny) {
|
|
||||||
--columns: 3;
|
--columns: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,3 +64,22 @@
|
|||||||
text-shadow: 0 2px 6px rgba($color: $color-primary-dark, $alpha: 0.4);
|
text-shadow: 0 2px 6px rgba($color: $color-primary-dark, $alpha: 0.4);
|
||||||
cursor: pointer;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -112,70 +112,83 @@ class Settings extends React.Component<SettingProps, {}> {
|
|||||||
<Header title={t('title')} />
|
<Header title={t('title')} />
|
||||||
<Card className="settings-card">
|
<Card className="settings-card">
|
||||||
<Row gutter={24} align="middle">
|
<Row gutter={24} align="middle">
|
||||||
<Col span={6} offset={1}>
|
<Col span={12}>
|
||||||
|
<Col span={14} offset={1}>
|
||||||
<span className="label">{t('labels.startAtLogin')}</span>
|
<span className="label">{t('labels.startAtLogin')}</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4} className="value-column">
|
<Col span={8} className="value-column">
|
||||||
<Switch disabled={!isClashX} checked={startAtLogin} onChange={this.handleStartAtLoginChange} />
|
<Switch disabled={!isClashX} checked={startAtLogin} onChange={this.handleStartAtLoginChange} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4} offset={1}>
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Col span={8} offset={1}>
|
||||||
<span className="label">{t('labels.language')}</span>
|
<span className="label">{t('labels.language')}</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={7} className="value-column">
|
<Col span={14} className="value-column">
|
||||||
<ButtonSelect options={this.languageOptions} value={lng.replace(/-.+$/, '')} onSelect={this.changeLanguage} />
|
<ButtonSelect options={this.languageOptions} value={lng.replace(/-.+$/, '')} onSelect={this.changeLanguage} />
|
||||||
</Col>
|
</Col>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row gutter={24} align="middle">
|
<Row gutter={24} align="middle">
|
||||||
<Col span={6} offset={1}>
|
<Col span={12}>
|
||||||
|
<Col span={14} offset={1}>
|
||||||
<span className="label">{t('labels.setAsSystemProxy')}</span>
|
<span className="label">{t('labels.setAsSystemProxy')}</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4} className="value-column">
|
<Col span={8} className="value-column">
|
||||||
<Switch
|
<Switch
|
||||||
disabled={!isClashX}
|
disabled={!isClashX}
|
||||||
checked={systemProxy}
|
checked={systemProxy}
|
||||||
onChange={this.handleSetSystemProxy}
|
onChange={this.handleSetSystemProxy}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={7} offset={1}>
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Col span={8} offset={1}>
|
||||||
<span className="label">{t('labels.allowConnectFromLan')}</span>
|
<span className="label">{t('labels.allowConnectFromLan')}</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4} className="value-column">
|
<Col span={14} className="value-column">
|
||||||
<Switch
|
<Switch
|
||||||
checked={allowLan}
|
checked={allowLan}
|
||||||
onChange={this.handleAllowLanChange}
|
onChange={this.handleAllowLanChange}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="settings-card">
|
<Card className="settings-card">
|
||||||
<Row gutter={24} align="middle">
|
<Row gutter={24} align="middle">
|
||||||
<Col span={3} offset={1}>
|
<Col span={12}>
|
||||||
|
<Col span={8} offset={1}>
|
||||||
<span className="label">{t('labels.proxyMode')}</span>
|
<span className="label">{t('labels.proxyMode')}</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={7} className="value-column">
|
<Col span={14} className="value-column">
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
options={proxyModeOptions}
|
options={proxyModeOptions}
|
||||||
value={mode}
|
value={mode}
|
||||||
onSelect={this.handleProxyModeChange}
|
onSelect={this.handleProxyModeChange}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={5} offset={1}>
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Col span={14} offset={1}>
|
||||||
<span className="label">{t('labels.socks5ProxyPort')}</span>
|
<span className="label">{t('labels.socks5ProxyPort')}</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={3} offset={3}>
|
<Col span={8}>
|
||||||
<Input
|
<Input
|
||||||
value={socks5ProxyPort}
|
value={socks5ProxyPort}
|
||||||
onChange={socks5ProxyPort => this.setState({ socks5ProxyPort: parseInt(socks5ProxyPort, 10) })}
|
onChange={socks5ProxyPort => this.setState({ socks5ProxyPort: parseInt(socks5ProxyPort, 10) })}
|
||||||
onBlur={this.handleSocksPortSave}
|
onBlur={this.handleSocksPortSave}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row gutter={24} align="middle">
|
<Row gutter={24} align="middle">
|
||||||
<Col span={5} offset={1}>
|
<Col span={12}>
|
||||||
|
<Col span={14} offset={1}>
|
||||||
<span className="label">{t('labels.httpProxyPort')}</span>
|
<span className="label">{t('labels.httpProxyPort')}</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={3} offset={2}>
|
<Col span={8}>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={httpProxyPort}
|
value={httpProxyPort}
|
||||||
@ -183,15 +196,18 @@ class Settings extends React.Component<SettingProps, {}> {
|
|||||||
onBlur={this.handleHttpPortSave}
|
onBlur={this.handleHttpPortSave}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={4} offset={1}>
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Col span={8} offset={1}>
|
||||||
<span className="label">{t('labels.externalController')}</span>
|
<span className="label">{t('labels.externalController')}</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col className="external-controller" span={6} offset={1}>
|
<Col className="external-controller" span={14}>
|
||||||
<span>{`${externalControllerHost}:${externalControllerPort}`}</span>
|
<span>{`${externalControllerHost}:${externalControllerPort}`}</span>
|
||||||
<span className="modify-btn" onClick={() => this.props.store.setShowAPIModal(true)}>
|
<span className="modify-btn" onClick={() => this.props.store.setShowAPIModal(true)}>
|
||||||
修改
|
修改
|
||||||
</span>
|
</span>
|
||||||
</Col>
|
</Col>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { NavLink } from 'react-router-dom'
|
import { NavLink } from 'react-router-dom'
|
||||||
import { translate } from 'react-i18next'
|
import { translate } from 'react-i18next'
|
||||||
|
import classnames from 'classnames'
|
||||||
import { I18nProps } from '@models'
|
import { I18nProps } from '@models'
|
||||||
|
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
@ -10,6 +11,7 @@ interface SidebarProps extends I18nProps {
|
|||||||
routes: {
|
routes: {
|
||||||
path: string
|
path: string
|
||||||
name: string
|
name: string
|
||||||
|
noMobile?: boolean
|
||||||
exact?: boolean
|
exact?: boolean
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
@ -23,8 +25,8 @@ class Sidebar extends React.Component<SidebarProps, {}> {
|
|||||||
<ul className="sidebar-menu">
|
<ul className="sidebar-menu">
|
||||||
{
|
{
|
||||||
routes.map(
|
routes.map(
|
||||||
({ path, name, exact }) => (
|
({ path, name, exact, noMobile }) => (
|
||||||
<li className="item" key={name}>
|
<li className={classnames('item', { 'no-mobile': noMobile })} key={name}>
|
||||||
<NavLink to={path} activeClassName="active" exact={!!exact}>{ t(name) }</NavLink>
|
<NavLink to={path} activeClassName="active" exact={!!exact}>{ t(name) }</NavLink>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -37,8 +37,8 @@ body {
|
|||||||
padding-left: 150px;
|
padding-left: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app.clash-x {
|
.app.not-clashx {
|
||||||
background: #f4f5f6;
|
background: $background;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-container {
|
.page-container {
|
||||||
@ -49,9 +49,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
padding: 20px 35px;
|
padding: 20px 35px 30px 0;
|
||||||
padding-left: 0;
|
|
||||||
padding-bottom: 30px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@ -62,3 +60,25 @@ body {
|
|||||||
.container {
|
.container {
|
||||||
margin: 20px 0;
|
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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
* Style Common Variables
|
* Style Common Variables
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
$background: #f4f5f6;
|
||||||
|
|
||||||
// primary colors
|
// primary colors
|
||||||
$color-primary: #57befc;
|
$color-primary: #57befc;
|
||||||
$color-primary-dark: #2c8af8;
|
$color-primary-dark: #2c8af8;
|
||||||
@ -31,9 +33,6 @@ $color-black: #000;
|
|||||||
}
|
}
|
||||||
|
|
||||||
$breakpoints: (
|
$breakpoints: (
|
||||||
'tiny': (
|
|
||||||
max-width: 680px
|
|
||||||
),
|
|
||||||
'xxs': (
|
'xxs': (
|
||||||
max-width: 760px
|
max-width: 760px
|
||||||
),
|
),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user