Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webengine projection #291

Merged
merged 11 commits into from
Aug 21, 2020
28 changes: 26 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import './css/main.scss';
// import react and js
import MediaPlayer from './js/MediaPlayer';
import NonMedia from './js/Templates/NonMedia/NonMedia'
import WebView from './js/Templates/WebView/WebView'
import LargeGraphicOnly from './js/Templates/LargeGraphicOnly/LargeGraphicOnly'
import LargeGraphicWithSoftbuttons from './js/Templates/LargeGraphicWithSoftbuttons/LargeGraphicWithSoftbuttons'
import GraphicWithTextButtons from './js/Templates/GraphicWithTextButtons/GraphicWithTextButtons'
Expand Down Expand Up @@ -78,14 +79,33 @@ class HMIApp extends React.Component {
{
this.props.webEngineApps.map((app) => {
let query = `?sdl-host=${flags.CoreHost}&sdl-port=${flags.CoreWebEngineAppPort}&sdl-transport-role=${app.transportType.toLowerCase()}-server`;
return (<WebEngineAppContainer key={app.policyAppID} policyAppID={app.policyAppID} iframeUrl={app.appUrl + app.entrypoint + query} />);
var style = { display: 'none' };
if (this.props.showWebView && app.runningAppId === this.props.activeAppId) {
style = app.style;
}
return (<WebEngineAppContainer key={app.policyAppID}
style={style}
policyAppID={app.policyAppID}
iframeUrl={app.appUrl + app.entrypoint + query} />);
})
}
</div>
)
}
componentDidMount() {
this.sdl.connectToSDL()
if (window.performance.memory) { // chromium --enable-precise-memory-info
this.memoryUsageInterval = setInterval(function(self) {
var mem = window.performance.memory;
if (mem.usedJSHeapSize / mem.jsHeapSizeLimit > 0.75) {
for (var app of self.props.webEngineApps) {
if (app.runningAppId && app.style.position === 'absolute') {
bcController.onExitApplication('RESOURCE_CONSTRAINT', app.runningAppId);
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the app is taking up too much memory, should the HMI also "delete" the iframe? Does that happen indirectly after the OnExitApplication message is sent?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This will indirectly remove the iframe after OnAppUnregistered is received by the HMI.

}
}
}
}, 10000, this);
}

FileSystemController.connect(flags.FileSystemApiUrl).then(() => {
console.log('Connected to FileSystemController');
Expand Down Expand Up @@ -119,13 +139,16 @@ class HMIApp extends React.Component {
}
componentWillUnmount() {
this.sdl.disconnectFromSDL()
clearInterval(this.memoryUsageInterval);
}
}

const mapStateToProps = (state) => {
return {
ptuWithModemEnabled: state.system.ptuWithModemEnabled,
webEngineApps: state.appStore.installedApps.filter(app => app.runningAppId)
webEngineApps: state.appStore.installedApps.filter(app => app.runningAppId),
showWebView: state.appStore.showWebView,
activeAppId: state.activeApp
}
}
HMIApp = connect(mapStateToProps)(HMIApp)
Expand All @@ -138,6 +161,7 @@ ReactDOM.render((
<Route path="/" exact component={HMIMenu} />
<Route path="/media" component={MediaPlayer} />
<Route path="/nonmedia" component={NonMedia} />
<Route path="/webview" component={WebView} />
<Route path="/large-graphic-only" component={LargeGraphicOnly} />
<Route path="/large-graphic-with-softbuttons" component={LargeGraphicWithSoftbuttons} />
<Route path="/graphic-with-text-buttons" component={GraphicWithTextButtons} />
Expand Down
1 change: 0 additions & 1 deletion src/js/AppHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ class AppHeader extends React.Component {
this.props.history.push("/" + nextProps.displayLayout)
}
}

else if(this.props.activeApp !== nextProps.activeApp) {
if(!this.props.activeApp && nextProps.activeApp) {
this.props.history.push("/" + nextProps.displayLayout)
Expand Down
16 changes: 15 additions & 1 deletion src/js/AppStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,25 @@ class AppStore extends React.Component {
return;
}

var style = {};
if (manifest.category === 'WEB_VIEW') {
style = {
position: 'absolute',
width: '960px',
Copy link
Collaborator

Choose a reason for hiding this comment

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

The size of the web view needs to take into account the dynamic height and width parameters set in the config and used by the scss files. It might be cleaner to define these styles in an scss file and pass a className within the js code instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good point, I applied these changes here: ab9eaef

height: '600px',
top: '75px',
left: '0px'
}
} else {
style.display = 'none';
}

store.dispatch(addAppPendingSetAppProperties(Object.assign(appDirEntry, {
policyAppID: manifest.appId,
version: manifest.appVersion,
entrypoint: manifest.entrypoint,
appUrl: params.appUrl
appUrl: params.appUrl,
style: style
}), true));

let addIfExists = (key) => {
Expand Down
14 changes: 11 additions & 3 deletions src/js/Controllers/BCController.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,21 @@ class BCController {
store.dispatch(deactivateApp(rpc.params.appID))
return true
case "OnAppRegistered":
store.dispatch(registerApplication(rpc.params.application.appID, rpc.params.application.isMediaApplication));
if (rpc.params.application.dayColorScheme || rpc.params.application.nightColorScheme) {
store.dispatch(updateColorScheme(
rpc.params.application.appID,
rpc.params.application.dayColorScheme ? rpc.params.application.dayColorScheme : null,
rpc.params.application.nightColorScheme ? rpc.params.application.nightColorScheme : null
));
}
var template = rpc.params.application.isMediaApplication ? "MEDIA" : "NON-MEDIA";
this.listener.send(RpcFactory.OnSystemCapabilityDisplay(template, rpc.params.application.appID));
if (rpc.params.application.appType.includes("WEB_VIEW")) {
store.dispatch(registerApplication(rpc.params.application.appID, "webview"));
this.listener.send(RpcFactory.OnSystemCapabilityDisplay("WEB_VIEW", rpc.params.application.appID));
} else {
var templates = rpc.params.application.isMediaApplication ? ["media","MEDIA"] : ["nonmedia","NON-MEDIA"];
store.dispatch(registerApplication(rpc.params.application.appID, templates[0]));
this.listener.send(RpcFactory.OnSystemCapabilityDisplay(templates[1], rpc.params.application.appID));
}
return null
case "OnAppUnregistered":
store.dispatch(deactivateApp(rpc.params.appID))
Expand Down Expand Up @@ -155,6 +160,9 @@ class BCController {
onIgnitionCycleOver() {
this.listener.send(RpcFactory.OnIgnitionCycleOverNotification())
}
onExitApplication(reason, appID) {
this.listener.send(RpcFactory.OnExitApplicationNotification(reason, appID))
}
onExitAllApplications(reason) {
this.listener.send(RpcFactory.OnExitAllApplicationsNotification(reason))
}
Expand Down
23 changes: 22 additions & 1 deletion src/js/Controllers/DisplayCapabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let imageOnlySoftButtonCapability = {
let templatesAvailable = [
"DEFAULT", "MEDIA", "NON-MEDIA", "LARGE_GRAPHIC_WITH_SOFTBUTTONS", "LARGE_GRAPHIC_ONLY",
"GRAPHIC_WITH_TEXTBUTTONS", "TEXTBUTTONS_WITH_GRAPHIC", "TEXTBUTTONS_ONLY",
"TEXT_WITH_GRAPHIC", "GRAPHIC_WITH_TEXT", "DOUBLE_GRAPHIC_WITH_SOFTBUTTONS"
"TEXT_WITH_GRAPHIC", "GRAPHIC_WITH_TEXT", "DOUBLE_GRAPHIC_WITH_SOFTBUTTONS", "WEB_VIEW"
]

let screenParams = {
Expand Down Expand Up @@ -256,6 +256,27 @@ let capabilities = {
softButtonCapability
]
},
"WEB_VIEW": {
"displayCapabilities": {
"displayType": "SDL_GENERIC",
"displayName": "GENERIC_DISPLAY",
"textFields": [
textField("templateTitle", 50),
textField("alertText1"),
textField("alertText2"),
textField("alertText3"),
],
"imageFields": [
imageField("appIcon", 50),
imageField("alertIcon", 225)
],
"mediaClockFormats": [],
"templatesAvailable": templatesAvailable,
"screenParams": screenParams,
"imageCapabilities": ["DYNAMIC", "STATIC"],
"menuLayoutsAvailable": ["LIST", "TILES"]
}
},
"LARGE_GRAPHIC_WITH_SOFTBUTTONS": {
"displayCapabilities": {
"displayType": "SDL_GENERIC",
Expand Down
10 changes: 10 additions & 0 deletions src/js/Controllers/RpcFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,16 @@ class RpcFactory {
'method': 'BasicCommunication.OnIgnitionCycleOver'
})
}
static OnExitApplicationNotification(reason, appID){
return ({
'jsonrpc': '2.0',
'method': 'BasicCommunication.OnExitApplication',
'params': {
'reason': reason,
'appID': appID
}
})
}
static OnExitAllApplicationsNotification(reason){
return ({
'jsonrpc': '2.0',
Expand Down
9 changes: 8 additions & 1 deletion src/js/Controllers/UIController.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import {
closeAlert,
setGlobalProperties,
deactivateInteraction,
showAppMenu
showAppMenu,
appStoreShowWebView,
appStoreHideWebView
} from '../actions'
import store from '../store'
import sdlController from './SDLController'
Expand Down Expand Up @@ -295,6 +297,11 @@ class UIController {
this.listener.send(RpcFactory.UIPerformInteractionResponse(choiceID, appID, msgID))
}
onSystemContext(context, appID) {
if (context === 'MAIN' || context === 'ALERT') {
store.dispatch(appStoreShowWebView());
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should you check that the current route is the appstore/webview before dispatching these actions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right, there's an issue here if an app registers as WEB_VIEW and changes templates. That app's iframe would still be shown. I will look into this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In a9d1f48 I added a variable to the state to check whether we were in /web-view and used this in index.js alongside this variable that reflected the current context. This worked fine, but I realized that checking if we are in /web-view is enough and the context doesn't matter after that. So in a1ab7d8 I removed the old state variable related to the context.

} else {
store.dispatch(appStoreHideWebView());
}
this.listener.send(RpcFactory.OnSystemContextNotification(context, appID))
}
onCommand(cmdID, appID) {
Expand Down
14 changes: 14 additions & 0 deletions src/js/Templates/WebView/WebView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import AppHeader from '../../containers/Header';

class WebView extends React.Component {
render() {
return (
<div className="webView">
<AppHeader backLink="/" menuName="Apps" icon="false"/>
Copy link
Collaborator

@Jack-Byrne Jack-Byrne Jul 29, 2020

Choose a reason for hiding this comment

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

I assume WebengineAppContainer was not included in this component because the iframe needs to be running when not in view?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Exactly, the iframe has to be outside the route context so it can run when we are in a menu etc.

</div>
)
}
}

export default WebView;
2 changes: 1 addition & 1 deletion src/js/WebEngineAppContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
export default class WebEngineAppContainer extends React.Component {
render() {
return (<div className="webengine-app-container">
<iframe src={this.props.iframeUrl} style={{ display: 'none' }} title={this.props.iframeUrl}/>
<iframe src={this.props.iframeUrl} style={this.props.style} title={this.props.iframeUrl}/>
</div>);
}
}
20 changes: 17 additions & 3 deletions src/js/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export const Actions = {
APPSTORE_APP_INSTALLED: "APPSTORE_APP_INSTALLED",
APPSTORE_APP_UNINSTALLED: "APPSTORE_APP_UNINSTALLED",
WEBENGINE_APP_LAUNCH: "WEBENGINE_APP_LAUNCH",
APPSTORE_BEGIN_INSTALL: "APPSTORE_BEGIN_INSTALL"
APPSTORE_BEGIN_INSTALL: "APPSTORE_BEGIN_INSTALL",
SHOW_WEB_VIEW: "SHOW_WEB_VIEW",
HIDE_WEB_VIEW: "HIDE_WEB_VIEW"
}

export const updateAppList = (applications) => {
Expand Down Expand Up @@ -221,11 +223,11 @@ export const setTemplateConfiguration = (displayLayout, appID, dayColorScheme, n
}
}

export const registerApplication = (appID, isMediaApplication) => {
export const registerApplication = (appID, displayLayout) => {
return {
type: Actions.REGISTER_APPLICATION,
appID: appID,
isMediaApplication: isMediaApplication
displayLayout: displayLayout
}
}

Expand Down Expand Up @@ -398,4 +400,16 @@ export const appStoreBeginInstall = (policyAppID) => {
type: Actions.APPSTORE_BEGIN_INSTALL,
policyAppID: policyAppID
}
}

export const appStoreShowWebView = () => {
return {
type: Actions.SHOW_WEB_VIEW
}
}

export const appStoreHideWebView = () => {
return {
type: Actions.HIDE_WEB_VIEW
}
}
19 changes: 12 additions & 7 deletions src/js/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,9 @@ function ui(state = {}, action) {
case "NON-MEDIA":
app.displayLayout = "nonmedia"
break
case "WEB_VIEW":
app.displayLayout = "webview"
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think web-view would be more inline with the naming conventions, except nonmedia is kind of ruining my claim.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated in 89d551f

If we changed nonmedia we could fix this special case and just use .toLower() for the registerApp call.

break
case "LARGE_GRAPHIC_ONLY":
app.displayLayout = "large-graphic-only"
break
Expand Down Expand Up @@ -472,10 +475,8 @@ function ui(state = {}, action) {
app.nightColorScheme = action.nightColorScheme
}
return newState
case Actions.REGISTER_APPLICATION:
if (!app.displayLayout) {
app.displayLayout = action.isMediaApplication ? "media" : "nonmedia"
}
case Actions.REGISTER_APPLICATION:
app.displayLayout = action.displayLayout;
return newState
case Actions.UNREGISTER_APPLICATION:
if (newState[action.appID]) {
Expand Down Expand Up @@ -553,6 +554,7 @@ function system(state = {}, action) {

function appStore(state = {
isConnected: false,
showWebView: false,
availableApps: [],
installedApps: [],
appsPendingSetAppProperties: []
Expand All @@ -579,14 +581,12 @@ function appStore(state = {
);
return newState;
}
// Update the existing app's properties
existingApp = Object.assign(existingApp, action.installedApp);
return newState;
case Actions.ADD_APP_PENDING_SET_APP_PROPERTIES:
newState.appsPendingSetAppProperties.push({ app: action.app, enable: action.enable});
return newState;
case Actions.APPSTORE_APP_INSTALLED:
// @shobhit, should we add a length check here like we did in SetAppProperties RPC?
var pendingAppInstall = newState.appsPendingSetAppProperties.shift()['app'];
if (!action.success) { return newState; }
var newInstalled = [ pendingAppInstall ].concat(newState.installedApps);
Expand All @@ -595,7 +595,6 @@ function appStore(state = {
if (appStoreAppInstalled) { appStoreAppInstalled.pendingInstall = false; }
return newState;
case Actions.APPSTORE_APP_UNINSTALLED:
// @shobhit, should we add a length check here like we did in SetAppProperties RPC?
let pendingAppUninstall = newState.appsPendingSetAppProperties.shift()['app'];
if (!action.success) { return newState; }
newState.installedApps = state.installedApps.filter(app => app.policyAppID !== pendingAppUninstall.policyAppID);
Expand All @@ -612,6 +611,12 @@ function appStore(state = {
let appStoreAppToInstall = newState.availableApps.find(app => app.policyAppID === action.policyAppID);
if (appStoreAppToInstall) { appStoreAppToInstall.pendingInstall = true; }
return newState;
case Actions.SHOW_WEB_VIEW:
newState.showWebView = true;
return newState;
case Actions.HIDE_WEB_VIEW:
newState.showWebView = false;
return newState;
default:
return state;
}
Expand Down