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

Integrate Java Lab with Code Studio #39111

Merged
merged 12 commits into from
Feb 19, 2021
4 changes: 2 additions & 2 deletions apps/Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ describe('entry tests', () => {
'eval',
'fish',
'flappy',
'javalab',
'gamelab',
'spritelab',
'jigsaw',
Expand Down Expand Up @@ -351,8 +352,7 @@ describe('entry tests', () => {
[
'build/package/css/foorm_editor.css',
'style/code-studio/foorm_editor.scss'
],
['build/package/css/javalab.css', 'style/javalab/style.scss']
]
].concat(
appsToBuild.map(function(app) {
return [
Expand Down
1 change: 1 addition & 0 deletions apps/i18n/common/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,7 @@
"projectTypeGamelabBeta": "Game Lab (beta)",
"projectTypeGumball": "The Amazing World of Gumball",
"projectTypeIceage": "Ice Age",
"projectTypeJavalab": "Java Lab",
"projectTypeInfinity": "Infinity",
"projectTypeK1": "Pre-reader",
"projectTypeMinecraft": "Minecraft",
Expand Down
1 change: 1 addition & 0 deletions apps/i18n/javalab/en_us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions apps/script/checkEntryPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const SILENCED = [
'fish',
'flappy',
'gamelab',
'javalab',
'jigsaw',
'maze',
'netsim',
Expand Down
125 changes: 125 additions & 0 deletions apps/src/javalab/Javalab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {getStore, registerReducers} from '../redux';
import JavalabView from './JavalabView';
import javalab from './javalabRedux';
import {TestResults} from '@cdo/apps/constants';

/**
* On small mobile devices, when in portrait orientation, we show an overlay
Copy link
Contributor

Choose a reason for hiding this comment

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

Is Java Lab going to be supported on mobile/tablet?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tablet yes, mobile not in pilot and I think only view-only.

* image telling the user to rotate their device to landscape mode.
*/
const MOBILE_PORTRAIT_WIDTH = 600;

/**
* An instantiable Javalab class
*/

const Javalab = function() {
this.skin = null;
this.level = null;

/** @type {StudioApp} */
this.studioApp_ = null;
};

/**
* Inject the studioApp singleton.
*/
Javalab.prototype.injectStudioApp = function(studioApp) {
Copy link
Contributor

@jmkulwik jmkulwik Feb 17, 2021

Choose a reason for hiding this comment

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

We haven't been consistent on this everywhere in other apps, but this should probably be JavaLab and JavaLab.js (similar to GameLab and SpriteLab). Reasoning: On the product side, we'll always refer to it as two separate words "Java Lab" so camelcasing the two words would make sense.

Copy link
Contributor

@jmkulwik jmkulwik Feb 17, 2021

Choose a reason for hiding this comment

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

Huh now I'm noticing we're very inconsistent about this in all our apps.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah I'm happy to go with either (camel casing probably makes more sense?) but it seemed like keeping lab lowercase was the norm in general

Copy link
Contributor

Choose a reason for hiding this comment

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

Well. haha. When I first read this, I felt strongly that camel casing was the right option. Then I saw what we do everywhere else. I'm happy with whatever you choose.

this.studioApp_ = studioApp;
};

/**
* Initialize this Javalab instance. Called on page load.
*/
Javalab.prototype.init = function(config) {
if (!this.studioApp_) {
throw new Error('Javalab requires a StudioApp');
}

this.skin = config.skin;
this.level = config.level;

config.makeYourOwn = false;
config.wireframeShare = true;
config.noHowItWorks = true;

// We don't want icons in instructions
config.skin.staticAvatar = null;
config.skin.smallStaticAvatar = null;
config.skin.failureAvatar = null;
config.skin.winAvatar = null;

// Provide a way for us to have top pane instructions disabled by default, but
// able to turn them on.
config.noInstructionsWhenCollapsed = true;

config.pinWorkspaceToBottom = true;

config.getCode = this.getCode.bind(this);

const onMount = () => {
// NOTE: Most other apps call studioApp.init(). Like WebLab, Ailab, and Fish, we don't.
this.studioApp_.setConfigValues_(config);

// NOTE: if we called studioApp_.init(), the code here would be executed
// automatically since pinWorkspaceToBottom is true...
const container = document.getElementById(config.containerId);
const bodyElement = document.body;
bodyElement.style.overflow = 'hidden';
bodyElement.className = bodyElement.className + ' pin_bottom';
container.className = container.className + ' pin_bottom';

// Fixes viewport for small screens. Also usually done by studioApp_.init().
var viewport = document.querySelector('meta[name="viewport"]');
if (viewport) {
this.studioApp_.fixViewportForSpecificWidthForSmallScreens_(
viewport,
MOBILE_PORTRAIT_WIDTH
);
}
};

// Push initial level properties into the Redux store
this.studioApp_.setPageConstants(config, {
channelId: config.channel,
noVisualization: true,
visualizationInWorkspace: true,
isProjectLevel: !!config.level.isProjectLevel
});

registerReducers({javalab});

ReactDOM.render(
<Provider store={getStore()}>
<JavalabView onMount={onMount} />
</Provider>,
document.getElementById(config.containerId)
);
};

// Called by the Javalab app when it wants to go to the next level.
Javalab.prototype.onContinue = function() {
const onReportComplete = result => {
this.studioApp_.onContinue();
};

this.studioApp_.report({
app: 'javalab',
level: this.level.id,
result: true,
testResult: TestResults.ALL_PASS,
program: '',
onComplete: result => {
onReportComplete(result);
}
});
};

Javalab.prototype.getCode = function() {
return '';
};

export default Javalab;
101 changes: 57 additions & 44 deletions apps/src/javalab/JavalabView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {appendOutputLog} from './javalabRedux';
import PropTypes from 'prop-types';
import FontAwesome from '@cdo/apps/templates/FontAwesome';
import color from '@cdo/apps/util/color';
import StudioAppWrapper from '@cdo/apps/templates/StudioAppWrapper';
import InstructionsWithWorkspace from '@cdo/apps/templates/instructions/InstructionsWithWorkspace';

const style = {
instructionsAndPreview: {
Expand Down Expand Up @@ -62,7 +64,11 @@ const style = {

class JavalabView extends React.Component {
static propTypes = {
onMount: PropTypes.func.isRequired,

// populated by redux
isProjectLevel: PropTypes.bool.isRequired,
isReadOnlyWorkspace: PropTypes.bool.isRequired,
appendOutputLog: PropTypes.func
};

Expand All @@ -78,58 +84,65 @@ class JavalabView extends React.Component {

render() {
return (
<div style={style.javalab}>
<div style={style.instructionsAndPreview}>
<div style={style.instructions}>
<PaneHeader hasFocus={true}>
<PaneSection>Instructions</PaneSection>
</PaneHeader>
<ul>
<li>Instruction 1</li>
<li>Another Instruction</li>
</ul>
</div>
<div style={style.preview}>
<PaneHeader hasFocus={true}>
<PaneSection>Preview</PaneSection>
</PaneHeader>
</div>
</div>
<div style={style.editorAndConsole}>
<JavalabEditor />
<div style={style.consoleAndButtons}>
<div style={style.buttons}>
<button
type="button"
style={style.singleButton}
onClick={this.compile}
>
<FontAwesome icon="cubes" className="fa-2x" />
<br />
Compile
</button>
<button
type="button"
style={style.singleButton}
onClick={this.run}
>
<FontAwesome icon="play" className="fa-2x" />
<br />
Run
</button>
<StudioAppWrapper>
<InstructionsWithWorkspace>
<div style={style.javalab}>
<div style={style.instructionsAndPreview}>
<div style={style.instructions}>
<PaneHeader hasFocus={true}>
<PaneSection>Instructions</PaneSection>
</PaneHeader>
<ul>
<li>Instruction 1</li>
<li>Another Instruction</li>
</ul>
</div>
<div style={style.preview}>
<PaneHeader hasFocus={true}>
<PaneSection>Preview</PaneSection>
</PaneHeader>
</div>
</div>
<div style={style.consoleStyle}>
<JavalabConsole />
<div style={style.editorAndConsole}>
<JavalabEditor />
<div style={style.consoleAndButtons}>
<div style={style.buttons}>
<button
type="button"
style={style.singleButton}
onClick={this.compile}
>
<FontAwesome icon="cubes" className="fa-2x" />
<br />
Compile
</button>
<button
type="button"
style={style.singleButton}
onClick={this.run}
>
<FontAwesome icon="play" className="fa-2x" />
<br />
Run
</button>
</div>
<div style={style.consoleStyle}>
<JavalabConsole />
</div>
</div>
</div>
</div>
</div>
</div>
</InstructionsWithWorkspace>
</StudioAppWrapper>
);
}
}

export default connect(
null,
state => ({
isProjectLevel: state.pageConstants.isProjectLevel,
isReadOnlyWorkspace: state.pageConstants.isReadOnlyWorkspace
}),
dispatch => ({
appendOutputLog: log => dispatch(appendOutputLog(log))
})
Expand Down
6 changes: 6 additions & 0 deletions apps/src/javalab/levels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Configuration for all levels.
*/
var levels = (module.exports = {});

levels.custom = {};
16 changes: 16 additions & 0 deletions apps/src/javalab/locale-do-not-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* DO NOT IMPORT THIS DIRECTLY. Instead do:
* ```
* import msg from '@cdo/javalab/locale'.
* ```
* This allows the webpack config to determine how locales should be loaded,
* which is important for making locale setup work seamlessly in tests.
*/
// locale for javalab

import safeLoadLocale from '@cdo/apps/util/safeLoadLocale';
import localeWithI18nStringTracker from '@cdo/apps/util/i18nStringTracker';

let locale = safeLoadLocale('javalab_locale');
locale = localeWithI18nStringTracker(locale, 'javalab_locale');
module.exports = locale;
8 changes: 8 additions & 0 deletions apps/src/javalab/locale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// locale for javalab

import safeLoadLocale from '@cdo/apps/util/safeLoadLocale';
import localeWithI18nStringTracker from '@cdo/apps/util/i18nStringTracker';

let locale = safeLoadLocale('javalab_locale');
locale = localeWithI18nStringTracker(locale, 'javalab_locale');
module.exports = locale;
12 changes: 12 additions & 0 deletions apps/src/sites/studio/pages/init/loadJavalab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import appMain from '@cdo/apps/appMain';
import {singleton as studioApp} from '@cdo/apps/StudioApp';
import Javalab from '@cdo/apps/javalab/Javalab';
import levels from '@cdo/apps/javalab/levels';

export default function loadJavalab(options) {
options.isEditorless = true;
const javalab = new Javalab();

javalab.injectStudioApp(studioApp());
appMain(javalab, levels, options);
}
4 changes: 4 additions & 0 deletions apps/src/sites/studio/pages/levels-javalab-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import loadAppOptions from '@cdo/apps/code-studio/initApp/loadApp';
import loadJavalab from './init/loadJavalab';

loadAppOptions().then(loadJavalab);
3 changes: 2 additions & 1 deletion apps/src/templates/projects/projectTypeMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export const PROJECT_TYPE_MAP = {
eval: i18n.projectTypeEval(),
calc: i18n.projectTypeCalc(),
dance: i18n.projectTypeDance(),
spritelab: i18n.projectTypeSpriteLab()
spritelab: i18n.projectTypeSpriteLab(),
javalab: i18n.projectTypeJavalab()
};

export const FEATURED_PROJECT_TYPE_MAP = {
Expand Down
4 changes: 4 additions & 0 deletions dashboard/app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ class ProjectsController < ApplicationController
},
eval: {
name: 'Eval Free Play'
},
javalab: {
name: 'New Java Lab Project',
levelbuilder_required: true
Copy link
Contributor

Choose a reason for hiding this comment

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

TIL about levelbuilder_required: true 🤯

}
}.with_indifferent_access.freeze

Expand Down
4 changes: 2 additions & 2 deletions dashboard/app/helpers/levels_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def app_options
@app_options =
if @level.is_a? Blockly
blockly_options
elsif @level.is_a?(Weblab) || @level.is_a?(Fish) || @level.is_a?(Ailab)
elsif @level.is_a?(Weblab) || @level.is_a?(Fish) || @level.is_a?(Ailab) || @level.is_a?(Javalab)
non_blockly_puzzle_options
elsif @level.is_a?(DSLDefined) || @level.is_a?(FreeResponse) || @level.is_a?(CurriculumReference)
question_options
Expand Down Expand Up @@ -310,7 +310,7 @@ def render_app_dependencies
use_gamelab = @level.is_a?(Gamelab)
use_weblab = @level.game == Game.weblab
use_phaser = @level.game == Game.craft
use_blockly = !use_droplet && !use_netsim && !use_weblab
use_blockly = !use_droplet && !use_netsim && !use_weblab && !(@level.game == Game.javalab)
use_p5 = @level.is_a?(Gamelab)
hide_source = app_options[:hideSource]
use_google_blockly = @level.is_a?(Flappy) || view_options[:useGoogleBlockly]
Expand Down