change(ui): fix editing, sorting by backend etc

This commit is contained in:
sylenien 2022-09-30 17:37:09 +02:00 committed by Delirium
parent 84ec8f4d76
commit f3872b83a0
14 changed files with 452 additions and 225 deletions

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Loader } from 'UI';
import { Loader, Modal } from 'UI';
import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
import { fetchList } from 'Duck/integrations';
import { PlayerProvider, injectNotes, connectPlayer, init as initPlayer, clean as cleanPlayer, Controls } from 'Player';
@ -12,6 +12,8 @@ import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
import PlayerBlock from '../Session_/PlayerBlock';
import styles from '../Session_/session.module.css';
import { countDaysFrom } from 'App/date';
import ReadNote from '../Session_/Player/Controls/components/ReadNote';
import { fetchList as fetchMembers } from 'Duck/member';
const TABS = {
EVENTS: 'User Actions',
@ -63,15 +65,23 @@ function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) {
function WebPlayer(props) {
const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props;
const { notesStore } = useStore()
const [activeTab, setActiveTab] = useState('');
const [showNoteModal, setShowNote] = useState(false)
const [noteItem, setNoteItem] = useState(null)
useEffect(() => {
fetchList('issues');
initPlayer(session, jwt);
props.fetchMembers()
notesStore.fetchSessionNotes(session.sessionId).then(r => {
injectNotes(r)
const note = props.query.get('note');
if (note) {
Controls.pause()
setNoteItem(notesStore.getNoteById(parseInt(note, 10), r))
setShowNote(true)
}
})
const jumptTime = props.query.get('jumpto');
@ -91,11 +101,27 @@ function WebPlayer(props) {
[]
);
const onNoteClose = () => {setShowNote(false); Controls.togglePlay()}
return (
<PlayerProvider>
<InitLoader className="flex-1">
<PlayerBlockHeader activeTab={activeTab} setActiveTab={setActiveTab} tabs={TABS} fullscreen={fullscreen} />
<PlayerContentConnected activeTab={activeTab} fullscreen={fullscreen} live={live} setActiveTab={setActiveTab} session={session} />
<Modal open={showNoteModal} onClose={onNoteClose}>
{showNoteModal ? (
<ReadNote
userEmail={props.members.find(m => m.id === noteItem.userId)?.email || noteItem.userId}
timestamp={noteItem.timestamp}
tags={noteItem.tags}
isPublic={noteItem.isPublic}
message={noteItem.message}
sessionId={noteItem.sessionId}
date={noteItem.createdAt}
noteId={noteItem.noteId}
onClose={onNoteClose}
/>
) : null}
</Modal>
</InitLoader>
</PlayerProvider>
);
@ -107,10 +133,12 @@ export default connect(
jwt: state.get('jwt'),
fullscreen: state.getIn(['components', 'player', 'fullscreen']),
showEvents: state.get('showEvents'),
members: state.getIn(['members', 'list']),
}),
{
toggleFullscreen,
closeBottomBlock,
fetchList,
fetchMembers,
}
)(withLocationHandlers()(WebPlayer));

View file

@ -1,15 +1,17 @@
import React from 'react';
import cn from 'classnames';
import { connect } from 'react-redux'
import { TextEllipsis } from 'UI';
import withToggle from 'HOCs/withToggle';
import { TYPES } from 'Types/session/event';
import Event from './Event'
import stl from './eventGroupWrapper.module.css';
import NoteEvent from './NoteEvent';
import { setEditNoteTooltip } from 'Duck/sessions';
// TODO: incapsulate toggler in LocationEvent
@withToggle("showLoadInfo", "toggleLoadInfo")
@connect(state => ({members: state.getIn(['members', 'list'])}), { setEditNoteTooltip })
class EventGroupWrapper extends React.Component {
toggleLoadInfo = (e) => {
@ -69,7 +71,7 @@ class EventGroupWrapper extends React.Component {
}
{isNote ? (
<NoteEvent
userId={event.userId}
userEmail={this.props.members.find(m => m.id === event.userId)?.email || event.userId}
timestamp={event.timestamp}
tags={event.tags}
isPublic={event.isPublic}
@ -78,6 +80,7 @@ class EventGroupWrapper extends React.Component {
date={event.createdAt}
noteId={event.noteId}
filterOutNote={filterOutNote}
onEdit={this.props.setEditNoteTooltip}
/>
) : isLocation
? <Event

View file

@ -12,7 +12,7 @@ import { confirm } from 'UI';
import { filterOutNote as filterOutTimelineNote } from 'Player';
interface Props {
userId: number;
userEmail: number;
timestamp: number;
tags: iTag[];
isPublic: boolean;
@ -21,16 +21,35 @@ interface Props {
date: string;
noteId: number;
filterOutNote: (id: number) => void;
onEdit: (noteTooltipObj: Record<string, any>) => void;
}
function NoteEvent(props: Props) {
const { settingsStore, notesStore } = useStore();
const { timezone } = settingsStore.sessionSettings;
const onEdit = () => {};
const onEdit = () => {
props.onEdit({
isVisible: true,
isEdit: true,
time: props.timestamp,
note: {
timestamp: props.timestamp,
tags: props.tags,
isPublic: props.isPublic,
message: props.message,
sessionId: props.sessionId,
noteId: props.noteId
},
});
};
const onCopy = () => {
copy(`${window.location.origin}${session(props.sessionId)}${props.timestamp > 0 ? '?jumpto=' + props.timestamp : ''}`);
copy(
`${window.location.origin}${session(props.sessionId)}${
props.timestamp > 0 ? '?jumpto=' + props.timestamp : ''
}`
);
toast.success('Note URL copied to clipboard');
};
@ -44,7 +63,7 @@ function NoteEvent(props: Props) {
) {
notesStore.deleteNote(props.noteId).then((r) => {
props.filterOutNote(props.noteId);
filterOutTimelineNote(props.noteId)
filterOutTimelineNote(props.noteId);
toast.success('Note deleted');
});
}
@ -64,7 +83,7 @@ function NoteEvent(props: Props) {
<Icon name="quotes" color="main" />
</div>
<div className="ml-2">
<div>{props.userId}</div>
<div>{props.userEmail}</div>
<div className="text-disabled-text">
{formatTimeOrDate(props.date as unknown as number, timezone)}
</div>
@ -80,6 +99,7 @@ function NoteEvent(props: Props) {
<div className="flex items-center gap-1">
{props.tags.map((tag) => (
<div
key={tag}
style={{ background: tagProps[tag], userSelect: 'none' }}
className="rounded-xl text-sm px-2 py-1 text-white"
>

View file

@ -0,0 +1,202 @@
import React from 'react';
import { Icon, Button, Checkbox } from 'UI';
import { Duration } from 'luxon';
import { connect } from 'react-redux';
import { WriteNote, tagProps, TAGS, iTag, Note } from 'App/services/NotesService';
import { setCreateNoteTooltip, addNote, updateNote } from 'Duck/sessions';
import stl from './styles.module.css';
import { useStore } from 'App/mstore';
import { toast } from 'react-toastify';
import { injectNotes } from 'Player';
interface Props {
isVisible: boolean;
time: number;
setCreateNoteTooltip: (state: any) => void;
addNote: (note: Note) => void;
updateNote: (note: Note) => void;
sessionId: string;
isEdit: string;
editNote: WriteNote;
}
function CreateNote({
isVisible,
time,
setCreateNoteTooltip,
sessionId,
addNote,
isEdit,
editNote,
updateNote,
}: Props) {
const [text, setText] = React.useState('');
const [isPublic, setPublic] = React.useState(false);
const [tags, setTags] = React.useState([]);
const [useTimestamp, setUseTs] = React.useState(false);
const { notesStore } = useStore();
React.useEffect(() => {
if (isEdit) {
setTags(editNote.tags);
setText(editNote.message);
setPublic(editNote.isPublic);
if (editNote.timestamp > 0) {
setUseTs(true);
}
}
}, [isEdit]);
const duration = Duration.fromMillis(time).toFormat('mm:ss');
const stopEvents = (e: any) => {
e.stopPropagation();
};
const onSubmit = () => {
const note: WriteNote = {
message: text,
tags,
timestamp: useTimestamp ? (isEdit ? editNote.timestamp : time) : -1,
isPublic,
};
if (isEdit) {
return notesStore.updateNote(editNote.noteId, note).then((r) => {
toast.success('Note updated');
notesStore.fetchSessionNotes(sessionId).then((notes) => {
injectNotes(notes);
updateNote(r);
});
})
.catch((e) => {
toast.error('Error updating note');
console.error(e);
})
.finally(() => {
setCreateNoteTooltip({ isVisible: false, time: 0 });
setText('');
setTags([]);
});
}
return notesStore
.addNote(sessionId, note)
.then((r) => {
toast.success('Note added');
notesStore.fetchSessionNotes(sessionId).then((notes) => {
injectNotes(notes);
addNote(r);
});
})
.catch((e) => {
toast.error('Error adding note');
console.error(e);
})
.finally(() => {
setCreateNoteTooltip({ isVisible: false, time: 0 });
setText('');
setTags([]);
});
};
const closeTooltip = () => {
setCreateNoteTooltip({ isVisible: false, time: 0 });
};
const tagActive = (tag: iTag) => tags.includes(tag);
const removeTag = (tag: iTag) => {
setTags(tags.filter((t) => t !== tag));
};
const addTag = (tag: iTag) => {
setTags([...tags, tag]);
};
return (
<div
className={stl.noteTooltip}
style={{
top: -260,
width: 350,
left: 'calc(50% - 175px)',
display: isVisible ? 'flex' : 'none',
flexDirection: 'column',
gap: '1rem',
}}
onClick={stopEvents}
>
<div className="flex items-center bg-gray-lightest">
<Icon name="quotes" size={20} />
<h3 className="text-xl ml-2 mr-4 font-semibold">Add Note</h3>
<div className="flex items-center cursor-pointer" onClick={() => setUseTs(!useTimestamp)}>
<Checkbox checked={useTimestamp} />
<span className="ml-1">{`at ${duration}`}</span>
</div>
<div className="ml-auto cursor-pointer" onClick={closeTooltip}>
<Icon name="close" size={20} />
</div>
</div>
<div className="">
<textarea
name="message"
id="message"
placeholder="Note..."
rows={3}
value={text}
onChange={(e) => setText(e.target.value)}
style={{
border: 'solid thin #ddd',
borderRadius: 3,
resize: 'none',
background: '#ffff',
}}
/>
</div>
<div className="flex items-center">
{TAGS.map((tag) => (
<div
key={tag}
style={{
background: tagActive(tag) ? tagProps[tag] : 'rgba(0,0,0, 0.38)',
userSelect: 'none',
}}
className="cursor-pointer rounded-xl px-2 py-1 mr-2 text-white"
onClick={() => (tagActive(tag) ? removeTag(tag) : addTag(tag))}
>
{tag}
</div>
))}
</div>
<div className="flex">
<Button variant="primary" className="mr-4" onClick={onSubmit}>
Add Note
</Button>
<div className="flex items-center cursor-pointer" onClick={() => setPublic(!isPublic)}>
<Checkbox checked={isPublic} />
<Icon name="user-friends" size={16} className="mx-1" />
Visible to the team
</div>
</div>
</div>
);
}
export default connect(
(state) => {
const {
isVisible,
time = 0,
isEdit,
note: editNote,
// @ts-ignore
} = state.getIn(['sessions', 'createNoteTooltip']);
// @ts-ignore
const sessionId = state.getIn(['sessions', 'current', 'sessionId']);
return { isVisible, time, sessionId, isEdit, editNote };
},
{ setCreateNoteTooltip, addNote, updateNote }
)(CreateNote);

View file

@ -1,139 +0,0 @@
import React from 'react';
import { Icon, Button, Checkbox } from 'UI';
import { Duration } from 'luxon';
import { connect } from 'react-redux';
import { WriteNote, tagProps, TAGS, iTag, Note } from 'App/services/NotesService'
import { setNoteTooltip, addNote } from 'Duck/sessions';
import stl from './styles.module.css';
import { useStore } from 'App/mstore'
import { toast } from 'react-toastify';
import { injectNotes } from 'Player';
interface Props {
isVisible: boolean;
time: number;
setNoteTooltip: (state: any) => void
addNote: (note: Note) => void
sessionId: string
}
function NoteTooltip({ isVisible, time, setNoteTooltip, sessionId, addNote }: Props) {
const [text, setText] = React.useState('')
const [isPublic, setPublic] = React.useState(false)
const [tags, setTags] = React.useState([])
const [useTimestamp, setUseTs] = React.useState(false)
const { notesStore } = useStore();
const duration = Duration.fromMillis(time).toFormat('mm:ss');
const stopEvents = (e: any) => {
e.stopPropagation();
};
const onSubmit = () => {
const note: WriteNote = {
message: text,
tags,
timestamp: useTimestamp ? time : -1,
isPublic,
}
notesStore.addNote(sessionId, note).then(r => {
toast.success('Note added')
notesStore.fetchSessionNotes(sessionId).then(notes => {
injectNotes(notes)
addNote(r)
})
}).catch(e => {
toast.error('Error adding note')
console.error(e)
}).finally(() => {
setNoteTooltip({ isVisible: false, time: 0 })
setText('')
setTags([])
})
}
const closeTooltip = () => {
setNoteTooltip({ isVisible: false, time: 0 })
}
const tagActive = (tag: iTag) => tags.includes(tag)
const removeTag = (tag: iTag) => {
setTags(tags.filter(t => t !== tag))
}
const addTag = (tag: iTag) => {
setTags([...tags, tag])
}
return (
<div
className={stl.noteTooltip}
style={{
top: -260,
width: 350,
left: 'calc(50% - 175px)',
display: isVisible ? 'flex' : 'none',
flexDirection: 'column',
gap: '1rem',
}}
onClick={stopEvents}
>
<div className="flex items-center bg-gray-lightest">
<Icon name="quotes" size={20} />
<h3 className="text-xl ml-2 mr-4 font-semibold">Add Note</h3>
<div className="flex items-center cursor-pointer" onClick={() => setUseTs(!useTimestamp)}>
<Checkbox checked={useTimestamp} />
<span className="ml-1">{`at ${duration}`}</span>
</div>
<div className="ml-auto cursor-pointer" onClick={closeTooltip}>
<Icon name="close" size={20} />
</div>
</div>
<div className="">
<textarea
name="message"
id="message"
placeholder="Note..."
rows={3}
value={text}
onChange={(e) => setText(e.target.value)}
style={{ border: 'solid thin #ddd', borderRadius: 3, resize: 'none', background: '#ffff' }}
/>
</div>
<div className="flex items-center">
{TAGS.map((tag) => (
<div
key={tag}
style={{ background: tagActive(tag) ? tagProps[tag] : 'rgba(0,0,0, 0.38)', userSelect: 'none' }}
className="cursor-pointer rounded-xl px-2 py-1 mr-2 text-white"
onClick={() => tagActive(tag) ? removeTag(tag) : addTag(tag)}
>
{tag}
</div>
))}
</div>
<div className="flex">
<Button variant="primary" className="mr-4" onClick={onSubmit}>
Add Note
</Button>
<div className="flex items-center cursor-pointer" onClick={() => setPublic(!isPublic)}>
<Checkbox checked={isPublic} />
<Icon name="user-friends" size={16} className="mx-1" />
Visible to the team
</div>
</div>
</div>
);
}
export default connect((state) => {
// @ts-ignore
const { isVisible, time = 0 } = state.getIn(['sessions', 'noteTooltip']);
// @ts-ignore
const sessionId = state.getIn(['sessions', 'current', 'sessionId'])
return { isVisible, time, sessionId };
}, { setNoteTooltip, addNote })(NoteTooltip);

View file

@ -0,0 +1,71 @@
import React from 'react';
import { Icon } from 'UI';
import { tagProps, iTag } from 'App/services/NotesService';
import { formatTimeOrDate } from 'App/date';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
interface Props {
userEmail: number;
timestamp: number;
tags: iTag[];
isPublic: boolean;
message: string;
sessionId: string;
date: string;
noteId: number;
onClose: () => void;
}
function ReadNote(props: Props) {
const { settingsStore } = useStore();
const { timezone } = settingsStore.sessionSettings;
return (
<div style={{ position: 'absolute', top: '45%', left: 'calc(50% - 300px)' }}>
<div
className="flex items-start flex-col p-4 border gap-2"
style={{ background: '#FFFEF5', width: 600 }}
>
<div className="flex items-center w-full">
<div className="p-2 bg-gray-light rounded-full">
<Icon name="quotes" color="main" size={16} />
</div>
<div className="ml-2">
<div>{props.userEmail}</div>
<div className="text-disabled-text">
{formatTimeOrDate(props.date as unknown as number, timezone)}
</div>
</div>
<div className="ml-auto cursor-pointer" onClick={props.onClose}>
<Icon name="close" size={18} />
</div>
</div>
<div>{props.message}</div>
<div>
<div className="flex items-center gap-2 flex-wrap w-full">
{props.tags.length ? (
<div className="flex items-center gap-1">
{props.tags.map((tag) => (
<div
style={{ background: tagProps[tag], userSelect: 'none' }}
className="rounded-xl text-sm px-2 py-1 text-white"
>
{tag}
</div>
))}
</div>
) : null}
{!props.isPublic ? null : (
<>
<Icon name="user-friends" className="ml-2 mr-1" color="gray-dark" /> Team
</>
)}
</div>
</div>
</div>
</div>
);
}
export default observer(ReadNote);

View file

@ -1,6 +1,6 @@
import React from 'react'
import TimeTooltip from './TimeTooltip';
import NoteTooltip from './NoteTooltip';
import CreateNote from './CreateNote';
import store from 'App/store';
import { Provider } from 'react-redux';
@ -10,7 +10,7 @@ function TooltipContainer({ live }: { live: boolean }) {
<Provider store={store}>
<>
<TimeTooltip liveTimeTravel={live} />
<NoteTooltip />
<CreateNote />
</>
</Provider>
)

View file

@ -2,12 +2,12 @@ import React from 'react'
import { Icon } from 'UI'
import { connectPlayer, pause } from 'Player';
import { connect } from 'react-redux'
import { setNoteTooltip } from 'Duck/sessions';
import { setCreateNoteTooltip } from 'Duck/sessions';
function NotePopup({ setNoteTooltip, time }: { setNoteTooltip: (args: any) => void, time: number }) {
function NotePopup({ setCreateNoteTooltip, time }: { setCreateNoteTooltip: (args: any) => void, time: number }) {
const toggleNotePopup = () => {
pause();
setNoteTooltip({ time: time, isVisible: true })
setCreateNoteTooltip({ time: time, isVisible: true })
};
return (
@ -28,7 +28,7 @@ const NotePopupPl = connectPlayer(
)(React.memo(NotePopup));
const NotePopupComp = connect(
null, { setNoteTooltip }
null, { setCreateNoteTooltip }
)(NotePopupPl)
export default React.memo(NotePopupComp)

View file

@ -76,7 +76,7 @@ function NoteItem(props: Props) {
<PlayLink
isAssist={false}
viewed={false}
sessionId={props.sessionId + (props.timestamp > 0 ? '?jumpto=' + props.timestamp : '')}
sessionId={props.sessionId + (props.timestamp > 0 ? `?jumpto=${props.timestamp}&note=${props.noteId}` : '')}
/>
</div>
<div className="ml-2 cursor-pointer">

View file

@ -1,82 +1,65 @@
import React from 'react';
import { NoContent, Pagination, Icon } from 'UI';
import { NoContent, Pagination, Loader, Icon } from 'UI';
import { sliceListPerPage } from 'App/utils';
import NoteItem from './NoteItem';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import { Note } from 'App/services/NotesService';
function NotesList({ members }: {members: Array<Record<string, any>>}) {
const { notesStore } = useStore()
const [list, setList] = React.useState<Note[]>([])
React.useEffect(() => {
if (!notesStore.notes.length) {
notesStore.fetchNotes().then(notes => setList(notes))
notesStore.fetchNotes()
}
}, [])
React.useEffect(() => {
if (notesStore.notes.length) {
if (notesStore.activeTags.length) {
const tagsLen = notesStore.activeTags.length
const filteredList: Note[] = notesStore.notes.filter(note => {
for (let i = 0; i < tagsLen; i++) {
const tag = notesStore.activeTags[i]
if (note.tags.includes(tag)) {
return note
}
}
})
setList(filteredList)
} else {
setList(notesStore.notes)
}
}
}, [notesStore.activeTags])
const list = notesStore.notes
return (
<NoContent
show={list.length === 0}
title={
<div className="flex flex-col items-center justify-center">
<Icon name="no-metrics" size={80} color="figmaColors-accent-secondary" />
<div className="text-center text-gray-600 my-4">No notes yet</div>
<Loader loading={notesStore.loading}>
<NoContent
show={list.length === 0}
title={
<div className="flex flex-col items-center justify-center">
<Icon name="no-dashboard" size={80} color="figmaColors-accent-secondary" />
<div className="text-center text-gray-600 my-4">No notes yet</div>
</div>
}
>
<div className="mt-3 border-b rounded bg-white">
{sliceListPerPage(list, notesStore.page - 1, notesStore.pageSize).map(note => (
<React.Fragment key={note.noteId}>
<NoteItem
userId={note.userId}
tags={note.tags}
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>
))}
</div>
}
>
<div className="mt-3 border-b rounded bg-white">
{sliceListPerPage(list, notesStore.page - 1, notesStore.pageSize).map(note => (
<React.Fragment key={note.noteId}>
<NoteItem
userId={note.userId}
tags={note.tags}
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>
))}
</div>
<div className="w-full flex items-center justify-between py-4 px-6">
<div className="text-disabled-text">
Showing <span className="font-semibold">{Math.min(list.length, notesStore.pageSize)}</span> out
of <span className="font-semibold">{list.length}</span> notes
<div className="w-full flex items-center justify-between py-4 px-6">
<div className="text-disabled-text">
Showing <span className="font-semibold">{Math.min(list.length, notesStore.pageSize)}</span> out
of <span className="font-semibold">{list.length}</span> notes
</div>
<Pagination
page={notesStore.page}
totalPages={Math.ceil(list.length / notesStore.pageSize)}
onPageChange={(page) => notesStore.changePage(page)}
limit={notesStore.pageSize}
debounceRequest={100}
/>
</div>
<Pagination
page={notesStore.page}
totalPages={Math.ceil(list.length / notesStore.pageSize)}
onPageChange={(page) => notesStore.changePage(page)}
limit={notesStore.pageSize}
debounceRequest={100}
/>
</div>
</NoContent>
</NoContent>
</Loader>
);
}

View file

@ -1,11 +1,19 @@
import React from 'react';
import Select from 'Shared/Select';
import { TAGS, iTag } from 'App/services/NotesService';
import { TagItem } from '../SessionTags';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
const sortOptionsMap = {
'createdAt-DESC': 'Newest',
'createdAt-ASC': 'Oldest',
};
const sortOptions = Object.entries(sortOptionsMap).map(([value, label]) => ({ value, label }));
function NoteTags() {
const { notesStore } = useStore()
const defaultOption = sortOptions[0].value;
return (
<div className="flex items-center">
@ -18,6 +26,8 @@ function NoteTags() {
/>
</div>
))}
<Select name="sortSessions" plain right options={sortOptions} onChange={({ value }) => notesStore.toggleSort(value.value)} defaultValue={defaultOption} />
</div>
);
}

View file

@ -26,9 +26,12 @@ const TOGGLE_CHAT_WINDOW = 'sessions/TOGGLE_CHAT_WINDOW';
const SET_FUNNEL_PAGE_FLAG = 'sessions/SET_FUNNEL_PAGE_FLAG';
const SET_TIMELINE_POINTER = 'sessions/SET_TIMELINE_POINTER';
const SET_TIMELINE_HOVER_POINTER = 'sessions/SET_TIMELINE_HOVER_POINTER';
const SET_NOTE_TOOLTIP = 'sessions/SET_NOTE_TOOLTIP'
const SET_CREATE_NOTE_TOOLTIP = 'sessions/SET_CREATE_NOTE_TOOLTIP'
const SET_EDIT_NOTE_TOOLTIP = 'sessions/SET_CREATE_NOTE_TOOLTIP'
const FILTER_OUT_NOTE = 'sessions/FILTER_OUT_NOTE'
const ADD_NOTE = 'sessions/ADD_NOTE'
const UPDATE_NOTE = 'sessions/UPDATE_NOTE'
const SET_SESSION_PATH = 'sessions/SET_SESSION_PATH';
const LAST_PLAYED_SESSION_ID = `${name}/LAST_PLAYED_SESSION_ID`;
@ -67,7 +70,7 @@ const initialState = Map({
sessionPath: {},
lastPlayedSessionId: null,
timeLineTooltip: { time: 0, offset: 0, isVisible: false },
noteTooltip: { time: 0, isVisible: false },
createNoteTooltip: { time: 0, isVisible: false, isEdit: false, note: null },
});
const reducer = (state = initialState, action = {}) => {
@ -196,14 +199,15 @@ const reducer = (state = initialState, action = {}) => {
return state.set('timelinePointer', action.pointer);
case SET_TIMELINE_HOVER_POINTER:
return state.set('timeLineTooltip', action.timeLineTooltip);
case SET_NOTE_TOOLTIP:
return state.set('noteTooltip', action.noteTooltip);
case SET_CREATE_NOTE_TOOLTIP:
return state.set('createNoteTooltip', action.noteTooltip);
case SET_EDIT_NOTE_TOOLTIP:
return state.set('createNoteTooltip', action.noteTooltip);
case FILTER_OUT_NOTE:
return state.updateIn(['current', 'notesWithEvents'], (list) =>
list.filter(evt => !evt.noteId || evt.noteId !== action.noteId)
)
case ADD_NOTE:
console.log(action.note)
return state.updateIn(['current', 'notesWithEvents'], (list) =>
list.push(action.note).sort((a, b) => {
const aTs = a.time || a.timestamp
@ -212,6 +216,9 @@ const reducer = (state = initialState, action = {}) => {
return aTs - bTs
})
)
case UPDATE_NOTE:
const index = state.getIn(['current', 'notesWithEvents']).findIndex(item => item.noteId === action.note.noteId)
return state.setIn(['current', 'notesWithEvents', index], action.note)
case SET_SESSION_PATH:
return state.set('sessionPath', action.path);
case LAST_PLAYED_SESSION_ID:
@ -382,9 +389,16 @@ export function setTimelineHoverTime(timeLineTooltip) {
};
}
export function setNoteTooltip(noteTooltip) {
export function setCreateNoteTooltip(noteTooltip) {
return {
type: SET_NOTE_TOOLTIP,
type: SET_CREATE_NOTE_TOOLTIP,
noteTooltip
}
}
export function setEditNoteTooltip(noteTooltip) {
return {
type: SET_EDIT_NOTE_TOOLTIP,
noteTooltip
}
}
@ -403,6 +417,13 @@ export function addNote(note) {
}
}
export function updateNote(note) {
return {
type: UPDATE_NOTE,
note
}
}
export function setSessionPath(path) {
return {
type: SET_SESSION_PATH,

View file

@ -13,7 +13,7 @@ export default class NotesStore {
page = 1
pageSize = 15
activeTags: iTag[] = []
sort = ''
sort = 'createdAt'
order: 'DESC' | 'ASC' = 'DESC'
constructor() {
@ -23,7 +23,7 @@ export default class NotesStore {
async fetchNotes() {
const filter: NotesFilter = {
page: this.page,
limit: 15,
limit: this.pageSize,
sort: this.sort,
order: this.order,
tags: this.activeTags,
@ -78,6 +78,24 @@ export default class NotesStore {
}
}
async updateNote(noteId: string, note: WriteNote) {
this.loading = true
try {
const updated = await notesService.updateNote(noteId, note)
return updated
} catch (e) {
console.error(e)
} finally {
this.loading = false
}
}
getNoteById(noteId: number, notes?: Note[]) {
const notesSource = notes ? notes : this.notes
return notesSource.find(note => note.noteId === noteId)
}
changePage(page: number) {
this.page = page
}
@ -85,8 +103,18 @@ export default class NotesStore {
toggleTag(tag: iTag) {
if (this.activeTags.includes(tag)) {
this.activeTags = this.activeTags.filter(exTag => tag !== exTag)
this.fetchNotes()
} else {
this.activeTags = [...this.activeTags, tag]
this.fetchNotes()
}
}
toggleSort(sort: string) {
const sortOrder = sort.split('-')[1]
// @ts-ignore
this.order = sortOrder
this.fetchNotes()
}
}

View file

@ -85,7 +85,7 @@ export default class NotesService {
})
}
updateNote(noteID: string, note: Note): Promise<Note> {
updateNote(noteID: string, note: WriteNote): Promise<Note> {
return this.client.post(`/notes/${noteID}`, note)
.then(r => {
if (r.ok) {