Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 9 additions & 2 deletions app/desktop/webpack.renderer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ const require = createRequire(import.meta.url);
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
const {version} = require('../../package.json');

const DEFAULT_TRANSLATE_SERVICE_URL = process.env.TRANSLATE_SERVICE_URL ||
'https://trampoline.simonshiki.top/translate/translate';
const DEFAULT_TTS_SERVICE_URL = process.env.TTS_SERVICE_URL ||
'https://trampoline.simonshiki.top/tts/synth';

/**
* @typedef {import('webpack-dev-server').Configuration} ConfigurationWithDevServer
*/
Expand Down Expand Up @@ -174,8 +179,10 @@ const rendererConfig = {
new webpack.DefinePlugin({
'process.env.DEBUG': Boolean(process.env.DEBUG),
'process.env.GA_ID': `"${process.env.GA_ID || 'UA-000000-01'}"`,
'clipcc.VERSION': version,
'clipcc.BUILD_TIME': Date.now()
'clipcc.VERSION': JSON.stringify(version),
'clipcc.BUILD_TIME': Date.now(),
'clipcc.DEFAULT_TRANSLATE_SERVICE_URL': JSON.stringify(DEFAULT_TRANSLATE_SERVICE_URL),
'clipcc.DEFAULT_TTS_SERVICE_URL': JSON.stringify(DEFAULT_TTS_SERVICE_URL)
}),
new NodePolyfillPlugin({
includeAliases: ['buffer', 'Buffer', 'events']
Expand Down
1 change: 1 addition & 0 deletions packages/gui/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
testEnvironment: 'jsdom',
setupFiles: [
'raf/polyfill',
'<rootDir>/test/helpers/clipcc-globals.js',
'<rootDir>/test/helpers/enzyme-setup.js'
],
testPathIgnorePatterns: [
Expand Down
4 changes: 4 additions & 0 deletions packages/gui/src/components/settings-modal/settings-modal.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@
width: 5rem;
}

.input-wide {
width: 18rem;
}

.input::-webkit-outer-spin-button, .input::-webkit-inner-spin-button {
appearance: none;
margin: 0;
Expand Down
98 changes: 97 additions & 1 deletion packages/gui/src/components/settings-modal/settings-modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ const messages = defineMessages({
description: 'Label of project',
id: 'gui.settingsModal.project'
},
network: {
defaultMessage: 'Network',
description: 'Label of network',
id: 'gui.settingsModal.network'
},
system: {
defaultMessage: 'Follow System',
description: 'Label of follow system',
Expand Down Expand Up @@ -62,7 +67,8 @@ class SettingsModal extends React.Component {
this.categoryRef = {
appearance: null, // @todo use React.createRef instead after React 16.3
player: null,
project: null
project: null,
network: null
};
bindAll(this, [
'handleJumpToCategoryWrapper'
Expand Down Expand Up @@ -97,6 +103,9 @@ class SettingsModal extends React.Component {
<p onClick={this.handleJumpToCategoryWrapper('project')}>
{this.props.intl.formatMessage(messages.project)}
</p>
<p onClick={this.handleJumpToCategoryWrapper('network')}>
{this.props.intl.formatMessage(messages.network)}
</p>
</Box>
<Box
className={classNames(styles.content, styles.scrollbar)}
Expand Down Expand Up @@ -393,6 +402,87 @@ class SettingsModal extends React.Component {
placeholder="300"
/>
</div>
<p
className={styles.category}
// eslint-disable-next-line no-return-assign
ref={ref => this.categoryRef.network = ref}
>
{this.props.intl.formatMessage(messages.network)}
</p>
<div className={styles.item}>
<div className={styles.label}>
<FormattedMessage
defaultMessage="Use Scratch Official API"
description="Label of use scratch official api"
id="gui.settingsModal.useScratchOfficialApi"
/>
<FormattedMessage
defaultMessage="Use official Scratch translate and text-to-speech services."
description="Description of use scratch official api"
id="gui.settingsModal.useScratchOfficialApiDescription"
/>
</div>
<Switch
value={this.props.useScratchOfficialApi}
onChange={this.props.onChangeUseScratchOfficialApi}
/>
</div>
<div className={styles.item}>
<div
className={classNames(
styles.label,
this.props.useScratchOfficialApi ? styles.disabled : null
)}
>
<FormattedMessage
defaultMessage="Translate Service URL"
description="Label of translate service url"
id="gui.settingsModal.translateServiceUrl"
/>
<FormattedMessage
defaultMessage="The API entrypoint used by translate blocks."
description="Description of translate service url"
id="gui.settingsModal.translateServiceUrlDescription"
/>
</div>
<BufferedInput
value={this.props.translateServiceUrl}
disabled={this.props.useScratchOfficialApi}
onSubmit={this.props.onChangeTranslateServiceUrl}
className={styles.inputWide}
small
tabIndex="0"
type="text"
/>
</div>
<div className={styles.item}>
<div
className={classNames(
styles.label,
this.props.useScratchOfficialApi ? styles.disabled : null
)}
>
<FormattedMessage
defaultMessage="Text-to-Speech Service URL"
description="Label of text-to-speech service url"
id="gui.settingsModal.ttsServiceUrl"
/>
<FormattedMessage
defaultMessage="The API entrypoint used by text-to-speech blocks."
description="Description of text-to-speech service url"
id="gui.settingsModal.ttsServiceUrlDescription"
/>
</div>
<BufferedInput
value={this.props.ttsServiceUrl}
disabled={this.props.useScratchOfficialApi}
onSubmit={this.props.onChangeTTSServiceUrl}
className={styles.inputWide}
small
tabIndex="0"
type="text"
/>
</div>
</Box>
</Box>
</Modal>
Expand All @@ -413,6 +503,9 @@ SettingsModal.propTypes = {
framerate: PropTypes.number.isRequired,
stageWidth: PropTypes.number.isRequired,
stageHeight: PropTypes.number.isRequired,
translateServiceUrl: PropTypes.string.isRequired,
ttsServiceUrl: PropTypes.string.isRequired,
useScratchOfficialApi: PropTypes.bool.isRequired,
theme: PropTypes.string.isRequired,
intl: intlShape.isRequired,
onClose: PropTypes.func.isRequired,
Expand All @@ -421,6 +514,9 @@ SettingsModal.propTypes = {
onChangeEdgelessStage: PropTypes.func.isRequired,
onChangeStageWidth: PropTypes.func.isRequired,
onChangeStageHeight: PropTypes.func.isRequired,
onChangeTranslateServiceUrl: PropTypes.func.isRequired,
onChangeTTSServiceUrl: PropTypes.func.isRequired,
onChangeUseScratchOfficialApi: PropTypes.func.isRequired,
onChangeUnlimitedListLength: PropTypes.func.isRequired,
onChangeUnlimitedPenSize: PropTypes.func.isRequired,
onChangeUnlimitedSoundStuffs: PropTypes.func.isRequired,
Expand Down
30 changes: 27 additions & 3 deletions packages/gui/src/containers/settings-modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ class SettingsModal extends React.Component {
'handleChangeAccurateCoordinates',
'handleChangeHideNonVanillaBlocks',
'handleChangeStageWidth',
'handleChangeStageHeight'
'handleChangeStageHeight',
'handleChangeTranslateServiceUrl',
'handleChangeTTSServiceUrl',
'handleChangeUseScratchOfficialApi'
]);
}
handleClose () {
Expand Down Expand Up @@ -82,6 +85,15 @@ class SettingsModal extends React.Component {
handleChangeStageHeight (height) {
this.props.updateSettings({stageHeight: Math.round(height)});
}
handleChangeTranslateServiceUrl (url) {
this.props.updateSettings({translateServiceUrl: url.trim().replace(/\/+$/, '')});
}
handleChangeTTSServiceUrl (url) {
this.props.updateSettings({ttsServiceUrl: url.trim().replace(/\/+$/, '')});
}
handleChangeUseScratchOfficialApi (value) {
this.props.updateSettings({useScratchOfficialApi: value});
}
render () {
return (
<SettingsModalComponent
Expand All @@ -98,6 +110,9 @@ class SettingsModal extends React.Component {
accurateCoordinates={this.props.accurateCoordinates}
stageHeight={this.props.stageHeight}
stageWidth={this.props.stageWidth}
translateServiceUrl={this.props.translateServiceUrl}
ttsServiceUrl={this.props.ttsServiceUrl}
useScratchOfficialApi={this.props.useScratchOfficialApi}
onClose={this.handleClose}
onChangeAutoSave={this.handleChangeAutoSave}
onChangeAutoSaveInterval={this.handleChangeAutoSaveInterval}
Expand All @@ -112,6 +127,9 @@ class SettingsModal extends React.Component {
onChangeHideNonVanillaBlocks={this.handleChangeHideNonVanillaBlocks}
onChangeStageWidth={this.handleChangeStageWidth}
onChangeStageHeight={this.handleChangeStageHeight}
onChangeTranslateServiceUrl={this.handleChangeTranslateServiceUrl}
onChangeTTSServiceUrl={this.handleChangeTTSServiceUrl}
onChangeUseScratchOfficialApi={this.handleChangeUseScratchOfficialApi}
/>
);
}
Expand All @@ -132,7 +150,10 @@ SettingsModal.propTypes = {
onClose: PropTypes.func.isRequired,
updateSettings: PropTypes.func.isRequired,
stageHeight: PropTypes.number.isRequired,
stageWidth: PropTypes.number.isRequired
stageWidth: PropTypes.number.isRequired,
translateServiceUrl: PropTypes.string.isRequired,
ttsServiceUrl: PropTypes.string.isRequired,
useScratchOfficialApi: PropTypes.bool.isRequired
};

const mapStateToProps = state => ({
Expand All @@ -148,7 +169,10 @@ const mapStateToProps = state => ({
framerate: state.scratchGui.settings.framerate,
theme: state.scratchGui.settings.theme,
stageHeight: state.scratchGui.settings.stageHeight,
stageWidth: state.scratchGui.settings.stageWidth
stageWidth: state.scratchGui.settings.stageWidth,
translateServiceUrl: state.scratchGui.settings.translateServiceUrl,
ttsServiceUrl: state.scratchGui.settings.ttsServiceUrl,
useScratchOfficialApi: state.scratchGui.settings.useScratchOfficialApi
});

const mapDispatchToProps = dispatch => ({
Expand Down
37 changes: 36 additions & 1 deletion packages/gui/src/lib/vm-manager-hoc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
projectError
} from '../reducers/project-state';

const OFFICIAL_TRANSLATE_SERVICE_URL = 'https://translate-service.scratch.mit.edu/translate';
const OFFICIAL_TTS_SERVICE_URL = 'https://synthesis-service.scratch.mit.edu/synth';

/**
* Higher Order Component to manage events emitted by the VM
* @param {React.Component} WrappedComponent component to manage VM events for
Expand Down Expand Up @@ -46,6 +49,10 @@ const vmManagerHOC = function (WrappedComponent) {
});
this.props.vm.setStageWidth(this.props.stageWidth);
this.props.vm.setStageHeight(this.props.stageHeight);
if (!this.props.useScratchOfficialApi) {
this.props.vm.setTranslateServiceUrl(this.props.translateServiceUrl);
this.props.vm.setTTSServiceUrl(this.props.ttsServiceUrl);
}
}
if (!this.props.isPlayerOnly && !this.props.isStarted) {
this.props.vm.start();
Expand Down Expand Up @@ -102,6 +109,23 @@ const vmManagerHOC = function (WrappedComponent) {
if (this.props.stageHeight !== prevProps.stageHeight) {
this.props.vm.setStageHeight(this.props.stageHeight);
}
if (this.props.useScratchOfficialApi !== prevProps.useScratchOfficialApi) {
if (this.props.useScratchOfficialApi) {
this.props.vm.setTranslateServiceUrl(OFFICIAL_TRANSLATE_SERVICE_URL);
this.props.vm.setTTSServiceUrl(OFFICIAL_TTS_SERVICE_URL);
} else {
this.props.vm.setTranslateServiceUrl(this.props.translateServiceUrl);
this.props.vm.setTTSServiceUrl(this.props.ttsServiceUrl);
}
}
if (!this.props.useScratchOfficialApi) {
if (this.props.translateServiceUrl !== prevProps.translateServiceUrl) {
this.props.vm.setTranslateServiceUrl(this.props.translateServiceUrl);
}
if (this.props.ttsServiceUrl !== prevProps.ttsServiceUrl) {
this.props.vm.setTTSServiceUrl(this.props.ttsServiceUrl);
}
}
}
loadProject () {
return this.props.vm.loadProject(this.props.projectData)
Expand Down Expand Up @@ -145,6 +169,11 @@ const vmManagerHOC = function (WrappedComponent) {
unlimitedPenSize,
unlimitedSoundStuffs,
accurateCoordinates,
stageWidth,
stageHeight,
translateServiceUrl,
ttsServiceUrl,
useScratchOfficialApi,
/* eslint-enable no-unused-vars */
isLoadingWithId: isLoadingWithIdProp,
vm,
Expand Down Expand Up @@ -185,6 +214,9 @@ const vmManagerHOC = function (WrappedComponent) {
accurateCoordinates: PropTypes.bool.isRequired,
stageWidth: PropTypes.number.isRequired,
stageHeight: PropTypes.number.isRequired,
translateServiceUrl: PropTypes.string.isRequired,
ttsServiceUrl: PropTypes.string.isRequired,
useScratchOfficialApi: PropTypes.bool.isRequired,
vm: PropTypes.instanceOf(VM).isRequired
};

Expand All @@ -208,7 +240,10 @@ const vmManagerHOC = function (WrappedComponent) {
unlimitedSoundStuffs: state.scratchGui.settings.unlimitedSoundStuffs,
accurateCoordinates: state.scratchGui.settings.accurateCoordinates,
stageWidth: state.scratchGui.settings.stageWidth,
stageHeight: state.scratchGui.settings.stageHeight
stageHeight: state.scratchGui.settings.stageHeight,
translateServiceUrl: state.scratchGui.settings.translateServiceUrl,
ttsServiceUrl: state.scratchGui.settings.ttsServiceUrl,
useScratchOfficialApi: state.scratchGui.settings.useScratchOfficialApi
};
};

Expand Down
34 changes: 15 additions & 19 deletions packages/gui/src/reducers/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export type SettingsState = {
theme: string;
stageWidth: number;
stageHeight: number;
translateServiceUrl: string;
ttsServiceUrl: string;
useScratchOfficialApi: boolean;
};

const defaultState: SettingsState = {
Expand All @@ -34,7 +37,10 @@ const defaultState: SettingsState = {
framerate: 30,
theme: 'system',
stageWidth: 480,
stageHeight: 360
stageHeight: 360,
translateServiceUrl: clipcc.DEFAULT_TRANSLATE_SERVICE_URL,
ttsServiceUrl: clipcc.DEFAULT_TTS_SERVICE_URL,
useScratchOfficialApi: false
};

const parseSavedSettings = (): Partial<SettingsState> => {
Expand All @@ -49,24 +55,14 @@ const parseSavedSettings = (): Partial<SettingsState> => {
return {};
}

return {
hideNonVanillaBlocks:
typeof parsed.hideNonVanillaBlocks === 'boolean' ? parsed.hideNonVanillaBlocks : undefined,
autoSave: typeof parsed.autoSave === 'boolean' ? parsed.autoSave : undefined,
infiniteCloning: typeof parsed.infiniteCloning === 'boolean' ? parsed.infiniteCloning : undefined,
edgelessStage: typeof parsed.edgelessStage === 'boolean' ? parsed.edgelessStage : undefined,
unlimitedListLength: typeof parsed.unlimitedListLength === 'boolean' ? parsed.unlimitedListLength : undefined,
unlimitedPenSize: typeof parsed.unlimitedPenSize === 'boolean' ? parsed.unlimitedPenSize : undefined,
unlimitedSoundStuffs:
typeof parsed.unlimitedSoundStuffs === 'boolean' ? parsed.unlimitedSoundStuffs : undefined,
accurateCoordinates: typeof parsed.accurateCoordinates === 'boolean' ? parsed.accurateCoordinates : undefined,
autoSaveInterval: typeof parsed.autoSaveInterval === 'number' ? parsed.autoSaveInterval : undefined,
compression: typeof parsed.compression === 'number' ? parsed.compression : undefined,
framerate: typeof parsed.framerate === 'number' ? parsed.framerate : undefined,
theme: typeof parsed.theme === 'string' ? parsed.theme : undefined,
stageWidth: typeof parsed.stageWidth === 'number' ? parsed.stageWidth : undefined,
stageHeight: typeof parsed.stageHeight === 'number' ? parsed.stageHeight : undefined
};
const result: Partial<SettingsState> = {};
for (const key of Object.keys(defaultState) as Array<keyof SettingsState>) {
const value = parsed[key];
if (value !== undefined && typeof value === typeof defaultState[key]) {
(result as Record<string, unknown>)[key] = value;
}
}
return result;
};

const initialState: SettingsState = {
Expand Down
Loading
Loading