【scratch二次开发教程 第4章】scratch-gui中集成自定义用户系统(一)
写在前面
该系列文章是为具有开发能力的朋友写作的,目的是帮助他们在scratch 3.0的基础上开发一套完整的集scratch
3.0编程工具、用户社区和作品云端存储及、品牌集成于一体的scratch编程平台。如果您不是开发者,但想要拥有自己的教育平台和品牌,也欢迎学习交流和洽谈合作。
所以如果您是想学习scratch少儿编程课程,那请忽略该系列的文章。
前言
前面我们将scratch-gui工程成功搭建运行起来,并且成功植入了我们的品牌logo,这让我们对整个项目有了初步的认识。
现在我们已经有了scratch编程工具,但是我们还缺少两个主要的后台,用户社区后台和GUI的存储后台。目前Scratch3.0团队没有透露社区后端和GUI的存储部分是否有开源计划,考虑到Scratch2.0的后端并未开源,3.0社区后端开源的可能性也不大。
scratch-www项目提供了用户社区的功能,但是需要通过接口去分析它后台的数据存储的结构,我觉得比较麻烦,不如我们自己来开发一个,集成到我们的编程工具scratch-gui中。
所以接下来我们的工作是自己来提供相关的两个后端平台,并与GUI集成到一起。
引入用户登录状态
我们先一步一步来,先做一个比较简单的用户系统,再一步一步迭代。
这一章,我们先来改造一下前面的scratch-gui,引入用户登录状态的检测。
在进入项目时,检测用户是否登录,如果用户未登录,则在右上角显示登录按钮,否则显示用户头像和姓名等基本信息。
先在reducers目录中创建user-state.js文件,用来记录用户的信息。
添加如下内容:
import keyMirror from 'keymirror';
const UserState = keyMirror({
NOT_LOGINED: null,
LOGINED: null
});
const UserStates = ob
ject.keys(UserState) const initialState = {
error: null,
userData: null,
loginState: UserState.NOT_LOGINED
};
const getIsLogined = loginState => ( loginState === UserState.LOGINED);
const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
}
export { reducer as default, initialState as userStateInitialState, UserState, UserStates, getIsLogined};
并将以上初始化信息配置在reducers/gui.js中,作为项目的用户相关的初始化信息。
在reducers/gui.js中引入user-state:
import userStateReducer, {userStateInitialState} from './user-state';
```加入到initialState中:```
const guiInitialState = { alerts: alertsInitialState, assetDrag: assetDragInitialState, blockDrag:
blockDragInitialState, cards: cardsInitialState, colorPicker: colorPickerInitialState, connectionModal:
connectionModalInitialState, customProcedures: customProceduresInitialState, editorTab: editorTabInitialState, mode:
modeInitialState, hoveredTarget: hoveredTargetInitialState, stageSize: stageSizeInitialState, menus: menuInitialState,
micIndicator: micIndicatorInitialState, modals: modalsInitialState, monitors: monitorsInitialState, monitorLayout:
monitorLayoutInitialState, projectChanged: projectChangedInitialState, projectState: projectStateInitialState,
projectTitle: projectTitleInitialState, fontsLoaded: fontsLoadedInitialState, restoreDeletion:
restoreDeletionInitialState, targets: targetsInitialState, timeout: timeoutInitialState, toolbox: toolboxInitialState,
vm: vmInitialState, vmStatus: vmStatusInitialState, userState:
userStateInitialState};```将reducer加入到guiReducer中:```const guiReducer = combineReducers({ alerts: alertsReducer,
assetDrag: assetDragReducer, blockDrag: blockDragReducer, cards: cardsReducer, colorPicker: colorPickerReducer,
connectionModal: connectionModalReducer, customProcedures: customProceduresReducer, editorTab: editorTabReducer, mode:
modeReducer, hoveredTarget: hoveredTargetReducer, stageSize: stageSizeReducer, menus: menuReducer, micIndicator:
micIndicatorReducer, modals: modalReducer, monitors: monitorReducer, monitorLayout: monitorLayoutReducer,
projectChanged: projectChangedReducer, projectState: projectStateReducer, projectTitle: projectTitleReducer,
fontsLoaded: fontsLoadedReducer, restoreDeletion: restoreDeletionReducer, targets: targetReducer, timeout:
timeoutReducer, toolbox: toolboxReducer, vm: vmReducer, vmStatus: vmStatusReducer, userState: userStateReducer});
下面去container/gui.jsx中为里面定义的GUI Component添加loginState这个props,用来标识用户是否登录:
GUI.propTypes = { assetHost: PropTypes.string, children: PropTypes.node, cloudHost: PropTypes.string, error:
PropTypes.oneOfType([PropTypes.ob
ject, PropTypes.string]), fetchingProject: PropTypes.bool, intl: intlShape, isError: PropTypes.bool, isLoading: PropTypes.bool, isScratchDesktop: PropTypes.bool, isShowingProject: PropTypes.bool,
loadingStateVisible: PropTypes.bool, onProjectLoaded: PropTypes.func, onSeeCommunity: PropTypes.func, onStorageInit:
PropTypes.func, onUpdateProjectId: PropTypes.func, onUpdateProjectTitle: PropTypes.func, onUpdateReduxProjectTitle:
PropTypes.func, onVmInit: PropTypes.func, projectHost: PropTypes.string, projectId:
PropTypes.oneOfType([PropTypes.string, PropTypes.number]), projectTitle: PropTypes.string, telemetryModalVisible:
PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired, loginState: PropTypes.bool};
这个`loginState` props的状态值来自于user-state.js中getIsLogined中检测当前的loginState(指state中的)是否等于UserState.LOGINED:
const mapStateToProps = state => { const loadingState = state.scratchGui.projectState.loadingState; const loginState =
state.scratchGui.userState.loginState; return { activeTabIndex: state.scratchGui.editorTab.activeTabIndex,
alertsVisible: state.scratchGui.alerts.visible, backdropLibraryVisible: state.scratchGui.modals.backdropLibrary,
blocksTabVisible: state.scratchGui.editorTab.activeTabIndex === BLOCKS_TAB_INDEX, cardsVisible:
state.scratchGui.cards.visible, connectionModalVisible: state.scratchGui.modals.connectionModal, costumeLibraryVisible:
state.scratchGui.modals.costumeLibrary, costumesTabVisible: state.scratchGui.editorTab.activeTabIndex ===
COSTUMES_TAB_INDEX, error: state.scratchGui.projectState.error, isError: getIsError(loadingState), isFullScreen:
state.scratchGui.mode.isFullScreen, isPlayerOnly: state.scratchGui.mode.isPlayerOnly, isRtl: state.locales.isRtl,
isShowingProject: getIsShowingProject(loadingState), loadingStateVisible: state.scratchGui.modals.loadingProject,
projectId: state.scratchGui.projectState.projectId, soundsTabVisible: state.scratchGui.editorTab.activeTabIndex ===
SOUNDS_TAB_INDEX, targetIsStage: ( state.scratchGui.targets.stage && state.scratchGui.targets.stage.id ===
state.scratchGui.targets.editingTarget ), telemetryModalVisible: state.scratchGui.modals.telemetryModal,
tipsLibraryVisible: state.scratchGui.modals.tipsLibrary, vm: state.scratchGui.vm, loginState: getIsLogined(loginState)
};};
现在container/gui.jsx中定义的Component GUI有了loginState属性了,我们要把它传到menu-bar中,因为我们要在menu-bar中去控制右上角的显示状态。
在这个GUI Component中使用了components/gui/gui.jsx定义的GUIComponent这一component,GUIComponent定义的整个项目的基本样式结构中,可以找到对MenuBar的使用。
首先,在GUIComponent的定义中引入之前定义的`loginState`:
const GUIComponent = props => { const { accountNavOpen, activeTabIndex, alertsVisible, authorId, authorThumbnailUrl,
authorUsername, ba
sePath, backdropLibraryVisible, backpackHost, backpackVisible, blocksTabVisible, cardsVisible, canCreateNew, canEditTitle, canRemix, canSave, canCreateCopy, canShare, canUseCloud, children, connectionModalVisible,
costumeLibraryVisible, costumesTabVisible, enableCommunity, intl, isCreating, isFullScreen, isPlayerOnly, isRtl,
isShared, loading, renderLogin, on
clickAccountNav, onCloseAccountNav, onLogOut, onOpenRegistration, onToggleLoginOpen, onUpdateProjectTitle, on
activateCostumesTab, on activateSoundsTab, on activateTab, on clickLogo, onExtensionButton click, onProjectTelemetryEvent, onRequestCloseBackdropLibrary, onRequestCloseCostumeLibrary, onRequestCloseTelemetryModal,
onSeeCommunity, onShare, onTelemetryModalCancel, onTelemetryModalOptIn, onTelemetryModalOptOut, showComingSoon,
soundsTabVisible, stageSizeMode, targetIsStage, telemetryModalVisible, tipsLibraryVisible, vm, loginState,
..ponentProps } = omit(props, 'dispatch');
...再在使用MenuBar的地方也为MenuBar定义`loginState`属性,它的值就是GUIComponent传进来的`loginState`的值:
accountNavOpen={accountNavOpen}
authorId={authorId}
authorThumbnailUrl={authorThumbnailUrl}
authorUsername={authorUsername}
canCreateCopy={canCreateCopy}
canCreateNew={canCreateNew}
canEditTitle={canEditTitle}
canRemix={canRemix}
canSave={canSave}
canShare={canShare}
className={styles.menuBarPosition}
enableCommunity={enableCommunity}
isShared={isShared}
renderLogin={renderLogin}
showComingSoon={showComingSoon}
on
clickAccountNav={on clickAccountNav} on
clickLogo={on clickLogo} onCloseAccountNav={onCloseAccountNav}
onLogOut={onLogOut}
onOpenRegistration={onOpenRegistration}
onProjectTelemetryEvent={onProjectTelemetryEvent}
onSeeCommunity={onSeeCommunity}
onShare={onShare}
onToggleLoginOpen={onToggleLoginOpen}
onUpdateProjectTitle={onUpdateProjectTitle}
loginState={loginState}
/>
最后修改components/menu-bar.jsx中的MenuBar组件的显示,将右上角替换成:
{this.props.loginState ? ({'scratch-cat'}) :Login}
如果用户已登录,就显示头像和姓名的样式(具体的用户信息需要跟后台打通,我们后面再实现):
否则显示登录按钮:
我们可以通过修改reducers/user-state.js中的loginState的初始值来查看效果:
loginState: UserState.NOT_LOGINED
loginState: UserState.LOGINED这个值我们会在后面根据用户登录的token去获取。
为了与项目整体风格一致,我们修改这个登录按钮的样式,在menu-bar目录中添加login-button.css和login-button.jsx文件,内容分别如下:
@i
mport ".._.._css/colors.css";.login-button { background: $data-primary;} import classNames from 'classnames';import {FormattedMessage} from 'react-intl';import PropTypes from
'prop-types';import React from 'react';import Button from '.._button/button.jsx';import styles from
'./login-button.css';const LoginButton = ({ className, on
click}) => ( styles.loginButton )} on
click={on click}>
ription="Label for login" id="gui.menuBar.login" /> );LoginButton.propTypes = { className: PropTypes.string, on
click: PropTypes.func};LoginButton.defaultProps = { on
click: () => {}};export default LoginButton;然后在menu-bar.jsx中如下使用: 这样看起来就好看多了:
好了,这里接入完成后,我们下一步就可以实现一个后台系统,然后对接后台系统登录和获取用户信息了。
我们下一章继续。