1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Resolves #7848: Made note list controls responsive (#7884)

This commit is contained in:
Julien 2023-03-12 23:26:15 +08:00 committed by GitHub
parent 9106fb82f3
commit b561460307
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 151 additions and 39 deletions

View File

@ -1,6 +1,6 @@
import { AppState } from '../../app.reducer';
import * as React from 'react';
import { useEffect, useRef } from 'react';
import { useEffect, useRef, useMemo, useState } from 'react';
import SearchBar from '../SearchBar/SearchBar';
import Button, { ButtonLevel, ButtonSize, buttonSizePx } from '../Button/Button';
import CommandService from '@joplin/lib/services/CommandService';
@ -11,6 +11,13 @@ import { _ } from '@joplin/lib/locale';
const { connect } = require('react-redux');
const styled = require('styled-components').default;
enum BaseBreakpoint {
Sm = 160,
Md = 190,
Lg = 40,
Xl = 474,
}
interface Props {
showNewNoteButtons: boolean;
sortOrderButtonsVisible: boolean;
@ -18,6 +25,14 @@ interface Props {
sortOrderReverse: boolean;
notesParentType: string;
height: number;
width: number;
}
interface Breakpoints {
Sm: number;
Md: number;
Lg: number;
Xl: number;
}
const StyledRoot = styled.div`
@ -34,7 +49,7 @@ const StyledButton = styled(Button)`
width: auto;
height: 26px;
min-height: 26px;
flex: 1 0 auto;
max-width: none;
.fa, .fas {
font-size: 11px;
@ -54,7 +69,13 @@ const StyledPairButtonR = styled(Button)`
width: auto;
`;
const RowContainer = styled.div`
const TopRow = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
`;
const BottomRow = styled.div`
display: flex;
flex-direction: row;
flex: 1 1 auto;
@ -68,7 +89,100 @@ const SortOrderButtonsContainer = styled.div`
`;
function NoteListControls(props: Props) {
const [dynamicBreakpoints, setDynamicBreakpoints] = useState<Breakpoints>({ Sm: BaseBreakpoint.Sm, Md: BaseBreakpoint.Md, Lg: BaseBreakpoint.Lg, Xl: BaseBreakpoint.Xl });
const searchBarRef = useRef(null);
const newNoteRef = useRef(null);
const newTodoRef = useRef(null);
const noteControlsRef = useRef(null);
const searchAndSortRef = useRef(null);
const getTextWidth = (text: string): number => {
const canvas = document.createElement('canvas');
if (!canvas) throw new Error('Failed to create canvas element');
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Failed to get context');
const fontWeight = getComputedStyle(newNoteRef.current).getPropertyValue('font-weight');
const fontSize = getComputedStyle(newNoteRef.current).getPropertyValue('font-size');
const fontFamily = getComputedStyle(newNoteRef.current).getPropertyValue('font-family');
ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`;
return ctx.measureText(text).width;
};
// Initialize language-specific breakpoints
useEffect(() => {
// Use the longest string to calculate the amount of extra width needed
const smAdditional = getTextWidth(_('note')) > getTextWidth(_('to-do')) ? getTextWidth(_('note')) : getTextWidth(_('to-do'));
const mdAdditional = getTextWidth(_('New note')) > getTextWidth(_('New to-do')) ? getTextWidth(_('New note')) : getTextWidth(_('New to-do'));
const Sm = BaseBreakpoint.Sm + smAdditional * 2;
const Md = BaseBreakpoint.Md + mdAdditional * 2;
const Lg = BaseBreakpoint.Lg + Md;
const Xl = BaseBreakpoint.Xl;
setDynamicBreakpoints({ Sm, Md, Lg, Xl });
}, []);
const breakpoint = useMemo(() => {
// Find largest breakpoint that width is less than
const index = Object.values(dynamicBreakpoints).findIndex(x => props.width < x);
return index === -1 ? dynamicBreakpoints.Xl : Object.values(dynamicBreakpoints)[index];
}, [props.width, dynamicBreakpoints]);
const noteButtonText = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return '';
} else if (breakpoint === dynamicBreakpoints.Md) {
return _('note');
} else {
return _('New note');
}
}, [breakpoint, dynamicBreakpoints]);
const todoButtonText = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return '';
} else if (breakpoint === dynamicBreakpoints.Md) {
return _('to-do');
} else {
return _('New to-do');
}
}, [breakpoint, dynamicBreakpoints]);
const noteIcon = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return 'icon-note';
} else {
return 'fas fa-plus';
}
}, [breakpoint, dynamicBreakpoints]);
const todoIcon = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return 'far fa-check-square';
} else {
return 'fas fa-plus';
}
}, [breakpoint, dynamicBreakpoints]);
const showTooltip = useMemo(() => {
if (breakpoint === dynamicBreakpoints.Sm) {
return true;
} else {
return false;
}
}, [breakpoint, dynamicBreakpoints.Sm]);
useEffect(() => {
if (breakpoint === dynamicBreakpoints.Xl) {
noteControlsRef.current.style.flexDirection = 'row';
searchAndSortRef.current.style.flex = '2 1 auto';
} else {
noteControlsRef.current.style.flexDirection = 'column';
}
}, [breakpoint, dynamicBreakpoints]);
useEffect(() => {
CommandService.instance().registerRuntime('focusSearch', focusSearchRuntime(searchBarRef));
@ -127,57 +241,55 @@ function NoteListControls(props: Props) {
if (!props.showNewNoteButtons) return null;
return (
<RowContainer>
<StyledButton
<TopRow className="new-note-todo-buttons">
<StyledButton ref={newNoteRef}
className="new-note-button"
tooltip={CommandService.instance().label('newNote')}
iconName="fas fa-plus"
title={_('%s', 'New note')}
tooltip={ showTooltip ? CommandService.instance().label('newNote') : '' }
iconName={noteIcon}
title={_('%s', noteButtonText)}
level={ButtonLevel.Primary}
size={ButtonSize.Small}
onClick={onNewNoteButtonClick}
/>
<StyledButton
<StyledButton ref={newTodoRef}
className="new-todo-button"
tooltip={CommandService.instance().label('newTodo')}
iconName="fas fa-plus"
title={_('%s', 'New to-do')}
tooltip={ showTooltip ? CommandService.instance().label('newTodo') : '' }
iconName={todoIcon}
title={_('%s', todoButtonText)}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onNewTodoButtonClick}
/>
</RowContainer>
</TopRow>
);
}
return (
<StyledRoot>
<StyledRoot ref={noteControlsRef}>
{renderNewNoteButtons()}
<RowContainer>
<BottomRow ref={searchAndSortRef} className="search-and-sort">
<SearchBar inputRef={searchBarRef}/>
<SortOrderButtonsContainer>
{showsSortOrderButtons() &&
<StyledPairButtonL
className="sort-order-field-button"
tooltip={sortOrderFieldTooltip()}
iconName={sortOrderFieldIcon()}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onSortOrderFieldButtonClick}
/>
}
{showsSortOrderButtons() &&
<StyledPairButtonR
className="sort-order-reverse-button"
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
iconName={sortOrderReverseIcon()}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onSortOrderReverseButtonClick}
/>
}
</SortOrderButtonsContainer>
</RowContainer>
{showsSortOrderButtons() &&
<SortOrderButtonsContainer>
<StyledPairButtonL
className="sort-order-field-button"
tooltip={sortOrderFieldTooltip()}
iconName={sortOrderFieldIcon()}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onSortOrderFieldButtonClick}
/>
<StyledPairButtonR
className="sort-order-reverse-button"
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
iconName={sortOrderReverseIcon()}
level={ButtonLevel.Secondary}
size={ButtonSize.Small}
onClick={onSortOrderReverseButtonClick}
/>
</SortOrderButtonsContainer>
}
</BottomRow>
</StyledRoot>
);
}

View File

@ -33,7 +33,7 @@ export default function NoteListWrapper(props: Props) {
return (
<StyledRoot>
<NoteListControls height={controlHeight}/>
<NoteListControls height={controlHeight} width={noteListSize.width}/>
<NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/>
</StyledRoot>
);