Skip to content

Commit 4863c62

Browse files
authored
Thumbnails (#363)
* Updated thumbnail paths * Changed embed url to download url * Added thumbnailUrl * Populating thumbnails * Fixed adding lesson thumbnails * Fixed expand * Removed temp logging * Added timeline rail * Added backgrounds to icons and border radius * Added mouse over indicators * Fixed spacing and font sizes * Header row updates * Removed box wrapper * Aligned times * Updated CPH
1 parent a899452 commit 4863c62

14 files changed

Lines changed: 1393 additions & 4144 deletions

package-lock.json

Lines changed: 1051 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"@churchapps/apphelper-login": "^0.6.15",
1111
"@churchapps/apphelper-markdown": "^0.6.15",
1212
"@churchapps/apphelper-website": "^0.6.17",
13-
"@churchapps/content-provider-helper": "^0.0.8",
13+
"@churchapps/content-provider-helper": "^0.0.9",
1414
"@churchapps/helpers": "^1.2.22",
1515
"@mui/icons-material": "^7.1.2",
1616
"@mui/material": "^7.1.2",

public/css/all.css

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -760,40 +760,122 @@ label .description {
760760
.planItemHeader {
761761
background-color: var(--bg-plan-header);
762762
border-bottom: 3px solid var(--c1l2);
763-
font-weight: bold;
764-
padding: 5px 5px 5px 10px;
763+
font-weight: 600;
764+
font-size: 0.9rem;
765+
padding: 10px 8px 10px 0;
765766
text-transform: uppercase;
767+
letter-spacing: 0.02em;
766768
}
767769

768770
.planItem {
769771
border-bottom: 1px solid var(--border-light);
770-
padding: 5px 5px 5px 10px;
771-
}
772-
773-
.planItem>div:first-of-type:not([style*="clear"]) {
774-
float: left;
775-
width: 100px
772+
padding: 10px 8px 10px 0;
773+
font-size: 0.95rem;
776774
}
777775

778776
.planItemDescription {
779777
font-style: italic;
780778
color: var(--text-muted);
781-
padding-left: 22px;
779+
line-height: 1.4;
782780
}
783781

784782
.planItemDescription p:first-child {
785783
margin-top: 0px;
786784
margin-bottom: 0px;
787785
}
788786

789-
.planItem .dragHandle, .planItemHeader .dragHandle {
787+
.planItem .dragHandle, .planItemHeader .dragHandle,
788+
.planItem .actionButton, .planItemHeader .actionButton {
790789
opacity: 0;
791790
transition: opacity 0.15s;
792791
}
793-
.planItem:hover .dragHandle, .planItemHeader:hover .dragHandle {
792+
.planItem:hover .dragHandle, .planItemHeader:hover .dragHandle,
793+
.planItem:hover .actionButton, .planItemHeader:hover .actionButton {
794794
opacity: 1;
795795
}
796796

797+
/* Clickable row hover */
798+
.clickableRow {
799+
transition: background-color 0.15s;
800+
}
801+
.clickableRow:hover {
802+
background-color: rgba(25, 118, 210, 0.08);
803+
}
804+
805+
/* Drag handle cursor */
806+
.dragHandle {
807+
cursor: grab;
808+
}
809+
.dragHandle:active {
810+
cursor: grabbing;
811+
}
812+
813+
/* Row controls (drag handle, edit/add buttons) hover */
814+
.rowControl, .actionButton {
815+
background: transparent;
816+
border-radius: 4px;
817+
padding: 2px;
818+
transition: background-color 0.15s, opacity 0.15s;
819+
}
820+
.rowControl:hover, .actionButton:hover {
821+
background-color: rgba(25, 118, 210, 0.15);
822+
}
823+
824+
/* Time Rail */
825+
.timeRailCell {
826+
width: 72px;
827+
flex-shrink: 0;
828+
position: relative;
829+
display: flex;
830+
align-items: center;
831+
justify-content: flex-end;
832+
padding-right: 20px;
833+
align-self: stretch;
834+
margin: -10px 0;
835+
padding-top: 10px;
836+
padding-bottom: 10px;
837+
background: var(--border-light);
838+
border-right: 1px solid var(--border-main);
839+
}
840+
841+
.timeRailLabel {
842+
font-family: 'Consolas', 'Monaco', monospace;
843+
font-size: 0.8rem;
844+
font-weight: 600;
845+
color: var(--text-muted);
846+
text-align: right;
847+
width: 100%;
848+
padding-right: 4px;
849+
}
850+
851+
.timeRailDot {
852+
position: absolute;
853+
right: 4px;
854+
width: 8px;
855+
height: 8px;
856+
border-radius: 50%;
857+
background: var(--border-main);
858+
border: 2px solid var(--border-light);
859+
z-index: 1;
860+
}
861+
862+
.timeRailLine {
863+
position: absolute;
864+
right: 7px;
865+
top: 0;
866+
bottom: 0;
867+
width: 2px;
868+
background: var(--border-main);
869+
}
870+
871+
.planItemHeader .timeRailCell {
872+
margin-bottom: -8px;
873+
padding-bottom: 8px;
874+
}
875+
876+
.planItemHeader .timeRailDot {
877+
background: var(--c1l2);
878+
}
797879

798880
.chordPro H3 {
799881
margin-bottom: 0px;

src/helpers/Interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export interface PlanItemInterface {
6767
providerId?: string;
6868
providerPath?: string;
6969
providerContentPath?: string;
70+
thumbnailUrl?: string;
7071

7172
children?: PlanItemInterface[];
7273
}

src/serving/components/ActionDialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ interface Props {
88
contentName?: string;
99
onClose: () => void;
1010
providerId?: string;
11-
/** Embed URL from provider - either iframe URL or direct media URL */
12-
embedUrl?: string;
11+
/** Download URL from provider - either iframe URL or direct media URL */
12+
downloadUrl?: string;
1313
/** Provider path for fetching content dynamically */
1414
providerPath?: string;
1515
/** Dot-notation path to specific content item */
@@ -27,7 +27,7 @@ export const ActionDialog: React.FC<Props> = (props) => {
2727
providerPath: props.providerPath,
2828
providerContentPath: props.providerContentPath,
2929
ministryId: props.ministryId,
30-
fallbackUrl: props.embedUrl
30+
fallbackUrl: props.downloadUrl
3131
});
3232

3333
useEffect(() => {

src/serving/components/ActionSelector.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ export const ActionSelector: React.FC<ActionSelectorProps> = ({ open, onClose, o
107107
const totalSeconds = section.children?.reduce((sum, action) => sum + (action.seconds || 0), 0) || 0;
108108
const path = mode === "browse" ? browser.currentPath : contentPath;
109109
const contentPathStr = generatePath(pathIndices);
110-
const embedUrl = section.embedUrl;
111-
onSelect(sectionId, sectionName, totalSeconds, provId, "providerSection", undefined, embedUrl, path, contentPathStr);
110+
const downloadUrl = section.downloadUrl;
111+
onSelect(sectionId, sectionName, totalSeconds, provId, "providerSection", section.thumbnail, downloadUrl, path, contentPathStr);
112112
onClose();
113113
}, [onSelect, onClose, mode, browser.currentPath, contentPath]);
114114

@@ -118,21 +118,26 @@ export const ActionSelector: React.FC<ActionSelectorProps> = ({ open, onClose, o
118118
const actionName = action.label || "Action";
119119
const path = mode === "browse" ? browser.currentPath : contentPath;
120120
const contentPathStr = generatePath(pathIndices);
121-
let embedUrl = action.embedUrl;
122-
if (!embedUrl && action.children && action.children.length > 0) {
123-
const childWithUrl = action.children.find(child => child.embedUrl);
124-
if (childWithUrl) embedUrl = childWithUrl.embedUrl;
121+
let downloadUrl = action.downloadUrl;
122+
if (!downloadUrl && action.children && action.children.length > 0) {
123+
const childWithUrl = action.children.find(child => child.downloadUrl);
124+
if (childWithUrl) downloadUrl = childWithUrl.downloadUrl;
125125
}
126-
onSelect(actionId, actionName, action.seconds, provId, "providerPresentation", undefined, embedUrl, path, contentPathStr);
126+
let thumbnail = action.thumbnail;
127+
if (!thumbnail && action.children && action.children.length > 0) {
128+
const childWithThumbnail = action.children.find((child: InstructionItem) => child.thumbnail);
129+
if (childWithThumbnail) thumbnail = childWithThumbnail.thumbnail;
130+
}
131+
onSelect(actionId, actionName, action.seconds, provId, "providerPresentation", thumbnail, downloadUrl, path, contentPathStr);
127132
onClose();
128133
}, [onSelect, onClose, mode, browser.currentPath, contentPath]);
129134

130135
// Handle adding a file
131136
const handleAddFile = useCallback((file: ContentFile, provId: string, pathIndices?: number[]) => {
132-
const embedUrl = file.embedUrl || file.url;
137+
const downloadUrl = file.downloadUrl || file.url;
133138
const path = mode === "browse" ? browser.currentPath : contentPath;
134139
const contentPathStr = pathIndices ? generatePath(pathIndices) : undefined;
135-
onSelect(file.id, file.title, file.seconds, provId, "providerFile", file.image, embedUrl, path, contentPathStr);
140+
onSelect(file.id, file.title, file.seconds, provId, "providerFile", file.thumbnail, downloadUrl, path, contentPathStr);
136141
onClose();
137142
}, [onSelect, onClose, mode, browser.currentPath, contentPath]);
138143

src/serving/components/BrowseGrid.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ export const BrowseGrid: React.FC<BrowseGridProps> = ({ folders, files = [], sel
3030
return (
3131
<Card key={`folder-${folder.id}`} sx={{ border: isSelected ? 2 : 1, borderColor: isSelected ? "primary.main" : "divider", bgcolor: isSelected ? "action.selected" : "background.paper" }}>
3232
<CardActionArea onClick={() => onFolderClick(folder)}>
33-
{folder.image ? (
34-
<CardMedia component="img" height="80" image={folder.image} alt={folder.title} sx={{ objectFit: "cover" }} />
33+
{folder.thumbnail ? (
34+
<CardMedia component="img" height="80" image={folder.thumbnail} alt={folder.title} sx={{ objectFit: "cover" }} />
3535
) : (
3636
<Box sx={{ height: 80, display: "flex", alignItems: "center", justifyContent: "center", bgcolor: isLeaf ? "primary.light" : "grey.200" }}>
3737
{isLeaf ? <PlayArrowIcon sx={{ fontSize: 40, color: "primary.contrastText" }} /> : <FolderIcon sx={{ fontSize: 40, color: "grey.500" }} />}
@@ -49,8 +49,8 @@ export const BrowseGrid: React.FC<BrowseGridProps> = ({ folders, files = [], sel
4949
{files.map((file, fileIndex) => (
5050
<Card key={`file-${file.id}`} sx={{ border: 1, borderColor: "divider" }}>
5151
<CardActionArea onClick={() => onFileClick?.(file, selectedProviderId, [0, fileIndex])}>
52-
{file.image ? (
53-
<CardMedia component="img" height="80" image={file.image} alt={file.title} sx={{ objectFit: "cover" }} />
52+
{file.thumbnail ? (
53+
<CardMedia component="img" height="80" image={file.thumbnail} alt={file.title} sx={{ objectFit: "cover" }} />
5454
) : (
5555
<Box sx={{ height: 80, display: "flex", alignItems: "center", justifyContent: "center", bgcolor: "secondary.light" }}>
5656
<AddIcon sx={{ fontSize: 40, color: "secondary.contrastText" }} />

src/serving/components/InstructionTree.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,18 @@ const InstructionItemRow: React.FC<{
2424
onAddAction: (action: InstructionItem, provId: string, pathIndices: number[]) => void;
2525
}> = ({ item, providerId, depth, pathIndices, expandedSections, onToggleExpanded, onAddSection, onAddAction }) => {
2626
const itemId = item.relatedId || item.id || "";
27-
const hasChildren = item.children && item.children.length > 0;
27+
// Filter out file type items - we only show sections and actions
28+
const visibleChildren = item.children?.filter(child => child.itemType !== 'file') || [];
29+
const hasChildren = visibleChildren.length > 0;
2830
const isExpanded = expandedSections.has(itemId);
2931
const isSection = item.itemType === 'section' || item.itemType === 'header';
3032

33+
// Get thumbnail from item or first file child (only for actions, not sections)
34+
const fileChild = item.children?.find(child => child.itemType === 'file');
35+
const thumbnail = !isSection
36+
? (item.thumbnail || fileChild?.thumbnail)
37+
: undefined;
38+
3139
// Items with children are expandable (sections, headers, or actions with files)
3240
if (hasChildren) {
3341
return (
@@ -46,6 +54,14 @@ const InstructionItemRow: React.FC<{
4654
<IconButton size="small" onClick={() => onToggleExpanded(itemId)} sx={{ mr: 1 }}>
4755
{isExpanded ? <ExpandMoreIcon /> : <ChevronRightIcon />}
4856
</IconButton>
57+
{thumbnail && (
58+
<Box
59+
component="img"
60+
src={thumbnail}
61+
alt=""
62+
sx={{ width: 40, height: 30, objectFit: "cover", borderRadius: 0.5, mr: 1.5 }}
63+
/>
64+
)}
4965
<Box sx={{ flex: 1 }}>
5066
<Typography sx={{ fontWeight: depth === 0 ? 500 : 400 }}>{item.label}</Typography>
5167
{item.description && (
@@ -69,7 +85,7 @@ const InstructionItemRow: React.FC<{
6985
</Box>
7086
{isExpanded && (
7187
<Box sx={{ pl: 4 }}>
72-
{item.children!.map((child, childIndex) => (
88+
{visibleChildren.map((child, childIndex) => (
7389
<InstructionItemRow
7490
key={child.relatedId || child.id || childIndex}
7591
item={child}
@@ -101,7 +117,16 @@ const InstructionItemRow: React.FC<{
101117
"&:hover": { bgcolor: "action.hover" }
102118
}}
103119
>
104-
<PlayArrowIcon sx={{ mr: 1, fontSize: 18, color: "primary.main" }} />
120+
{thumbnail ? (
121+
<Box
122+
component="img"
123+
src={thumbnail}
124+
alt=""
125+
sx={{ width: 40, height: 30, objectFit: "cover", borderRadius: 0.5, mr: 1.5 }}
126+
/>
127+
) : (
128+
<PlayArrowIcon sx={{ mr: 1, fontSize: 18, color: "primary.main" }} />
129+
)}
105130
<Box sx={{ flex: 1 }}>
106131
<Typography variant="body2">{item.label}</Typography>
107132
{item.description && (

src/serving/components/LessonDialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ interface Props {
3838
onExpandToActions?: () => void;
3939
// Provider-based section support
4040
providerId?: string;
41-
embedUrl?: string;
41+
downloadUrl?: string;
4242
/** Provider path for fetching content dynamically */
4343
providerPath?: string;
4444
/** Dot-notation path to specific content item */
@@ -57,7 +57,7 @@ export const LessonDialog: React.FC<Props> = (props) => {
5757
providerPath: props.providerPath,
5858
providerContentPath: props.providerContentPath,
5959
ministryId: props.ministryId,
60-
fallbackUrl: props.embedUrl
60+
fallbackUrl: props.downloadUrl
6161
});
6262

6363
useEffect(() => {
@@ -98,7 +98,7 @@ export const LessonDialog: React.FC<Props> = (props) => {
9898

9999
// If a child is selected, show its content
100100
if (selectedChild) {
101-
const childUrl = selectedChild.embedUrl;
101+
const childUrl = selectedChild.downloadUrl;
102102

103103
if (childUrl) {
104104
return (

0 commit comments

Comments
 (0)