feat: store color theme in state

This commit is contained in:
Andy O'Neill committed 2023-02-15 13:28:15 -05:00
1 parent 79c46e5405
commit b2d4021455
12 files changed
+410 -58

No files matched your search

+6 -1
View File
@@ -117,6 +117,7 @@ const GUIComponent = props => {
stageSizeMode,
targetIsStage,
telemetryModalVisible,
theme,
tipsLibraryVisible,
vm,
...componentProps
@@ -303,10 +304,12 @@ const GUIComponent = props => {
canUseCloud={canUseCloud}
grow={1}
isVisible={blocksTabVisible}
key={theme}
options={{
media: `${basePath}static/blocks-media/`
}}
stageSize={stageSize}
theme={theme}
vm={vm}
/>
</Box>
@@ -422,6 +425,7 @@ GUIComponent.propTypes = {
stageSizeMode: PropTypes.oneOf(Object.keys(STAGE_SIZE_MODES)),
targetIsStage: PropTypes.bool,
telemetryModalVisible: PropTypes.bool,
theme: PropTypes.string,
tipsLibraryVisible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
};
@@ -448,7 +452,8 @@ GUIComponent.defaultProps = {
const mapStateToProps = state => ({
// This is the button's mode, as opposed to the actual current state
stageSizeMode: state.scratchGui.stageSize.stageSize
stageSizeMode: state.scratchGui.stageSize.stageSize,
theme: state.scratchGui.theme.theme
});
export default injectIntl(connect(
+7 -27
View File
@@ -18,6 +18,7 @@ import {BLOCKS_DEFAULT_SCALE, STAGE_DISPLAY_SIZES} from '../lib/layout-constants
import DropAreaHOC from '../lib/drop-area-hoc.jsx';
import DragConstants from '../lib/drag-constants';
import defineDynamicBlock from '../lib/define-dynamic-block';
import {getColorsForTheme, themes} from '../lib/themes';
import {connect} from 'react-redux';
import {updateToolbox} from '../reducers/toolbox';
@@ -94,7 +95,7 @@ class Blocks extends React.Component {
const workspaceConfig = defaultsDeep({},
Blocks.defaultOptions,
this.props.options,
{rtl: this.props.isRtl, toolbox: this.props.toolboxXML}
{rtl: this.props.isRtl, toolbox: this.props.toolboxXML, colours: getColorsForTheme(this.props.theme)}
);
this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);
@@ -351,7 +352,8 @@ class Blocks extends React.Component {
return makeToolboxXML(false, target.isStage, target.id, dynamicBlocksXML,
targetCostumes[targetCostumes.length - 1].name,
stageCostumes[stageCostumes.length - 1].name,
targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : ''
targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : '',
getColorsForTheme(this.props.theme)
);
} catch {
return null;
@@ -620,22 +622,11 @@ Blocks.propTypes = {
wheel: PropTypes.bool,
startScale: PropTypes.number
}),
colours: PropTypes.shape({
workspace: PropTypes.string,
flyout: PropTypes.string,
toolbox: PropTypes.string,
toolboxSelected: PropTypes.string,
scrollbar: PropTypes.string,
scrollbarHover: PropTypes.string,
insertionMarker: PropTypes.string,
insertionMarkerOpacity: PropTypes.number,
fieldShadow: PropTypes.string,
dragShadowOpacity: PropTypes.number
}),
comments: PropTypes.bool,
collapse: PropTypes.bool
}),
stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired,
theme: PropTypes.oneOf(themes),
toolboxXML: PropTypes.string,
updateMetrics: PropTypes.func,
updateToolboxState: PropTypes.func,
@@ -656,18 +647,6 @@ Blocks.defaultOptions = {
length: 2,
colour: '#ddd'
},
colours: {
workspace: '#F9F9F9',
flyout: '#F9F9F9',
toolbox: '#FFFFFF',
toolboxSelected: '#E9EEF2',
scrollbar: '#CECDCE',
scrollbarHover: '#CECDCE',
insertionMarker: '#000000',
insertionMarkerOpacity: 0.2,
fieldShadow: 'rgba(255, 255, 255, 0.3)',
dragShadowOpacity: 0.6
},
comments: true,
collapse: false,
sounds: false
@@ -675,7 +654,8 @@ Blocks.defaultOptions = {
Blocks.defaultProps = {
isVisible: true,
options: Blocks.defaultOptions
options: Blocks.defaultOptions,
theme: 'standard'
};
const mapStateToProps = state => ({
+54 -30
View File
@@ -1,17 +1,19 @@
import ScratchBlocks from 'scratch-blocks';
import {defaultColors} from './themes';
const categorySeparator = '<sep gap="36"/>';
const blockSeparator = '<sep gap="36"/>'; // At default scale, about 28px
/* eslint-disable no-unused-vars */
const motion = function (isInitialSetup, isStage, targetId) {
const motion = function (isInitialSetup, isStage, targetId, colors) {
const stageSelected = ScratchBlocks.ScratchMsgs.translate(
'MOTION_STAGE_SELECTED',
'Stage selected: no motion blocks'
);
// Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
<category name="%{BKY_CATEGORY_MOTION}" id="motion" colour="#4C97FF" secondaryColour="#3373CC">
<category name="%{BKY_CATEGORY_MOTION}" id="motion" colour="${colors.primary}" secondaryColour="${colors.tertiary}">
${isStage ? `
<label text="${stageSelected}"></label>
` : `
@@ -151,11 +153,12 @@ const xmlEscape = function (unsafe) {
});
};
const looks = function (isInitialSetup, isStage, targetId, costumeName, backdropName) {
const looks = function (isInitialSetup, isStage, targetId, costumeName, backdropName, colors) {
const hello = ScratchBlocks.ScratchMsgs.translate('LOOKS_HELLO', 'Hello!');
const hmm = ScratchBlocks.ScratchMsgs.translate('LOOKS_HMM', 'Hmm...');
// Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
<category name="%{BKY_CATEGORY_LOOKS}" id="looks" colour="#9966FF" secondaryColour="#774DCB">
<category name="%{BKY_CATEGORY_LOOKS}" id="looks" colour="${colors.primary}" secondaryColour="${colors.tertiary}">
${isStage ? '' : `
<block type="looks_sayforsecs">
<value name="MESSAGE">
@@ -288,9 +291,10 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop
`;
};
const sound = function (isInitialSetup, isStage, targetId, soundName) {
const sound = function (isInitialSetup, isStage, targetId, soundName, colors) {
// Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
<category name="%{BKY_CATEGORY_SOUND}" id="sound" colour="#D65CD6" secondaryColour="#BD42BD">
<category name="%{BKY_CATEGORY_SOUND}" id="sound" colour="${colors.primary}" secondaryColour="${colors.tertiary}">
<block id="${targetId}_sound_playuntildone" type="sound_playuntildone">
<value name="SOUND_MENU">
<shadow type="sound_sounds_menu">
@@ -343,9 +347,10 @@ const sound = function (isInitialSetup, isStage, targetId, soundName) {
`;
};
const events = function (isInitialSetup, isStage) {
const events = function (isInitialSetup, isStage, targetId, colors) {
// Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
<category name="%{BKY_CATEGORY_EVENTS}" id="events" colour="#FFD500" secondaryColour="#CC9900">
<category name="%{BKY_CATEGORY_EVENTS}" id="events" colour="${colors.primary}" secondaryColour="${colors.tertiary}">
<block type="event_whenflagclicked"/>
<block type="event_whenkeypressed">
</block>
@@ -382,9 +387,14 @@ const events = function (isInitialSetup, isStage) {
`;
};
const control = function (isInitialSetup, isStage) {
const control = function (isInitialSetup, isStage, targetId, colors) {
// Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
<category name="%{BKY_CATEGORY_CONTROL}" id="control" colour="#FFAB19" secondaryColour="#CF8B17">
<category
name="%{BKY_CATEGORY_CONTROL}"
id="control"
colour="${colors.primary}"
secondaryColour="${colors.tertiary}">
<block type="control_wait">
<value name="DURATION">
<shadow type="math_positive_number">
@@ -429,10 +439,15 @@ const control = function (isInitialSetup, isStage) {
`;
};
const sensing = function (isInitialSetup, isStage) {
const sensing = function (isInitialSetup, isStage, targetId, colors) {
const name = ScratchBlocks.ScratchMsgs.translate('SENSING_ASK_TEXT', 'What\'s your name?');
// Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
<category name="%{BKY_CATEGORY_SENSING}" id="sensing" colour="#4CBFE6" secondaryColour="#2E8EB8">
<category
name="%{BKY_CATEGORY_SENSING}"
id="sensing"
colour="${colors.primary}"
secondaryColour="${colors.tertiary}">
${isStage ? '' : `
<block type="sensing_touchingobject">
<value name="TOUCHINGOBJECTMENU">
@@ -504,12 +519,17 @@ const sensing = function (isInitialSetup, isStage) {
`;
};
const operators = function (isInitialSetup) {
const operators = function (isInitialSetup, isStage, targetId, colors) {
const apple = ScratchBlocks.ScratchMsgs.translate('OPERATORS_JOIN_APPLE', 'apple');
const banana = ScratchBlocks.ScratchMsgs.translate('OPERATORS_JOIN_BANANA', 'banana');
const letter = ScratchBlocks.ScratchMsgs.translate('OPERATORS_LETTEROF_APPLE', 'a');
// Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
<category name="%{BKY_CATEGORY_OPERATORS}" id="operators" colour="#40BF4A" secondaryColour="#389438">
<category
name="%{BKY_CATEGORY_OPERATORS}"
id="operators"
colour="${colors.primary}"
secondaryColour="${colors.tertiary}">
<block type="operator_add">
<value name="NUM1">
<shadow type="math_number">
@@ -691,25 +711,27 @@ const operators = function (isInitialSetup) {
`;
};
const variables = function () {
const variables = function (isInitialSetup, isStage, targetId, colors) {
// Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
<category
name="%{BKY_CATEGORY_VARIABLES}"
id="variables"
colour="#FF8C1A"
secondaryColour="#DB6E00"
colour="${colors.primary}"
secondaryColour="${colors.tertiary}"
custom="VARIABLE">
</category>
`;
};
const myBlocks = function () {
const myBlocks = function (isInitialSetup, isStage, targetId, colors) {
// Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color.
return `
<category
name="%{BKY_CATEGORY_MYBLOCKS}"
id="myBlocks"
colour="#FF6680"
secondaryColour="#FF4D6A"
colour="${colors.primary}"
secondaryColour="${colors.tertiary}"
custom="PROCEDURE">
</category>
`;
@@ -732,10 +754,11 @@ const xmlClose = '</xml>';
* @param {?string} costumeName - The name of the default selected costume dropdown.
* @param {?string} backdropName - The name of the default selected backdrop dropdown.
* @param {?string} soundName - The name of the default selected sound dropdown.
* @param {?object} colors - The colors for the theme.
* @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox.
*/
const makeToolboxXML = function (isInitialSetup, isStage = true, targetId, categoriesXML = [],
costumeName = '', backdropName = '', soundName = '') {
costumeName = '', backdropName = '', soundName = '', colors = defaultColors) {
isStage = isInitialSetup || isStage;
const gap = [categorySeparator];
@@ -753,15 +776,16 @@ const makeToolboxXML = function (isInitialSetup, isStage = true, targetId, categ
}
// return `undefined`
};
const motionXML = moveCategory('motion') || motion(isInitialSetup, isStage, targetId);
const looksXML = moveCategory('looks') || looks(isInitialSetup, isStage, targetId, costumeName, backdropName);
const soundXML = moveCategory('sound') || sound(isInitialSetup, isStage, targetId, soundName);
const eventsXML = moveCategory('event') || events(isInitialSetup, isStage, targetId);
const controlXML = moveCategory('control') || control(isInitialSetup, isStage, targetId);
const sensingXML = moveCategory('sensing') || sensing(isInitialSetup, isStage, targetId);
const operatorsXML = moveCategory('operators') || operators(isInitialSetup, isStage, targetId);
const variablesXML = moveCategory('data') || variables(isInitialSetup, isStage, targetId);
const myBlocksXML = moveCategory('procedures') || myBlocks(isInitialSetup, isStage, targetId);
const motionXML = moveCategory('motion') || motion(isInitialSetup, isStage, targetId, colors.motion);
const looksXML = moveCategory('looks') ||
looks(isInitialSetup, isStage, targetId, costumeName, backdropName, colors.looks);
const soundXML = moveCategory('sound') || sound(isInitialSetup, isStage, targetId, soundName, colors.sounds);
const eventsXML = moveCategory('event') || events(isInitialSetup, isStage, targetId, colors.event);
const controlXML = moveCategory('control') || control(isInitialSetup, isStage, targetId, colors.control);
const sensingXML = moveCategory('sensing') || sensing(isInitialSetup, isStage, targetId, colors.sensing);
const operatorsXML = moveCategory('operators') || operators(isInitialSetup, isStage, targetId, colors.operators);
const variablesXML = moveCategory('data') || variables(isInitialSetup, isStage, targetId, colors.data);
const myBlocksXML = moveCategory('procedures') || myBlocks(isInitialSetup, isStage, targetId, colors.more);
const everything = [
xmlOpen,
+8
View File
@@ -0,0 +1,8 @@
const blockColors = {
motion: {
primary: '#AAAAAA'
},
text: '#BBBBBB'
};
export default blockColors;
@@ -0,0 +1,11 @@
const blockColors = {
motion: {
primary: '#111111',
secondary: '#222222',
tertiary: '#333333'
},
text: '#444444',
workspace: '#555555'
};
export default blockColors;
+66
View File
@@ -0,0 +1,66 @@
const blockColors = {
motion: {
primary: '#0F1E33',
secondary: '#4C4C4C',
tertiary: '#4C97FF'
},
looks: {
primary: '#1E1433',
secondary: '#4C4C4C',
tertiary: '#9966FF'
},
sounds: {
primary: '#291329',
secondary: '#4C4C4C',
tertiary: '#CF63CF'
},
control: {
primary: '#332205',
secondary: '#4C4C4C',
tertiary: '#FFAB19'
},
event: {
primary: '#332600',
secondary: '#4C4C4C',
tertiary: '#FFBF00'
},
sensing: {
primary: '#12232A',
secondary: '#4C4C4C',
tertiary: '#5CB1D6'
},
pen: {
primary: '#03251C',
secondary: '#4C4C4C',
tertiary: '#0fBD8C'
},
operators: {
primary: '#112611',
secondary: '#4C4C4C',
tertiary: '#59C059'
},
data: {
primary: '#331C05',
secondary: '#4C4C4C',
tertiary: '#FF8C1A'
},
data_lists: {
primary: '#331405',
secondary: '#4C4C4C',
tertiary: '#FF661A'
},
more: {
primary: '#331419',
secondary: '#4C4C4C',
tertiary: '#FF6680'
},
text: '#E5E5E5',
workspace: '#121212',
toolboxSelected: '#4C4C4C',
toolboxText: '#E5E5E5',
toolbox: '#121212',
flyout: '#121212',
textField: '#4C4C4C'
};
export default blockColors;
+90
View File
@@ -0,0 +1,90 @@
const blockColors = {
motion: {
primary: '#4C97FF',
secondary: '#4280D7',
tertiary: '#3373CC'
},
looks: {
primary: '#9966FF',
secondary: '#855CD6',
tertiary: '#774DCB'
},
sounds: {
primary: '#CF63CF',
secondary: '#C94FC9',
tertiary: '#BD42BD'
},
control: {
primary: '#FFAB19',
secondary: '#EC9C13',
tertiary: '#CF8B17'
},
event: {
primary: '#FFBF00',
secondary: '#E6AC00',
tertiary: '#CC9900'
},
sensing: {
primary: '#5CB1D6',
secondary: '#47A8D1',
tertiary: '#2E8EB8'
},
pen: {
primary: '#0fBD8C',
secondary: '#0DA57A',
tertiary: '#0B8E69'
},
operators: {
primary: '#59C059',
secondary: '#46B946',
tertiary: '#389438'
},
data: {
primary: '#FF8C1A',
secondary: '#FF8000',
tertiary: '#DB6E00'
},
// This is not a new category, but rather for differentiation
// between lists and scalar variables.
data_lists: {
primary: '#FF661A',
secondary: '#FF5500',
tertiary: '#E64D00'
},
more: {
primary: '#FF6680',
secondary: '#FF4D6A',
tertiary: '#FF3355'
},
text: '#575E75',
workspace: '#F9F9F9',
toolboxHover: '#4C97FF',
toolboxSelected: '#E9EEF2',
toolboxText: '#575E75',
toolbox: '#FFFFFF',
flyout: '#F9F9F9',
scrollbar: '#CECDCE',
scrollbarHover: '#CECDCE',
textField: '#FFFFFF',
insertionMarker: '#000000',
insertionMarkerOpacity: 0.2,
dragShadowOpacity: 0.6,
stackGlow: '#FFF200',
stackGlowSize: 4,
stackGlowOpacity: 1,
replacementGlow: '#FFFFFF',
replacementGlowSize: 2,
replacementGlowOpacity: 1,
colourPickerStroke: '#FFFFFF',
// CSS colours: support RGBA
fieldShadow: 'rgba(255, 255, 255, 0.3)',
dropDownShadow: 'rgba(0, 0, 0, .3)',
numPadBackground: '#547AB2',
numPadBorder: '#435F91',
numPadActiveBackground: '#435F91',
numPadText: 'white', // Do not use hex here, it cannot be inlined with data-uri SVG
valueReportBackground: '#FFFFFF',
valueReportBorder: '#AAAAAA'
};
export default blockColors;
+87
View File
@@ -0,0 +1,87 @@
const blockColors = {
motion: {
primary: '#80B5FF',
secondary: '#B3D2FF',
tertiary: '#3373CC',
// this field is not used in the "classic" Scratch 3 color scheme
// If we add "modes" we'd have to handle what happens when this field is not defined.
quaternary: '#CCE1FF'
},
looks: {
primary: '#CCB3FF',
secondary: '#DDCCFF',
tertiary: '#774DCB',
quaternary: '#EEE5FF'
},
sounds: {
primary: '#E19DE1',
secondary: '#FFB3FF',
tertiary: '#BD42BD',
quaternary: '#FFCCFF'
},
control: {
primary: '#FFBE4C',
secondary: '#FFDA99',
tertiary: '#CF8B17',
quaternary: '#FFE3B3'
},
event: {
primary: '#FFD966',
secondary: '#FFECB3',
tertiary: '#CC9900',
quaternary: '#FFF2CC'
},
sensing: {
primary: '#85C4E0',
secondary: '#AED8EA',
tertiary: '#2E8EB8',
quaternary: '#C2E2F0'
},
pen: {
primary: '#13ECAF',
secondary: '#75F0CD',
tertiary: '#0B8E69',
quaternary: '#A3F5DE'
},
operators: {
primary: '#7ECE7E',
secondary: '#B5E3B5',
tertiary: '#389438',
quaternary: '#DAF1DA'
},
data: {
primary: '#FFA54C',
secondary: '#FFCC99',
tertiary: '#DB6E00',
quaternary: '#FFE5CC'
},
// This is not a new category, but rather for differentiation
// between lists and scalar variables.
data_lists: {
primary: '#FF9966',
secondary: '#FFCAB0', // I don't think this is used, b/c we don't have any droppable fields in list blocks
tertiary: '#E64D00',
quaternary: '#FFDDCC'
},
more: {
primary: '#FF99AA',
secondary: '#FFCCD5',
tertiary: '#FF3355',
quaternary: '#FFE5EA'
},
// For productionizing: we should look into making this field be the color of the text on the blocks
// e.g. move and steps in move (10) steps, and rename this property below for controlling the text inside
// of inputs e.g. 10
text: '#000000', // Text inside of inputs e.g. 90 in [point in direction (90)]
toolboxText: '#000000', // Toolbox text, color picker text (used to be #575E75)
// The color that the category menu label (e.g. 'motion', 'looks', etc.) changes to on hover
toolboxHover: '#3373CC',
insertionMarker: '#000000',
insertionMarkerOpacity: 0.2,
fieldShadow: 'rgba(255, 255, 255, 0.3)',
dragShadowOpacity: 0.6
};
export default blockColors;
+31
View File
@@ -0,0 +1,31 @@
import defaultsDeep from 'lodash.defaultsdeep';
import darkMode from './dark-mode';
import highContrast from './high-contrast';
import defaultColors from './default-colors';
const mergeWithDefaults = colors => defaultsDeep({}, colors, defaultColors);
const themeMap = {
'dark-mode': mergeWithDefaults(darkMode),
'high-contrast': mergeWithDefaults(highContrast),
'standard': defaultColors
};
const getColorsForTheme = theme => {
const colors = themeMap[theme];
if (!colors) {
throw new Error(`Undefined theme ${theme}`);
}
return colors;
};
const themes = Object.keys(themeMap);
export {
defaultColors,
getColorsForTheme,
themes
};
+3
View File
@@ -21,6 +21,7 @@ import fontsLoadedReducer, {fontsLoadedInitialState} from './fonts-loaded';
import restoreDeletionReducer, {restoreDeletionInitialState} from './restore-deletion';
import stageSizeReducer, {stageSizeInitialState} from './stage-size';
import targetReducer, {targetsInitialState} from './targets';
import themeReducer, {themeInitialState} from './theme';
import timeoutReducer, {timeoutInitialState} from './timeout';
import toolboxReducer, {toolboxInitialState} from './toolbox';
import vmReducer, {vmInitialState} from './vm';
@@ -55,6 +56,7 @@ const guiInitialState = {
fontsLoaded: fontsLoadedInitialState,
restoreDeletion: restoreDeletionInitialState,
targets: targetsInitialState,
theme: themeInitialState,
timeout: timeoutInitialState,
toolbox: toolboxInitialState,
vm: vmInitialState,
@@ -154,6 +156,7 @@ const guiReducer = combineReducers({
fontsLoaded: fontsLoadedReducer,
restoreDeletion: restoreDeletionReducer,
targets: targetReducer,
theme: themeReducer,
timeout: timeoutReducer,
toolbox: toolboxReducer,
vm: vmReducer,
+25
View File
@@ -0,0 +1,25 @@
const SET_THEME = 'scratch-gui/theme/SET_THEME';
const initialState = {
theme: 'standard'
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_THEME:
return {...state, theme: action.theme};
default:
return state;
}
};
const setTheme = theme => ({
type: SET_THEME,
theme
});
export {
reducer as default,
initialState as themeInitialState,
setTheme
};
+22
View File
@@ -0,0 +1,22 @@
import {defaultColors, getColorsForTheme} from '../../../src/lib/themes';
jest.mock('../../../src/lib/themes/default-colors');
jest.mock('../../../src/lib/themes/dark-mode');
describe('themes', () => {
test('provides the default theme colors', () => {
expect(defaultColors.motion.primary).toEqual('#111111');
});
test('returns the dark mode', () => {
const colors = getColorsForTheme('dark-mode');
expect(colors.motion.primary).toEqual('#AAAAAA');
});
test('uses default theme colors when not specified', () => {
const colors = getColorsForTheme('dark-mode');
expect(colors.motion.secondary).toEqual('#222222');
});
});