Skip to content

Commit bf42a76

Browse files
Merge pull request #984 from DemocracyLab/iframes#978
Iframes#978
2 parents 17ab670 + 1c598ff commit bf42a76

File tree

21 files changed

+293
-27
lines changed

21 files changed

+293
-27
lines changed

civictechprojects/static/css/partials/_Profile.scss

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,38 @@
114114
}
115115
}
116116

117+
// for use with IframeGroupDisplay to make it full size in iframe
118+
.frame-full {
119+
margin: 0;
120+
width: 100%!important; // important to override the media queries
121+
}
122+
123+
.Profile-primary-container.frame-full .AboutGroup-card-container .row .ProjectCardContainer .row{
124+
margin: 0;
125+
row-gap: 1rem;
126+
column-gap: 1rem;
127+
justify-content: space-evenly;
128+
height: 100vh; // force the flex box to use the whole iframe height that it is in
129+
}
130+
131+
.Profile-primary-container.frame-full .AboutGroup-card-container .row .ProjectCardContainer .row .col-sm-12.col-lg-6 .ProjectCard-root {
132+
margin: 0;
133+
}
134+
135+
@include media-breakpoint-up(sm){
136+
// for the project cards burried in the iframe, give them a fixed width and let them fill the frame - unless we are on a really small viewport
137+
.Profile-primary-container.frame-full .AboutGroup-card-container .row .ProjectCardContainer .row .col-sm-12.col-lg-6 {
138+
flex: 0 0 26rem;
139+
}
140+
}
141+
142+
.override-breakpoint-max-width {
143+
max-width: 1392px;
144+
}
145+
146+
.override-breakpoint-width {
147+
width: 100%!important;
148+
}
117149

118150
@include media-breakpoint-up(lg) {
119151
.Profile-top-section-content {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from "react";
2+
import { isWithinIframe } from "../utils/iframe.js";
3+
4+
// This is a wrapper component that works with https://www.npmjs.com/package/iframe-resizer
5+
// to allow the components detect that they are within an iframe who's parent is running iframeresizer
6+
// so they can change styles and act differently
7+
8+
export default class IframeResizerInParent extends React.Component {
9+
// iframe-resizer will create window.iFrameResizer
10+
// this code adds additional properties to that
11+
12+
static inParent(){ // true if component is running in an iframe and the parent is running iframe-resizer
13+
return window.iFrameResizer?.inParent;
14+
}
15+
16+
static onInParent(cb){ // when iframe-resizer is started by the parent, execute the call back so the component can rerender or something
17+
// iframe-resizer will create window.iFrameResize when it runs, but it may not exist yet
18+
if(!window.iFrameResizer) window.iFrameResizer={};
19+
if(!window.iFrameResizer.onInParent) window.iFrameResizer.onInParent=[];
20+
window.iFrameResizer.onInParent.push(cb);
21+
}
22+
23+
static _detected(...args){
24+
// we are running in an iframe, and the parent has just started iframe-resizer
25+
if(!args[0].data.includes('[iFrameSizer]')) return;
26+
if(!window.iFrameResizer) window.iFrameResizer={};
27+
window.iFrameResizer.inParent=true;
28+
/*There could be multiple components needing to be re-rendered after iframe resizer starts, each will add a callback
29+
* to the array: onInParent.
30+
* Components may need to rerender after iframeresizer starts because their style needs to change if rendering
31+
* in an iframe that resizes v. rendering in an iframe that doesn't resize.
32+
*/
33+
let func;
34+
while((func=window.iFrameResizer.onInParent?.shift()))
35+
func();
36+
window.removeEventListener('message',IframeResizerInParent._detected);
37+
}
38+
39+
componentDidMount(){
40+
// if page is not running within an iframe, this component is just a pass through
41+
if (window && document && isWithinIframe()) {
42+
// we are running within an iframe
43+
// allow parent frame to determine background color
44+
document.getElementsByTagName('body')[0].style.backgroundColor='transparent';
45+
// be prepared to detect IframeResizer has been run in parent window
46+
window.addEventListener('message', IframeResizerInParent._detected);
47+
// add the child side (in the iframe) javascript code to this page -- it's only relevant if running within an iframe
48+
const script = document.createElement('script');
49+
const body = document.getElementsByTagName('body')[0];
50+
script.src = '/static/iframeResizer.contentWindow.min.js';
51+
body.appendChild(script);
52+
}
53+
}
54+
render(): Array<React$Node> {
55+
return this.props.children;
56+
}
57+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// @flow
2+
// derived from AboutGroupDisplay
3+
import React from "react";
4+
import { Container } from "flux/utils";
5+
import AboutGroupDisplay from "./AboutGroupDisplay.jsx"
6+
import type { GroupDetailsAPIData } from "../../utils/GroupAPIUtils.js";
7+
import ProjectCardsContainer from "../../componentsBySection/FindProjects/ProjectCardsContainer.jsx";
8+
9+
class IframeGroupDisplay extends AboutGroupDisplay<Props, State> {
10+
render(): React$Node {
11+
const group: GroupDetailsAPIData = this.state.group;
12+
return (
13+
<div className="Profile-primary-section col-12 col-lg-auto flex-lg-shrink-1">
14+
<div className="Profile-primary-container frame-full">
15+
{group.group_project_count > 0 && (
16+
<div className="AboutGroup-card-container">
17+
<div className="row">
18+
<ProjectCardsContainer
19+
showSearchControls={false}
20+
suppressHeader={true}
21+
staticHeaderText=""
22+
fullWidth={true}
23+
selectableCards={false}
24+
handleEmptyProject={() => {}}
25+
/>
26+
</div>
27+
</div>
28+
)}
29+
</div>
30+
</div>
31+
);
32+
}
33+
}
34+
35+
export default Container.create(IframeGroupDisplay, { pure: false });

common/components/common/projects/AboutProjectDisplay.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import Tabs from "react-bootstrap/Tabs";
3232
import Tab from "react-bootstrap/Tab";
3333
import Button from "react-bootstrap/Button";
3434
import AllowMarkdown from "../richtext/AllowMarkdown.jsx";
35+
import { isWithinIframe } from "../../utils/iframe.js";
3536

3637
type Props = {|
3738
project: ?ProjectDetailsAPIData,
@@ -108,8 +109,10 @@ class AboutProjectDisplay extends React.PureComponent<Props, State> {
108109

109110
render(): React$Node {
110111
const project = this.state.project;
112+
const widthModifier=isWithinIframe() ? ' override-breakpoint-max-width' : '';
111113
return (
112-
<div className="container Profile-root">
114+
<div className={"container Profile-root" + widthModifier }>
115+
{isWithinIframe() && <base target="_blank" />}
113116
{this._renderHeader(project)}
114117
<div className="row">
115118
<div className="Profile-top-section col-12">
@@ -189,8 +192,9 @@ class AboutProjectDisplay extends React.PureComponent<Props, State> {
189192

190193
// tabbed primary section content
191194
_renderPrimarySection(project) {
195+
const widthModifier=isWithinIframe() ? ' override-breakpoint-width' : '';
192196
return (
193-
<div className="Profile-primary-container">
197+
<div className={"Profile-primary-container" + widthModifier } >
194198
<Tabs defaultActiveKey="proj-details" id="AboutProject-tabs">
195199
<Tab
196200
eventKey="proj-details"

common/components/componentsBySection/FindProjects/ProjectCard.jsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import FavoriteToggle from "./FavoriteToggle.jsx";
1717
import CurrentUser from "../../utils/CurrentUser.js";
1818
import type { Dictionary } from "../../types/Generics.jsx";
1919
import JoinConferenceButton from "../../common/event_projects/JoinConferenceButton.jsx";
20+
import { isWithinIframe } from "../../utils/iframe";
21+
import IframeResizerInParent from "../../common/IframeResizerInParent.jsx";
2022

2123
type Props = {|
2224
project: ProjectData,
@@ -39,6 +41,7 @@ class ProjectCard extends React.PureComponent<Props, State> {
3941
}),
4042
showModal: false,
4143
};
44+
IframeResizerInParent.onInParent(this.forceUpdate.bind(this));
4245
}
4346

4447
onClickShowVideo(event: SyntheticMouseEvent): void {
@@ -53,11 +56,15 @@ class ProjectCard extends React.PureComponent<Props, State> {
5356
}
5457

5558
render(): React$Node {
56-
const url: string =
57-
this.props.project.cardUrl ||
58-
urlHelper.section(Section.AboutProject, {
59-
id: this.props.project.slug || this.props.project.id,
60-
});
59+
const url: string = (urlHelper.atSection(Section.IframeGroup) && IframeResizerInParent.inParent()) ?
60+
urlHelper.section(Section.IframeProject, {
61+
id: this.props.project.slug || this.props.project.id,
62+
}) :
63+
( this.props.project.cardUrl ||
64+
urlHelper.section(Section.AboutProject, {
65+
id: this.props.project.slug || this.props.project.id,
66+
})
67+
);
6168
return (
6269
<div className="ProjectCard-root">
6370
{this.props.project.video && (
@@ -68,7 +75,7 @@ class ProjectCard extends React.PureComponent<Props, State> {
6875
videoTitle={this.props.project.name}
6976
/>
7077
)}
71-
<a href={url} rel="noopener noreferrer">
78+
<a href={url} rel="noopener noreferrer" target={isWithinIframe() && !IframeResizerInParent.inParent() ? 'blank' : ''}>
7279
{this._renderLogo()}
7380
{this._renderSubInfo()}
7481
{this._renderTitleAndIssue()}

common/components/componentsBySection/FindProjects/ProjectCardsContainer.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Props = {|
2222
selectableCards: ?boolean,
2323
alreadySelectedProjects: ?List<string>, // todo: proper state management
2424
handleEmptyProject: ?Function,
25+
suppressHeader: ?boolean,
2526
|};
2627

2728
type State = {|
@@ -71,7 +72,7 @@ class ProjectCardsContainer extends React.Component<Props, State> {
7172
</React.Fragment>
7273
) : null}
7374
<div className="row">
74-
{!_.isEmpty(this.state.projects) && (
75+
{!_.isEmpty(this.state.projects) && !this.props.suppressHeader && (
7576
<h3 className="ProjectCardContainer-header">
7677
{this._renderCardHeaderText()}
7778
</h3>

common/components/controllers/AboutGroupController.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class AboutGroupController extends React.PureComponent<{||}, State> {
6060

6161
_renderLoading(): React$Node {
6262
return this.state.statusCode
63-
? this_.renderLoadErrorMessage()
63+
? this._renderLoadErrorMessage()
6464
: this._renderLoadingSpinner();
6565
}
6666

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @flow
2+
import React from "react";
3+
import AboutGroupController from "./AboutGroupController.jsx";
4+
import IframeGroupDisplay from "../common/groups/IframeGroupDisplay.jsx";
5+
6+
export default class IframeGroupController extends AboutGroupController<{||}, State> {
7+
_renderDetails(): React$Node {
8+
return (
9+
<React.Fragment>
10+
<IframeGroupDisplay group={this.state.group} viewOnly={false} />
11+
</React.Fragment>
12+
);
13+
}
14+
}
15+

common/components/controllers/MainController.jsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import SponsorFooter from "../chrome/SponsorFooter.jsx";
88
import SiteFooter from "../chrome/SiteFooter.jsx";
99
import url from "../../components/utils/url.js";
1010
import { loadHeap } from "../utils/heapApi.js";
11+
import IframeResizerInParent from "../common/IframeResizerInParent.jsx";
12+
import urlHelper from "../../components/utils/url.js";
13+
import Section from "../enums/Section.js";
1114

1215
type State = {|
1316
headerHeight: number,
@@ -22,6 +25,12 @@ class MainController extends React.Component<{||}, State> {
2225
headerHeight: 0,
2326
currentSection: null,
2427
};
28+
IframeResizerInParent.onInParent(()=>{
29+
//if we are running within a resizable iframe, let the height go auto rather than being the height defined by the parent - since we will be resizing
30+
const style = document.createElement("style");
31+
style.textContent = ".Profile-primary-container.frame-full .AboutGroup-card-container .row .ProjectCardContainer .row {height: auto!important;}";
32+
document.head.appendChild(style);
33+
})
2534
}
2635

2736
componentWillMount(): void {
@@ -49,18 +58,21 @@ class MainController extends React.Component<{||}, State> {
4958
}
5059

5160
render(): Array<React$Node> {
52-
return [
53-
<MainHeader
54-
key="main_header"
55-
onMainHeaderHeightChange={this._mainHeaderHeightChange.bind(this)}
56-
/>,
57-
<SectionController
58-
key="section_controller"
59-
headerHeight={this.state.headerHeight}
60-
/>,
61-
<SponsorFooter key="sponsor_footer" />,
62-
<SiteFooter key="site_footer" />,
63-
];
61+
const ShowHeadAndFoot=!(urlHelper.atSection(Section.IframeProject)||urlHelper.atSection(Section.IframeGroup));
62+
return (
63+
<IframeResizerInParent>
64+
{ShowHeadAndFoot && <MainHeader
65+
key="main_header"
66+
onMainHeaderHeightChange={this._mainHeaderHeightChange.bind(this)}
67+
/>}
68+
<SectionController
69+
key="section_controller"
70+
headerHeight={this.state.headerHeight}
71+
/>
72+
{ShowHeadAndFoot && <SponsorFooter key="sponsor_footer" />}
73+
{ShowHeadAndFoot && <SiteFooter key="site_footer" />}
74+
</IframeResizerInParent>
75+
)
6476
}
6577
}
6678

common/components/controllers/SectionController.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import MyGroupsController from "./MyGroupsController.jsx";
3030
import LiveEventController from "./LiveEventController.jsx";
3131
import AboutEventController from "./AboutEventController.jsx";
3232
import AboutGroupController from "./AboutGroupController.jsx";
33+
import IframeGroupController from "./IframeGroupController.jsx";
3334
import ErrorController from "./ErrorController.jsx";
3435
import FindGroupsController from "./FindGroupsController.jsx";
3536
import FindEventsController from "./FindEventsController.jsx";
@@ -71,6 +72,8 @@ class SectionController extends React.Component<{||}, State> {
7172

7273
_getController(): React$Node {
7374
switch (this.state.section) {
75+
case Section.IframeProject:
76+
return <AboutProjectController />
7477
case Section.AboutProject:
7578
return <AboutProjectController />;
7679
case Section.AboutUs:
@@ -114,6 +117,8 @@ class SectionController extends React.Component<{||}, State> {
114117
return <CreateEventController />;
115118
case Section.AboutGroup:
116119
return <AboutGroupController />;
120+
case Section.IframeGroup:
121+
return <IframeGroupController />;
117122
case Section.MyGroups:
118123
return <MyGroupsController />;
119124
case Section.MyEvents:

0 commit comments

Comments
 (0)