Make interface mobile responsive (#2273)

This commit is contained in:
Rihan 2020-09-13 18:02:25 +01:00 committed by GitHub
parent 9a21584c42
commit 9a4c0d8ba7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 130 additions and 69 deletions

View file

@ -11,7 +11,7 @@ import styled from 'styled-components/macro';
import * as config from '@/../../tailwind.config.js';
const Navigation = styled.div`
${tw`w-full bg-neutral-900 shadow-md`};
${tw`w-full bg-neutral-900 shadow-md overflow-x-auto`};
& > div {
${tw`mx-auto w-full flex items-center`};

View file

@ -14,8 +14,26 @@ import { httpErrorToHuman } from '@/api/http';
import { format } from 'date-fns';
import PageContentBlock from '@/components/elements/PageContentBlock';
import tw from 'twin.macro';
import { breakpoint } from '@/theme';
import styled from 'styled-components/macro';
import GreyRowBox from '@/components/elements/GreyRowBox';
const Container = styled.div`
${tw`flex flex-wrap my-10`};
& > div {
${tw`w-full`};
${breakpoint('md')`
width: calc(50% - 1rem);
`}
${breakpoint('xl')`
${tw`w-auto flex-1`};
`}
}
`;
export default () => {
const [ deleteIdentifier, setDeleteIdentifier ] = useState('');
const [ keys, setKeys ] = useState<ApiKey[]>([]);
@ -50,11 +68,11 @@ export default () => {
return (
<PageContentBlock title={'Account API'}>
<FlashMessageRender byKey={'account'} css={tw`mb-4`}/>
<div css={tw`flex`}>
<ContentBox title={'Create API Key'} css={tw`flex-1`}>
<Container>
<ContentBox title={'Create API Key'}>
<CreateApiKeyForm onKeyCreated={key => setKeys(s => ([ ...s!, key ]))}/>
</ContentBox>
<ContentBox title={'API Keys'} css={tw`ml-10 flex-1`}>
<ContentBox title={'API Keys'} css={tw`mt-8 md:mt-0 md:ml-8`}>
<SpinnerOverlay visible={loading}/>
<ConfirmationModal
visible={!!deleteIdentifier}
@ -106,7 +124,7 @@ export default () => {
))
}
</ContentBox>
</div>
</Container>
</PageContentBlock>
);
};

View file

@ -55,16 +55,16 @@ export default ({ server, className }: { server: Server; className?: string }) =
return (
<GreyRowBox as={Link} to={`/server/${server.id}`} className={className}>
<div className={'icon'}>
<div className={'icon'} css={tw`hidden md:block`}>
<FontAwesomeIcon icon={faServer}/>
</div>
<div css={tw`flex-1 ml-4`}>
<div css={tw`flex-1 md:ml-4`}>
<p css={tw`text-lg`}>{server.name}</p>
{!!server.description &&
<p css={tw`text-sm text-neutral-300`}>{server.description}</p>
}
</div>
<div css={tw`w-48 overflow-hidden self-start`}>
<div css={tw`w-48 overflow-hidden self-start hidden lg:block`}>
<div css={tw`flex ml-4 justify-end`}>
<FontAwesomeIcon icon={faEthernet} css={tw`text-neutral-500`}/>
<p css={tw`text-sm text-neutral-400 ml-2`}>
@ -76,7 +76,7 @@ export default ({ server, className }: { server: Server; className?: string }) =
</p>
</div>
</div>
<div css={tw`w-1/3 flex items-baseline justify-center relative`}>
<div css={tw`w-1/3 sm:w-1/2 lg:w-1/3 flex items-baseline justify-center relative`}>
{!stats ?
!statsError ?
<Spinner size={'small'}/>
@ -95,7 +95,7 @@ export default ({ server, className }: { server: Server; className?: string }) =
</div>
:
<React.Fragment>
<div css={tw`flex-1 flex ml-4 justify-center`}>
<div css={tw`flex-1 flex md:ml-4 sm:flex hidden justify-center`}>
<FontAwesomeIcon
icon={faMicrochip}
css={[
@ -113,7 +113,7 @@ export default ({ server, className }: { server: Server; className?: string }) =
{stats.cpuUsagePercent} %
</p>
</div>
<div css={tw`flex-1 ml-4`}>
<div css={tw`flex-1 ml-4 sm:block hidden`}>
<div css={tw`flex justify-center`}>
<FontAwesomeIcon
icon={faMemory}
@ -134,7 +134,7 @@ export default ({ server, className }: { server: Server; className?: string }) =
</div>
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {memorylimit}</p>
</div>
<div css={tw`flex-1 ml-4`}>
<div css={tw`flex-1 ml-4 sm:block hidden`}>
<div css={tw`flex justify-center`}>
<FontAwesomeIcon
icon={faHdd}
@ -155,6 +155,19 @@ export default ({ server, className }: { server: Server; className?: string }) =
</div>
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {disklimit}</p>
</div>
<div css={tw`flex-1 flex justify-end sm:hidden`}>
<div css={tw`flex items-end text-right`}>
<div
css={[
tw`w-3 h-3 rounded-full`,
(!stats?.status || stats?.status === 'offline')
? tw`bg-red-500`
: (stats?.status === 'running' ? tw`bg-green-500` : tw`bg-yellow-500`),
]}
/>
</div>
</div>
</React.Fragment>
}
</div>

View file

@ -4,6 +4,7 @@ import { faTimes } from '@fortawesome/free-solid-svg-icons';
import Spinner from '@/components/elements/Spinner';
import tw from 'twin.macro';
import styled from 'styled-components/macro';
import { breakpoint } from '@/theme';
import Fade from '@/components/elements/Fade';
export interface RequiredModalProps {
@ -26,9 +27,16 @@ export const ModalMask = styled.div`
`;
const ModalContainer = styled.div<{ alignTop?: boolean }>`
${breakpoint('xs')`
max-width: 95%;
`};
${breakpoint('md')`
max-width: 50%;
`};
${tw`relative flex flex-col w-full m-auto`};
max-height: calc(100vh - 8rem);
max-width: 50%;
// @todo max-w-screen-lg perhaps?
${props => props.alignTop && 'margin-top: 10%'};

View file

@ -4,14 +4,14 @@ import tw from 'twin.macro';
import config from '../../../../tailwind.config';
const SubNavigation = styled.div`
${tw`w-full bg-neutral-700 shadow`};
${tw`w-full bg-neutral-700 shadow overflow-x-auto`};
& > div {
${tw`flex items-center text-sm mx-auto px-2`};
max-width: 1200px;
& > a, & > div {
${tw`inline-block py-3 px-4 text-neutral-300 no-underline transition-all duration-150`};
${tw`inline-block py-3 px-4 text-neutral-300 no-underline whitespace-no-wrap transition-all duration-150`};
&:not(:first-of-type) {
${tw`ml-2`};

View file

@ -63,8 +63,8 @@ export default () => {
const memorylimit = limits.memory ? megabytesToHuman(limits.memory) : 'Unlimited';
return (
<ServerContentBlock title={'Console'} css={tw`flex`}>
<div css={tw`w-1/4`}>
<ServerContentBlock title={'Console'} css={tw`flex flex-wrap`}>
<div css={tw`w-full md:w-1/4`}>
<TitledGreyBox title={name} icon={faServer}>
<p css={tw`text-xs uppercase`}>
<FontAwesomeIcon
@ -137,7 +137,7 @@ export default () => {
</div>
}
</div>
<div css={tw`flex-1 ml-4`}>
<div css={tw`w-full md:flex-1 md:ml-4 mt-4 md:mt-0`}>
<SuspenseSpinner>
<ChunkedConsole/>
<ChunkedStatGraphs/>

View file

@ -141,8 +141,8 @@ export default () => {
}, [ instance, connected, memory, cpu ]);
return (
<div css={tw`flex mt-4`}>
<TitledGreyBox title={'Memory usage'} icon={faMemory} css={tw`flex-1 mr-2`}>
<div css={tw`flex flex-wrap mt-4`}>
<TitledGreyBox title={'Memory usage'} icon={faMemory} css={tw`md:flex-1 w-full md:w-1/2 md:mr-2`}>
{status !== 'offline' ?
<canvas id={'memory_chart'} ref={memoryRef} aria-label={'Server Memory Usage Graph'} role={'img'}/>
:
@ -151,7 +151,7 @@ export default () => {
</p>
}
</TitledGreyBox>
<TitledGreyBox title={'CPU usage'} icon={faMicrochip} css={tw`flex-1 ml-2`}>
<TitledGreyBox title={'CPU usage'} icon={faMicrochip} css={tw`md:flex-1 w-full md:w-1/2 md:ml-2 mt-4 md:mt-0`}>
{status !== 'offline' ?
<canvas id={'cpu_chart'} ref={cpuRef} aria-label={'Server CPU Usage Graph'} role={'img'}/>
:

View file

@ -43,7 +43,7 @@ export default ({ backup, className }: Props) => {
<GreyRowBox css={tw`flex items-center`} className={className}>
<div css={tw`mr-4`}>
{backup.completedAt ?
<FontAwesomeIcon icon={faArchive} css={tw`text-neutral-300`}/>
<FontAwesomeIcon icon={faArchive} css={tw`text-neutral-300 hidden md:block`}/>
:
<Spinner size={'small'}/>
}
@ -57,10 +57,10 @@ export default ({ backup, className }: Props) => {
}
{backup.name}
{(backup.completedAt && backup.isSuccessful) &&
<span css={tw`ml-3 text-neutral-300 text-xs font-thin`}>{bytesToHuman(backup.bytes)}</span>
<span css={tw`ml-3 text-neutral-300 text-xs font-thin hidden sm:inline`}>{bytesToHuman(backup.bytes)}</span>
}
</p>
<p css={tw`text-xs text-neutral-400 font-mono`}>
<p css={tw`text-xs text-neutral-400 font-mono hidden md:block`}>
{backup.uuid}
</p>
</div>

View file

@ -9,7 +9,7 @@ const ChecksumModal = ({ checksum, ...props }: RequiredModalProps & { checksum:
The checksum of this file is:
</p>
<pre css={tw`mt-2 text-sm p-2 bg-neutral-900 rounded`}>
<code css={tw`block font-mono`}>{checksum}</code>
<code css={tw`block font-mono overflow-auto`}>{checksum}</code>
</pre>
</Modal>
);

View file

@ -111,8 +111,20 @@ export default ({ database, className }: Props) => {
<Modal visible={connectionVisible} onDismissed={() => setConnectionVisible(false)}>
<FlashMessageRender byKey={'database-connection-modal'} css={tw`mb-6`}/>
<h3 css={tw`mb-6`}>Database connection details</h3>
<div>
<Label>Endpoint</Label>
<Input type={'text'} readOnly value={database.connectionString} />
</div>
<div css={tw`mt-6`}>
<Label>Connections from</Label>
<Input type={'text'} readOnly value={database.allowConnectionsFrom} />
</div>
<div css={tw`mt-6`}>
<Label>Username</Label>
<Input type={'text'} readOnly value={database.username} />
</div>
<Can action={'database.view_password'}>
<div>
<div css={tw`mt-6`}>
<Label>Password</Label>
<Input type={'text'} readOnly value={database.password}/>
</div>
@ -134,22 +146,22 @@ export default ({ database, className }: Props) => {
</Button>
</div>
</Modal>
<GreyRowBox $hoverable={false} className={className}>
<div>
<GreyRowBox $hoverable={false} className={className} css={tw`mb-2`}>
<div css={tw`hidden md:block`}>
<FontAwesomeIcon icon={faDatabase} fixedWidth/>
</div>
<div css={tw`flex-1 ml-4`}>
<p css={tw`text-lg`}>{database.name}</p>
</div>
<div css={tw`ml-8 text-center`}>
<div css={tw`ml-8 text-center hidden md:block`}>
<p css={tw`text-sm`}>{database.connectionString}</p>
<p css={tw`mt-1 text-2xs text-neutral-500 uppercase select-none`}>Endpoint</p>
</div>
<div css={tw`ml-8 text-center`}>
<div css={tw`ml-8 text-center hidden md:block`}>
<p css={tw`text-sm`}>{database.allowConnectionsFrom}</p>
<p css={tw`mt-1 text-2xs text-neutral-500 uppercase select-none`}>Connections from</p>
</div>
<div css={tw`ml-8 text-center`}>
<div css={tw`ml-8 text-center hidden md:block`}>
<p css={tw`text-sm`}>{database.username}</p>
<p css={tw`mt-1 text-2xs text-neutral-500 uppercase select-none`}>Username</p>
</div>

View file

@ -73,12 +73,12 @@ const FileObjectRow = ({ file }: { file: FileObject }) => (
{file.name}
</div>
{file.isFile &&
<div css={tw`w-1/6 text-right mr-4`}>
<div css={tw`w-1/6 text-right mr-4 hidden sm:block`}>
{bytesToHuman(file.size)}
</div>
}
<div
css={tw`w-1/5 text-right mr-4`}
css={tw`w-1/5 text-right mr-4 hidden md:block`}
title={file.modifiedAt.toString()}
>
{Math.abs(differenceInHours(file.modifiedAt, new Date())) > 48 ?

View file

@ -68,8 +68,8 @@ const NetworkContainer = () => {
<Spinner size={'large'} centered/>
:
data.map(({ id, ip, port, alias, notes, isDefault }, index) => (
<GreyRowBox key={`${ip}:${port}`} css={index > 0 ? tw`mt-2` : undefined} $hoverable={false}>
<div css={tw`pl-4 pr-6 text-neutral-400`}>
<GreyRowBox key={`${ip}:${port}`} css={index > 0 ? tw`mt-2 overflow-x-auto` : tw`overflow-x-auto`} $hoverable={false}>
<div css={tw`hidden md:block pl-4 pr-6 text-neutral-400`}>
<FontAwesomeIcon icon={faNetworkWired}/>
</div>
<div css={tw`mr-4`}>
@ -80,7 +80,7 @@ const NetworkContainer = () => {
<Code>{port}</Code>
<Label>Port</Label>
</div>
<div css={tw`px-8 flex-1 self-start`}>
<div css={tw`px-8 flex-none sm:flex-1 self-start`}>
<InputSpinner visible={loading === id}>
<Textarea
css={tw`bg-neutral-800 hover:border-neutral-600 border-transparent`}
@ -90,7 +90,7 @@ const NetworkContainer = () => {
/>
</InputSpinner>
</div>
<div css={tw`w-32 text-right`}>
<div css={tw`w-32 text-right mr-2 md:mr-0`}>
{isDefault ?
<span css={tw`bg-green-500 py-1 px-2 rounded text-green-50 text-xs`}>
Primary

View file

@ -52,7 +52,7 @@ export default ({ match, history }: RouteComponentProps) => {
as={'a'}
key={schedule.id}
href={`${match.url}/${schedule.id}`}
css={tw`cursor-pointer mb-2`}
css={tw`cursor-pointer mb-2 flex-wrap`}
onClick={(e: any) => {
e.preventDefault();
history.push(`${match.url}/${schedule.id}`, { schedule });

View file

@ -60,7 +60,7 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
<Spinner size={'large'} centered/>
:
<>
<GreyRowBox>
<GreyRowBox css={tw`cursor-pointer mb-2 flex-wrap`}>
<ScheduleRow schedule={schedule}/>
</GreyRowBox>
<EditScheduleModal

View file

@ -7,42 +7,52 @@ import tw from 'twin.macro';
export default ({ schedule }: { schedule: Schedule }) => (
<>
<div>
<div css={tw`hidden md:block`}>
<FontAwesomeIcon icon={faCalendarAlt} fixedWidth/>
</div>
<div css={tw`flex-1 ml-4`}>
<div css={tw`flex-1 md:ml-4`}>
<p>{schedule.name}</p>
<p css={tw`text-xs text-neutral-400`}>
Last run
at: {schedule.lastRunAt ? format(schedule.lastRunAt, 'MMM do \'at\' h:mma') : 'never'}
</p>
</div>
<div css={tw`flex items-center mx-8`}>
<div>
<p css={tw`font-medium text-center`}>{schedule.cron.minute}</p>
<div>
<p
css={[
tw`py-1 px-3 rounded text-xs uppercase text-white sm:hidden`,
schedule.isActive ? tw`bg-green-600` : tw`bg-neutral-400`,
]}
>
{schedule.isActive ? 'Active' : 'Inactive'}
</p>
</div>
<div css={tw`flex items-center mx-auto sm:mx-8 w-full sm:w-auto mt-4 sm:mt-0`}>
<div css={tw`w-1/5 sm:w-auto text-center`}>
<p css={tw`font-medium`}>{schedule.cron.minute}</p>
<p css={tw`text-2xs text-neutral-500 uppercase`}>Minute</p>
</div>
<div css={tw`ml-4`}>
<p css={tw`font-medium text-center`}>{schedule.cron.hour}</p>
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
<p css={tw`font-medium`}>{schedule.cron.hour}</p>
<p css={tw`text-2xs text-neutral-500 uppercase`}>Hour</p>
</div>
<div css={tw`ml-4`}>
<p css={tw`font-medium text-center`}>{schedule.cron.dayOfMonth}</p>
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
<p css={tw`font-medium`}>{schedule.cron.dayOfMonth}</p>
<p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Month)</p>
</div>
<div css={tw`ml-4`}>
<p css={tw`font-medium text-center`}>*</p>
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
<p css={tw`font-medium`}>*</p>
<p css={tw`text-2xs text-neutral-500 uppercase`}>Month</p>
</div>
<div css={tw`ml-4`}>
<p css={tw`font-medium text-center`}>{schedule.cron.dayOfWeek}</p>
<div css={tw`w-1/5 sm:w-auto text-center ml-4`}>
<p css={tw`font-medium`}>{schedule.cron.dayOfWeek}</p>
<p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Week)</p>
</div>
</div>
<div>
<p
css={[
tw`py-1 px-3 rounded text-xs uppercase text-white`,
tw`py-1 px-3 rounded text-xs uppercase text-white hidden sm:block`,
schedule.isActive ? tw`bg-green-600` : tw`bg-neutral-400`,
]}
>

View file

@ -56,7 +56,7 @@ export default ({ schedule, task }: Props) => {
const [ title, icon ] = getActionDetails(task.action);
return (
<div css={tw`flex items-center bg-neutral-700 border border-neutral-600 mb-2 px-6 py-4 rounded`}>
<div css={tw`flex flex-wrap items-center bg-neutral-700 border border-neutral-600 mb-2 px-6 py-4 rounded`}>
<SpinnerOverlay visible={isLoading} fixed size={'large'}/>
{isEditing && <TaskDetailsModal
schedule={schedule}
@ -72,13 +72,13 @@ export default ({ schedule, task }: Props) => {
>
Are you sure you want to delete this task? This action cannot be undone.
</ConfirmationModal>
<FontAwesomeIcon icon={icon} css={tw`text-lg text-white`}/>
<div css={tw`flex-1`}>
<p css={tw`ml-6 text-neutral-300 uppercase text-xs`}>
<FontAwesomeIcon icon={icon} css={tw`text-lg text-white hidden md:block`}/>
<div css={tw`flex-none sm:flex-1 mb-4 sm:mb-0 w-full md:w-auto overflow-x-auto`}>
<p css={tw`md:ml-6 text-neutral-300 uppercase text-xs`}>
{title}
</p>
{task.payload &&
<div css={tw`ml-6 mt-2`}>
<div css={tw`md:ml-6 mt-2`}>
{task.action === 'backup' &&
<p css={tw`text-xs uppercase text-neutral-400 mb-1`}>Ignoring files & folders:</p>}
<div css={tw`font-mono bg-neutral-800 rounded py-1 px-2 text-sm w-auto whitespace-pre inline-block`}>
@ -101,7 +101,7 @@ export default ({ schedule, task }: Props) => {
<button
type={'button'}
aria-label={'Edit scheduled task'}
css={tw`block text-sm p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mr-4`}
css={tw`block text-sm p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mr-4 ml-auto sm:ml-0`}
onClick={() => setIsEditing(true)}
>
<FontAwesomeIcon icon={faPencilAlt}/>

View file

@ -55,7 +55,7 @@ const StartupContainer = () => {
</p>
</div>
</TitledGreyBox>
<div css={tw`grid gap-8 grid-cols-2 mt-10`}>
<div css={tw`grid gap-8 md:grid-cols-2 mt-10`}>
{data.variables.map(variable => <VariableBox key={variable.envVariable} variable={variable}/>)}
</div>
</ServerContentBlock>

View file

@ -47,13 +47,13 @@ const VariableBox = ({ variable }: Props) => {
title={
<p css={tw`text-sm uppercase`}>
{!variable.isEditable &&
<span css={tw`bg-neutral-700 text-xs py-1 px-2 rounded-full mr-2`}>Read Only</span>
<span css={tw`bg-neutral-700 text-xs py-1 px-2 rounded-full mr-2 mb-1`}>Read Only</span>
}
{variable.name}
</p>
}
>
<FlashMessageRender byKey={FLASH_KEY} css={tw`mb-4`}/>
<FlashMessageRender byKey={FLASH_KEY} css={tw`mb-2 md:mb-4`}/>
<InputSpinner visible={loading}>
<Input
onKeyUp={e => {

View file

@ -27,11 +27,11 @@ export default ({ subuser }: Props) => {
onDismissed={() => setVisible(false)}
/>
}
<div css={tw`w-10 h-10 rounded-full bg-white border-2 border-neutral-800 overflow-hidden`}>
<div css={tw`w-10 h-10 rounded-full bg-white border-2 border-neutral-800 overflow-hidden hidden md:block`}>
<img css={tw`w-full h-full`} src={`${subuser.image}?s=400`}/>
</div>
<div css={tw`ml-4 flex-1`}>
<p css={tw`text-sm`}>{subuser.email}</p>
<p css={tw`text-sm truncate`}>{subuser.email}</p>
</div>
<div css={tw`ml-4`}>
<p css={tw`font-medium text-center`}>
@ -43,9 +43,9 @@ export default ({ subuser }: Props) => {
/>
&nbsp;
</p>
<p css={tw`text-2xs text-neutral-500 uppercase`}>2FA Enabled</p>
<p css={tw`text-2xs text-neutral-500 uppercase hidden md:block`}>2FA Enabled</p>
</div>
<div css={tw`ml-4`}>
<div css={tw`ml-4 hidden md:block`}>
<p css={tw`font-medium text-center`}>
{subuser.permissions.filter(permission => permission !== 'websocket.connect').length}
</p>
@ -56,7 +56,7 @@ export default ({ subuser }: Props) => {
<button
type={'button'}
aria-label={'Edit subuser'}
css={tw`block text-sm p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mx-4`}
css={tw`block text-sm p-1 md:p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mx-4`}
onClick={() => setVisible(true)}
>
<FontAwesomeIcon icon={faPencilAlt}/>

View file

@ -48,8 +48,8 @@
</tr>
@foreach ($packs as $pack)
<tr>
<td class="middle" data-toggle="tooltip" data-placement="right" title="{{ $pack->uuid }}"><code>{{ $pack->id }}</code></td>
<td class="middle"><a href="{{ route('admin.packs.view', $pack->id) }}">{{ $pack->name }}</a></td>
<td class="middle"><code>{{ $pack->id }}</code></td>
<td class="middle"><a href="{{ route('admin.packs.view', $pack->id) }}" data-toggle="tooltip" data-placement="right" title="{{ $pack->uuid }}">{{ $pack->name }}</a></td>
<td class="middle"><code>{{ $pack->version }}</code></td>
<td class="col-md-6">{{ str_limit($pack->description, 150) }}</td>
<td class="middle"><a href="{{ route('admin.nests.egg.view', $pack->egg->id) }}">{{ $pack->egg->name }}</a></td>