fix(ui): change notes sort and insertion

This commit is contained in:
sylenien 2022-10-04 14:44:41 +02:00
parent 6cb2197ce8
commit cc32eb0d68
11 changed files with 114 additions and 149 deletions

View file

@ -111,13 +111,7 @@ function WebPlayer(props) {
{showNoteModal ? ( {showNoteModal ? (
<ReadNote <ReadNote
userEmail={props.members.find(m => m.id === noteItem?.userId)?.email || ''} userEmail={props.members.find(m => m.id === noteItem?.userId)?.email || ''}
timestamp={noteItem?.timestamp} note={noteItem}
tags={noteItem?.tags}
isPublic={noteItem?.isPublic}
message={noteItem?.message}
sessionId={noteItem?.sessionId}
date={noteItem?.createdAt}
noteId={noteItem?.noteId}
onClose={onNoteClose} onClose={onNoteClose}
notFound={!noteItem} notFound={!noteItem}
/> />

View file

@ -8,6 +8,7 @@ import Event from './Event'
import stl from './eventGroupWrapper.module.css'; import stl from './eventGroupWrapper.module.css';
import NoteEvent from './NoteEvent'; import NoteEvent from './NoteEvent';
import { setEditNoteTooltip } from 'Duck/sessions'; import { setEditNoteTooltip } from 'Duck/sessions';
import { Note } from 'App/services/NotesService';
// TODO: incapsulate toggler in LocationEvent // TODO: incapsulate toggler in LocationEvent
@withToggle("showLoadInfo", "toggleLoadInfo") @withToggle("showLoadInfo", "toggleLoadInfo")
@ -74,13 +75,7 @@ class EventGroupWrapper extends React.Component {
{isNote ? ( {isNote ? (
<NoteEvent <NoteEvent
userEmail={this.props.members.find(m => m.id === event.userId)?.email || event.userId} userEmail={this.props.members.find(m => m.id === event.userId)?.email || event.userId}
timestamp={event.timestamp} note={event}
tags={event.tags}
isPublic={event.isPublic}
message={event.message}
sessionId={event.sessionId}
date={event.createdAt}
noteId={event.noteId}
filterOutNote={filterOutNote} filterOutNote={filterOutNote}
onEdit={this.props.setEditNoteTooltip} onEdit={this.props.setEditNoteTooltip}
noEdit={this.props.currentUserId !== event.userId} noEdit={this.props.currentUserId !== event.userId}

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Icon } from 'UI'; import { Icon } from 'UI';
import { tagProps, iTag } from 'App/services/NotesService'; import { tagProps, iTag, Note } from 'App/services/NotesService';
import { formatTimeOrDate } from 'App/date'; import { formatTimeOrDate } from 'App/date';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
@ -12,15 +12,9 @@ import { confirm } from 'UI';
import { filterOutNote as filterOutTimelineNote } from 'Player'; import { filterOutNote as filterOutTimelineNote } from 'Player';
interface Props { interface Props {
userEmail: number; note: Note;
timestamp: number;
tags: iTag[];
isPublic: boolean;
message: string;
sessionId: string;
date: string;
noteId: number;
noEdit: boolean; noEdit: boolean;
userEmail: string;
filterOutNote: (id: number) => void; filterOutNote: (id: number) => void;
onEdit: (noteTooltipObj: Record<string, any>) => void; onEdit: (noteTooltipObj: Record<string, any>) => void;
} }
@ -29,28 +23,28 @@ function NoteEvent(props: Props) {
const { settingsStore, notesStore } = useStore(); const { settingsStore, notesStore } = useStore();
const { timezone } = settingsStore.sessionSettings; const { timezone } = settingsStore.sessionSettings;
console.log(props.noEdit) console.log(props.noEdit);
const onEdit = () => { const onEdit = () => {
props.onEdit({ props.onEdit({
isVisible: true, isVisible: true,
isEdit: true, isEdit: true,
time: props.timestamp, time: props.note.timestamp,
note: { note: {
timestamp: props.timestamp, timestamp: props.note.timestamp,
tags: props.tags, tag: props.note.tag,
isPublic: props.isPublic, isPublic: props.note.isPublic,
message: props.message, message: props.note.message,
sessionId: props.sessionId, sessionId: props.note.sessionId,
noteId: props.noteId, noteId: props.note.noteId,
}, },
}); });
}; };
const onCopy = () => { const onCopy = () => {
copy( copy(
`${window.location.origin}/${window.location.pathname.split('/')[1]}${session(props.sessionId)}${ `${window.location.origin}/${window.location.pathname.split('/')[1]}${session(
props.timestamp > 0 ? '?jumpto=' + props.timestamp : '' props.note.sessionId
}` )}${props.note.timestamp > 0 ? '?jumpto=' + props.note.timestamp : ''}`
); );
toast.success('Note URL copied to clipboard'); toast.success('Note URL copied to clipboard');
}; };
@ -63,9 +57,9 @@ function NoteEvent(props: Props) {
confirmation: `Are you sure you want to delete this note?`, confirmation: `Are you sure you want to delete this note?`,
}) })
) { ) {
notesStore.deleteNote(props.noteId).then((r) => { notesStore.deleteNote(props.note.noteId).then((r) => {
props.filterOutNote(props.noteId); props.filterOutNote(props.note.noteId);
filterOutTimelineNote(props.noteId); filterOutTimelineNote(props.note.noteId);
toast.success('Note deleted'); toast.success('Note deleted');
}); });
} }
@ -87,35 +81,31 @@ function NoteEvent(props: Props) {
<div className="ml-2"> <div className="ml-2">
<div>{props.userEmail}</div> <div>{props.userEmail}</div>
<div className="text-disabled-text"> <div className="text-disabled-text">
{formatTimeOrDate(props.date as unknown as number, timezone)} {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)}
</div> </div>
</div> </div>
<div className="cursor-pointer ml-auto"> <div className="cursor-pointer ml-auto">
<ItemMenu bold items={menuItems} /> <ItemMenu bold items={menuItems} />
</div> </div>
</div> </div>
<div>{props.message}</div> <div>{props.note.message}</div>
<div> <div>
<div className="flex items-center gap-2 flex-wrap w-full"> <div className="flex items-center gap-2 flex-wrap w-full">
{props.tags.length ? ( {props.note.tag ? (
<div className="flex items-center gap-1"> <div
{props.tags.map((tag) => ( key={props.note.tag}
<div style={{
key={tag} background: tagProps[props.note.tag],
style={{ userSelect: 'none',
background: tagProps[tag], minWidth: 60,
userSelect: 'none', textAlign: 'center',
minWidth: 60, }}
textAlign: 'center', className="rounded-full text-sm px-2 py-1 text-white"
}} >
className="rounded-full text-sm px-2 py-1 text-white" {props.note.tag}
>
{tag}
</div>
))}
</div> </div>
) : null} ) : null}
{!props.isPublic ? null : ( {!props.note.isPublic ? null : (
<> <>
<Icon name="user-friends" className="ml-2 mr-1" color="gray-dark" /> Team <Icon name="user-friends" className="ml-2 mr-1" color="gray-dark" /> Team
</> </>

View file

@ -32,14 +32,14 @@ function CreateNote({
}: Props) { }: Props) {
const [text, setText] = React.useState(''); const [text, setText] = React.useState('');
const [isPublic, setPublic] = React.useState(false); const [isPublic, setPublic] = React.useState(false);
const [tags, setTags] = React.useState([]); const [tag, setTag] = React.useState<iTag>();
const [useTimestamp, setUseTs] = React.useState(true); const [useTimestamp, setUseTs] = React.useState(true);
const { notesStore } = useStore(); const { notesStore } = useStore();
React.useEffect(() => { React.useEffect(() => {
if (isEdit) { if (isEdit) {
setTags(editNote.tags); setTag(editNote.tag);
setText(editNote.message); setText(editNote.message);
setPublic(editNote.isPublic); setPublic(editNote.isPublic);
if (editNote.timestamp > 0) { if (editNote.timestamp > 0) {
@ -56,7 +56,7 @@ function CreateNote({
const onSubmit = () => { const onSubmit = () => {
const note: WriteNote = { const note: WriteNote = {
message: text, message: text,
tags, tag,
timestamp: useTimestamp ? (isEdit ? editNote.timestamp : time) : -1, timestamp: useTimestamp ? (isEdit ? editNote.timestamp : time) : -1,
isPublic, isPublic,
}; };
@ -76,7 +76,7 @@ function CreateNote({
.finally(() => { .finally(() => {
setCreateNoteTooltip({ isVisible: false, time: 0 }); setCreateNoteTooltip({ isVisible: false, time: 0 });
setText(''); setText('');
setTags([]); setTag(undefined);
}); });
} }
@ -96,7 +96,7 @@ function CreateNote({
.finally(() => { .finally(() => {
setCreateNoteTooltip({ isVisible: false, time: 0 }); setCreateNoteTooltip({ isVisible: false, time: 0 });
setText(''); setText('');
setTags([]); setTag(undefined);
}); });
}; };
@ -104,10 +104,10 @@ function CreateNote({
setCreateNoteTooltip({ isVisible: false, time: 0 }); setCreateNoteTooltip({ isVisible: false, time: 0 });
}; };
const tagActive = (tag: iTag) => tags.includes(tag); const tagActive = (noteTag: iTag) => tag === noteTag;
const addTag = (tag: iTag) => { const addTag = (tag: iTag) => {
setTags([tag]); setTag(tag);
}; };
return ( return (

View file

@ -1,21 +1,15 @@
import React from 'react'; import React from 'react';
import { Icon } from 'UI'; import { Icon } from 'UI';
import { tagProps, iTag } from 'App/services/NotesService'; import { tagProps, Note } from 'App/services/NotesService';
import { formatTimeOrDate } from 'App/date'; import { formatTimeOrDate } from 'App/date';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
interface Props { interface Props {
userEmail: number; userEmail: string;
timestamp: number; note: Note;
tags: iTag[];
isPublic: boolean;
message: string;
sessionId: string;
date: string;
noteId: number;
onClose: () => void;
notFound?: boolean; notFound?: boolean;
onClose: () => void;
} }
function ReadNote(props: Props) { function ReadNote(props: Props) {
@ -60,29 +54,25 @@ function ReadNote(props: Props) {
<div className="ml-2"> <div className="ml-2">
<div>{props.userEmail}</div> <div>{props.userEmail}</div>
<div className="text-disabled-text"> <div className="text-disabled-text">
{formatTimeOrDate(props.date as unknown as number, timezone)} {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)}
</div> </div>
</div> </div>
<div className="ml-auto cursor-pointer self-start" onClick={props.onClose}> <div className="ml-auto cursor-pointer self-start" onClick={props.onClose}>
<Icon name="close" size={18} /> <Icon name="close" size={18} />
</div> </div>
</div> </div>
<div>{props.message}</div> <div>{props.note.message}</div>
<div className="w-full"> <div className="w-full">
<div className="flex items-center gap-2 flex-wrap w-full"> <div className="flex items-center gap-2 flex-wrap w-full">
{props.tags.length ? ( {props.note.tag ? (
<div className="flex items-center gap-1">
{props.tags.map((tag) => (
<div <div
style={{ background: tagProps[tag], userSelect: 'none' }} style={{ background: tagProps[props.note.tag], userSelect: 'none' }}
className="rounded-xl text-sm px-2 py-1 text-white" className="rounded-xl text-sm px-2 py-1 text-white"
> >
{tag} {props.note.tag}
</div> </div>
))}
</div>
) : null} ) : null}
{!props.isPublic ? null : ( {!props.note.isPublic ? null : (
<> <>
<Icon name="user-friends" className="ml-2 mr-1" color="gray-dark" /> Team <Icon name="user-friends" className="ml-2 mr-1" color="gray-dark" /> Team
</> </>

View file

@ -12,7 +12,7 @@ type ValueObject = {
interface Props<Value extends ValueObject> { interface Props<Value extends ValueObject> {
options: Value[]; options: Value[];
isSearchable?: boolean; isSearchable?: boolean;
defaultValue?: string; defaultValue?: string | number;
plain?: boolean; plain?: boolean;
components?: any; components?: any;
styles?: any; styles?: any;
@ -103,7 +103,7 @@ export default function<Value extends ValueObject>({ placeholder='Select', name
singleValue: (provided: any, state: { isDisabled: any; }) => { singleValue: (provided: any, state: { isDisabled: any; }) => {
const opacity = state.isDisabled ? 0.5 : 1; const opacity = state.isDisabled ? 0.5 : 1;
const transition = 'opacity 300ms'; const transition = 'opacity 300ms';
return { ...provided, opacity, transition, fontWeight: '500' }; return { ...provided, opacity, transition, fontWeight: '500' };
}, },
input: (provided: any) => ({ input: (provided: any) => ({
@ -160,13 +160,13 @@ const DropdownIndicator = (
const CustomValueContainer = ({ children, ...rest }: any) => { const CustomValueContainer = ({ children, ...rest }: any) => {
const selectedCount = rest.getValue().length const selectedCount = rest.getValue().length
const conditional = (selectedCount < 3) const conditional = (selectedCount < 3)
let firstChild: any = [] let firstChild: any = []
if (!conditional) { if (!conditional) {
firstChild = [children[0].shift(), children[1]] firstChild = [children[0].shift(), children[1]]
} }
return ( return (
<ValueContainer {...rest}> <ValueContainer {...rest}>
{conditional ? children : firstChild} {conditional ? children : firstChild}

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Icon, Link } from 'UI'; import { Icon, Link } from 'UI';
import PlayLink from 'Shared/SessionItem/PlayLink'; import PlayLink from 'Shared/SessionItem/PlayLink';
import { tagProps, iTag } from 'App/services/NotesService'; import { tagProps, Note } from 'App/services/NotesService';
import { formatTimeOrDate } from 'App/date'; import { formatTimeOrDate } from 'App/date';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
@ -11,14 +11,7 @@ import { toast } from 'react-toastify';
import { session } from 'App/routes'; import { session } from 'App/routes';
interface Props { interface Props {
userId: number; note: Note;
timestamp: number;
tags: iTag[];
isPublic: boolean;
description: string;
sessionId: string;
date: string;
noteId: number;
userEmail: string; userEmail: string;
} }
@ -29,13 +22,13 @@ function NoteItem(props: Props) {
const onCopy = () => { const onCopy = () => {
copy( copy(
`${window.location.origin}/${window.location.pathname.split('/')[1]}${session( `${window.location.origin}/${window.location.pathname.split('/')[1]}${session(
props.sessionId props.note.sessionId
)}${props.timestamp > 0 ? '?jumpto=' + props.timestamp : ''}` )}${props.note.timestamp > 0 ? '?jumpto=' + props.note.timestamp : ''}`
); );
toast.success('Note URL copied to clipboard'); toast.success('Note URL copied to clipboard');
}; };
const onDelete = () => { const onDelete = () => {
notesStore.deleteNote(props.noteId).then((r) => { notesStore.deleteNote(props.note.noteId).then((r) => {
notesStore.fetchNotes(); notesStore.fetchNotes();
toast.success('Note deleted'); toast.success('Note deleted');
}); });
@ -49,32 +42,36 @@ function NoteItem(props: Props) {
className="flex items-center p-4 border-b" className="flex items-center p-4 border-b"
style={{ background: 'rgba(253, 243, 155, 0.1)' }} style={{ background: 'rgba(253, 243, 155, 0.1)' }}
> >
<Link style={{ width: '90%' }} to={session(props.sessionId)+(props.timestamp > 0 ? `?jumpto=${props.timestamp}&note=${props.noteId}` : '')}> <Link
style={{ width: '90%' }}
to={
session(props.note.sessionId) +
(props.note.timestamp > 0
? `?jumpto=${props.note.timestamp}&note=${props.note.noteId}`
: '')
}
>
<div className="flex flex-col gap-1 cursor-pointer"> <div className="flex flex-col gap-1 cursor-pointer">
<div>{props.description}</div> <div>{props.note.message}</div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{props.tags.length ? ( {props.note.tag ? (
<div className="flex items-center gap-1"> <div
{props.tags.map((tag) => ( style={{
<div background: tagProps[props.note.tag],
key={tag} userSelect: 'none',
style={{ minWidth: 60,
background: tagProps[tag], textAlign: 'center',
userSelect: 'none', }}
minWidth: 60, className="rounded-full px-2 py-1 mr-2 text-white"
textAlign: 'center', >
}} {props.note.tag}
className="rounded-full px-2 py-1 mr-2 text-white"
>
{tag}
</div>
))}
</div> </div>
) : null} ) : null}
<div className="text-disabled-text flex items-center"> <div className="text-disabled-text flex items-center">
<span className="text-figmaColors-text-primary mr-1">By </span> <span className="text-figmaColors-text-primary mr-1">By </span>
{props.userEmail}, {formatTimeOrDate(props.date as unknown as number, timezone)} {props.userEmail},{' '}
{!props.isPublic ? null : ( {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)}
{!props.note.isPublic ? null : (
<> <>
<Icon name="user-friends" className="ml-4 mr-1" color="gray-dark" /> Team <Icon name="user-friends" className="ml-4 mr-1" color="gray-dark" /> Team
</> </>
@ -84,13 +81,7 @@ function NoteItem(props: Props) {
</div> </div>
</Link> </Link>
<div className="ml-auto"> <div className="ml-auto">
<PlayLink <PlayLink isAssist={false} viewed={false} sessionId={props.note.sessionId} />
isAssist={false}
viewed={false}
sessionId={
props.sessionId
}
/>
</div> </div>
<div className="ml-2 cursor-pointer"> <div className="ml-2 cursor-pointer">
<ItemMenu bold items={menuItems} /> <ItemMenu bold items={menuItems} />

View file

@ -5,16 +5,16 @@ import NoteItem from './NoteItem';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
function NotesList({ members }: {members: Array<Record<string, any>>}) { function NotesList({ members }: { members: Array<Record<string, any>> }) {
const { notesStore } = useStore() const { notesStore } = useStore();
React.useEffect(() => { React.useEffect(() => {
if (!notesStore.notes.length) { if (!notesStore.notes.length) {
notesStore.fetchNotes() notesStore.fetchNotes();
} }
}, []) }, []);
const list = notesStore.notes const list = notesStore.notes;
return ( return (
<Loader loading={notesStore.loading}> <Loader loading={notesStore.loading}>
@ -28,18 +28,11 @@ function NotesList({ members }: {members: Array<Record<string, any>>}) {
} }
> >
<div className="border-b rounded bg-white"> <div className="border-b rounded bg-white">
{sliceListPerPage(list, notesStore.page - 1, notesStore.pageSize).map(note => ( {sliceListPerPage(list, notesStore.page - 1, notesStore.pageSize).map((note) => (
<React.Fragment key={note.noteId}> <React.Fragment key={note.noteId}>
<NoteItem <NoteItem
userId={note.userId} note={note}
tags={note.tags} userEmail={members.find((m) => m.id === note.userId)?.email || note.userId}
timestamp={note.timestamp}
isPublic={note.isPublic}
description={note.message}
date={note.createdAt}
noteId={note.noteId}
sessionId={note.sessionId}
userEmail={members.find(m => m.id === note.userId)?.email || note.userId}
/> />
</React.Fragment> </React.Fragment>
))} ))}
@ -47,7 +40,8 @@ function NotesList({ members }: {members: Array<Record<string, any>>}) {
<div className="w-full flex items-center justify-between py-4 px-6"> <div className="w-full flex items-center justify-between py-4 px-6">
<div className="text-disabled-text"> <div className="text-disabled-text">
Showing <span className="font-semibold">{Math.min(list.length, notesStore.pageSize)}</span> out Showing{' '}
<span className="font-semibold">{Math.min(list.length, notesStore.pageSize)}</span> out
of <span className="font-semibold">{list.length}</span> notes of <span className="font-semibold">{list.length}</span> notes
</div> </div>
<Pagination <Pagination

View file

@ -10,10 +10,10 @@ const sortOptionsMap = {
'createdAt-ASC': 'Oldest', 'createdAt-ASC': 'Oldest',
}; };
const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ value, label })); const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ value, label }));
const notesOwner = [{ value: '0', label: 'All Notes'},{ value: '1', label: 'My Notes'}]
function NoteTags() { function NoteTags() {
const { notesStore } = useStore() const { notesStore } = useStore()
const defaultOption = sortOptions[0].value;
return ( return (
<div className="flex items-center"> <div className="flex items-center">
@ -34,7 +34,9 @@ function NoteTags() {
</div> </div>
))} ))}
<div className="ml-2" /> <div className="ml-2" />
<Select name="sortSessions" plain right options={sortOptions} onChange={({ value }) => notesStore.toggleSort(value.value)} defaultValue={defaultOption} /> <Select name="sortNotes" plain right options={sortOptions} onChange={({ value }) => notesStore.toggleSort(value.value)} defaultValue={sortOptions[0].value} />
<div className="ml-2" />
<Select name="notesOwner" plain right options={notesOwner} onChange={({ value }) => notesStore.toggleShared(value.value === '1')} defaultValue={notesOwner[0].value} />
</div> </div>
); );
} }

View file

@ -15,6 +15,7 @@ export default class NotesStore {
activeTags: iTag[] = [] activeTags: iTag[] = []
sort = 'createdAt' sort = 'createdAt'
order: 'DESC' | 'ASC' = 'DESC' order: 'DESC' | 'ASC' = 'DESC'
ownOnly = false
constructor() { constructor() {
makeAutoObservable(this) makeAutoObservable(this)
@ -27,6 +28,8 @@ export default class NotesStore {
sort: this.sort, sort: this.sort,
order: this.order, order: this.order,
tags: this.activeTags, tags: this.activeTags,
mineOnly: this.ownOnly,
sharedOnly: false
} }
this.loading = true this.loading = true
@ -110,6 +113,11 @@ export default class NotesStore {
} }
} }
toggleShared(ownOnly: boolean) {
this.ownOnly = ownOnly
this.fetchNotes()
}
toggleSort(sort: string) { toggleSort(sort: string) {
const sortOrder = sort.split('-')[1] const sortOrder = sort.split('-')[1]
// @ts-ignore // @ts-ignore

View file

@ -6,7 +6,6 @@ export const tagProps = {
'ISSUE': '#CC0000', 'ISSUE': '#CC0000',
'TASK': '#7986CB', 'TASK': '#7986CB',
'OTHER': 'rgba(0, 0, 0, 0.26)', 'OTHER': 'rgba(0, 0, 0, 0.26)',
'ALL': ''
} }
export type iTag = keyof typeof tagProps | "ALL" export type iTag = keyof typeof tagProps | "ALL"
@ -15,7 +14,7 @@ export const TAGS = Object.keys(tagProps) as unknown as (keyof typeof tagProps)[
export interface WriteNote { export interface WriteNote {
message: string message: string
tags: string[] tag: iTag
isPublic: boolean isPublic: boolean
timestamp: number timestamp: number
noteId?: string noteId?: string
@ -30,7 +29,7 @@ export interface Note {
noteId: number noteId: number
projectId: number projectId: number
sessionId: string sessionId: string
tags: iTag[] tag: iTag
timestamp: number timestamp: number
userId: number userId: number
} }
@ -41,6 +40,8 @@ export interface NotesFilter {
sort: string sort: string
order: 'DESC' | 'ASC' order: 'DESC' | 'ASC'
tags: iTag[] tags: iTag[]
sharedOnly: boolean
mineOnly: boolean
} }
export default class NotesService { export default class NotesService {