mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 00:06:23 +00:00
feat: size the week all-day area from the days in view and fold it to three rows (bulwarkmail/webmail#1122)
This commit is contained in:
@@ -11,6 +11,7 @@ const SRC = join(__dirname, '..', '..');
|
|||||||
const TRANSLATED_FILES: Record<string, string[]> = {
|
const TRANSLATED_FILES: Record<string, string[]> = {
|
||||||
'screens/CalendarScreen.tsx': [],
|
'screens/CalendarScreen.tsx': [],
|
||||||
'components/calendar/AgendaView.tsx': [],
|
'components/calendar/AgendaView.tsx': [],
|
||||||
|
'components/calendar/AllDayToggle.tsx': [],
|
||||||
'components/calendar/EventBlock.tsx': [],
|
'components/calendar/EventBlock.tsx': [],
|
||||||
'components/calendar/EventCard.tsx': [],
|
'components/calendar/EventCard.tsx': [],
|
||||||
'components/calendar/MonthScrollView.tsx': [],
|
'components/calendar/MonthScrollView.tsx': [],
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Pressable, StyleSheet, Text } from 'react-native';
|
||||||
|
import { ChevronDown, ChevronUp } from '../icons';
|
||||||
|
import { typography } from '../../theme/tokens';
|
||||||
|
import { useColors } from '../../theme/colors';
|
||||||
|
import { useLocaleStore } from '../../stores/locale-store';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** Rows the collapsed area leaves out. */
|
||||||
|
hiddenRows: number;
|
||||||
|
expanded: boolean;
|
||||||
|
onToggle: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Under the "All day" label of the week and day grids: "+N" opens the rows
|
||||||
|
* past the first three, and closes them again (webmail calendar-week-view,
|
||||||
|
* #1122).
|
||||||
|
*/
|
||||||
|
export function AllDayToggle({ hiddenRows, expanded, onToggle }: Props) {
|
||||||
|
const c = useColors();
|
||||||
|
const t = useLocaleStore((s) => s.t);
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
onPress={onToggle}
|
||||||
|
hitSlop={8}
|
||||||
|
style={styles.button}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel={expanded ? t('calendar.events.show_less', 'Show less') : t('calendar.events.show_more', 'Show more')}
|
||||||
|
accessibilityState={{ expanded }}
|
||||||
|
>
|
||||||
|
{expanded ? null : <Text style={[styles.count, { color: c.textMuted }]}>+{hiddenRows}</Text>}
|
||||||
|
{expanded ? <ChevronUp size={12} color={c.textMuted} /> : <ChevronDown size={12} color={c.textMuted} />}
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
button: { flexDirection: 'row', alignItems: 'center', gap: 1, marginTop: 'auto', paddingVertical: 2 },
|
||||||
|
count: { ...typography.small },
|
||||||
|
});
|
||||||
@@ -37,7 +37,9 @@ import { displayNow, displayNowMinutes } from '../../lib/calendar-timezone';
|
|||||||
import { isInactiveEvent } from '../../lib/calendar-participants';
|
import { isInactiveEvent } from '../../lib/calendar-participants';
|
||||||
import { eventBlockColors } from '../../lib/event-colors';
|
import { eventBlockColors } from '../../lib/event-colors';
|
||||||
import {
|
import {
|
||||||
|
allDayRowsInView,
|
||||||
buildAllDaySegments,
|
buildAllDaySegments,
|
||||||
|
shownAllDayRows,
|
||||||
columnsPerScreen,
|
columnsPerScreen,
|
||||||
headerColumnRange,
|
headerColumnRange,
|
||||||
hourAtOffset,
|
hourAtOffset,
|
||||||
@@ -46,6 +48,7 @@ import {
|
|||||||
type TimeGridMode,
|
type TimeGridMode,
|
||||||
} from '../../lib/calendar-time-grid';
|
} from '../../lib/calendar-time-grid';
|
||||||
import { AllDayEventBar, TIME_LINE_MIN_MINUTES, TimedEventBlock } from './EventBlock';
|
import { AllDayEventBar, TIME_LINE_MIN_MINUTES, TimedEventBlock } from './EventBlock';
|
||||||
|
import { AllDayToggle } from './AllDayToggle';
|
||||||
|
|
||||||
const HOUR_HEIGHT = 48;
|
const HOUR_HEIGHT = 48;
|
||||||
const GRID_HEIGHT = 24 * HOUR_HEIGHT;
|
const GRID_HEIGHT = 24 * HOUR_HEIGHT;
|
||||||
@@ -174,16 +177,12 @@ function TimeGridScrollViewInner({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// All-day bars over the whole window (cut per week, or per day in the day
|
// All-day bars over the whole window (cut per week, or per day in the day
|
||||||
// view), so the strip keeps one height while scrolling.
|
// view). The area is sized from the columns in view, below.
|
||||||
const allDaySegments = React.useMemo<CalendarWeekSegment[]>(
|
const allDaySegments = React.useMemo<CalendarWeekSegment[]>(
|
||||||
() => buildAllDaySegments(index, days, perScreen),
|
() => buildAllDaySegments(index, days, perScreen),
|
||||||
[index, days, perScreen],
|
[index, days, perScreen],
|
||||||
);
|
);
|
||||||
const allDayRows = React.useMemo(
|
const [allDayExpanded, setAllDayExpanded] = React.useState(false);
|
||||||
() => allDaySegments.reduce((max, s) => Math.max(max, s.row + 1), 0),
|
|
||||||
[allDaySegments],
|
|
||||||
);
|
|
||||||
const allDayHeight = allDayRows > 0 ? allDayRows * (ALL_DAY_CHIP_HEIGHT + ALL_DAY_GAP) + spacing.xs : 0;
|
|
||||||
|
|
||||||
// The first column in view. Kept as a day so columns added at the start
|
// The first column in view. Kept as a day so columns added at the start
|
||||||
// don't move it.
|
// don't move it.
|
||||||
@@ -370,8 +369,13 @@ function TimeGridScrollViewInner({
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// The all-day area holds the rows of the days in view, three until it is
|
||||||
|
// expanded (webmail #1122, #1293).
|
||||||
|
const allDayRows = allDayRowsInView(allDaySegments, anchorIndex, anchorIndex + perScreen - 1);
|
||||||
|
const allDayShownRows = shownAllDayRows(allDayRows, allDayExpanded);
|
||||||
|
const allDayHeight = allDayShownRows > 0 ? allDayShownRows * (ALL_DAY_CHIP_HEIGHT + ALL_DAY_GAP) + spacing.xs : 0;
|
||||||
const visibleSegments = allDaySegments.filter(
|
const visibleSegments = allDaySegments.filter(
|
||||||
(s) => s.startIndex <= to && s.startIndex + s.span - 1 >= from,
|
(s) => s.startIndex <= to && s.startIndex + s.span - 1 >= from && s.row < allDayShownRows,
|
||||||
);
|
);
|
||||||
|
|
||||||
const threshold = perScreen === 1 ? 3 : 1;
|
const threshold = perScreen === 1 ? 3 : 1;
|
||||||
@@ -418,6 +422,13 @@ function TimeGridScrollViewInner({
|
|||||||
<Text style={styles.allDayLabel} numberOfLines={1} adjustsFontSizeToFit minimumFontScale={0.6}>
|
<Text style={styles.allDayLabel} numberOfLines={1} adjustsFontSizeToFit minimumFontScale={0.6}>
|
||||||
{t('calendar.events.all_day', 'All day')}
|
{t('calendar.events.all_day', 'All day')}
|
||||||
</Text>
|
</Text>
|
||||||
|
{allDayRows > shownAllDayRows(allDayRows, false) ? (
|
||||||
|
<AllDayToggle
|
||||||
|
hiddenRows={allDayRows - shownAllDayRows(allDayRows, false)}
|
||||||
|
expanded={allDayExpanded}
|
||||||
|
onToggle={() => setAllDayExpanded((v) => !v)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ import {
|
|||||||
getEventColor,
|
getEventColor,
|
||||||
isTimedEventFullDayOnDate,
|
isTimedEventFullDayOnDate,
|
||||||
layoutOverlappingEvents,
|
layoutOverlappingEvents,
|
||||||
packWeekSegments,
|
|
||||||
type CalendarWeekSegment,
|
type CalendarWeekSegment,
|
||||||
type EventDayIndex,
|
type EventDayIndex,
|
||||||
} from '../../lib/calendar-utils';
|
} from '../../lib/calendar-utils';
|
||||||
import { isInactiveEvent } from '../../lib/calendar-participants';
|
import { isInactiveEvent } from '../../lib/calendar-participants';
|
||||||
import { eventBlockColors } from '../../lib/event-colors';
|
import { eventBlockColors } from '../../lib/event-colors';
|
||||||
import { AllDayEventBar, TIME_LINE_MIN_MINUTES, TimedEventBlock } from './EventBlock';
|
import { AllDayEventBar, TIME_LINE_MIN_MINUTES, TimedEventBlock } from './EventBlock';
|
||||||
|
import { AllDayToggle } from './AllDayToggle';
|
||||||
|
import { packAllDaySegments, shownAllDayRows } from '../../lib/calendar-time-grid';
|
||||||
|
|
||||||
const HOUR_HEIGHT = 48;
|
const HOUR_HEIGHT = 48;
|
||||||
const GUTTER_WIDTH = 44;
|
const GUTTER_WIDTH = 44;
|
||||||
@@ -91,12 +92,16 @@ function WeekViewInner({
|
|||||||
events.filter((e) => !e.showWithoutTime),
|
events.filter((e) => !e.showWithoutTime),
|
||||||
weekDays,
|
weekDays,
|
||||||
);
|
);
|
||||||
return packWeekSegments([...explicit, ...timedFull]);
|
return packAllDaySegments([...explicit, ...timedFull]);
|
||||||
}, [events, weekDays]);
|
}, [events, weekDays]);
|
||||||
|
|
||||||
const allDayRowCount = React.useMemo(() => {
|
const allDayRows = React.useMemo(() => {
|
||||||
return allDaySegments.reduce((max, s) => Math.max(max, s.row + 1), 0);
|
return allDaySegments.reduce((max, s) => Math.max(max, s.row + 1), 0);
|
||||||
}, [allDaySegments]);
|
}, [allDaySegments]);
|
||||||
|
// Three rows until expanded (webmail #1122).
|
||||||
|
const [allDayExpanded, setAllDayExpanded] = React.useState(false);
|
||||||
|
const allDayRowCount = shownAllDayRows(allDayRows, allDayExpanded);
|
||||||
|
const allDayHiddenRows = allDayRows - shownAllDayRows(allDayRows, false);
|
||||||
|
|
||||||
const allDayStripHeight =
|
const allDayStripHeight =
|
||||||
allDayRowCount > 0
|
allDayRowCount > 0
|
||||||
@@ -181,6 +186,13 @@ function WeekViewInner({
|
|||||||
<View style={[styles.allDayStrip, { height: allDayStripHeight }]}>
|
<View style={[styles.allDayStrip, { height: allDayStripHeight }]}>
|
||||||
<View style={[styles.gutter, styles.allDayLabelWrap]}>
|
<View style={[styles.gutter, styles.allDayLabelWrap]}>
|
||||||
<Text style={styles.allDayLabel} numberOfLines={1} adjustsFontSizeToFit minimumFontScale={0.6}>{t('calendar.events.all_day', 'All day')}</Text>
|
<Text style={styles.allDayLabel} numberOfLines={1} adjustsFontSizeToFit minimumFontScale={0.6}>{t('calendar.events.all_day', 'All day')}</Text>
|
||||||
|
{allDayHiddenRows > 0 ? (
|
||||||
|
<AllDayToggle
|
||||||
|
hiddenRows={allDayHiddenRows}
|
||||||
|
expanded={allDayExpanded}
|
||||||
|
onToggle={() => setAllDayExpanded((v) => !v)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.allDayGrid}>
|
<View style={styles.allDayGrid}>
|
||||||
{/* Empty per-day columns to draw vertical separators */}
|
{/* Empty per-day columns to draw vertical separators */}
|
||||||
@@ -189,7 +201,7 @@ function WeekViewInner({
|
|||||||
))}
|
))}
|
||||||
{/* Segments overlay - multi-day events span their date range */}
|
{/* Segments overlay - multi-day events span their date range */}
|
||||||
<View style={styles.allDayOverlay} pointerEvents="box-none">
|
<View style={styles.allDayOverlay} pointerEvents="box-none">
|
||||||
{allDaySegments.map((segment) => {
|
{allDaySegments.filter((segment) => segment.row < allDayRowCount).map((segment) => {
|
||||||
const leftPct = (segment.startIndex / weekDays.length) * 100;
|
const leftPct = (segment.startIndex / weekDays.length) * 100;
|
||||||
const widthPct = (segment.span / weekDays.length) * 100;
|
const widthPct = (segment.span / weekDays.length) * 100;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import type { CalendarEvent } from '../../api/types';
|
||||||
|
import type { CalendarWeekSegment } from '../calendar-utils';
|
||||||
|
import {
|
||||||
|
ALL_DAY_COLLAPSED_ROWS,
|
||||||
|
allDayRowsInView,
|
||||||
|
packAllDaySegments,
|
||||||
|
shownAllDayRows,
|
||||||
|
} from '../calendar-time-grid';
|
||||||
|
|
||||||
|
// The week grids' all-day area was sized from every week of the scroll
|
||||||
|
// window, packed task chips in among the events, and grew without limit
|
||||||
|
// (file 10 "week all-day area (#1122)"). Webmail calendar-week-view.
|
||||||
|
|
||||||
|
const seg = (id: string, startIndex: number, span = 1, title = id): CalendarWeekSegment => ({
|
||||||
|
event: { id, title, start: '2026-11-02T00:00:00', showWithoutTime: true } as CalendarEvent,
|
||||||
|
startIndex,
|
||||||
|
span,
|
||||||
|
row: 0,
|
||||||
|
continuesBefore: false,
|
||||||
|
continuesAfter: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const rowsOf = (segments: CalendarWeekSegment[]) =>
|
||||||
|
Object.fromEntries(segments.map((s) => [s.event.id, s.row]));
|
||||||
|
|
||||||
|
describe('week all-day area', () => {
|
||||||
|
it("stacks each day's tasks under that day's events", () => {
|
||||||
|
// "a task" sorts before "b event" by title; packed together it took row 0.
|
||||||
|
const packed = packAllDaySegments([
|
||||||
|
seg('task:1', 1, 1, 'a task'),
|
||||||
|
seg('trip', 0, 3, 'b trip'),
|
||||||
|
seg('event', 1, 1, 'b event'),
|
||||||
|
seg('task:2', 1, 1, 'c task'),
|
||||||
|
seg('task:3', 4, 1, 'd task'),
|
||||||
|
]);
|
||||||
|
expect(rowsOf(packed)).toEqual({ trip: 0, event: 1, 'task:1': 2, 'task:2': 3, 'task:3': 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is sized from the days in view', () => {
|
||||||
|
const packed = packAllDaySegments([seg('a', 0), seg('b', 0), seg('c', 0), seg('d', 0), seg('e', 8)]);
|
||||||
|
expect(allDayRowsInView(packed, 0, 6)).toBe(4);
|
||||||
|
// The next week holds one event: no empty rows from the crowded day.
|
||||||
|
expect(allDayRowsInView(packed, 7, 13)).toBe(1);
|
||||||
|
expect(allDayRowsInView(packed, 14, 20)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows three rows until expanded', () => {
|
||||||
|
expect(ALL_DAY_COLLAPSED_ROWS).toBe(3);
|
||||||
|
expect(shownAllDayRows(5, false)).toBe(3);
|
||||||
|
expect(shownAllDayRows(5, true)).toBe(5);
|
||||||
|
expect(shownAllDayRows(2, false)).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// No RN renderer in the test env: check the grids' wiring.
|
||||||
|
it('is drawn that way by both week grids', () => {
|
||||||
|
const read = (rel: string) => fs.readFileSync(path.resolve(__dirname, '../../components/calendar', rel), 'utf8');
|
||||||
|
const scroll = read('TimeGridScrollView.tsx');
|
||||||
|
expect(scroll).toMatch(/allDayRowsInView\(allDaySegments, anchorIndex, anchorIndex \+ perScreen - 1\)/);
|
||||||
|
expect(scroll).toMatch(/s\.row < allDayShownRows/);
|
||||||
|
expect(scroll).toMatch(/<AllDayToggle/);
|
||||||
|
const week = read('WeekView.tsx');
|
||||||
|
expect(week).toMatch(/return packAllDaySegments\(\[\.\.\.explicit, \.\.\.timedFull\]\);/);
|
||||||
|
expect(week).toMatch(/allDaySegments\.filter\(\(segment\) => segment\.row < allDayRowCount\)/);
|
||||||
|
expect(week).toMatch(/<AllDayToggle/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -103,7 +103,65 @@ export function buildAllDaySegments(
|
|||||||
raw.push({ ...segment, startIndex: segment.startIndex + from });
|
raw.push({ ...segment, startIndex: segment.startIndex + from });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return packWeekSegments(raw);
|
return packAllDaySegments(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The id prefix CalendarScreen gives the due tasks it draws on the grid. */
|
||||||
|
export const TASK_CHIP_ID_PREFIX = 'task:';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packs the all-day bars into rows, with each day's task chips stacked under
|
||||||
|
* that day's all-day events rather than packed in among them (webmail
|
||||||
|
* calendar-week-view, #1107, #1270, #1122).
|
||||||
|
*/
|
||||||
|
export function packAllDaySegments(raw: CalendarWeekSegment[]): CalendarWeekSegment[] {
|
||||||
|
const isTask = (s: CalendarWeekSegment) => s.event.id.startsWith(TASK_CHIP_ID_PREFIX);
|
||||||
|
const events = packWeekSegments(raw.filter((s) => !isTask(s)));
|
||||||
|
const tasks = raw.filter(isTask).sort((a, b) =>
|
||||||
|
a.startIndex - b.startIndex || (a.event.title || '').localeCompare(b.event.title || ''));
|
||||||
|
if (tasks.length === 0) return events;
|
||||||
|
// Each day's next free row: under every event bar that covers it, then
|
||||||
|
// under the tasks already put there.
|
||||||
|
const nextRow = new Map<number, number>();
|
||||||
|
const rowAt = (index: number) => {
|
||||||
|
let row = nextRow.get(index);
|
||||||
|
if (row === undefined) {
|
||||||
|
row = events.reduce(
|
||||||
|
(max, s) => (index >= s.startIndex && index < s.startIndex + s.span ? Math.max(max, s.row + 1) : max),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
};
|
||||||
|
const stacked = tasks.map((task) => {
|
||||||
|
// A task covers one day; a longer one stacks under every day it covers.
|
||||||
|
let row = 0;
|
||||||
|
for (let i = task.startIndex; i < task.startIndex + task.span; i++) row = Math.max(row, rowAt(i));
|
||||||
|
for (let i = task.startIndex; i < task.startIndex + task.span; i++) nextRow.set(i, row + 1);
|
||||||
|
return { ...task, row };
|
||||||
|
});
|
||||||
|
return [...events, ...stacked];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rows the all-day area needs for the columns `first`..`last` in view: a
|
||||||
|
* crowded day elsewhere in the window adds no empty rows here (webmail
|
||||||
|
* #1293).
|
||||||
|
*/
|
||||||
|
export function allDayRowsInView(segments: CalendarWeekSegment[], first: number, last: number): number {
|
||||||
|
let rows = 0;
|
||||||
|
for (const s of segments) {
|
||||||
|
if (s.startIndex <= last && s.startIndex + s.span - 1 >= first) rows = Math.max(rows, s.row + 1);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The all-day area shows this many rows until it is expanded (webmail DEFAULT_ALL_DAY_MAX_ROWS). */
|
||||||
|
export const ALL_DAY_COLLAPSED_ROWS = 3;
|
||||||
|
|
||||||
|
/** Rows drawn: all of them when expanded or few enough, else the collapsed three. */
|
||||||
|
export function shownAllDayRows(rows: number, expanded: boolean): number {
|
||||||
|
return expanded ? rows : Math.min(rows, ALL_DAY_COLLAPSED_ROWS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The hour a long press at `offsetY` in a day column lands on. */
|
/** The hour a long press at `offsetY` in a day column lands on. */
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
useWindowDimensions,
|
useWindowDimensions,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { multiDayCount } from '../lib/calendar-time-grid';
|
import { TASK_CHIP_ID_PREFIX, multiDayCount } from '../lib/calendar-time-grid';
|
||||||
import {
|
import {
|
||||||
Calendar as CalendarIcon,
|
Calendar as CalendarIcon,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@@ -340,7 +340,7 @@ export default function CalendarScreen() {
|
|||||||
const allDay = !!task.showWithoutTime || dateOnly;
|
const allDay = !!task.showWithoutTime || dateOnly;
|
||||||
out.push({
|
out.push({
|
||||||
...task,
|
...task,
|
||||||
id: `task:${task.id}`,
|
id: `${TASK_CHIP_ID_PREFIX}${task.id}`,
|
||||||
start: dateOnly ? `${task.due}T00:00:00` : task.due,
|
start: dateOnly ? `${task.due}T00:00:00` : task.due,
|
||||||
showWithoutTime: allDay,
|
showWithoutTime: allDay,
|
||||||
duration: allDay ? 'P1D' : 'PT30M',
|
duration: allDay ? 'P1D' : 'PT30M',
|
||||||
@@ -623,8 +623,8 @@ export default function CalendarScreen() {
|
|||||||
|
|
||||||
// Task chips route to the tasks sheet; everything else opens the detail sheet.
|
// Task chips route to the tasks sheet; everything else opens the detail sheet.
|
||||||
const handleSelectEvent = React.useCallback((event: CalendarEvent) => {
|
const handleSelectEvent = React.useCallback((event: CalendarEvent) => {
|
||||||
if (event.id.startsWith('task:')) {
|
if (event.id.startsWith(TASK_CHIP_ID_PREFIX)) {
|
||||||
setTasksInitialId(event.id.slice('task:'.length));
|
setTasksInitialId(event.id.slice(TASK_CHIP_ID_PREFIX.length));
|
||||||
setTasksVisible(true);
|
setTasksVisible(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user