mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
parent
9106fb82f3
commit
b561460307
@ -1,6 +1,6 @@
|
|||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useMemo, useState } from 'react';
|
||||||
import SearchBar from '../SearchBar/SearchBar';
|
import SearchBar from '../SearchBar/SearchBar';
|
||||||
import Button, { ButtonLevel, ButtonSize, buttonSizePx } from '../Button/Button';
|
import Button, { ButtonLevel, ButtonSize, buttonSizePx } from '../Button/Button';
|
||||||
import CommandService from '@joplin/lib/services/CommandService';
|
import CommandService from '@joplin/lib/services/CommandService';
|
||||||
@ -11,6 +11,13 @@ import { _ } from '@joplin/lib/locale';
|
|||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const styled = require('styled-components').default;
|
const styled = require('styled-components').default;
|
||||||
|
|
||||||
|
enum BaseBreakpoint {
|
||||||
|
Sm = 160,
|
||||||
|
Md = 190,
|
||||||
|
Lg = 40,
|
||||||
|
Xl = 474,
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
showNewNoteButtons: boolean;
|
showNewNoteButtons: boolean;
|
||||||
sortOrderButtonsVisible: boolean;
|
sortOrderButtonsVisible: boolean;
|
||||||
@ -18,6 +25,14 @@ interface Props {
|
|||||||
sortOrderReverse: boolean;
|
sortOrderReverse: boolean;
|
||||||
notesParentType: string;
|
notesParentType: string;
|
||||||
height: number;
|
height: number;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Breakpoints {
|
||||||
|
Sm: number;
|
||||||
|
Md: number;
|
||||||
|
Lg: number;
|
||||||
|
Xl: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledRoot = styled.div`
|
const StyledRoot = styled.div`
|
||||||
@ -34,7 +49,7 @@ const StyledButton = styled(Button)`
|
|||||||
width: auto;
|
width: auto;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
min-height: 26px;
|
min-height: 26px;
|
||||||
flex: 1 0 auto;
|
max-width: none;
|
||||||
|
|
||||||
.fa, .fas {
|
.fa, .fas {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@ -54,7 +69,13 @@ const StyledPairButtonR = styled(Button)`
|
|||||||
width: auto;
|
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;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
@ -68,7 +89,100 @@ const SortOrderButtonsContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
function NoteListControls(props: Props) {
|
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 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(() => {
|
useEffect(() => {
|
||||||
CommandService.instance().registerRuntime('focusSearch', focusSearchRuntime(searchBarRef));
|
CommandService.instance().registerRuntime('focusSearch', focusSearchRuntime(searchBarRef));
|
||||||
@ -127,36 +241,36 @@ function NoteListControls(props: Props) {
|
|||||||
if (!props.showNewNoteButtons) return null;
|
if (!props.showNewNoteButtons) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowContainer>
|
<TopRow className="new-note-todo-buttons">
|
||||||
<StyledButton
|
<StyledButton ref={newNoteRef}
|
||||||
className="new-note-button"
|
className="new-note-button"
|
||||||
tooltip={CommandService.instance().label('newNote')}
|
tooltip={ showTooltip ? CommandService.instance().label('newNote') : '' }
|
||||||
iconName="fas fa-plus"
|
iconName={noteIcon}
|
||||||
title={_('%s', 'New note')}
|
title={_('%s', noteButtonText)}
|
||||||
level={ButtonLevel.Primary}
|
level={ButtonLevel.Primary}
|
||||||
size={ButtonSize.Small}
|
size={ButtonSize.Small}
|
||||||
onClick={onNewNoteButtonClick}
|
onClick={onNewNoteButtonClick}
|
||||||
/>
|
/>
|
||||||
<StyledButton
|
<StyledButton ref={newTodoRef}
|
||||||
className="new-todo-button"
|
className="new-todo-button"
|
||||||
tooltip={CommandService.instance().label('newTodo')}
|
tooltip={ showTooltip ? CommandService.instance().label('newTodo') : '' }
|
||||||
iconName="fas fa-plus"
|
iconName={todoIcon}
|
||||||
title={_('%s', 'New to-do')}
|
title={_('%s', todoButtonText)}
|
||||||
level={ButtonLevel.Secondary}
|
level={ButtonLevel.Secondary}
|
||||||
size={ButtonSize.Small}
|
size={ButtonSize.Small}
|
||||||
onClick={onNewTodoButtonClick}
|
onClick={onNewTodoButtonClick}
|
||||||
/>
|
/>
|
||||||
</RowContainer>
|
</TopRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRoot>
|
<StyledRoot ref={noteControlsRef}>
|
||||||
{renderNewNoteButtons()}
|
{renderNewNoteButtons()}
|
||||||
<RowContainer>
|
<BottomRow ref={searchAndSortRef} className="search-and-sort">
|
||||||
<SearchBar inputRef={searchBarRef}/>
|
<SearchBar inputRef={searchBarRef}/>
|
||||||
<SortOrderButtonsContainer>
|
|
||||||
{showsSortOrderButtons() &&
|
{showsSortOrderButtons() &&
|
||||||
|
<SortOrderButtonsContainer>
|
||||||
<StyledPairButtonL
|
<StyledPairButtonL
|
||||||
className="sort-order-field-button"
|
className="sort-order-field-button"
|
||||||
tooltip={sortOrderFieldTooltip()}
|
tooltip={sortOrderFieldTooltip()}
|
||||||
@ -165,8 +279,6 @@ function NoteListControls(props: Props) {
|
|||||||
size={ButtonSize.Small}
|
size={ButtonSize.Small}
|
||||||
onClick={onSortOrderFieldButtonClick}
|
onClick={onSortOrderFieldButtonClick}
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
{showsSortOrderButtons() &&
|
|
||||||
<StyledPairButtonR
|
<StyledPairButtonR
|
||||||
className="sort-order-reverse-button"
|
className="sort-order-reverse-button"
|
||||||
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
|
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
|
||||||
@ -175,9 +287,9 @@ function NoteListControls(props: Props) {
|
|||||||
size={ButtonSize.Small}
|
size={ButtonSize.Small}
|
||||||
onClick={onSortOrderReverseButtonClick}
|
onClick={onSortOrderReverseButtonClick}
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
</SortOrderButtonsContainer>
|
</SortOrderButtonsContainer>
|
||||||
</RowContainer>
|
}
|
||||||
|
</BottomRow>
|
||||||
</StyledRoot>
|
</StyledRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ export default function NoteListWrapper(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRoot>
|
<StyledRoot>
|
||||||
<NoteListControls height={controlHeight}/>
|
<NoteListControls height={controlHeight} width={noteListSize.width}/>
|
||||||
<NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/>
|
<NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/>
|
||||||
</StyledRoot>
|
</StyledRoot>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user