Optimize frontend for small screens

This commit is contained in:
IRBorisov 2024-01-16 13:47:29 +03:00
parent 46d8d93c4b
commit 5a196b8fa1
13 changed files with 81 additions and 27 deletions

View File

@ -442,6 +442,7 @@ class RSForm:
if transfer_term: if transfer_term:
substitution.term_raw = original.term_raw substitution.term_raw = original.term_raw
substitution.term_forms = original.term_forms substitution.term_forms = original.term_forms
substitution.term_resolved = original.term_resolved
substitution.save() substitution.save()
original.delete() original.delete()
self.on_term_change([substitution.alias]) self.on_term_change([substitution.alias])

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="ru">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" /> <link rel="icon" href="/favicon.svg" />

View File

@ -11,7 +11,7 @@ function ApplicationLayout() {
const { viewportHeight, mainHeight, showScroll } = useConceptTheme(); const { viewportHeight, mainHeight, showScroll } = useConceptTheme();
return ( return (
<NavigationState> <NavigationState>
<div className='min-w-[30rem] clr-app antialiased'> <div className='min-w-[20rem] clr-app antialiased'>
<ConceptToaster <ConceptToaster
className='mt-[4rem] text-sm' // prettier: split lines className='mt-[4rem] text-sm' // prettier: split lines
autoClose={3000} autoClose={3000}

View File

@ -15,8 +15,8 @@ function Footer() {
tabIndex={-1} tabIndex={-1}
className={clsx( className={clsx(
'z-navigation', 'z-navigation',
'px-4 py-2 flex flex-col items-center gap-1', 'sm:px-4 sm:py-2 flex flex-col items-center gap-1',
'text-sm select-none whitespace-nowrap' 'text-xs sm:text-sm select-none whitespace-nowrap'
)} )}
> >
<div className='flex gap-3'> <div className='flex gap-3'>

View File

@ -1,12 +1,19 @@
import clsx from 'clsx';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import useWindowSize from '@/hooks/useWindowSize';
function Logo() { function Logo() {
const { darkMode } = useConceptTheme(); const { darkMode } = useConceptTheme();
const size = useWindowSize();
return ( return (
<img <img
alt='Логотип КонцептПортал' alt='Логотип КонцептПортал'
className='max-h-[1.6rem] min-w-[11.5rem]' className={clsx('max-h-[1.6rem] min-w-fit', 'text-start', {
src={!darkMode ? '/logo_full.svg' : '/logo_full_dark.svg'} 'min-w-[11.5rem]': size.isSmall
})}
src={size.isSmall ? '/logo_sign.svg' : !darkMode ? '/logo_full.svg' : '/logo_full_dark.svg'}
/> />
); );
} }

View File

@ -29,7 +29,7 @@ function NavigationButton({ icon, title, onClick, text }: NavigationButtonProps)
)} )}
> >
{icon ? <span>{icon}</span> : null} {icon ? <span>{icon}</span> : null}
{text ? <span>{text}</span> : null} {text ? <span className='hidden sm:inline'>{text}</span> : null}
</button> </button>
); );
} }

View File

@ -2,13 +2,16 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { SMALL_SCREEN_WIDTH } from '@/utils/constants';
function useWindowSize() { function useWindowSize() {
const isClient = typeof window === 'object'; const isClient = typeof window === 'object';
function getSize() { function getSize() {
return { return {
width: isClient ? window.innerWidth : undefined, width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined height: isClient ? window.innerHeight : undefined,
isSmall: isClient && window.innerWidth < SMALL_SCREEN_WIDTH
}; };
} }

View File

@ -47,9 +47,10 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
<div <div
className={clsx( className={clsx(
'sticky top-0', // prettier: split lines 'sticky top-0', // prettier: split lines
'w-full max-h-[2.3rem]', 'w-full max-h-[2.2rem]',
'pr-40 flex', 'sm:pr-40 flex',
'border-b', 'border-b',
'text-sm',
'clr-input' 'clr-input'
)} )}
> >

View File

@ -1,10 +1,10 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo } from 'react'; import { useLayoutEffect, useMemo, useState } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import DataTable, { createColumnHelper } from '@/components/DataTable'; import DataTable, { createColumnHelper, VisibilityState } from '@/components/DataTable';
import HelpButton from '@/components/Help/HelpButton'; import HelpButton from '@/components/Help/HelpButton';
import FlexColumn from '@/components/ui/FlexColumn'; import FlexColumn from '@/components/ui/FlexColumn';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
@ -12,6 +12,7 @@ import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useUsers } from '@/context/UsersContext'; import { useUsers } from '@/context/UsersContext';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import useWindowSize from '@/hooks/useWindowSize';
import { ILibraryItem } from '@/models/library'; import { ILibraryItem } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
@ -33,6 +34,16 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
const handleOpenItem = (item: ILibraryItem) => router.push(`/rsforms/${item.id}`); const handleOpenItem = (item: ILibraryItem) => router.push(`/rsforms/${item.id}`);
const windowSize = useWindowSize();
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
useLayoutEffect(() => {
setColumnVisibility({
owner: !windowSize.isSmall
});
}, [windowSize]);
const columns = useMemo( const columns = useMemo(
() => [ () => [
columnHelper.display({ columnHelper.display({
@ -46,43 +57,53 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
columnHelper.accessor('alias', { columnHelper.accessor('alias', {
id: 'alias', id: 'alias',
header: 'Шифр', header: 'Шифр',
size: 200, size: 150,
minSize: 200, minSize: 80,
maxSize: 200, maxSize: 150,
enableSorting: true, enableSorting: true,
sortingFn: 'text' sortingFn: 'text'
}), }),
columnHelper.accessor('title', { columnHelper.accessor('title', {
id: 'title', id: 'title',
header: 'Название', header: 'Название',
size: 2000, size: 1200,
minSize: 400, minSize: 200,
maxSize: 2000, maxSize: 1200,
enableSorting: true, enableSorting: true,
sortingFn: 'text' sortingFn: 'text'
}), }),
columnHelper.accessor(item => item.owner ?? 0, { columnHelper.accessor(item => item.owner ?? 0, {
id: 'owner', id: 'owner',
header: 'Владелец', header: 'Владелец',
size: 600, size: 400,
minSize: 200, minSize: 100,
maxSize: 600, maxSize: 400,
cell: props => getUserLabel(props.cell.getValue()), cell: props => getUserLabel(props.cell.getValue()),
enableSorting: true, enableSorting: true,
sortingFn: 'text' sortingFn: 'text'
}), }),
columnHelper.accessor('time_update', { columnHelper.accessor('time_update', {
id: 'time_update', id: 'time_update',
header: 'Обновлена', header: windowSize.isSmall ? 'Дата' : 'Обновлена',
cell: props => ( cell: props => (
<div className='text-sm whitespace-nowrap'>{new Date(props.cell.getValue()).toLocaleString(intl.locale)}</div> <div className='whitespace-nowrap'>
{new Date(props.cell.getValue()).toLocaleString(intl.locale, {
year: '2-digit',
month: '2-digit',
day: '2-digit',
...(!windowSize.isSmall && {
hour: '2-digit',
minute: '2-digit'
})
})}
</div>
), ),
enableSorting: true, enableSorting: true,
sortingFn: 'datetime', sortingFn: 'datetime',
sortDescFirst: true sortDescFirst: true
}) })
], ],
[intl, getUserLabel, user] [intl, getUserLabel, user, windowSize]
); );
return ( return (
@ -102,7 +123,8 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
<DataTable <DataTable
columns={columns} columns={columns}
data={items} data={items}
headPosition='2.3rem' headPosition='2.2rem'
className='text-xs sm:text-sm'
noDataComponent={ noDataComponent={
<FlexColumn className='p-3 items-center min-h-[6rem]'> <FlexColumn className='p-3 items-center min-h-[6rem]'>
<p>Список схем пуст</p> <p>Список схем пуст</p>
@ -112,6 +134,7 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
</p> </p>
</FlexColumn> </FlexColumn>
} }
columnVisibility={columnVisibility}
onRowClicked={handleOpenItem} onRowClicked={handleOpenItem}
enableSorting enableSorting
initialSorting={{ initialSorting={{

View File

@ -12,7 +12,14 @@ interface TopicsListProps {
function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) { function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
return ( return (
<div <div
className={clsx('sticky top-0 left-0', 'min-w-[13rem] self-start', 'border-x', 'clr-controls', '', 'select-none')} className={clsx(
'sticky top-0 left-0',
'self-start',
'border-x',
'clr-controls',
'text-xs sm:text-sm',
'select-none'
)}
> >
{Object.values(HelpTopic).map((topic, index) => ( {Object.values(HelpTopic).map((topic, index) => (
<div <div

View File

@ -70,7 +70,7 @@ function EditorRSForm({
onDestroy={onDestroy} onDestroy={onDestroy}
onToggleSubscribe={onToggleSubscribe} onToggleSubscribe={onToggleSubscribe}
/> />
<div tabIndex={-1} className='flex' onKeyDown={handleInput}> <div tabIndex={-1} className='flex flex-col sm:flex-row w-fit' onKeyDown={handleInput}>
<FlexColumn className='px-4 pb-2'> <FlexColumn className='px-4 pb-2'>
<FormRSForm <FormRSForm
disabled={!isMutable} disabled={!isMutable}

View File

@ -31,6 +31,7 @@ html {
} }
:root { :root {
font-size: 16px;
font-family: var(--font-main); font-family: var(--font-main);
color: var(--cl-fg-100); color: var(--cl-fg-100);
@ -44,6 +45,12 @@ html {
} }
} }
@media only screen and (max-width: 640px) {
:root {
font-size: 12px;
}
}
:focus { :focus {
outline-width: 2px; outline-width: 2px;
outline-style: solid; outline-style: solid;

View File

@ -14,6 +14,11 @@ export const buildConstants = {
*/ */
export const TIMEOUT_UI_REFRESH = 100; export const TIMEOUT_UI_REFRESH = 100;
/**
* Threshold for small screen size optimizations.
*/
export const SMALL_SCREEN_WIDTH = 640; // == tailwind:xs
/** /**
* Timeout [in ms] for graph refresh. * Timeout [in ms] for graph refresh.
*/ */