From f3872b83a0c4528b95759d56348bf19513b12527 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 30 Sep 2022 17:37:09 +0200 Subject: [PATCH] change(ui): fix editing, sorting by backend etc --- frontend/app/components/Session/WebPlayer.js | 32 ++- .../Session_/EventsBlock/EventGroupWrapper.js | 7 +- .../Session_/EventsBlock/NoteEvent.tsx | 30 ++- .../Player/Controls/components/CreateNote.tsx | 202 ++++++++++++++++++ .../Controls/components/NoteTooltip.tsx | 139 ------------ .../Player/Controls/components/ReadNote.tsx | 71 ++++++ .../Controls/components/TooltipContainer.tsx | 4 +- .../Session_/components/NotePopup.tsx | 8 +- .../components/Notes/NoteItem.tsx | 2 +- .../components/Notes/NoteList.tsx | 103 ++++----- .../components/Notes/NoteTags.tsx | 10 + frontend/app/duck/sessions.js | 35 ++- frontend/app/mstore/notesStore.ts | 32 ++- frontend/app/services/NotesService.ts | 2 +- 14 files changed, 452 insertions(+), 225 deletions(-) create mode 100644 frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx delete mode 100644 frontend/app/components/Session_/Player/Controls/components/NoteTooltip.tsx create mode 100644 frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js index 6ef5bbba1..9ddf3c403 100644 --- a/frontend/app/components/Session/WebPlayer.js +++ b/frontend/app/components/Session/WebPlayer.js @@ -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 ( + + {showNoteModal ? ( + 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} + ); @@ -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)); diff --git a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js index 49694ca5f..6e7c83503 100644 --- a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js +++ b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js @@ -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 ? ( 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 ? void; + onEdit: (noteTooltipObj: Record) => 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) {
-
{props.userId}
+
{props.userEmail}
{formatTimeOrDate(props.date as unknown as number, timezone)}
@@ -80,6 +99,7 @@ function NoteEvent(props: Props) {
{props.tags.map((tag) => (
diff --git a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx new file mode 100644 index 000000000..7ae2b702d --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx @@ -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 ( +
+
+ +

Add Note

+
setUseTs(!useTimestamp)}> + + {`at ${duration}`} +
+ +
+ +
+
+ +
+