Skip to content

Commit

Permalink
fix popup block flow issue (Simon-Initiative#3545)
Browse files Browse the repository at this point in the history
replace bootstrap popup with react tiny popover impl
allow better control over audio content within a popup
  • Loading branch information
eliknebel committed May 18, 2023
1 parent 059036d commit eee4ba9
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@ export const HintsDelivery: React.FC<Props> = ({
className="d-flex align-items-center mb-2"
>
<span className="mr-2">{index + 1}.</span>
<HtmlContentModelRenderer
content={hint.content}
context={context}
style={{ width: '100%' }}
/>
<HtmlContentModelRenderer content={hint.content} context={context} />
</div>
))}
{hasMoreHints && (
Expand Down
52 changes: 0 additions & 52 deletions assets/src/components/common/Popup.tsx

This file was deleted.

100 changes: 100 additions & 0 deletions assets/src/components/content/Popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useCallback, useState } from 'react';
import { ArrowContainer, Popover } from 'react-tiny-popover';
import { Popup as PopupModel } from 'data/content/model/elements/types';
import { isEmptyContent } from '../../data/content/utils';
import { useAudio } from '../hooks/useAudio';

interface Props {
children: React.ReactNode;
popupContent: React.ReactNode;
popup: PopupModel;
}
export const Popup: React.FC<Props> = ({ children, popupContent, popup }) => {
const { audioPlayer, playAudio, isPlaying } = useAudio(popup.audioSrc);
const hasAudio = !!popup.audioSrc;

const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);

const pauseAudio = useCallback(() => {
// if the audio is already playing, pause it
if (isPlaying) playAudio();
}, [isPlaying, playAudio]);

const onHover = useCallback(
(e: React.MouseEvent) => popup.trigger === 'hover' && (setIsPopoverOpen(true), playAudio()),
[popup, setIsPopoverOpen, playAudio],
);
const onBlur = useCallback(
(e: React.MouseEvent) => popup.trigger === 'hover' && (setIsPopoverOpen(false), pauseAudio()),
[popup, setIsPopoverOpen, pauseAudio],
);
const onClick = useCallback(
(e: React.MouseEvent) => {
if (hasAudio) {
// Click behaves differently depending if the popup has audio or not to allow better control of audio playback.
// If the popup is hover triggered and has audio, then a click should control the audio.
// This allows a user to control the audio playback while still being able to see popup content
isPlaying ? pauseAudio() : playAudio();

if (popup.trigger !== 'hover' && hasAudio && !isPopoverOpen) {
// If the popup is triggered via click and has audio, then a click should open the popup.
// To dismiss the popup, the user must click outside the popup
setIsPopoverOpen(true);
}
} else {
// If the popup has no audio, then a click should simply toggle the popup
setIsPopoverOpen(!isPopoverOpen);
}

// prevent click from submitting a response when the popup is clicked inside a choice
// https://eliterate.atlassian.net/browse/MER-1503
e.preventDefault();
},
[popup.trigger, isPlaying, isPopoverOpen, hasAudio, setIsPopoverOpen, playAudio, pauseAudio],
);

return (
<Popover
isOpen={isPopoverOpen}
onClickOutside={() => (setIsPopoverOpen(false), pauseAudio())}
positions={['top', 'bottom', 'left', 'right']}
content={({ position, childRect, popoverRect }) => (
<ArrowContainer
position={position}
childRect={childRect}
popoverRect={popoverRect}
arrowSize={10}
arrowColor="white"
>
<div className="popup-content bg-body p-4 drop-shadow rounded">
{isEmptyContent(popup.content) ? (
<i className="fa-solid fa-volume-high"></i>
) : (
popupContent
)}
</div>
</ArrowContainer>
)}
containerClassName="react-tiny-popover structured-content"
>
<span
className={`popup-anchor${popup.trigger === 'hover' ? '' : ' popup-click'}`}
onMouseEnter={onHover}
onMouseLeave={onBlur}
onClick={onClick}
>
{children}
{hasAudio && (
<>
{isPlaying ? (
<i className="fa-solid fa-circle-pause ml-2"></i>
) : (
<i className="fa-solid fa-volume-high ml-2"></i>
)}
{audioPlayer}
</>
)}
</span>
</Popover>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ export const PopupEditor = (props: Props) => {
}
>
<span>
<span {...props.attributes} className="popup-anchor">
<span
{...props.attributes}
className={`popup-anchor${props.model.trigger === 'hover' ? '' : ' popup-click'}`}
>
<InlineChromiumBugfix />
{props.children}
<InlineChromiumBugfix />
Expand Down
2 changes: 1 addition & 1 deletion assets/src/data/content/writers/html.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ import {
MathJaxLatexFormula,
MathJaxMathMLFormula,
} from '../../../components/common/MathJaxFormula';
import { Popup } from '../../../components/common/Popup';
import { Pronunciation } from '../../../components/common/Pronunciation';
import { TableConjugation } from '../../../components/common/TableConjugation';
import { Popup } from '../../../components/content/Popup';
import { cellAttributes } from '../../../components/editing/elements/table/table-util';
import { VideoPlayer } from '../../../components/video_player/VideoPlayer';
import { WriterContext } from './context';
Expand Down
7 changes: 1 addition & 6 deletions assets/src/data/content/writers/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,10 @@ import { ContentWriter } from './writer';
interface Props {
content: RichText;
context: WriterContext;
style?: React.CSSProperties;
}
export const HtmlContentModelRenderer: React.FC<Props> = (props) => {
// Support content persisted when RichText had a `model` property.
const content = (props.content as any).model ? (props.content as any).model : props.content;

return (
<div style={props.style}>
{new ContentWriter().render(props.context, content, new HtmlParser())}
</div>
);
return <>{new ContentWriter().render(props.context, content, new HtmlParser())}</>;
};
13 changes: 5 additions & 8 deletions assets/styles/common/elements.scss
Original file line number Diff line number Diff line change
Expand Up @@ -459,20 +459,17 @@ $element-margin-bottom: 1.5em;
}

.popup-anchor {
color: var(--color-green-600);
color: var(--color-emerald-600);
font-style: italic;
cursor: pointer;
font-weight: bold;
}

.popup-click {
text-decoration: underline;
&.popup-click {
text-decoration: underline;
}
}

.popup-content {
&.show {
max-width: 100% !important;
}
p:last-of-type {
margin: 0;
}
Expand All @@ -481,7 +478,7 @@ $element-margin-bottom: 1.5em;
.popup-anchor,
span.term {
font-weight: bold;
color: #63a372;
color: var(--color-emerald-600);
font-style: italic;
}

Expand Down
30 changes: 20 additions & 10 deletions lib/oli/rendering/content/html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,21 @@ defmodule Oli.Rendering.Content.Html do
end

def ecl(%Context{} = context, _, attrs) do

attempt_guid = case context.resource_attempt do
nil -> ""
attempt -> attempt.attempt_guid
end
attempt_guid =
case context.resource_attempt do
nil -> ""
attempt -> attempt.attempt_guid
end

{:safe, ecl} =
ReactPhoenix.ClientSide.react_component("Components.ECLRepl",
%{"code" => attrs["code"], "id" => attrs["id"], "slug" => context.section_slug, "attemptGuid" => attempt_guid }
ReactPhoenix.ClientSide.react_component(
"Components.ECLRepl",
%{
"code" => attrs["code"],
"id" => attrs["id"],
"slug" => context.section_slug,
"attemptGuid" => attempt_guid
}
)

ecl
Expand Down Expand Up @@ -698,9 +704,13 @@ defmodule Oli.Rendering.Content.Html do

def popup(%Context{}, _next, element) do
{:safe, rendered} =
ReactPhoenix.ClientSide.react_component("Components.DeliveryElementRenderer", %{
"element" => element
})
ReactPhoenix.ClientSide.react_component(
"Components.DeliveryElementRenderer",
%{
"element" => element
},
html_element: "span"
)

rendered
end
Expand Down
41 changes: 25 additions & 16 deletions lib/oli_web/components/delivery/nav_sidebar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule OliWeb.Components.Delivery.NavSidebar do

def main_with_nav(assigns) do
~H"""
<main role="main" class="h-screen flex flex-col relative lg:flex-row z-0">
<main role="main" class="flex flex-col relative lg:flex-row z-0">
<.navbar {assigns} path_info={@conn.path_info} />
<div class="flex-1 flex flex-col lg:pl-[200px] z-10">
Expand Down Expand Up @@ -88,17 +88,19 @@ defmodule OliWeb.Components.Delivery.NavSidebar do
active: true
},
%{name: "Discussion", href: "#", active: false},
%{name: "Assignments", href: "#", active: false},

%{name: "Assignments", href: "#", active: false}
]
|> then(fn links ->
case section do
%Section{contains_explorations: true} ->
links ++ [%{
name: "Exploration",
href: "#",
active: false
}]
links ++
[
%{
name: "Exploration",
href: "#",
active: false
}
]

_ ->
links
Expand All @@ -109,7 +111,8 @@ defmodule OliWeb.Components.Delivery.NavSidebar do
defp get_preview_links(assigns) do
project = assigns[:project]

hierarchy = AuthoringResolver.full_hierarchy(project.slug)
hierarchy =
AuthoringResolver.full_hierarchy(project.slug)
|> translate_to_outline()

[
Expand All @@ -122,7 +125,12 @@ defmodule OliWeb.Components.Delivery.NavSidebar do
name: "Course Content",
popout: %{
component: "Components.CourseContentOutline",
props: %{hierarchy: hierarchy, sectionSlug: nil, projectSlug: project.slug, isPreview: true}
props: %{
hierarchy: hierarchy,
sectionSlug: nil,
projectSlug: project.slug,
isPreview: true
}
},
active: true
},
Expand Down Expand Up @@ -288,30 +296,31 @@ defmodule OliWeb.Components.Delivery.NavSidebar do
resource_type_id: ^container_id,
title: title,
slug: slug
}} ->
}
} ->
# container
%{
type: "container",
children: Enum.map(children, fn c -> translate_to_outline(c) end),
title: title,
id: id,
slug: slug,
slug: slug
}

%{
uuid: id,
revision: %Revision{
title: title,
slug: slug
}} ->
}
} ->
# page
%{
type: "page",
title: title,
id: id,
slug: slug,
slug: slug
}

end
end

end

0 comments on commit eee4ba9

Please sign in to comment.