Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8074b8f
Merge pull request #554 from shift72/release-1.9.18
jacobtoye Jun 26, 2024
a54aa95
Merge pull request #558 from shift72/release-1.9.19
l0ud0gg Aug 13, 2024
a31a52a
Merge pull request #561 from shift72/release-1.9.20
l0ud0gg Nov 21, 2024
0aea26e
Merge pull request #567 from shift72/release-1.9.21
l0ud0gg Feb 25, 2025
c6f3b84
Merge pull request #569 from shift72/release-1.9.22
l0ud0gg Feb 26, 2025
ba7419d
Merge pull request #574 from shift72/release-1.9.23
l0ud0gg Apr 2, 2025
dce8e6e
Merge pull request #576 from shift72/release-1.9.24
l0ud0gg Apr 2, 2025
794d7e9
Switch from buble to babel
sam-shift72 Apr 14, 2025
f8ac4be
ok, the .jsx extension wasn't necessary for plugin-babel to do its thing
sam-shift72 Apr 14, 2025
ebe7fd8
Merge pull request #579 from shift72/release-1.9.25
l0ud0gg Apr 21, 2025
415eb75
Add detail player to film detail page
flyspraysandwich May 8, 2025
383d859
border 0
flyspraysandwich May 8, 2025
f5d2e8e
theatre mode toggle event change
flyspraysandwich May 9, 2025
5031697
theatre mode postmessage value
flyspraysandwich May 11, 2025
41cbac0
move detail player into core-template amongst other changes based on …
flyspraysandwich May 16, 2025
da0bbe1
css linting
flyspraysandwich May 18, 2025
73690db
pr fixes
flyspraysandwich May 19, 2025
2a6f7a2
.
flyspraysandwich May 20, 2025
8686878
changelog
flyspraysandwich May 20, 2025
b8ccc04
icons
flyspraysandwich Jun 3, 2025
8658aa0
Merge branch 'main' of ssh://github.com/shift72/core-template into cr…
flyspraysandwich Jun 3, 2025
e99c506
Merge branch 'switch-to-babel' of ssh://github.com/shift72/core-templ…
flyspraysandwich Jun 3, 2025
6acd384
babel and icon changes
flyspraysandwich Jun 3, 2025
f7726e5
endscreen navigate postmessage
flyspraysandwich Jun 16, 2025
3bca5c4
play button logic readability
flyspraysandwich Jun 16, 2025
01ab080
add title image to detail player
flyspraysandwich Jun 25, 2025
a120abd
pr fixes
flyspraysandwich Jul 1, 2025
2c10b9f
decided I didn't like the blur
flyspraysandwich Jul 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Added

- Bluesky support in footer links
- Video player on film detail page for content creators

### Changed

Expand Down
15,837 changes: 9,147 additions & 6,690 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
],
"author": "Shift72",
"dependencies": {
"@rollup/plugin-buble": "^0.21.3",
"autoprefixer": "^7.1.2",
"bootstrap": "^4.6.1",
"concat": "^1.0.3",
Expand All @@ -58,6 +57,9 @@
"sass": "^1.36.0"
},
"devDependencies": {
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@rollup/plugin-babel": "^6.0.4",
"eslint": "^7.30.0",
"eslint-config-preact": "^1.1.4",
"eslint-config-prettier": "^8.3.0",
Expand Down Expand Up @@ -147,5 +149,11 @@
"scss/at-extend-no-missing-placeholder": null,
"scss/at-import-no-partial-leading-underscore": null
}
}
},
"browserslist": [
"defaults, > 0.2% and not dead",
"chrome >= 58",
"ios >= 13",
"safari >= 13"
]
}
14 changes: 10 additions & 4 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import buble from '@rollup/plugin-buble';
import { terser } from "rollup-plugin-terser";
import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';

const production = process.env.NODE_ENV == 'production';

Expand All @@ -19,10 +19,16 @@ export default {
sourcemap: !production
},
plugins: [
buble({ jsx: 's72.ui.h', objectAssign: 'Object.assign' }),
babel({
babelHelpers: 'bundled',
presets: [
['@babel/preset-env'],
['@babel/preset-react', { pragma: 's72.ui.h' }]
],
}),
(production && terser())
],
watch: {
clearScreen: false
}
};
};
24 changes: 24 additions & 0 deletions site/static/js/detail-player/detail-player-iframe.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component } from 's72.ui';

export class DetailPlayerIframe extends Component {
constructor(props, context) {
super(props, context);
}

render(props) {
const {slug, onLoad} = props;
let startTime = new URLSearchParams(window.location.search).get('t');
let t = startTime ? startTime : 0;
return (
<iframe
id="detail-player"
src={`/play/#${slug}?tm=true&t=${t}&from=`}
width="100%"
height="100%"
frameborder={0}
allowFullScreen
onLoad={onLoad}
></iframe>
);
}
}
161 changes: 161 additions & 0 deletions site/static/js/detail-player/detail-player-placeholder.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { i18n } from 's72';
import { AppComponent, classes } from 's72.ui';
import { LockOpenIcon, LockClosedIcon, PlayIcon } from './icons';

export class DetailPlayerPlaceholder extends AppComponent {

constructor(props, context) {
super(props, context);
this.state = {
bgLoaded: !!!props.bgImage,
titleLoaded: !!!props.titleImage,
availabilityLoaded: false,
canBeWatched: false,
wigglin: false,
playClicked: false,
purchaseCompleted: false,
unlockBegin: false,
unlockAnim: 0,
}
}

async componentDidMount() {
this.app.messagebus.subscribe('shopping-session-completed', async () => {
if (await this.canBeWatched()) {
this.setState({ purchaseCompleted: true });
}
});
window.addEventListener('message', e => {
if (e.data.event == 's72-checkout:close' && this.state.purchaseCompleted) {
this.playUnlockAnimation();
}
});
this.setState({ availabilityLoaded: true, canBeWatched: await this.canBeWatched() });
}

componentWillUnmount() {
this.app.messagebus.unsubscribe('shopping-session-completed');
}

canBeWatched() {
return this.app.availabilityService.getAvailability(this.props.slug)
.then(a => a?.canBeWatched ?? false)
.catch(() => false);
}

render(props, state) {
const {playerLoaded} = props;
const showPlaceholder = (state.availabilityLoaded && state.bgLoaded && state.titleLoaded) && !playerLoaded;
return (
<div
class={classes('detail-player-placeholder', {'detail-player-placeholder--visible': showPlaceholder})}
onClick={() => this.playWiggleAnimation()}
>
{this.placeholderBG()}
<div class="detail-player-placeholder-overlay">
{state.canBeWatched && this.playButton() }
Copy link
Contributor

@l0ud0gg l0ud0gg Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We found a bug here when a site does not have the "disable_anonymous_watch_now" meta config set. It will display a play button for anonymous users and if clicked will open the sign in page within the detail player iframe. For now its prolly fine as we can ensure this config is a prereq for this feature, but might do some wierd things otherwise for other business models.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah there are a bunch of edge cases that still need to be sorted with this feature before it's ready for widespread deployment but I don't think they're blockers for merge.

{!state.canBeWatched && this.lockedMessage()}
<div class="detail-player-placeholder-gradient" />
{this.titleLogo()}
</div>
</div>
);
}

placeholderBG() {
return (
<ImageLoader
src={this.props.bgImage}
state={this.state.bgLoaded}
onLoad={() => this.setState({ bgLoaded: true })}
>
<div
class="detail-player-placeholder-bg"
style={`background-image: url(${this.props.bgImage})`}
/>
</ImageLoader>
);
}

titleLogo() {
return (
<ImageLoader
src={this.props.titleImage}
state={this.state.titleLoaded}
onLoad={() => this.setState({ titleLoaded: true })}
>
<img
class="detail-player-placeholder-title"
src={this.props.titleImage}
/>
</ImageLoader>
);
}

playWiggleAnimation() {
if (!this.state.wigglin && !this.state.unlockBegin) {
this.setState({ wigglin: true });
setTimeout(() => {
this.setState({ wigglin: false });
}, 900);
}
}

lockedMessage() {
return (
<div class={classes('detail-player-locked-message', {'detail-player-locked-message--unlocked': this.state.unlockBegin})}>
<div class={classes('detail-player-lock-icon-wrapper', {
'detail-player-lock-icon-wrapper--wiggle': this.state.wigglin,
'detail-player-lock-icon-wrapper--large': this.state.unlockBegin
})}>
<div class={`detail-player-lock-icon detail-player-lock-icon--anim-${this.state.unlockAnim}`}>
{this.state.unlockAnim < 2 ? <LockClosedIcon /> : <LockOpenIcon />}
</div>
</div>
<span class={classes('detail-player-locked-message-text', { 'detail-player-locked-message-text--unlocked': this.state.unlockBegin })}>
{i18n('detail_player_subscribe')}
</span>
</div>
);
}

playButton() {
return (
<div class={classes('detail-player-play-button', {'detail-player-play-button--clicked': this.state.playClicked})}
onClick={() => {
this.setState({ playClicked: true });
this.props.showPlayer();
}}
>
<PlayIcon />
</div>
);
}

playUnlockAnimation() {
setTimeout(() => { this.setState({ unlockBegin: true }) }, 500);
setTimeout(() => { this.setState({ unlockAnim: 1 }); }, 1000);
setTimeout(() => { this.setState({ unlockAnim: 2 }); }, 1800);
setTimeout(() => { this.setState({ unlockAnim: 3 }); }, 1900);
setTimeout(() => { this.props.showPlayer() }, 2000);
}

}

function ImageLoader(props) {
const {src, state, onLoad, children} = props;
if (!src) {
return;
}
return (
<div>
{!state && <img
class="detail-player-image-loader"
src={src}
onLoad={onLoad()}
onError={onLoad()}
/>}
{state && children}
</div>
);
}
54 changes: 54 additions & 0 deletions site/static/js/detail-player/detail-player.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { bindEachComponent, AppComponent, attrs, h, render } from 's72.ui';
import { DetailPlayerIframe } from './detail-player-iframe.component';
import { DetailPlayerPlaceholder } from './detail-player-placeholder.component';

export default class DetailPlayer extends AppComponent {

constructor(props, context) {
super(props, context);
this.state = {
playerLoaded: false,
playerVisible: false,
placeholderVisible: true,
};
}

componentDidMount() {
window.addEventListener('message', e => {
const {event, value} = e.data;
if (event == 's72-player:theatre-mode-change') {
const container = document.querySelector('#main .detail-player-container');
container.querySelector('detail-player').scrollIntoView({ behavior: 'smooth', block: 'start' });
container.classList.toggle('detail-player-theatre-mode', value);
document.querySelector('.meta-detail-bg').classList.toggle('meta-detail-bg--lights-out', value);
document.querySelector('.poster-wrapper').classList.toggle('d-none', value);
}
});
}

render(props, state) {
if (!props.slug) {
return;
}
return (
<div class="detail-player-inner">
{state.playerVisible && <DetailPlayerIframe
slug={props.slug}
onLoad={() => {
this.setState({ playerLoaded: true });
setTimeout(() => this.setState({ placeholderVisible: false }), 1000);
}}
/>}
{state.placeholderVisible && <DetailPlayerPlaceholder
slug={props.slug}
bgImage={props.bgImage}
titleImage={props.titleImage}
showPlayer={() => setTimeout(() => this.setState({ playerVisible: true }), 500)}
playerLoaded={state.playerLoaded}
/>}
</div>
);
}
}

bindEachComponent('detail-player', e => render(h(DetailPlayer, attrs(e)), e));
31 changes: 31 additions & 0 deletions site/static/js/detail-player/icons.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions site/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './modernizr-custom.js';
import './can-be-watched-button.component.js';
import './external-purchase-button.component.js';
import './carousel-video-mute-button.component.js';
import './detail-player/detail-player.component.js';

/*global Swiper, Modernizr, s72*/

Expand Down Expand Up @@ -567,3 +568,11 @@ document.addEventListener('s72loaded', event => {
let app = event.detail.app;
documentReady(app);
});

window.addEventListener('message', e => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be added to the componentDidMount of the detail player component instead? That would keep the code together.

Even if we were building a proper SPA we'd add this event listener when the detail player mounts, and then unsubscribe in componentDidUnmount, but our Relish components are all sloppy about that

const {event, url, type} = e.data;
if (event == 's72-player:navigate-to') {
console.log(`navigating to ${url}, reason: ${type}`);
window.location.href = `${url}`;
}
});
2 changes: 1 addition & 1 deletion site/styles/_collections.scss
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@
}

.caption {
padding: 0px 0 8px;
padding: 0 0 8px;

.crumb {
@extend .d-flex;
Expand Down
Loading