mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Optimize frontend for small screens
This commit is contained in:
parent
46d8d93c4b
commit
5a196b8fa1
|
@ -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])
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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'>
|
||||||
|
|
|
@ -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'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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={{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue
Block a user