Skip to content

Commit

Permalink
[FEATURE] Presentation from notes (#395)
Browse files Browse the repository at this point in the history
#395

* got basic markdown rendering working

* got basic markdown rendering working with node content

* Add Presenter component for Notes slides, UI Changes

* Remove multiple Reveal instances on dom, show Presenter on Overlay instead of route

* Vertical sections within slides

* Webembed added

* Add Slide section creator using ***

* Code block support basic added

* Add Presenter in Public Notes, Use query based flag for presentation

* Feature Flag Support

* changeset added

---------

Co-authored-by: Dinesh Singh <[email protected]>
  • Loading branch information
rpPanda and dineshsingh1 authored Mar 6, 2023
1 parent 375a4cd commit 9ce693a
Show file tree
Hide file tree
Showing 32 changed files with 897 additions and 30 deletions.
6 changes: 6 additions & 0 deletions .changeset/little-boxes-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'mexit': patch
'mexit-webapp': patch
---

Presentation from Notes!
3 changes: 2 additions & 1 deletion apps/extension/src/Editor/plugins/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import {
ELEMENT_PARAGRAPH,
insertNodes,
PlatePlugin,
setNodes} from '@udecode/plate'
setNodes
} from '@udecode/plate'

import { useAuth } from '@workduck-io/dwindle'

Expand Down
3 changes: 3 additions & 0 deletions apps/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"ky": "^0.32.2",
"lodash": "^4.17.21",
"lottie-react": "^2.2.1",
"markdown-to-jsx": "^7.1.9",
"match-sorter": "^6.3.1",
"md5": "2.3.0",
"mixpanel-browser": "^2.43.0",
Expand All @@ -74,6 +75,7 @@
"react-select": "^4.3.1",
"react-spring": "^9.4.3",
"react-use-websocket": "^4.2.0",
"reveal.js": "^4.4.0",
"semver": "^7.3.8",
"slate": "^0.78.0",
"slate-history": "^0.66.0",
Expand All @@ -98,6 +100,7 @@
"@types/react-modal": "^3.13.1",
"@types/react-router-dom": "^5.3.3",
"@types/react-select": "^4.0.17",
"@types/reveal.js": "^4.4.2",
"@types/styled-components": "^5.1.20",
"@vitejs/plugin-react-swc": "^3.0.1",
"typescript": "^4.7.4",
Expand Down
7 changes: 4 additions & 3 deletions apps/webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BrowserRouter as Router } from 'react-router-dom'

import { Provider, useThemeContext } from '@workduck-io/mex-themes'

import { compareVersions, mog, useAppStore } from '@mexit/core'
import { Notification } from '@mexit/shared'

import { version as packageJsonVersion } from '../package.json'
Expand All @@ -14,7 +15,6 @@ import Main from './Components/Main'
import Modals from './Components/Modals'
import { useForceLogout } from './Stores/useAuth'
import { useUserPreferenceStore } from './Stores/userPreferenceStore'
import { compareVersions, useVersionStore } from './Stores/useVersionStore'
import GlobalStyle from './Style/GlobalStyle'
import Switch from './Switch'

Expand Down Expand Up @@ -45,12 +45,13 @@ const Providers: React.FC<{ children: React.ReactNode }> = ({ children }) => {
}

const App = () => {
const setVersion = useVersionStore((store) => store.setVersion)
const setVersion = useAppStore((store) => store.setVersion)
const { forceLogout } = useForceLogout()

useEffect(() => {
async function forceLogoutAndSetVersion() {
const persistedVersion = useVersionStore.getState()?.version
const persistedVersion = useAppStore.getState()?.version
mog('PersistedVersion | PackageJSONVersion', { persistedVersion, packageJsonVersion })
setVersion(packageJsonVersion)
if (!(persistedVersion && compareVersions(persistedVersion, FORCE_LOGOUT_VERSION) >= 0)) {
await forceLogout()
Expand Down
38 changes: 29 additions & 9 deletions apps/webapp/src/Components/EditorInfobar/Metadata.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import React, { useEffect, useMemo, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { useLocation, useNavigate } from 'react-router-dom'

import timeLine from '@iconify-icons/ri/time-line'
import styled from 'styled-components'

import { IconButton, MexIcon } from '@workduck-io/mex-components'

import { NodeMetadata } from '@mexit/core'
import { DataGroup, DataWrapper, FlexBetween, MetadataWrapper, ProfileIcon, RelativeTime } from '@mexit/shared'
import { FeatureFlags, NodeMetadata } from '@mexit/core'
import {
DataGroup,
DataWrapper,
FlexBetween,
MetadataWrapper,
ProfileIcon,
RelativeTime,
useFeatureFlag
} from '@mexit/shared'

import { useMentions } from '../../Hooks/useMentions'
import { compareAccessLevel, usePermissions } from '../../Hooks/usePermissions'
Expand Down Expand Up @@ -37,6 +45,8 @@ interface MetadataProps {

const Metadata = ({ nodeId, namespaceId, fadeOnHover = true, publicMetadata }: MetadataProps) => {
const noteMetadata = useMetadataStore((state) => state.metadata.notes[nodeId])
const { isEnabled } = useFeatureFlag(FeatureFlags.PRESENTATION)

const location = useLocation()
const openShareModal = useShareModalStore((store) => store.openModal)
const [metadata, setMetadata] = useState<NodeMetadata | undefined>(publicMetadata)
Expand All @@ -53,6 +63,8 @@ const Metadata = ({ nodeId, namespaceId, fadeOnHover = true, publicMetadata }: M
return accessPriority !== 'MANAGE' && accessPriority !== 'OWNER'
}

const navigate = useNavigate()

const hideShareDetails = getIsSharedDisabled()

const isEmpty =
Expand Down Expand Up @@ -85,6 +97,11 @@ const Metadata = ({ nodeId, namespaceId, fadeOnHover = true, publicMetadata }: M
openShareModal('permission', 'note', nodeId)
}

const onPresentNoteClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
// * Show element in full screen mode
navigate({ search: '?present=true' }, { replace: true })
}

if (!publicMetadata && (noteMetadata === undefined || metadata === undefined || isEmpty)) return null

return (
Expand All @@ -110,12 +127,15 @@ const Metadata = ({ nodeId, namespaceId, fadeOnHover = true, publicMetadata }: M
</DataWrapper>
)}
</DataGroup>
{!publicMetadata && !hideShareDetails && (
<Data>
<AvatarGroups users={sharedUsers} limit={5} margin="0 1.5rem 0" />
<IconButton title="Share Note" icon={'ri:share-line'} onClick={onNoteShareClick} />
</Data>
)}
<Data>
{!publicMetadata && !hideShareDetails && (
<>
<AvatarGroups users={sharedUsers} limit={5} margin="0 1.5rem 0" />
<IconButton title="Share Note" icon={'ri:share-line'} onClick={onNoteShareClick} />
</>
)}
{isEnabled && <IconButton title="Present Note" icon={'bx:slideshow'} onClick={onPresentNoteClick} />}
</Data>
</FlexBetween>
</MetadataWrapper>
)
Expand Down
98 changes: 98 additions & 0 deletions apps/webapp/src/Components/Presenter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'reveal.js/dist/reveal.css'

import { useEffect, useRef, useState } from 'react'
import { useParams, useSearchParams } from 'react-router-dom'

import { usePlateEditorRef } from '@udecode/plate'
import Markdown from 'markdown-to-jsx'
import Reveal from 'reveal.js'

import { tinykeys } from '@workduck-io/tinykeys'

import { ELEMENT_PARAGRAPH, FeatureFlags, SECTION_SEPARATOR, SLIDE_SEPARATOR, useContentStore } from '@mexit/core'
import { FeatureFlag } from '@mexit/shared'

import parseToMarkdown from '../../Editor/utils'

import { PresenterContainer } from './styled'

const Presenter = () => {
const [markdown, setMarkdown] = useState('#Loading...')
const presenterRef = useRef<HTMLDivElement>(null)

const noteId = useParams().nodeId
// * Get query parameter present from url using react-router-dom v6
const [searchParams, setSearchParams] = useSearchParams()
const isPresenting = searchParams.get('present') === 'true'

const editor = usePlateEditorRef(noteId)

const goFullScreen = (element: any) => {
if (element.requestFullscreen) {
element.requestFullscreen()
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen()
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen()
}
}

const setMarkdownFromEditor = () => {
const content = editor?.children ?? useContentStore.getState().getContent(noteId)?.content
const markdownContent = parseToMarkdown({ children: content, type: ELEMENT_PARAGRAPH })
setMarkdown(markdownContent)
}

useEffect(() => {
if (isPresenting) {
Reveal.initialize().then(() => {
setMarkdownFromEditor()
})

const unsubscribe = tinykeys(window, {
Escape: (e) => {
setSearchParams()

// * If overview is open, close it
if (Reveal.isOverview()) {
Reveal.toggleOverview()
}
}
})

return () => {
unsubscribe()
setSearchParams()

// * Destroy reveal instance, as it creates multiple elements in the DOM
Reveal.destroy()
}
}
}, [isPresenting, noteId])

if (!markdown?.length) return

return (
<PresenterContainer $isPresenting={isPresenting} className="reveal" ref={presenterRef}>
<div className="slides">
{markdown?.split(SLIDE_SEPARATOR)?.map((slideContent, idx) => (
<section key={idx}>
{slideContent?.split(SECTION_SEPARATOR)?.map((sectionContent, idxN) => (
<section key={`${idx}_${idxN}`}>
<Markdown>{sectionContent}</Markdown>
</section>
))}
</section>
))}
</div>
</PresenterContainer>
)
}

const PresentationFeature = () => (
<FeatureFlag name={FeatureFlags.PRESENTATION}>
<Presenter />
</FeatureFlag>
)

export default PresentationFeature
Loading

0 comments on commit 9ce693a

Please sign in to comment.