【scratch二次开发教程 第4章】scratch-gui中集成自定义用户系统(一)


作者:管理员
分类:scratch 二开
发布时间:2019-12-14 10:53:04
热度:1868

					

【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 = object.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.object, 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, basePath, 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, onclickAccountNav, onCloseAccountNav, onLogOut, onOpenRegistration, onToggleLoginOpen,

onUpdateProjectTitle, onactivateCostumesTab, onactivateSoundsTab, onactivateTab, onclickLogo, onExtensionButtonclick,

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} 

onclickAccountNav={onclickAccountNav} 

onclickLogo={onclickLogo}

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文件,内容分别如下:

@import ".._.._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, onclick}) => (