Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import crypto from 'crypto'
- import electron from 'electron'
- import { Component } from 'react'
- import { func, bool, string, object } from 'prop-types'
- import ms from 'ms'
- import makeUnique from 'make-unique'
- import isDev from 'electron-is-dev'
- import setRef from 'react-refs'
- import Identicon from 'identicon.js'
- import debug from '../../utils/debug'
- import {
- wrapStyle,
- listStyle,
- itemStyle,
- helperStyle
- } from '../../styles/components/feed/switcher'
- import Clear from '../../vectors/clear'
- import Avatar from './avatar'
- class Switcher extends Component {
- state = {
- teams: [],
- scope: null,
- updateFailed: false,
- initialized: false,
- syncInterval: '5s',
- queue: []
- }
- remote = electron.remote || false
- ipcRenderer = electron.ipcRenderer || false
- setReference = setRef.bind(this)
- load = file => {
- if (electron.remote) {
- return electron.remote.require(file)
- }
- return null
- }
- configUtils = this.load('./utils/config')
- // Ensure that config doesn't get checked when the
- // file is updated from this component
- savingConfig = false
- // ;global.timer 何時會 set:
- // componentdidmount 時侯就會執行 listtimer 而佢入面就會 set 佐 global.timer
- showWindow = () => {
- debug('switcher', 'showWindow', 'global.timer:', global.timer, 'this.state.syncInterval:', this.state.syncInterval)
- // 當曾經 componentdidmount 和曾經 hide 過 window 後,再 show window 時執行
- if (global.timer && this.state.syncInterval !== '5s') {
- // 清除 timer
- debug('switcher', 'showWindow', 'clear timer')
- // Refresh the teams and events when the window gets
- // shown, so that they're always up-to-date
- // 載入多次 teams
- debug('switcher', 'showWindow', 'loadTeams')
- this.loadTeams()
- // Restart the timer so we keep everything in sync every 5s
- // 開返個 timer
- debug('switcher', 'showWindow', 'start the timer with 5s')
- this.listTimer()
- // 因為已經 show windows, 所以 timer 每五秒更新一次
- this.setState({ syncInterval: '5s' })
- }
- }
- // 若果此時 window 是 show 的狀態
- // hide window 時會 取消 timer, 重新啟用 timer 為每 5m 執行一次
- hideWindow = () => {
- debug('switcher', 'hideWindow', 'global.timer:', global.timer, 'this.state.syncInterval:', this.state.syncInterval)
- if (global.timer && this.state.syncInterval !== '5m') {
- debug('switcher', 'hideWindow', 'clear timer')
- // Restart the timer so we keep everything in sync every 5m
- debug('switcher', 'hideWindow', 'start the timer with 5m')
- this.listTimer()
- this.setState({ syncInterval: '5m' })
- }
- }
- // 當 switcher 收到 new props 時, 執行 changeScope
- // eslint-disable-next-line react/no-deprecated
- componentWillReceiveProps({ activeScope }) {
- debug('switcher', 'componentWillReceiveProps')
- if (activeScope) {
- debug('switcher', 'componentWillReceiveProps', 'changeScope with activeScope')
- this.changeScope(activeScope, true, true, true)
- return
- }
- if (this.state.scope !== null) {
- return
- }
- debug('switcher', 'componentWillReceiveProps', 'this.state.scope:', this.state.scope)
- debug('switcher', 'componentWillReceiveProps', 'set state.scope to default scope')
- this.setState({
- scope: this.props.defaultScope
- })
- }
- // 主要作用係 set global.timer
- // 而 global.timer 的作用就係執行 this.loadteam
- listTimer = () => {
- debug('switcher:listTimer', 'listTimer')
- const { getCurrentWindow } = this.remote
- const { isVisible } = getCurrentWindow()
- const time = isVisible() ? '5s' : '5m'
- debug('switcher:listTimer', 'refresh time:', time)
- debug('switcher:listTimer', 'clear timer', global.timer)
- clearTimeout(global.timer)
- debug('switcher:listTimer', 'start the timeout')
- global.timer = setTimeout(async () => {
- try {
- // It's important that this is being `await`ed
- debug('switcher:listTimer', 'loadTeams')
- await this.loadTeams()
- } catch (error) {
- if (isDev) {
- console.error(error)
- }
- }
- // Once everything is done or has failed,
- // try it again after some time.
- debug('switcher:listTimer', 'call listTimer again!!!')
- this.listTimer()
- }, ms(time))
- debug('switcher:listTimer', 'global.timer:', global.timer)
- }
- // eslint-disable-next-line react/no-deprecated
- componentWillMount () {
- }
- // 增加 showWindow 同 hideWindow 的 listener
- // eslint-disable-next-line react/no-deprecated
- async componentDidMount() {
- debug('switcher', 'componentDidMount')
- // Support SSR
- if (!this.remote || typeof window === 'undefined') {
- return
- }
- const currentWindow = this.remote.getCurrentWindow()
- if (!currentWindow) {
- return
- }
- debug('switcher:componentWillMount', 'add show/hide listener')
- currentWindow.removeListener('show', this.showWindow)
- currentWindow.removeListener('hide', this.hideWindow)
- currentWindow.on('show', this.showWindow)
- currentWindow.on('hide', this.hideWindow)
- window.addEventListener('beforeunload', () => {
- debug('switcher', 'beforeunload', 'remove show/hide listener')
- currentWindow.removeListener('show', this.showWindow)
- currentWindow.removeListener('hide', this.hideWindow)
- })
- // 當收到 update-failed 就會 set state, 由更新委員會傳過黎
- // Show a UI banner if the installation
- // of an update failed
- debug('switcher', 'componentDidMount', 'setup update-failed event')
- this.ipcRenderer.on('update-failed', () => {
- this.setState({ updateFailed: true })
- })
- // Only start updating teams once they're loaded!
- // This needs to be async so that we can already
- // start the state timer below for the data that's already cached
- if (!this.props.online) {
- debug('switcher', 'componentDidMount', 'not online, start the list timer')
- this.listTimer()
- return
- }
- debug('switcher', 'componentDidMount', 'loadTeams(firstLoad) (async)')
- this.loadTeams(true)
- .then(() => {
- debug('switcher', 'componentDidMount', 'call listTimer, after loadTeams(firstLoad) (async)')
- this.listTimer()
- })
- .catch(() => {
- debug('switcher', 'componentDidMount', 'call listTimer, after loadTeams(firstLoad) (async)')
- this.listTimer()
- })
- // Check the config for `currentTeam`
- debug('switcher', 'componentDidMount', 'checkCurrentTeam')
- await this.checkCurrentTeam()
- // Update the scope if the config changes
- debug('switcher', 'componentDidMount', 'listenToConfig')
- this.listenToConfig()
- }
- componentWillUnmount () {
- debug('switcher:componentWillUnmount', 'componentWillUnmount')
- // Support SSR
- if (!this.remote || typeof window === 'undefined') {
- return
- }
- const currentWindow = this.remote.getCurrentWindow()
- if (!currentWindow) {
- return
- }
- currentWindow.removeListener('show', this.showWindow)
- currentWindow.removeListener('hide', this.hideWindow)
- }
- listenToConfig() {
- debug('switcher', 'listenToConfig')
- if (!this.ipcRenderer) {
- return
- }
- debug('switcher', 'listenToConfig', 'setup config-changed event')
- this.ipcRenderer.on('config-changed', async (event, config) => {
- if (this.state.teams.length === 0) {
- return
- }
- if (this.savingConfig) {
- this.savingConfig = false
- return
- }
- // Load the teams in case there is a brand new team
- debug('switcher', 'config-changed', 'loadTeams')
- await this.loadTeams()
- // Check for the `currentTeam` property in the config
- debug('switcher', 'config-changed', 'checkCurrentTeam')
- await this.checkCurrentTeam(config)
- })
- }
- resetScope() {
- debug('switcher', 'resetScope')
- this.changeScope({ id: this.props.defaultScope })
- }
- async checkCurrentTeam(config) {
- debug('switcher', 'checkCurrentTeam')
- if (!this.remote) {
- return
- }
- if (!config) {
- debug('switcher', 'checkCurrentTeam', 'getConfig')
- const { getConfig } = this.remote.require('./utils/config')
- try {
- config = await getConfig()
- } catch (error) {
- // The config is not valid, so no need to update
- // the current team.
- console.log(error)
- return
- }
- debug('switcher', 'checkCurrentTeam', 'getConfig end', config)
- }
- if (!config.currentTeam) {
- debug('switcher', 'checkCurrentTeam', 'resetScope')
- this.resetScope()
- return
- }
- // Legacy config
- if (typeof config.currentTeam === 'object') {
- debug('switcher', 'checkCurrentTeam', 'changeScope', config.currentTeam)
- this.changeScope(config.currentTeam, true)
- return
- }
- debug('switcher', 'checkCurrentTeam', 'getTeams')
- const { teams } = this.getTeams()
- debug('switcher', 'checkCurrentTeam', 'getTeams end')
- const related = teams.find(team => team.id === config.currentTeam)
- debug('switcher', 'checkCurrentTeam', 'get related team object', { teamId: config.currentTeam })
- // The team was deleted
- if (!related) {
- debug('switcher', 'checkCurrentTeam', 'call resetScope because no found related team object')
- this.resetScope()
- return
- }
- debug('switcher', 'checkCurrentTeam', 'call changeScope')
- this.changeScope(related, true)
- }
- async saveConfig(newConfig) {
- debug('switcher', 'saveConfig')
- const { saveConfig } = this.configUtils
- // Ensure that we're not handling the
- // event triggered by changes made to the config
- // because the changes were triggered manually
- // inside this app
- this.savingConfig = true
- // Then update the config file
- debug('switcher', 'saveConfig', 'call saveConfig')
- await saveConfig(newConfig, 'config')
- }
- generateAvatar = (str) => {
- debug('switcher:generateAvatar', 'str:', str)
- const hash = crypto.createHash('md5')
- hash.update(str)
- const imgData = new Identicon(hash.digest('hex')).toString()
- debug('switcher:generateAvatar', 'end', imgData.substr(0, 10))
- return 'data:image/png;base64,' + imgData
- }
- getTeams = () => {
- debug('switcher', 'getTeams')
- const result = {
- teams: [
- {
- id: 'aaa',
- name: 'AAA',
- avatarUrl: this.generateAvatar('aaa111')
- },
- {
- id: 'bbb',
- name: 'BBB',
- avatarUrl: this.generateAvatar('bbb222')
- }
- ]
- }
- debug('switcher', 'getTeams end')
- return result
- }
- merge(first, second) {
- debug('switcher', 'merge')
- const merged = first.concat(second)
- return makeUnique(merged, (a, b) => a.id === b.id)
- }
- async haveUpdated(data) {
- debug('switcher', 'haveUpdated')
- const newData = JSON.parse(JSON.stringify(data))
- let currentData = JSON.parse(JSON.stringify(this.state.teams))
- if (currentData.length > 0) {
- // Remove teams that the user has left
- currentData = currentData.filter(team => {
- return Boolean(newData.find(item => item.id === team.id))
- })
- }
- const ordered = this.merge(currentData, newData)
- debug('switcher', 'haveUpdated', 'after merge')
- return ordered
- }
- // 邊到用到, showWindow, listTimer, componentDidMount
- // 載入 teams 並放到 state
- // 有時用 setState
- // 有時用 setTeams
- async loadTeams(firstLoad) {
- debug('switcher:loadTeams', 'firstLoad:', firstLoad)
- if (!this.remote) {
- return
- }
- const currentWindow = this.remote.getCurrentWindow()
- // 若 window 處於隱藏狀態, 並且已經經過 componentdidmount initialized
- // 那就 setTeams null
- // 為什麼呢? 為了清除所有 teams 當 window 處於隱藏狀態
- // If the window isn't visible, don't pull the teams
- // Ensure to always load the first chunk
- if (!currentWindow.isVisible() && this.state.initialized) {
- if (this.props.setTeams) {
- // When passing `null`, the feed will only
- // update the events, not the teams
- debug('switcher:loadTeams', 'props.setTeams null means tell feed.js update events only')
- await this.props.setTeams(null, firstLoad)
- }
- return
- }
- // Call api 返回一個 object
- // 裡面包含 teams
- debug('switcher:loadTeams', 'getTeams')
- const data = this.getTeams()
- if (!data || !data.teams) {
- return
- }
- const { teams } = data
- const updated = await this.haveUpdated(teams)
- const scopeExists = updated.find(team => {
- return this.state.scope === team.id
- })
- if (!scopeExists) {
- debug('switcher:loadTeams', 'scope (team id) not found in updated teams, so reset scope')
- this.resetScope()
- }
- if (updated) {
- debug('switcher:loadTeams', 'set temas state to updated teams')
- this.setState({ teams: updated })
- }
- if (this.props.setTeams) {
- // When passing `null`, the feed will only
- // update the events, not the teams
- debug('switcher:loadTeams', 'call props.setTeams to tell feed.js')
- await this.props.setTeams(updated || null, firstLoad)
- }
- }
- componentDidUpdate() {
- const aaa = parseInt(Math.random() * 1000, 10)
- console.log('componentDidUpdate1', aaa, this.state)
- debug('switcher', 'componentDidUpdate start')
- const { teams } = this.state
- console.log('componentDidUpdate2', aaa)
- debug('switcher', 'componentDidUpdate', 'queue length:', this.state.queue.length)
- while (this.state.queue.length > 0) {
- const { queue } = this.state
- queue.shift()()
- this.setState({ queue })
- }
- console.log('componentDidUpdate3', aaa)
- debug('switcher', 'componentDidUpdate', 'queue length:', this.state.queue.length)
- debug('switcher', 'componentDidUpdate', 'this.state.initialized:', this.state.initialized)
- if (this.state.initialized) {
- return
- }
- console.log('componentDidUpdate4', aaa)
- const teamsCount = teams.length
- debug('switcher', 'componentDidUpdate', 'teamsCount:', teamsCount)
- if (teamsCount === 0) {
- return
- }
- console.log('componentDidUpdate5', aaa)
- const when = 100 + 100 * teamsCount + 600
- console.log('componentDidUpdate6', aaa)
- debug('switcher', 'componentDidUpdate', 'ready to set initialized state to true')
- setTimeout(() => {
- // Ensure that the animations for the teams
- // fading in works after recovering from offline mode
- if (!this.props.online) {
- return
- }
- console.log('set initialized to true', aaa, this.state)
- debug('switcher', 'componentDidUpdate', 'set initialized state to true')
- this.setState({
- initialized: true
- })
- }, when)
- console.log('componentDidUpdate7', aaa)
- debug('switcher', 'componentDidUpdate end')
- }
- async updateConfig(team) {
- debug('switcher', 'updateConfig')
- if (!this.remote) {
- return
- }
- const info = {
- currentTeam: team
- }
- // And then update the config file
- debug('switcher', 'updateConfig', 'this.saveConfig')
- await this.saveConfig(info)
- }
- // 當 switcher 收到 new props 時
- // activeScope 有改動時
- // 就會 changeScope
- // saveToConfig: 會將 team 儲存到 config file,但需要經過 queue 排序才會執行
- // byHand: queue 會用到 (但唔知用黎做乜)
- // noFeed: true 不會執行 this.props.setFeedScope
- changeScope(team, saveToConfig, byHand, noFeed) {
- debug('switcher:changeScope', { saveToConfig, byHand, noFeed })
- // If the clicked item in the team switcher is
- // already the active one, don't do anything
- debug('switcher:changeScope', 'change to the same scope?', this.state.scope === team.id)
- if (this.state.scope === team.id) {
- return
- }
- if (!noFeed && this.props.setFeedScope) {
- // Load different messages into the feed
- debug('switcher:changeScope', 'call props.setFeedScope', 'team.id:', team.id)
- this.props.setFeedScope(team.id)
- }
- // Make the team/user icon look active by
- // syncing the scope with the feed
- debug('switcher:changeScope', 'set state scope to', 'team.id:', team.id)
- this.setState({ scope: team.id })
- // Save the new `currentTeam` to the config
- if (saveToConfig) {
- debug('switcher:changeScope', 'add queue function updateConfig with params')
- const queueFunction = (fn, context, params) => {
- return () => {
- fn.apply(context, params)
- }
- }
- this.setState({
- queue: this.state.queue.concat([
- queueFunction(this.updateConfig, this, [team, byHand])
- ])
- })
- }
- }
- openMenu = () => {
- // The menu toggler element has children
- // we have the ability to prevent the event from
- // bubbling up from those, but we need to
- // use `this.menu` to make sure the menu always gets
- // bounds to the parent
- const { bottom, left, height, width } = this.menu.getBoundingClientRect()
- const sender = electron.ipcRenderer || false
- if (!sender) {
- return
- }
- sender.send('open-menu', {
- x: left,
- y: bottom,
- height,
- width
- })
- }
- renderItem() {
- // eslint-disable-next-line new-cap
- return ({ team }) => {
- const isActive = this.state.scope === team.id
- const index = this.state.teams.indexOf(team)
- const shouldScale = !this.state.initialized
- const { darkBg } = this.props
- const classes = []
- if (isActive) {
- classes.push('active')
- }
- if (darkBg) {
- classes.push('dark')
- }
- const clicked = event => {
- event.preventDefault()
- this.changeScope(team, true, true)
- }
- return (
- <li onClick={clicked} className={classes.join(' ')} key={team.id}>
- <Avatar
- team={team}
- scale={shouldScale}
- delay={index}
- avatarUrl={team.avatarUrl}
- />
- <style jsx>{itemStyle}</style>
- </li>
- )
- }
- }
- renderTeams() {
- const Item = this.renderItem()
- return this.state.teams.map((team, index) => (
- <Item key={team.id} index={index} team={team} />
- ))
- }
- renderList() {
- const teams = this.renderTeams()
- // eslint-disable-next-line new-cap
- return () => (
- <ul>
- {teams}
- <style jsx>{listStyle}</style>
- </ul>
- )
- }
- retryUpdate = () => {
- if (!this.remote) {
- return
- }
- const { app } = this.remote
- // Restart the application
- app.relaunch()
- app.exit(0)
- }
- closeUpdateMessage = () => {
- this.setState({
- updateFailed: false
- })
- }
- render() {
- console.log('switcher render', this.state, '\n\n')
- debug('switcher', 'render')
- const List = this.renderList()
- const { updateFailed } = this.state
- const { darkBg, online } = this.props
- return (
- <div>
- {updateFailed && (
- <span className="update-failed">
- <p>
- The app failed to update! —{' '}
- <a onClick={this.retryUpdate}>Retry?</a>
- </p>
- <Clear onClick={this.closeUpdateMessage} color="#fff" />
- </span>
- )}
- <aside className={darkBg ? 'dark' : ''}>
- {online ? (
- <div className="list-container" ref={this.setReference} name="list">
- <div className="list-scroll">
- <List
- axis="x"
- lockAxis="x"
- helperClass="switcher-helper"
- lockToContainerEdges={true}
- lockOffset="0%"
- />
- </div>
- </div>
- ) : (
- <p className="offline">{'You are offline'}</p>
- )}
- <a
- className="toggle-menu"
- onClick={this.openMenu}
- onContextMenu={this.openMenu}
- ref={this.setReference}
- name="menu"
- >
- <i />
- <i />
- <i />
- </a>
- </aside>
- <style jsx>{wrapStyle}</style>
- <style jsx global>
- {helperStyle}
- </style>
- </div>
- )
- }
- }
- Switcher.propTypes = {
- setFeedScope: func,
- setTeams: func,
- activeScope: object,
- darkBg: bool,
- online: bool,
- defaultScope: string
- }
- export default Switcher
Add Comment
Please, Sign In to add comment