<commit_before>import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

import axios from 'axios'
import storeConfig from './config.js'

function request({dispatch, state}, method, url, args, callback) {
    axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('token');
    return method(state.apiUrl + url, args)
        .then(function(response) {
            return callback(response)
        })
        .catch(function (error) {
            dispatch('handleError', {'name': url, 'error': error})
        })
}

function get({dispatch, state}, url, args, callback) {
    return request({dispatch, state}, axios.get, url, args, callback)
}

function post({dispatch, state}, url, args, callback) {
    return request({dispatch, state}, axios.post, url, args, callback)
}

function put({dispatch, state}, url, args, callback) {
    return request({dispatch, state}, axios.put, url, args, callback)
}

export default new Vuex.Store({
    state: {
        apiUrl: "",
        token: null,
        reCaptchaSiteKey: "",
        loading: false,
        error: false,
        account: {
            username: ""
        },
        wallet: [
            {
                balance_pretty: ""
            }
        ],
        ticket: {},
        vps: {},
        profile: {},
        ticketMessages: {},
        services: {"paging": {"total_pages": 1}},
        pagination: {"paging": {"total_pages": 1}},
        count: 1,
        toolbarTitle: "",
        toolbarTitleArgs: {},
        version: "",
        language: "",
        meta: {},
        logout: false
    },
    actions: {
        boot ({dispatch, state, commit}) {
            commit('setVersion', "v" + storeConfig.version)
            commit('setReCaptchaSiteKey', storeConfig.reCaptchaSiteKey)
        },
        logOut ({dispatch, state, commit}) {
            localStorage.removeItem("token");
            commit('setAccount', {})
            commit('setWallet', [{balance_pretty: ""}])
            commit('setServices', [])
            commit('setToken', null)
            commit('setError', false)
            commit('setLogout', true)
        },
        handleError({dispatch, state, commit}, args) {
            let error = args.error, msg = error.message, response = error.response, fn = args.name;
            console.log(msg + ' while calling ' + fn);
            if (typeof response !== 'undefined') {
                switch (response.status) {
                    case 401:
                    case 403:
                        if (fn !== 'accountLogin') {
                            commit('setError', 'invalidtoken')
                        }
                        break
                    case 429:
                        commit('setError', 'ratelimit')
                        break
                    default:
                        commit('setError', 'unknown')
                        break
                }
            } else {
                //no response == network error
                commit('setError', 'network')
            }
        },
        //return true if not authorized
        checkSession ({dispatch, state}) {
            state.apiUrl = storeConfig.apiUrl

            //logout, force login
            if (state.logout) {
                return true
            }
            //no token, force login
            if (localStorage.getItem("token") === null) {
                return true
            } else {
                dispatch('accountInfo').then((res) => {
                    //if no proper response then "log out" and force new login
                    if (res !== true) {
                        localStorage.removeItem("token");
                        return true
                    }
                })
                dispatch('walletInfo')
            }
        },
        accountLogin ({commit, dispatch, state}, args) {
            return post({dispatch, state}, 'auth/login', args, function (res) {
                if (typeof res.data.token !== 'undefined') {
                    localStorage.setItem('token', res.data.token);
                    return true;
                }
            })
        },
        accountRegister({state, dispatch}, args) {
            return post({dispatch, state}, 'auth/register', args, function (res) {
                return true
            })
        },
        accountInfo ({commit, dispatch, state}) {
            return get({dispatch, state}, 'me', {}, function (res) {
                if (typeof res.data.username !== 'undefined') {
                    commit('setAccount', res.data)
                    return true
                }
            })
        },
        walletInfo ({commit, dispatch, state}) {
            return get({dispatch, state}, 'me/balance', {}, function (res) {
                if (typeof res.data[0].balance !== 'undefined') {
                    commit('setWallet', res.data)
                }
            })
        },
        ticketInfo ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'help/ticket/' + args.id, {}, function (res) {
                if (typeof res.data.id !== 'undefined') {
                    //http://stackoverflow.com/a/784547/1351857
                    res.data.message = res.data.message.replace(/(?:\r\n|\r|\n)/g, '
');
                    commit('setTicket', res.data)
                }
            })
        },
        ticketMessages ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'help/ticket/' + args.id + '/message', {}, function (res) {
                for (let i = 0; i < res.data.messages.length; i++) {
                    res.data.messages[i].message = res.data.messages[i].message.replace(/(?:\r\n|\r|\n)/g, '
')
                }
                commit('setTicketMessages', res.data)
            })
        },
        ticketAddMessage ({commit, dispatch, state}, args) {
            return post({dispatch, state}, 'help/ticket/' + args.id + '/message', args, function (res) {
                if (typeof res.data.error !== false) {
                    //commit('setTicketMessages', res.data)
                    return true
                }
            })
        },
        servicesList ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'vps?page=' + args.page + '&limit=' + args.limit, {}, function (res) {
                if (typeof res.data !== 'undefined') {
                    commit('setServices', res.data)
                }
            })
        },
        paginationList ({commit, dispatch, state}, args) {
            return get({dispatch, state}, args.url + '?page=' + args.page + '&limit=' + args.limit, {}, function (res) {
                if (typeof res.data !== 'undefined') {
                    if (res.data.error) {
                        res.data.paging = {}
                        res.data.paging.total_pages = 1
                    }
                    commit('setPagination', res.data)
                }
            })
        },
        vpsInfo ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'vps/' + args.id, {}, function (res) {
                if (typeof res.data.status !== 'undefined') {
                    if (typeof state.vps.status !== 'undefined' && state.vps.status === 'running') {
                        let s = res.data.uptime_s - state.vps.uptime_s;
                        if (s > 0) {
                            let netInChange = res.data.net_in_b - state.vps.net_in_b;
                            let netOutChange = res.data.net_out_b - state.vps.net_out_b;
                            res.data.net_in_bps = netInChange / s;
                            res.data.net_out_bps = netOutChange / s;
                            if (state.vps.virt == 'kvm') {
                                let diskReadChange = res.data.disk_read_b - state.vps.disk_read_b;
                                let diskWriteChange = res.data.disk_write_b - state.vps.disk_write_b;
                                res.data.disk_read_bps = diskReadChange / s;
                                res.data.disk_write_bps = diskWriteChange / s;
                            }
                        } else {
                            res.data.net_in_bps = state.vps.net_int_bps;
                            res.data.net_out_bps = state.vps.net_out_bps;
                            if (state.vps.virt == 'kvm') {
                                res.data.disk_read_bps = state.vps.disk_read_bps;
                                res.data.disk_write_bps = state.vps.disk_write_bps;
                            }
                        }
                    } else {
                        res.data.net_in_bps = -1;
                        res.data.net_out_bps = -1;
                        res.data.disk_read_bps = -1;
                        res.data.disk_write_bps = -1;
                    }
                    res.data.ip = state.vps.ip
                    commit('setVps', res.data)
                }
            })
        },
        vpsOn ({commit, dispatch, state}, args) {
            return post({dispatch, state}, 'vps/' + args.id + '/on', {}, function (res) {
                return true
            })
        },
        vpsOff ({commit, dispatch, state}, args) {
            return post({dispatch, state}, 'vps/' + args.id + '/off', {}, function (res) {
                return true
            })
        },
        vpsIps({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'vps/' + args.id + '/ip', {}, function (res) {
                commit('setVpsIps', res.data)
            })
        },
        vpsChangeInfo({commit, dispatch, state}, args) {
            return put({dispatch, state}, 'vps/' + args.id, args, function (res) {
                state.vps.name = args.name
            })
        },
        profileInfo ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'me', {}, function (res) {
                if (typeof res.data.id !== 'undefined') {
                    commit('setProfile', res.data)
                }
            })
        }
    },
    mutations: {
        setVersion (state, newVersion) {
            state.version = newVersion;
        },
        setToken (state, newToken) {
            state.token = newToken;
        },
        setError (state, newErrorState) {
            state.error = newErrorState;
        },
        setReCaptchaSiteKey (state, newKey) {
            state.reCaptchaSiteKey = newKey;
        },
        setUsername (state, newUsername) {
            state.account.username = newUsername;
        },
        setAccount (state, newAccount) {
            state.account = newAccount;
        },
        setWallet (state, newWallet) {
            state.wallet = newWallet;
        },
        setTicket (state, newTicket) {
            state.ticket = newTicket;
        },
        setVps (state, newVps) {
            state.vps = newVps;
        },
        setVpsIps(state, newIps) {
            state.vps.ip = {}
            state.vps.ip.main = newIps.main
            state.vps.ip.additional = newIps.additional
        },
        setProfile (state, newProfile) {
            state.profile = newProfile
        },
        setTicketMessages (state, newTicketMessages) {
            state.ticketMessages = newTicketMessages;
        },
        setServices (state, newServices) {
            state.services = newServices;
        },
        setPagination (state, newPagination) {
            state.pagination = newPagination;
        },
        setToolbarTitle (state, newTitle) {
            state.toolbarTitle = newTitle;
        },
        setToolbarTitleArgs (state, newTitleArgs) {
            state.toolbarTitleArgs = newTitleArgs;
        },
        setLanguage (state, newLanguage) {
            state.language = newLanguage;
            Vue.config.lang = newLanguage;
        },
        setMeta(state, newMeta) {
            state.meta = newMeta;
        },
        setLoading (state) {
            state.loading = true;
        },
        setLoaded (state) {
            state.loading = false;
        },
        setLogout(state, newLogout) {
            state.logout = newLogout;
        },
        setTitle (state, newTitle) {
            document.title = newTitle;
        },
        setDescription (state, newDescription) {
            document.head.querySelector('meta[name=description]').content = newDescription;
        },
        setKeywords (state, newKeywords) {
            document.head.querySelector('meta[name=keywords]').content = newKeywords;
        }
    }
})
<commit_msg>Pretty requests urls<commit_after>import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

import axios from 'axios'
import storeConfig from './config.js'

function request({dispatch, state}, method, url, args, callback) {
    for (let key in args) {
        let find = '{' + key + '}';
        while (url.indexOf(find) !== -1) {
            url = url.replace(find, args[key]);
        }
    }
    axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('token');
    return method(state.apiUrl + url, args)
        .then(function(response) {
            return callback(response)
        })
        .catch(function (error) {
            dispatch('handleError', {'name': url, 'error': error})
        })
}

function get({dispatch, state}, url, args, callback) {
    return request({dispatch, state}, axios.get, url, args, callback)
}

function post({dispatch, state}, url, args, callback) {
    return request({dispatch, state}, axios.post, url, args, callback)
}

function put({dispatch, state}, url, args, callback) {
    return request({dispatch, state}, axios.put, url, args, callback)
}

export default new Vuex.Store({
    state: {
        apiUrl: "",
        token: null,
        reCaptchaSiteKey: "",
        loading: false,
        error: false,
        account: {
            username: ""
        },
        wallet: [
            {
                balance_pretty: ""
            }
        ],
        ticket: {},
        vps: {},
        profile: {},
        ticketMessages: {},
        services: {"paging": {"total_pages": 1}},
        pagination: {"paging": {"total_pages": 1}},
        count: 1,
        toolbarTitle: "",
        toolbarTitleArgs: {},
        version: "",
        language: "",
        meta: {},
        logout: false
    },
    actions: {
        boot ({dispatch, state, commit}) {
            commit('setVersion', "v" + storeConfig.version)
            commit('setReCaptchaSiteKey', storeConfig.reCaptchaSiteKey)
        },
        logOut ({dispatch, state, commit}) {
            localStorage.removeItem("token");
            commit('setAccount', {})
            commit('setWallet', [{balance_pretty: ""}])
            commit('setServices', [])
            commit('setToken', null)
            commit('setError', false)
            commit('setLogout', true)
        },
        handleError({dispatch, state, commit}, args) {
            let error = args.error, msg = error.message, response = error.response, url = args.name;
            console.log(msg + ' while calling ' + url);
            if (typeof response !== 'undefined') {
                switch (response.status) {
                    case 401:
                    case 403:
                        if (url !== 'auth/login') {
                            commit('setError', 'invalidtoken')
                        }
                        break
                    case 429:
                        commit('setError', 'ratelimit')
                        break
                    default:
                        commit('setError', 'unknown')
                        break
                }
            } else {
                //no response == network error
                commit('setError', 'network')
            }
        },
        //return true if not authorized
        checkSession ({dispatch, state}) {
            state.apiUrl = storeConfig.apiUrl

            //logout, force login
            if (state.logout) {
                return true
            }
            //no token, force login
            if (localStorage.getItem("token") === null) {
                return true
            } else {
                dispatch('accountInfo').then((res) => {
                    //if no proper response then "log out" and force new login
                    if (res !== true) {
                        localStorage.removeItem("token");
                        return true
                    }
                })
                dispatch('walletInfo')
            }
        },
        accountLogin ({commit, dispatch, state}, args) {
            return post({dispatch, state}, 'auth/login', args, function (res) {
                if (typeof res.data.token !== 'undefined') {
                    localStorage.setItem('token', res.data.token);
                    return true;
                }
            })
        },
        accountRegister({state, dispatch}, args) {
            return post({dispatch, state}, 'auth/register', args, function (res) {
                return true
            })
        },
        accountInfo ({commit, dispatch, state}) {
            return get({dispatch, state}, 'me', {}, function (res) {
                if (typeof res.data.username !== 'undefined') {
                    commit('setAccount', res.data)
                    return true
                }
            })
        },
        walletInfo ({commit, dispatch, state}) {
            return get({dispatch, state}, 'me/balance', {}, function (res) {
                if (typeof res.data[0].balance !== 'undefined') {
                    commit('setWallet', res.data)
                }
            })
        },
        ticketInfo ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'help/ticket/{id}', args, function (res) {
                if (typeof res.data.id !== 'undefined') {
                    //http://stackoverflow.com/a/784547/1351857
                    res.data.message = res.data.message.replace(/(?:\r\n|\r|\n)/g, '
');
                    commit('setTicket', res.data)
                }
            })
        },
        ticketMessages ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'help/ticket/{id}/message', args, function (res) {
                for (let i = 0; i < res.data.messages.length; i++) {
                    res.data.messages[i].message = res.data.messages[i].message.replace(/(?:\r\n|\r|\n)/g, '
')
                }
                commit('setTicketMessages', res.data)
            })
        },
        ticketAddMessage ({commit, dispatch, state}, args) {
            return post({dispatch, state}, 'help/ticket/{id}/message', args, function (res) {
                if (typeof res.data.error !== false) {
                    //commit('setTicketMessages', res.data)
                    return true
                }
            })
        },
        servicesList ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'vps?page={page}&limit={limit}', args, function (res) {
                if (typeof res.data !== 'undefined') {
                    commit('setServices', res.data)
                }
            })
        },
        paginationList ({commit, dispatch, state}, args) {
            return get({dispatch, state}, '{url}?page={page}&limit={limit}', args, function (res) {
                if (typeof res.data !== 'undefined') {
                    if (res.data.error) {
                        res.data.paging = {}
                        res.data.paging.total_pages = 1
                    }
                    commit('setPagination', res.data)
                }
            })
        },
        vpsInfo ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'vps/{id}', args, function (res) {
                if (typeof res.data.status !== 'undefined') {
                    if (typeof state.vps.status !== 'undefined' && state.vps.status === 'running') {
                        let s = res.data.uptime_s - state.vps.uptime_s;
                        if (s > 0) {
                            let netInChange = res.data.net_in_b - state.vps.net_in_b;
                            let netOutChange = res.data.net_out_b - state.vps.net_out_b;
                            res.data.net_in_bps = netInChange / s;
                            res.data.net_out_bps = netOutChange / s;
                            if (state.vps.virt == 'kvm') {
                                let diskReadChange = res.data.disk_read_b - state.vps.disk_read_b;
                                let diskWriteChange = res.data.disk_write_b - state.vps.disk_write_b;
                                res.data.disk_read_bps = diskReadChange / s;
                                res.data.disk_write_bps = diskWriteChange / s;
                            }
                        } else {
                            res.data.net_in_bps = state.vps.net_int_bps;
                            res.data.net_out_bps = state.vps.net_out_bps;
                            if (state.vps.virt == 'kvm') {
                                res.data.disk_read_bps = state.vps.disk_read_bps;
                                res.data.disk_write_bps = state.vps.disk_write_bps;
                            }
                        }
                    } else {
                        res.data.net_in_bps = -1;
                        res.data.net_out_bps = -1;
                        res.data.disk_read_bps = -1;
                        res.data.disk_write_bps = -1;
                    }
                    res.data.ip = state.vps.ip
                    commit('setVps', res.data)
                }
            })
        },
        vpsOn ({commit, dispatch, state}, args) {
            return post({dispatch, state}, 'vps/{id}/on', args, function (res) {
                return true
            })
        },
        vpsOff ({commit, dispatch, state}, args) {
            return post({dispatch, state}, 'vps/{id}/off', args, function (res) {
                return true
            })
        },
        vpsIps({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'vps/{id}/ip', args, function (res) {
                commit('setVpsIps', res.data)
            })
        },
        vpsChangeInfo({commit, dispatch, state}, args) {
            return put({dispatch, state}, 'vps/{id}', args, function (res) {
                state.vps.name = args.name
            })
        },
        profileInfo ({commit, dispatch, state}, args) {
            return get({dispatch, state}, 'me', {}, function (res) {
                if (typeof res.data.id !== 'undefined') {
                    commit('setProfile', res.data)
                }
            })
        }
    },
    mutations: {
        setVersion (state, newVersion) {
            state.version = newVersion;
        },
        setToken (state, newToken) {
            state.token = newToken;
        },
        setError (state, newErrorState) {
            state.error = newErrorState;
        },
        setReCaptchaSiteKey (state, newKey) {
            state.reCaptchaSiteKey = newKey;
        },
        setUsername (state, newUsername) {
            state.account.username = newUsername;
        },
        setAccount (state, newAccount) {
            state.account = newAccount;
        },
        setWallet (state, newWallet) {
            state.wallet = newWallet;
        },
        setTicket (state, newTicket) {
            state.ticket = newTicket;
        },
        setVps (state, newVps) {
            state.vps = newVps;
        },
        setVpsIps(state, newIps) {
            state.vps.ip = {}
            state.vps.ip.main = newIps.main
            state.vps.ip.additional = newIps.additional
        },
        setProfile (state, newProfile) {
            state.profile = newProfile
        },
        setTicketMessages (state, newTicketMessages) {
            state.ticketMessages = newTicketMessages;
        },
        setServices (state, newServices) {
            state.services = newServices;
        },
        setPagination (state, newPagination) {
            state.pagination = newPagination;
        },
        setToolbarTitle (state, newTitle) {
            state.toolbarTitle = newTitle;
        },
        setToolbarTitleArgs (state, newTitleArgs) {
            state.toolbarTitleArgs = newTitleArgs;
        },
        setLanguage (state, newLanguage) {
            state.language = newLanguage;
            Vue.config.lang = newLanguage;
        },
        setMeta(state, newMeta) {
            state.meta = newMeta;
        },
        setLoading (state) {
            state.loading = true;
        },
        setLoaded (state) {
            state.loading = false;
        },
        setLogout(state, newLogout) {
            state.logout = newLogout;
        },
        setTitle (state, newTitle) {
            document.title = newTitle;
        },
        setDescription (state, newDescription) {
            document.head.querySelector('meta[name=description]').content = newDescription;
        },
        setKeywords (state, newKeywords) {
            document.head.querySelector('meta[name=keywords]').content = newKeywords;
        }
    }
})
<|endoftext|>


<commit_before>import Vue from 'vue'
import Vuex from 'vuex'
import { fetchItems, fetchIdsByType, fetchUser } from './api'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    activeType: null,
    itemsPerPage: 20,
    items: {/* [id: number]: Item */},
    users: {/* [id: string]: User */},
    lists: {
      top: [/* number */],
      new: [],
      show: [],
      ask: [],
      job: []
    }
  },

  actions: {
    // ensure data for rendering given list type
    FETCH_LIST_DATA: ({ commit, dispatch, state }, { type }) => {
      commit('SET_ACTIVE_TYPE', { type })
      return fetchIdsByType(type)
        .then(ids => commit('SET_LIST', { type, ids }))
        .then(() => dispatch('ENSURE_ACTIVE_ITEMS'))
    },

    // ensure all active items are fetched
    ENSURE_ACTIVE_ITEMS: ({ dispatch, getters }) => {
      return dispatch('FETCH_ITEMS', {
        ids: getters.activeIds
      })
    },

    FETCH_ITEMS: ({ commit, state }, { ids }) => {
      if (ids.length) {
        return fetchItems(ids).then(items => commit('SET_ITEMS', { items }))
      } else {
        return Promise.resolve()
      }
    },

    FETCH_USER: ({ commit, state }, { id }) => {
      return state.users[id]
        ? Promise.resolve(state.users[id])
        : fetchUser(id).then(user => commit('SET_USER', { user }))
    }
  },

  mutations: {
    SET_ACTIVE_TYPE: (state, { type }) => {
      state.activeType = type
    },

    SET_LIST: (state, { type, ids }) => {
      state.lists[type] = ids
    },

    SET_ITEMS: (state, { items }) => {
      items.forEach(item => {
        if (item) {
          Vue.set(state.items, item.id, item)
        }
      })
    },

    SET_USER: (state, { user }) => {
      Vue.set(state.users, user.id, user)
    }
  },

  getters: {
    // ids of the items that should be currently displayed based on
    // current list type and current pagination
    activeIds (state) {
      const { activeType, itemsPerPage, lists } = state
      const page = Number(state.route.params.page) || 1
      if (activeType) {
        const start = (page - 1) * itemsPerPage
        const end = page * itemsPerPage
        return lists[activeType].slice(start, end)
      } else {
        return []
      }
    },

    // items that should be currently displayed.
    // this Array may not be fully fetched.
    activeItems (state, getters) {
      return getters.activeIds.map(id => state.items[id]).filter(_ => _)
    }
  }
})

export default store
<commit_msg>limit client store item cache to 3 minutes<commit_after>import Vue from 'vue'
import Vuex from 'vuex'
import { fetchItems, fetchIdsByType, fetchUser } from './api'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    activeType: null,
    itemsPerPage: 20,
    items: {/* [id: number]: Item */},
    users: {/* [id: string]: User */},
    lists: {
      top: [/* number */],
      new: [],
      show: [],
      ask: [],
      job: []
    }
  },

  actions: {
    // ensure data for rendering given list type
    FETCH_LIST_DATA: ({ commit, dispatch, state }, { type }) => {
      commit('SET_ACTIVE_TYPE', { type })
      return fetchIdsByType(type)
        .then(ids => commit('SET_LIST', { type, ids }))
        .then(() => dispatch('ENSURE_ACTIVE_ITEMS'))
    },

    // ensure all active items are fetched
    ENSURE_ACTIVE_ITEMS: ({ dispatch, getters }) => {
      return dispatch('FETCH_ITEMS', {
        ids: getters.activeIds
      })
    },

    FETCH_ITEMS: ({ commit, state }, { ids }) => {
      // on the client, the store itself serves as a cache.
      // only fetch items that we do not already have, or has expired (3 minutes)
      const now = Date.now()
      ids = ids.filter(id => {
        const item = state.items[id]
        if (!item) {
          return true
        }
        if (now - item.__lastUpdated > 1000 * 60 * 3) {
          return true
        }
        return false
      })
      if (ids.length) {
        return fetchItems(ids).then(items => commit('SET_ITEMS', { items }))
      } else {
        return Promise.resolve()
      }
    },

    FETCH_USER: ({ commit, state }, { id }) => {
      return state.users[id]
        ? Promise.resolve(state.users[id])
        : fetchUser(id).then(user => commit('SET_USER', { user }))
    }
  },

  mutations: {
    SET_ACTIVE_TYPE: (state, { type }) => {
      state.activeType = type
    },

    SET_LIST: (state, { type, ids }) => {
      state.lists[type] = ids
    },

    SET_ITEMS: (state, { items }) => {
      items.forEach(item => {
        if (item) {
          Vue.set(state.items, item.id, item)
        }
      })
    },

    SET_USER: (state, { user }) => {
      Vue.set(state.users, user.id, user)
    }
  },

  getters: {
    // ids of the items that should be currently displayed based on
    // current list type and current pagination
    activeIds (state) {
      const { activeType, itemsPerPage, lists } = state
      const page = Number(state.route.params.page) || 1
      if (activeType) {
        const start = (page - 1) * itemsPerPage
        const end = page * itemsPerPage
        return lists[activeType].slice(start, end)
      } else {
        return []
      }
    },

    // items that should be currently displayed.
    // this Array may not be fully fetched.
    activeItems (state, getters) {
      return getters.activeIds.map(id => state.items[id]).filter(_ => _)
    }
  }
})

export default store
<|endoftext|>


<commit_before>import { EventEmitter } from 'events'
import { Promise } from 'es6-promise'
// import Firebase from 'firebase'
import Vue from 'vue'

const store = new EventEmitter()

/**
 * Constants
 */
 const objectiveIds = [
   '94-35', '95-35', '96-35', '94-32', '95-32', '96-32', '94-33', '95-33', '96-33',
   '94-34', '95-34', '96-34', '94-36', '95-36', '96-36', '94-37', '95-37', '96-37',
   '94-38', '95-38', '96-38', '94-39', '95-39', '96-39', '94-40', '95-40', '96-40',
   '94-50', '95-50', '96-50', '94-51', '95-51', '96-51', '94-52', '95-52', '96-52',
   '94-53', '95-53', '96-53', '38-1', '38-2', '38-3', '38-4', '38-5', '38-6',
   '38-7', '38-8', '38-9', '38-10', '38-11', '38-12', '38-13', '38-14', '38-15',
   '38-16', '38-17', '38-18', '38-19', '38-20', '38-21', '38-22'
 ]

/**
 * URLs for API Requests
 */
const matchesUrl = 'https://api.guildwars2.com/v2/wvw/matches?ids=all'
const worldsUrl = 'https://api.guildwars2.com/v2/worlds?ids=all'
const objectivesUrl = 'https://api.guildwars2.com/v2/wvw/objectives?ids='
const guildUrl = 'https://api.guildwars2.com/v1/guild_details.json?guild_id='
const glickoUrl = 'http://www.wvwstats.com/api/v1/officialglicko'
const weekleaderboardUrl = 'http://www.wvwstats.com/api/v1/weekleaderboard'
// let api = new Firebase('http://project-4821868053848732451.firebaseio.com')
/**
 * Local Cache variables
 */
let selectedWorld = 0
let matchesCache = []
let worldsCache = []
let glickoCache = {}
let predictedGlickoCache = {}
let objectiveCache = []
let guildCache = {}
let weekleaderboardCache = {}

// ------- BEGIN STORE.
export default store

/**
 * Initial Updaters
 */
var initialUpdate = setTimeout(function () {
  store.updateMatches()
  store.updateWorlds()
  store.updateGlicko()
  store.updateObjectives()
  store.updateWeekleaderboard()
}, 1)

/**
 * Update timers
 */
var matchUpdateTimer = setInterval(function () {
  store.updateMatches()
}, 10000)

var weekleaderboardUpdateTimer =  setInterval(function () {
  store.updateWeekleaderboard()
}, 60000)

/**
 * Updater Functions
 */
 store.updateMatches = () => {
   Vue.http.get(matchesUrl).then((response) => {
     matchesCache = response.data
     store.emit('matches-updated')
   }, (response) => {
     store.updateMatches()
   })
 }

 store.updateWorlds = () => {
   Vue.http.get(worldsUrl).then((response) => {
     worldsCache = response.data
     store.emit('worlds-updated')
   }, (response) => {
     store.updateWorlds()
   })
 }

 store.updateGlicko = () => {
  //  var ref = api.child('official_glicko')
  //  ref.once('value', (snapshot) => {
  //    glickoCache = snapshot.val()
  //    store.emit('glicko-updated')
  //  })
  Vue.http.get(glickoUrl).then((response) => {
    for (var i = 0; i < response.data.length; i++) {
      let curGlicko = response.data[i]
      glickoCache[response.data[i].id] = response.data[i].glicko
    }
    store.emit('glicko-updated')
  }, (response) => {
    store.updateGlicko()
  })
 }

 store.updateWeekleaderboard = () => {
   Vue.http.get(weekleaderboardUrl).then( (response) => {
     weekleaderboardCache = response.data
     store.emit('weekleaderboard-updated')
   }, (response) => {
     store.updateWeekleaderboard()
   })
 }

 store.updateObjectives = () => {
   var requestIds = ''
   for (var id in objectiveIds) {
     requestIds += objectiveIds[id] + ','
   }

   Vue.http.get(objectivesUrl + requestIds).then((response) => {
     objectiveCache = response.data
     store.emit('objectives-updated')
   }, (response) => {
     store.updateObjectives()
   })
 }

 store.updateSelectedWorld = id => {
   selectedWorld = id
   store.emit('selectedWorld-updated')
 }

 store.updatePredictedGlicko = (id, glicko) => {
   predictedGlickoCache[id] = glicko
   store.emit('predictedGlicko-updated')
 }

/**
 * Getters used from pages
 */
store.fetchMatches = () => {
  return matchesCache
}

store.fetchWorlds = () => {
  return worldsCache
}

store.fetchGlicko = () => {
  return glickoCache
}

store.fetchObjectives = () => {
  return objectiveCache
}

store.fetchObjectiveIds = () => {
  return objectiveIds
}

store.fetchSelectedWorld = () => {
  return selectedWorld
}

store.fetchPredictedGlicko = () => {
  return predictedGlickoCache
}

store.fetchWeekleaderboard = () => {
  return weekleaderboardCache
}

store.fetchGuildById = id => {
  return new Promise( (resolve, reject) => {
    if (guildCache[id]) {
      resolve(guildCache[id])
    } else {
      Vue.http.get(guildUrl + id).then((response) => {
        const guild = guildCache[id] = response.data
        resolve(guild)
      }, (response) => {
        reject('Failed to get guild info.')
      })
    }
  })
}
<commit_msg>Add match archive api support<commit_after>import { EventEmitter } from 'events'
import { Promise } from 'es6-promise'
// import Firebase from 'firebase'
import Vue from 'vue'

const store = new EventEmitter()

/**
 * Constants
 */
 const objectiveIds = [
   '94-35', '95-35', '96-35', '94-32', '95-32', '96-32', '94-33', '95-33', '96-33',
   '94-34', '95-34', '96-34', '94-36', '95-36', '96-36', '94-37', '95-37', '96-37',
   '94-38', '95-38', '96-38', '94-39', '95-39', '96-39', '94-40', '95-40', '96-40',
   '94-50', '95-50', '96-50', '94-51', '95-51', '96-51', '94-52', '95-52', '96-52',
   '94-53', '95-53', '96-53', '38-1', '38-2', '38-3', '38-4', '38-5', '38-6',
   '38-7', '38-8', '38-9', '38-10', '38-11', '38-12', '38-13', '38-14', '38-15',
   '38-16', '38-17', '38-18', '38-19', '38-20', '38-21', '38-22'
 ]

/**
 * URLs for API Requests
 */
const matchesUrl = 'https://api.guildwars2.com/v2/wvw/matches?ids=all'
const worldsUrl = 'https://api.guildwars2.com/v2/worlds?ids=all'
const objectivesUrl = 'https://api.guildwars2.com/v2/wvw/objectives?ids='
const guildUrl = 'https://api.guildwars2.com/v1/guild_details.json?guild_id='
const glickoUrl = 'http://www.wvwstats.com/api/v1/officialglicko'
const weekleaderboardUrl = 'http://www.wvwstats.com/api/v1/weekleaderboard'
const matcharchiveUrl = 'http://www.wvwstats.com/api/v1/matcharchive'
// let api = new Firebase('http://project-4821868053848732451.firebaseio.com')
/**
 * Local Cache variables
 */
let selectedWorld = 0
let matchesCache = []
let worldsCache = []
let glickoCache = {}
let predictedGlickoCache = {}
let objectiveCache = []
let guildCache = {}
let weekleaderboardCache = {}

// ------- BEGIN STORE.
export default store

/**
 * Initial Updaters
 */
var initialUpdate = setTimeout(function () {
  store.updateMatches()
  store.updateWorlds()
  store.updateGlicko()
  store.updateObjectives()
  store.updateWeekleaderboard()
}, 1)

/**
 * Update timers
 */
var matchUpdateTimer = setInterval(function () {
  store.updateMatches()
}, 10000)

var weekleaderboardUpdateTimer =  setInterval(function () {
  store.updateWeekleaderboard()
}, 60000)

/**
 * Updater Functions
 */
 store.updateMatches = () => {
   Vue.http.get(matchesUrl).then((response) => {
     matchesCache = response.data
     store.emit('matches-updated')
   }, (response) => {
     store.updateMatches()
   })
 }

 store.updateWorlds = () => {
   Vue.http.get(worldsUrl).then((response) => {
     worldsCache = response.data
     store.emit('worlds-updated')
   }, (response) => {
     store.updateWorlds()
   })
 }

 store.updateGlicko = () => {
  //  var ref = api.child('official_glicko')
  //  ref.once('value', (snapshot) => {
  //    glickoCache = snapshot.val()
  //    store.emit('glicko-updated')
  //  })
  Vue.http.get(glickoUrl).then((response) => {
    for (var i = 0; i < response.data.length; i++) {
      let curGlicko = response.data[i]
      glickoCache[response.data[i].id] = response.data[i].glicko
    }
    store.emit('glicko-updated')
  }, (response) => {
    store.updateGlicko()
  })
 }

 store.updateWeekleaderboard = () => {
   Vue.http.get(weekleaderboardUrl).then( (response) => {
     weekleaderboardCache = response.data
     store.emit('weekleaderboard-updated')
   }, (response) => {
     store.updateWeekleaderboard()
   })
 }

 store.updateObjectives = () => {
   var requestIds = ''
   for (var id in objectiveIds) {
     requestIds += objectiveIds[id] + ','
   }

   Vue.http.get(objectivesUrl + requestIds).then((response) => {
     objectiveCache = response.data
     store.emit('objectives-updated')
   }, (response) => {
     store.updateObjectives()
   })
 }

 store.updateSelectedWorld = id => {
   selectedWorld = id
   store.emit('selectedWorld-updated')
 }

 store.updatePredictedGlicko = (id, glicko) => {
   predictedGlickoCache[id] = glicko
   store.emit('predictedGlicko-updated')
 }

/**
 * Getters used from pages
 */
store.fetchMatches = () => {
  return matchesCache
}

store.fetchWorlds = () => {
  return worldsCache
}

store.fetchGlicko = () => {
  return glickoCache
}

store.fetchObjectives = () => {
  return objectiveCache
}

store.fetchObjectiveIds = () => {
  return objectiveIds
}

store.fetchSelectedWorld = () => {
  return selectedWorld
}

store.fetchPredictedGlicko = () => {
  return predictedGlickoCache
}

store.fetchWeekleaderboard = () => {
  return weekleaderboardCache
}

// TODO: Cache the results..
store.fetchArchiveData = (matchid, data, start_time, end_time) => {
  var tmp = new Date(start_time)
  var tmp2 = new Date(end_time)
  var timeOffset = tmp.getTimezoneOffset() / 60
  start_time = new Date(tmp.getFullYear(), tmp.getMonth(), tmp.getDate(),
    tmp.getHours() - timeOffset, tmp.getMinutes(), tmp.getSeconds()).toISOString()
    .replace('.000Z','Z')
  end_time = new Date(tmp2.getFullYear(), tmp2.getMonth(), tmp2.getDate(),
    tmp2.getHours() - timeOffset, tmp2.getMinutes(), tmp2.getSeconds()).toISOString()
    .replace('.000Z','Z')

  var url = `${matcharchiveUrl}?data=${data}&match=${matchid}&start_time=${start_time}&end_time=${end_time}`
  return Vue.http.get(url)
}

store.fetchGuildById = (id) => {
  return new Promise( (resolve, reject) => {
    if (guildCache[id]) {
      resolve(guildCache[id])
    } else {
      Vue.http.get(guildUrl + id).then((response) => {
        const guild = guildCache[id] = response.data
        resolve(guild)
      }, (response) => {
        reject('Failed to get guild info.')
      })
    }
  })
}
<|endoftext|>


<commit_before>import { actions as ReduxToastrActions } from 'react-redux-toastr';

import { setSector } from 'store/api/local';
import { uploadSector, updateSyncedSector } from 'store/api/firebase';

export const SuccessToast = ({
  title = 'Sector Saved',
  message = 'Your sector has been saved.',
}) =>
  ReduxToastrActions.add({
    options: {
      removeOnHover: true,
      showCloseButton: true,
    },
    position: 'bottom-left',
    type: 'success',
    message,
    title,
  });

export const ErrorToast = () =>
  ReduxToastrActions.add({
    options: {
      removeOnHover: true,
      showCloseButton: true,
    },
    position: 'bottom-left',
    type: 'error',
    title: 'There has been an error',
    message: 'Report a problem if it persists.',
  });

export const creatorOrUpdateSector = (state, sector) => {
  let promise;
  if (state.user.model) {
    if (state.sector.generated) {
      promise = uploadSector(sector, state.user.model.uid);
    } else {
      promise = updateSyncedSector(sector.key, sector);
    }
  } else {
    promise = setSector(sector.key, {
      ...sector,
      updated: Date.now(),
      created: sector.created || Date.now(),
    });
  }
  return promise.then((uploadedSector = sector) => uploadedSector);
};
<commit_msg>Never sync `isCloudSave`<commit_after>import { actions as ReduxToastrActions } from 'react-redux-toastr';
import { omit } from 'lodash';

import { setSector } from 'store/api/local';
import { uploadSector, updateSyncedSector } from 'store/api/firebase';

export const SuccessToast = ({
  title = 'Sector Saved',
  message = 'Your sector has been saved.',
}) =>
  ReduxToastrActions.add({
    options: {
      removeOnHover: true,
      showCloseButton: true,
    },
    position: 'bottom-left',
    type: 'success',
    message,
    title,
  });

export const ErrorToast = () =>
  ReduxToastrActions.add({
    options: {
      removeOnHover: true,
      showCloseButton: true,
    },
    position: 'bottom-left',
    type: 'error',
    title: 'There has been an error',
    message: 'Report a problem if it persists.',
  });

export const creatorOrUpdateSector = (state, sector) => {
  let promise;
  if (state.user.model) {
    if (state.sector.generated) {
      promise = uploadSector(sector, state.user.model.uid);
    } else {
      promise = updateSyncedSector(sector.key, sector);
    }
  } else {
    promise = setSector(
      sector.key,
      omit(
        {
          ...sector,
          updated: Date.now(),
          created: sector.created || Date.now(),
        },
        'isCloudSave',
      ),
    );
  }
  return promise.then((uploadedSector = sector) => uploadedSector);
};
<|endoftext|>


<commit_before>import color from 'tinycolor2';

export const transparentize = (c, amount) => {
  return color(c)
    .setAlpha(amount)
    .toString();
};

export const isDark = c => {
  return color(c).getBrightness() <= 165;
};

export const isLight = c => {
  return color(c).getBrightness() > 165;
};

export const darken = (c, amount) => {
  return color(c)
    .darken(amount)
    .toHexString();
};

export const lighten = (c, amount) => {
  return color(c)
    .lighten(amount)
    .toHexString();
};

export const DEFAULT_ACCENT = '#ec4e28';
export const DEFAULT_BACKGROUND = '#141517';
export const TRANSITION_LIGHTNING = '200ms';
export const TRANSITION_FAST = '300ms';
export const TRANSITION_MEDIUM = '500ms';
export const TRANSITION_SLOW = '700ms';

export const stripTransition = transition => parseInt(transition.replace('ms', ''), 10);

export const createTheme = (background = DEFAULT_BACKGROUND, accent = DEFAULT_ACCENT) => ({
  accent,
  background,
  font_primary: isLight(background) ? '#141517' : '#ececec',
  font_secondary: isLight(background) ? '#555' : '#888',
  blue: '#007AA5',
  green: '#009d00',
  red: '#9d0000',

  // Nav
  background_nav: lighten(background, 3),
  border_nav: darken(background, 3),

  // Page
  background_page: lighten(background, 3),
  border_page: darken(background, 3),

  // Cards
  background_card: lighten(background, 3),

  // Player
  background_player: lighten(background, 5),

  // Menu
  background_menu: lighten(background, 5),
  border_menu: darken(background, 1),

  // Search
  background_search: lighten(background, 8),
  border_search: lighten(background, 2),
});
<commit_msg>Updated theme to follow closer to how Material UI works<commit_after>import color from 'tinycolor2';

export const transparentize = (c, amount) => {
  return color(c)
    .setAlpha(amount)
    .toString();
};

export const isDark = c => {
  return color(c).getBrightness() <= 165;
};

export const isLight = c => {
  return color(c).getBrightness() > 165;
};

export const darken = (c, amount) => {
  return color(c)
    .darken(amount)
    .toHexString();
};

export const lighten = (c, amount) => {
  return color(c)
    .lighten(amount)
    .toHexString();
};

export const DEFAULT_ACCENT = '#ec4e28';
export const DEFAULT_BACKGROUND = '#141517';
export const FONT_LIGHT = '#141517';
export const FONT_DARK = '#ececec';
export const TRANSITION_LIGHTNING = '200ms';
export const TRANSITION_FAST = '300ms';
export const TRANSITION_MEDIUM = '500ms';
export const TRANSITION_SLOW = '700ms';

export const stripTransition = transition => parseInt(transition.replace('ms', ''), 10);

export const createTheme = (background = DEFAULT_BACKGROUND, accent = DEFAULT_ACCENT) => ({
  black: '#000',
  white: '#fff',
  font_primary: isLight(background) ? FONT_LIGHT : FONT_DARK,
  font_secondary: isLight(background) ? lighten(FONT_LIGHT, 25) : darken(FONT_DARK, 25),
  red: '#9d0000',

  B25: lighten(background, 13),
  B50: lighten(background, 11),
  B100: lighten(background, 9),
  B200: lighten(background, 7),
  B300: lighten(background, 5),
  B400: lighten(background, 3),
  B500: background,
  B600: darken(background, 3),
  B700: darken(background, 5),
  B800: darken(background, 7),
  B900: darken(background, 9),

  A50: lighten(accent, 11),
  A100: lighten(accent, 9),
  A200: lighten(accent, 7),
  A300: lighten(accent, 5),
  A400: lighten(accent, 3),
  A500: accent,
  A600: darken(accent, 3),
  A700: darken(accent, 5),
  A800: darken(accent, 7),
  A900: darken(accent, 9),
});
<|endoftext|>


<commit_before>var config = require('./config')
var net = require('net')
var log = require('./log')
var gpp = require('./gpp')

var next_tunnel_id = 1
var alive_tunnel_list = []

exports.start = function() {

	var server = net.createServer()
    var intv = null

	server.on('listening', function() {
	    log.info('[tcp_station] server.listening ip=${address}, port=${port}', server.address())
        // 每隔一段时间自动清理无效 tunnel
        intv = setInterval(clear_dead_tunnel, 10 * 1000)
	})

	server.on('connection', function (client) {
	    log.info('[tcp_station] server.connection ip=${remoteAddress}, port=${remotePort}', client)
	    var tunnel = new Tunnel(client, on_tunnel_close)
        alive_tunnel_list.push(tunnel)
	})

	server.on('error', function (err) {
	    log.error('[tcp_station] server.error ${0}', [err.toString()])
	})

	server.on('close', function () {
	    log.info('[tcp_station] server.close')
        clearInterval(intv)
	})

	server.listen(config.get("local.tcp.port"), config.get("local.tcp.host"));
}

function clear_dead_tunnel() {return
    var length_before = alive_tunnel_list.length
    alive_tunnel_list = alive_tunnel_list.filter(function(tunnel) {
        var is_dead = tunnel.left_socket_closed && (!tunnel.right_socket || tunnel.right_socket_closed)
        return !is_dead
    })
    var length_after = alive_tunnel_list.length
    log.info('[tcp_station] clear dead tunnel, before ${0} after ${1}', [length_before, length_after])
}

function on_tunnel_close(tunnel) {
    alive_tunnel_list = alive_tunnel_list.filter(function(_tunnel) {
        return _tunnel !== tunnel
    })
    log.info('[tcp_station] tunnel[${0}] close, rest tunnel ${1}', [tunnel.id, alive_tunnel_list.length])
}

// # on_close(tunnel)
function Tunnel(left_socket, on_close) {
    this.id = next_tunnel_id++
    this.left_socket = left_socket
    this.right_socket = null
    this.left_socket_closed = false
    this.right_socket_closed = true
    this.on_close = on_close || function() {}

    if (config.get('mode') === 'gpp_to_tcpudp') {
        this.context = {
            parser: new gpp.HeaderParser(),
            handler: this.on_left_socket_data_gpp_to_tcpudp_mode_handler.bind(this)
        }
    }
    else if (config.get('mode') === 'gpp_to_gpp') {
        throw new Error('TODO')
    }
    else {
        throw new Error('Unknown mode')
    }

    log.info('[tcp_station] tunnel[${0}] created, tcp_no_delay=${1}', [this.id, config.get('optimize.tcp_no_delay')])
    //t.left_socket.setNoDelay(config.get('optimize.tcp_no_delay'))

    this.left_socket.on('data', this.on_left_socket_data.bind(this))
    this.left_socket.on('error', this.on_left_socket_error.bind(this))
    this.left_socket.on('close', this.on_left_socket_close.bind(this))
}

Tunnel.prototype.on_left_socket_data = function(chunk) {
    log.info('[tcp_station] tunnel[${0}] left data length=${1}', [this.id, chunk.length])
    this.context.handler(chunk)
}

Tunnel.prototype.on_left_socket_data_gpp_to_tcpudp_mode_handler = function(chunk) {
    var parser = this.context.parser
    // 头部解析尚未完成吗？
    if (!parser.is_finished()) {
        // 是的，继续吃 chunk 来解析
        parser.eat(chunk)
        // 完成了？
        if (parser.is_finished()) {
            // 成功了？
            if (parser.is_successful()) {
                var header = parser.get_header()
                // 可以进行代理中转了
                log.info('[tcp_station] tunnel[${0}] left header parsed ${1|json}', [this.id, header])
                this.create_right_socket(header.ip, header.port)
                // 如果有尾块，要记得发送
                if (parser.exists_tail_chunk()) {
                    this.right_socket.write(chunk)
                }
            }
            // 失败了
            else {
                // 头部错误，强行断开连接
                log.info('[tcp_station] tunnel[${0}] left header parsed failed', [this.id])
                this.left_socket.destroy()
            }
        }
        // 还是没完成
        else {
            // 没关系，等待下一个数据块
        }
    }
    // 头部解析已经完成了
    // 成功了吗？
    else if (parser.is_successful()) {
        // 是的，直接转发数据即可
        this.right_socket.write(chunk)
    }
    // 失败了，竟然还收到数据？忽略即可
    else {
        // 不需要做什么
    }
}

Tunnel.prototype.on_left_socket_error = function(err) {
    log.info('[tcp_station] tunnel[${0}] left error ${1}', [this.id, err.toString()])
}

Tunnel.prototype.on_left_socket_close = function() {
    log.info('[tcp_station] tunnel[${0}] left close', [this.id])
    this.left_socket_closed = true
    if (this.right_socket && !this.right_socket_closed) {
        this.right_socket.end()
    }
}

Tunnel.prototype.create_right_socket = function(host, port) {
    // 开始连接
    this.right_socket = net.connect(port, host)
    //this.right_socket = net.connect(80)
    // 订阅各个事件
    this.right_socket.on('connect', this.on_right_socket_connect.bind(this))
    this.right_socket.on('data', this.on_right_socket_data.bind(this))
    this.right_socket.on('error', this.on_right_socket_error.bind(this))
    this.right_socket.on('close', this.on_right_socket_close.bind(this))
}

Tunnel.prototype.on_right_socket_connect = function() {
    log.info('[tcp_station] tunnel[${0}] right connect', [this.id])
}

Tunnel.prototype.on_right_socket_data = function(chunk) {
    log.info('[tcp_station] tunnel[${0}] right data length=${1}', [this.id, chunk.length])
    // 转发数据
    this.left_socket.write(chunk)
}

Tunnel.prototype.on_right_socket_error = function(err) {
    log.info('[tcp_station] tunnel[${0}] right error ${1}', [this.id, err.toString()])
}

Tunnel.prototype.on_right_socket_close = function() {
    log.info('[tcp_station] tunnel[${0}] right close', [this.id])
    this.right_socket_closed = true
    if (this.left_socket && !this.left_socket_closed) {
        this.left_socket.end()
    }
}<commit_msg>修正了一部分 bug<commit_after>var config = require('./config')
var net = require('net')
var log = require('./log')
var gpp = require('./gpp')

var next_tunnel_id = 1
var alive_tunnel_list = []

exports.start = function() {

	var server = net.createServer()
    //var intv = null

	server.on('listening', function() {
	    log.info('[tcp_station] server.listening ip=${address}, port=${port}', server.address())
        // 每隔一段时间自动清理无效 tunnel
        //intv = setInterval(clear_dead_tunnel, 10 * 1000)
	})

	server.on('connection', function (client) {
	    log.info('[tcp_station] server.connection ip=${remoteAddress}, port=${remotePort}', client)
	    var tunnel = new Tunnel(client, on_tunnel_close)
        alive_tunnel_list.push(tunnel)
	})

	server.on('error', function (err) {
	    log.error('[tcp_station] server.error ${0}', [err.toString()])
	})

	server.on('close', function () {
	    log.info('[tcp_station] server.close')
        //clearInterval(intv)
	})

	server.listen(config.get("local.tcp.port"), config.get("local.tcp.host"));
}

function clear_dead_tunnel() {
    var length_before = alive_tunnel_list.length
    alive_tunnel_list = alive_tunnel_list.filter(function(tunnel) {
        var is_dead = tunnel.left_socket_closed && (!tunnel.right_socket || tunnel.right_socket_closed)
        return !is_dead
    })
    var length_after = alive_tunnel_list.length
    log.info('[tcp_station] clear dead tunnel, before ${0} after ${1}', [length_before, length_after])
}

function on_tunnel_close(tunnel) {
    alive_tunnel_list = alive_tunnel_list.filter(function(_tunnel) {
        return _tunnel !== tunnel
    })
    log.info('[tcp_station] tunnel[${0}] close, rest tunnel ${1}', [tunnel.id, alive_tunnel_list.length])
}

// # on_close(tunnel)
function Tunnel(left_socket, on_close) {
    this.id = next_tunnel_id++
    this.left_socket = left_socket
    this.right_socket = null
    this.left_socket_closed = false
    this.right_socket_closed = true
    this.on_close = on_close || function() {}

    if (config.get('mode') === 'gpp_to_tcpudp') {
        this.context = {
            parser: new gpp.HeaderParser(),
            handler: this.on_left_socket_data_gpp_to_tcpudp_mode_handler.bind(this)
        }
    }
    else if (config.get('mode') === 'gpp_to_gpp') {
        throw new Error('TODO')
    }
    else {
        throw new Error('Unknown mode')
    }

    log.info('[tcp_station] tunnel[${0}] created, tcp_no_delay=${1}', [this.id, config.get('optimize.tcp_no_delay')])
    //t.left_socket.setNoDelay(config.get('optimize.tcp_no_delay'))

    this.left_socket.on('data', this.on_left_socket_data.bind(this))
    this.left_socket.on('error', this.on_left_socket_error.bind(this))
    this.left_socket.on('close', this.on_left_socket_close.bind(this))
}

Tunnel.prototype.on_left_socket_data = function(chunk) {
    log.info('[tcp_station] tunnel[${0}] left data length=${1}', [this.id, chunk.length])
    this.context.handler(chunk)
}

Tunnel.prototype.on_left_socket_data_gpp_to_tcpudp_mode_handler = function(chunk) {debugger
    var parser = this.context.parser
    // 头部解析尚未完成吗？
    if (!parser.is_finished()) {
        // 是的，继续吃 chunk 来解析
        parser.eat(chunk)
        // 完成了？
        if (parser.is_finished()) {
            // 成功了？
            if (parser.is_successful()) {
                var header = parser.get_header()
                // 可以进行代理中转了
                log.info('[tcp_station] tunnel[${0}] left header parsed ${1|json}', [this.id, header])
                this.create_right_socket(header.ip, header.port)
                // 如果有尾块，要记得发送
                if (parser.exists_tail_chunk()) {
                    this.right_socket.write(parser.get_tail_chunk())
                }
            }
            // 失败了
            else {
                // 头部错误，强行断开连接
                log.info('[tcp_station] tunnel[${0}] left header parsed failed', [this.id])
                this.left_socket.destroy()
            }
        }
        // 还是没完成
        else {
            // 没关系，等待下一个数据块
        }
    }
    // 头部解析已经完成了
    // 成功了吗？
    else if (parser.is_successful()) {
        // 是的，直接转发数据即可
        this.right_socket.write(chunk)
    }
    // 失败了，竟然还收到数据？忽略即可
    else {
        // 不需要做什么
    }
}

Tunnel.prototype.on_left_socket_error = function(err) {
    log.info('[tcp_station] tunnel[${0}] left error ${1}', [this.id, err.toString()])
}

Tunnel.prototype.on_left_socket_close = function() {
    log.info('[tcp_station] tunnel[${0}] left close', [this.id])
    // 把 left_socket 标记为 closed
    this.left_socket_closed = true
    // 如果 right_socket 尚未建立，或者已经关闭
    // 那么我们应当触发 this.on_close 回调
    if (!this.right_socket || this.right_socket_closed) {
        this.on_close(this)
    }
    else {
        // 关闭 right_socket
        this.right_socket.end()
    }
}

Tunnel.prototype.create_right_socket = function(host, port) {
    // 开始连接
    this.right_socket = net.connect(port, host)
    //this.right_socket = net.connect(80)
    // 订阅各个事件
    this.right_socket.on('connect', this.on_right_socket_connect.bind(this))
    this.right_socket.on('data', this.on_right_socket_data.bind(this))
    this.right_socket.on('error', this.on_right_socket_error.bind(this))
    this.right_socket.on('close', this.on_right_socket_close.bind(this))
}

Tunnel.prototype.on_right_socket_connect = function() {
    log.info('[tcp_station] tunnel[${0}] right connect', [this.id])
}

Tunnel.prototype.on_right_socket_data = function(chunk) {
    log.info('[tcp_station] tunnel[${0}] right data length=${1}', [this.id, chunk.length])
    // 转发数据
    this.left_socket.write(chunk)
}

Tunnel.prototype.on_right_socket_error = function(err) {
    log.info('[tcp_station] tunnel[${0}] right error ${1}', [this.id, err.toString()])
}

Tunnel.prototype.on_right_socket_close = function() {
    log.info('[tcp_station] tunnel[${0}] right close', [this.id])
    this.right_socket_closed = true
    // 如果 left_socket 已经关闭
    // 那么我们应当触发 this.on_close 回调
    if (this.left_socket_closed) {
        this.on_close(this)
    }
    else {
        // 关闭 left_socket
        this.left_socket.end()
    }
}<|endoftext|>


<commit_before>var path = require("path"),
  fs = require("fs"),
  _ = require("lodash"),
  acorn = require("acorn"),
  clc = require("cli-color"),
  walk = require("acorn/dist/walk"),
  settings = require("./settings");

var filterByTags = function(files, tags) {
  // Tidy up tag input. If we have a comma-delimited list, tokenize and clean it up
  if (typeof tags === "string") {
    tags = tags.trim();

    if (tags.indexOf(",") > -1) {
      tags = tags.trim().split(",").map(function (tag) {
        return tag.trim();
      });
    } else {
      tags = [tags];
    }
  }

  // If tags are empty or malformed, ignore them
  if (!_.isArray(tags) || tags.length === 0) {
    return files;
  }

  console.log("Using tag filter: ", tags);

  // FIXME: Handle an error with an interop that doesn't implement a tag_filter here
  var filterImplementation = require("./interop/" + settings.framework + "/tag_filter");
  return files.filter(filterImplementation.bind(this, tags));
};

var PREDEFINED_FILTERS = {

  // Filter by "group", which really means filename prefix, i.e:
  //
  //  --group=test/groupname
  //  --group=test/abc/xyz/Regression
  //  --group=test/abc/xyz/Smoke
  //
  group: function(files, partialFilename) {
    console.log("Using group filter: ", partialFilename);

    return files.filter(function(f) {
      var pass = true;

      if (partialFilename) {
        if (typeof partialFilename === "string") {
          partialFilename = [partialFilename];
        }
        pass = partialFilename.some(function(pfn) {
          return f.indexOf(pfn) > -1;
        });
      }

      return (f.indexOf(".js") === f.length - 3) && pass;
    }).map(function(f) {
      return f.trim();
    });
  },

  // Filter by one exact relative filename match, eg:
  // --test=path/to/exact/test/filename.js
  test: function(files, filename) {
    console.log("Using test filter: ", filename);

    return files.filter(function(f) {
      //
      // TODO: instead check if this is an instance of a Path object, not "mocha" substring check
      //
      if (settings.framework.indexOf("mocha") > -1) {
        if (f.path === filename) {
          return true;
        }
      } else {
        if (path.resolve(f.trim()) === path.resolve(filename.trim())) {
          return true;
        }
      }
    });
  },

  // Filter by tag or tags, i.e:
  //
  //  --tag=tagname
  //  --tags=tagname
  //  --tags=tagname1,tagname2
  //
  tag: filterByTags,
  tags: filterByTags

};

module.exports = {

  // Detect and return filters specified by command line arguments
  // from an argv object args
  detectFromCLI: function (args) {
    var filters = {};

    _.keys(PREDEFINED_FILTERS).forEach(function (f) {
      if (args[f]) {
        filters[f] = args[f];
      }
    });

    return filters;
  },

  // Successively reduce files to a smaller set of files by
  // running a list of filters on the list repeatedly
  filter: function(files, filters) {
    var allFiles = files;

    _.forEach(filters, function(n, k) {
      if (PREDEFINED_FILTERS[k]) {
        // if we have this filter predefined in settings.js
        // do filter here
        allFiles = PREDEFINED_FILTERS[k](allFiles, filters[k]);
      }
    });

    return allFiles;
  }

};<commit_msg>start moving filter code out<commit_after>var path = require("path"),
  fs = require("fs"),
  _ = require("lodash"),
  clc = require("cli-color"),
  settings = require("./settings");

// var filterByTags = function(files, tags) {
//   // Tidy up tag input. If we have a comma-delimited list, tokenize and clean it up
//   if (typeof tags === "string") {
//     tags = tags.trim();

//     if (tags.indexOf(",") > -1) {
//       tags = tags.trim().split(",").map(function (tag) {
//         return tag.trim();
//       });
//     } else {
//       tags = [tags];
//     }
//   }

//   // If tags are empty or malformed, ignore them
//   if (!_.isArray(tags) || tags.length === 0) {
//     return files;
//   }

//   console.log("Using tag filter: ", tags);

//   // FIXME: Handle an error with an interop that doesn't implement a tag_filter here
//   var filterImplementation = require("./interop/" + settings.framework + "/tag_filter");
//   return files.filter(filterImplementation.bind(this, tags));
// };

// var PREDEFINED_FILTERS = {

//   // Filter by "group", which really means filename prefix, i.e:
//   //
//   //  --group=test/groupname
//   //  --group=test/abc/xyz/Regression
//   //  --group=test/abc/xyz/Smoke
//   //
//   group: function(files, partialFilename) {
//     console.log("Using group filter: ", partialFilename);

//     return files.filter(function(f) {
//       var pass = true;

//       if (partialFilename) {
//         if (typeof partialFilename === "string") {
//           partialFilename = [partialFilename];
//         }
//         pass = partialFilename.some(function(pfn) {
//           return f.indexOf(pfn) > -1;
//         });
//       }

//       return (f.indexOf(".js") === f.length - 3) && pass;
//     }).map(function(f) {
//       return f.trim();
//     });
//   },

//   // Filter by one exact relative filename match, eg:
//   // --test=path/to/exact/test/filename.js
//   test: function(files, filename) {
//     console.log("Using test filter: ", filename);

//     return files.filter(function(f) {
//       //
//       // TODO: instead check if this is an instance of a Path object, not "mocha" substring check
//       //
//       if (settings.framework.indexOf("mocha") > -1) {
//         if (f.path === filename) {
//           return true;
//         }
//       } else {
//         if (path.resolve(f.trim()) === path.resolve(filename.trim())) {
//           return true;
//         }
//       }
//     });
//   },

//   // Filter by tag or tags, i.e:
//   //
//   //  --tag=tagname
//   //  --tags=tagname
//   //  --tags=tagname1,tagname2
//   //
//   tag: filterByTags,
//   tags: filterByTags

// };

module.exports = {

  // Detect and return filters specified by command line arguments
  // from an argv object args
  detectFromCLI: function (args) {
    var filters = {};

    _.keys(settings.testFramework.filters).forEach(function (f) {
      if (args[f]) {
        filters[f] = args[f];
      }
    });

    return filters;
  },

  // Successively reduce files to a smaller set of files by
  // running a list of filters on the list repeatedly
  filter: function(files, filters) {
    var allFiles = files;

    _.forEach(filters, function(n, k) {
      if (settings.testFramework.filters[k]) {
        // if we have this filter predefined in settings.js
        // do filter here
        allFiles = settings.testFramework.filters[k](allFiles, filters[k]);
      }
    });

    return allFiles;
  }

};<|endoftext|>


<commit_before>(function() {

Dagaz.Model.WIN_CNT = 24;

Dagaz.View.DX       = 0;
Dagaz.View.DY       = 0;
Dagaz.View.MX       = 25;

var cache = [];
var size  = 5;

if (!_.isUndefined(Dagaz.Controller.addSound)) {
    Dagaz.Controller.addSound(2, "../sounds/win.wav");
}

var checkVersion = Dagaz.Model.checkVersion;

Dagaz.Model.checkVersion = function(design, name, value) {
  if (name == "tchuka-ruma-extension") {
      size = +value;
  } else {
      checkVersion(design, name, value);
  }
}

var createPiece = function(design, player, value) {
  if (value != 0) {
      if (!_.isUndefined(cache[player]) && !_.isUndefined(cache[player][value])) {
          return cache[player][value];
      }
      var r = Dagaz.Model.createPiece(0, player).setValue(0, value);
      if (_.isUndefined(cache[player])) {
          cache[player] = [];
      }
      cache[player][value] = r;
      return r;
  } else {
      return null;
  }
}

var CheckInvariants = Dagaz.Model.CheckInvariants;

Dagaz.Model.CheckInvariants = function(board) {
  var design = Dagaz.Model.design;
  _.each(board.moves, function(move) {
      if (move.isSimpleMove()) {
          var pos = move.actions[0][0][0];
          var piece = board.getPiece(pos);
          var cnt = Math.abs(+piece.getValue(0));
          if (_.isUndefined(cache[piece.player])) {
              cache[piece.player] = [];
              cache[piece.player][cnt] = piece;
          }
          var result = [];
          result.push(0);
          for (var ix = 1; cnt > 0; cnt--, ix++) {
               pos = design.navigate(board.player, pos, 0);
               if (pos === null) {
                   move.failed = true;
                   return;
               }
               if (ix >= size) {
                   ix = 0;
               }
               piece = board.getPiece(pos);
               if (ix < result.length) {
                   result[ix]++;
               } else {
                   if (piece === null) {
                       result.push(1);
                   } else {
                       result.push(Math.abs(+piece.getValue(0)) + 1);
                   }
               }
          }
          result[result.length - 1] = -result[result.length - 1];
          var pos = move.actions[0][0][0];
          for (var ix = 0; ix < result.length; ix++) {
               var player = board.player;
               var piece  = createPiece(design, player, result[ix]);
               if (result[ix] == 0) {
                   if (ix > 0) {
                       move.capturePiece(pos);
                       if (ix == 1) {
                           move.actions[0][2] = [ Dagaz.Model.createPiece(1, board.player) ];
                       }
                   }
               } else {
                   if (piece !== null) {
                       if (ix == 1) {
                           move.actions[0][2] = [ piece ];
                       } else {
                           if ((board.getPiece(pos) !== null) && (ix > 0)) {
                               move.movePiece(pos, pos, piece);
                           } else {
                               move.dropPiece(pos, piece);
                           }
                       }
                   }
               }
               pos = design.navigate(board.player, pos, 0);
          }
          var f = true;
          _.each(move.actions, function(a) {
              if ((a[1] !== null) && (a[1][0] == size - 1)) {
                  f = false;
              }
          });
          if (f) {
              var pos = size - 1;
              var piece = board.getPiece(pos);
              if (piece !== null) {
                  var value = +piece.getValue(0);
                  if (value < 0) {
                      piece = piece.setValue(0, -value);
                      move.movePiece(pos, pos, piece);
                  }
              }
          }
      }
  });
  var ko = [];
  _.each(design.allPositions(), function(pos) {
      if (design.inZone(0, 1, pos)) {
          var piece = board.getPiece(pos);
          if (piece !== null) {
              var value = +piece.getValue(0);
              if ((value !== null) && (value < -1)) {
                  ko.push(pos);
              }
          }
      }
  });
  if (ko.length > 0) {
      board.ko = ko;
  }
  CheckInvariants(board);
}

})();
<commit_msg>Tchuka Ruma (bugfix)<commit_after>(function() {

Dagaz.View.DX       = 0;
Dagaz.View.DY       = 0;
Dagaz.View.MX       = 25;

var cache = [];
var size  = 5;

if (!_.isUndefined(Dagaz.Controller.addSound)) {
    Dagaz.Controller.addSound(2, "../sounds/win.wav");
}

var checkVersion = Dagaz.Model.checkVersion;

Dagaz.Model.checkVersion = function(design, name, value) {
  if (name == "tchuka-ruma-extension") {
      size = +value;
  } else {
      checkVersion(design, name, value);
  }
}

var createPiece = function(design, player, value) {
  if (value != 0) {
      if (!_.isUndefined(cache[player]) && !_.isUndefined(cache[player][value])) {
          return cache[player][value];
      }
      var r = Dagaz.Model.createPiece(0, player).setValue(0, value);
      if (_.isUndefined(cache[player])) {
          cache[player] = [];
      }
      cache[player][value] = r;
      return r;
  } else {
      return null;
  }
}

var CheckInvariants = Dagaz.Model.CheckInvariants;

Dagaz.Model.CheckInvariants = function(board) {
  var design = Dagaz.Model.design;
  _.each(board.moves, function(move) {
      if (move.isSimpleMove()) {
          var pos = move.actions[0][0][0];
          var piece = board.getPiece(pos);
          var cnt = Math.abs(+piece.getValue(0));
          if (_.isUndefined(cache[piece.player])) {
              cache[piece.player] = [];
              cache[piece.player][cnt] = piece;
          }
          var result = [];
          result.push(0);
          for (var ix = 1; cnt > 0; cnt--, ix++) {
               pos = design.navigate(board.player, pos, 0);
               if (pos === null) {
                   move.failed = true;
                   return;
               }
               if (ix >= size) {
                   ix = 0;
               }
               piece = board.getPiece(pos);
               if (ix < result.length) {
                   result[ix]++;
               } else {
                   if (piece === null) {
                       result.push(1);
                   } else {
                       result.push(Math.abs(+piece.getValue(0)) + 1);
                   }
               }
          }
          result[ix - 1] = -result[ix - 1];
          var pos = move.actions[0][0][0];
          for (var ix = 0; ix < result.length; ix++) {
               var player = board.player;
               var piece  = createPiece(design, player, result[ix]);
               if (result[ix] == 0) {
                   if (ix > 0) {
                       move.capturePiece(pos);
                       if (ix == 1) {
                           move.actions[0][2] = [ Dagaz.Model.createPiece(1, board.player) ];
                       }
                   }
               } else {
                   if (piece !== null) {
                       if (ix == 1) {
                           move.actions[0][2] = [ piece ];
                       } else {
                           if ((board.getPiece(pos) !== null) && (ix > 0)) {
                               move.movePiece(pos, pos, piece);
                           } else {
                               move.dropPiece(pos, piece);
                           }
                       }
                   }
               }
               pos = design.navigate(board.player, pos, 0);
          }
          var f = true;
          _.each(move.actions, function(a) {
              if ((a[1] !== null) && (a[1][0] == size - 1)) {
                  f = false;
              }
          });
          if (f) {
              var pos = size - 1;
              var piece = board.getPiece(pos);
              if (piece !== null) {
                  var value = +piece.getValue(0);
                  if (value < 0) {
                      piece = piece.setValue(0, -value);
                      move.movePiece(pos, pos, piece);
                  }
              }
          }
      }
  });
  var ko = [];
  _.each(design.allPositions(), function(pos) {
      if (design.inZone(0, 1, pos)) {
          var piece = board.getPiece(pos);
          if (piece !== null) {
              var value = +piece.getValue(0);
              if ((value !== null) && (value < -1)) {
                  ko.push(pos);
              }
          }
      }
  });
  if (ko.length > 0) {
      board.ko = ko;
  }
  CheckInvariants(board);
}

})();
<|endoftext|>


<commit_before>!( function( factory ) {
  if ( typeof define === "function" && define.amd ) {
    define( factory );
  } else {
    factory( );
  }
}( function() {

  /**
   * alertMsg
   * @param  {[string, object]} options
   * @return {[object]}         [callback function]
   */
  var div = null;
	var alertMsg = function( options ) {

    var callback
      , defaults = {
          width     : 160,
          content   : "?",
          done      : null,
          time      : 1200,
          autohide  : true
        };

    // Build options
    if ( typeof( options ) === "string" ) {

      defaults.content = options;
      options = defaults;

    } else {

      for (var o in options) {
        defaults[o] = options[o]
      }
      options = defaults;
    }

    if (!div) {
      div = document.createElement('div');
      div.classList.add('toast');
      document.body.appendChild(div);
    }

    div.innerHTML = options.content;
    div.style.display = 'block';

    setTimeout(function(){
      div.style.marginTop = '-' + (window.getComputedStyle(div).height.replace(/px/, '') / 2) + 'px';
      div.style.opacity = "1";
    }, 10)

		if ( options.autohide ) {

			setTimeout( function() {

        div.style.opacity = 0;

        // After anmiation.
        setTimeout(function(){

          div.style.cssText = ''

          typeof callback === 'function' && callback();
          typeof options.done === 'function' && options.done();
        }, 1000)

			}, options.time );

		}

    return {
      then: function(cb){
        callback = cb;
      }
    };
	};

  window.toast = window.toast || alertMsg;

}));
<commit_msg>Reset default message.<commit_after>!( function( factory ) {
  if ( typeof define === "function" && define.amd ) {
    define( factory );
  } else {
    factory( );
  }
}( function() {

  /**
   * alertMsg
   * @param  {[string, object]} options
   * @return {[object]}         [callback function]
   */
  var div = null;
	var alertMsg = function( options ) {

    var callback
      , defaults = {
          width     : 160,
          content   : "显示信息为空",
          done      : null,
          time      : 1200,
          autohide  : true
        };

    // Build options
    if ( typeof( options ) === "string" ) {

      defaults.content = options;
      options = defaults;

    } else {

      for (var o in options) {
        defaults[o] = options[o]
      }
      options = defaults;
    }

    if (!div) {
      div = document.createElement('div');
      div.classList.add('toast');
      document.body.appendChild(div);
    }

    div.innerHTML = options.content;
    div.style.display = 'block';

    setTimeout(function(){
      div.style.marginTop = '-' + (window.getComputedStyle(div).height.replace(/px/, '') / 2) + 'px';
      div.style.opacity = "1";
    }, 10)

		if ( options.autohide ) {

			setTimeout( function() {

        div.style.opacity = 0;

        // After anmiation.
        setTimeout(function(){

          div.style.cssText = ''

          typeof callback === 'function' && callback();
          typeof options.done === 'function' && options.done();
        }, 1000)

			}, options.time );

		}

    return {
      then: function(cb){
        callback = cb;
      }
    };
	};

  window.toast = window.toast || alertMsg;

  return alertMsg;
}));
<|endoftext|>


<commit_before>var data = {
	portfolio: [
		{
			imgPath: "project-1",
			link: "http://denistrator.esy.es/mountains",
			titleEN: "Mountains",
			titleRU: "Mountains",
			githubLink: "https://github.com/denistrator/training-01-mountains",
			mod: ""
		},
		{
			imgPath: "project-6",
			link: "http://gorcons.apanshin-dev.ru/",
			titleRU: "Горконс",
			titleEN: "Gorcons",
			githubLink: "",
			mod: "",
			commercial: true,
			alternateImg: true
		},
		{
			imgPath: "project-2",
			link: "http://denistrator.esy.es/modest",
			titleEN: "Modest",
			titleRU: "Modest",
			githubLink: "https://github.com/denistrator/spalah-lessons--lesson-8",
			mod: ""
		},
		{
			imgPath: "project-no-image",
			link: "http://denistrator.esy.es/counter/",
			titleEN: "Counter",
			titleRU: "Счётчик",
			githubLink: "https://github.com/denistrator/counter",
			mod: "",
			alternateImg: true
		},
		{
			imgPath: "project-3",
			link: "http://denistrator.esy.es/tajam",
			titleEN: "Tajam",
			titleRU: "Tajam",
			githubLink: "https://github.com/denistrator/spalah-lessons--lesson-7",
			mod: "portfolio-collapse-pos",
			alternateImg: true
		},
		{
			imgPath: "project-4",
			link: "http://denistrator.esy.es/yellow_moon",
			titleEN: "Yellow Moon",
			titleRU: "Yellow Moon",
			githubLink: "https://github.com/denistrator/spalah-lessons--lesson-6",
			mod: ""
		},
		{
			imgPath: "project-no-image",
			link: "http://denistrator.esy.es/cosmetologylife",
			titleRU: "Косметология Жизни",
			titleEN: "Cosmetology Life",
			githubLink: "",
			mod: "old",
			commercial: true,
			alternateImg: true
		},
		{
			imgPath: "project-5",
			link: "http://denistrator.esy.es/Freebie_Page_Atech/",
			titleEN: "Atech",
			titleRU: "Atech",
			githubLink: "",
			mod: "old",
			alternateImg: true
		},
		{
			imgPath: "project-no-image",
			link: "http://denistrator.esy.es/naturetour/",
			titleEN: "Naturetour",
			titleRU: "Naturetour",
			githubLink: "",
			mod: "old",
			alternateImg: true
		},
		{
			imgPath: "project-no-image",
			link: "http://denistrator.esy.es/extended/",
			titleEN: "Extended",
			titleRU: "Extended",
			githubLink: "",
			mod: "old",
			alternateImg: true
		}
	]
};<commit_msg>remove items from portfolio<commit_after>var data = {
	portfolio: [
		{
			imgPath: "project-1",
			link: "http://denistrator.esy.es/mountains",
			titleEN: "Mountains",
			titleRU: "Mountains",
			githubLink: "https://github.com/denistrator/training-01-mountains",
			mod: ""
		},
		{
			imgPath: "project-6",
			link: "http://gorcons.apanshin-dev.ru/",
			titleRU: "Горконс",
			titleEN: "Gorcons",
			githubLink: "",
			mod: "",
			commercial: true,
			alternateImg: true
		},
		{
			imgPath: "project-2",
			link: "http://denistrator.esy.es/modest",
			titleEN: "Modest",
			titleRU: "Modest",
			githubLink: "https://github.com/denistrator/spalah-lessons--lesson-8",
			mod: ""
		},
		// {
		// 	imgPath: "project-no-image",
		// 	link: "http://denistrator.esy.es/counter/",
		// 	titleEN: "Counter",
		// 	titleRU: "Счётчик",
		// 	githubLink: "https://github.com/denistrator/counter",
		// 	mod: "",
		// 	alternateImg: true
		// },
		{
			imgPath: "project-3",
			link: "http://denistrator.esy.es/tajam",
			titleEN: "Tajam",
			titleRU: "Tajam",
			githubLink: "https://github.com/denistrator/spalah-lessons--lesson-7",
			mod: "portfolio-collapse-pos",
			alternateImg: true
		},
		{
			imgPath: "project-4",
			link: "http://denistrator.esy.es/yellow_moon",
			titleEN: "Yellow Moon",
			titleRU: "Yellow Moon",
			githubLink: "https://github.com/denistrator/spalah-lessons--lesson-6",
			mod: ""
		},
		// {
		// 	imgPath: "project-no-image",
		// 	link: "http://denistrator.esy.es/cosmetologylife",
		// 	titleRU: "Косметология Жизни",
		// 	titleEN: "Cosmetology Life",
		// 	githubLink: "",
		// 	mod: "old",
		// 	commercial: true,
		// 	alternateImg: true
		// },
		{
			imgPath: "project-5",
			link: "http://denistrator.esy.es/Freebie_Page_Atech/",
			titleEN: "Atech",
			titleRU: "Atech",
			githubLink: "",
			mod: "old",
			alternateImg: true
		},
		{
			imgPath: "project-no-image",
			link: "http://denistrator.esy.es/naturetour/",
			titleEN: "Naturetour",
			titleRU: "Naturetour",
			githubLink: "",
			mod: "old",
			alternateImg: true
		},
		// {
		// 	imgPath: "project-no-image",
		// 	link: "http://denistrator.esy.es/extended/",
		// 	titleEN: "Extended",
		// 	titleRU: "Extended",
		// 	githubLink: "",
		// 	mod: "old",
		// 	alternateImg: true
		// }
	]
};<|endoftext|>


<commit_before><commit_msg>add jimmychoo.com<commit_after><|endoftext|>


<commit_before><commit_msg>Add tooltips to local mode<commit_after><|endoftext|>


<commit_before><commit_msg>Update gateio.js<commit_after><|endoftext|>


<commit_before><commit_msg>sync fix<commit_after><|endoftext|>


<commit_before>import {Transform} from "prosemirror-transform"
import {Mark} from "prosemirror-model"

const UPDATED_SEL = 1, UPDATED_MARKS = 2, UPDATED_SCROLL = 4

// ::- An editor state transaction, which can be applied to a state to
// create an updated state. Use
// [`EditorState.tr`](#state.EditorState.tr) to create an instance.
//
// Transactions track changes to the document (they are a subclass of
// [`Transform`](#transform.Transform)), but also other state changes,
// like selection updates and adjustments of the set of [stored
// marks](#state.EditorState.storedMarks). In addition, you can store
// metadata properties in a transaction, which are extra pieces of
// information that client code or plugins can use to describe what a
// transacion represents, so that they can update their [own
// state](#state.StateField) accordingly.
//
// The [editor view](#view.EditorView) uses a few metadata properties:
// it will attach a property `"pointer"` with the value `true` to
// selection transactions directly caused by mouse or touch input, and
// a `"uiEvent"` property of that may be `"paste"`, `"cut"`, or `"drop"`.
export class Transaction extends Transform {
  constructor(state) {
    super(state.doc)
    // :: number
    // The timestamp associated with this transaction, in the same
    // format as `Date.now()`.
    this.time = Date.now()
    this.curSelection = state.selection
    // The step count for which the current selection is valid.
    this.curSelectionFor = 0
    // :: ?[Mark]
    // The stored marks set by this transaction, if any.
    this.storedMarks = state.storedMarks
    // Bitfield to track which aspects of the state were updated by
    // this transaction.
    this.updated = 0
    // Object used to store metadata properties for the transaction.
    this.meta = Object.create(null)
  }

  // :: Selection
  // The transaction's current selection. This defaults to the editor
  // selection [mapped](#state.Selection.map) through the steps in the
  // transaction, but can be overwritten with
  // [`setSelection`](#state.Transaction.setSelection).
  get selection() {
    if (this.curSelectionFor < this.steps.length) {
      this.curSelection = this.curSelection.map(this.doc, this.mapping.slice(this.curSelectionFor))
      this.curSelectionFor = this.steps.length
    }
    return this.curSelection
  }

  // :: (Selection) → Transaction
  // Update the transaction's current selection. Will determine the
  // selection that the editor gets when the transaction is applied.
  setSelection(selection) {
    this.curSelection = selection
    this.curSelectionFor = this.steps.length
    this.updated = (this.updated | UPDATED_SEL) & ~UPDATED_MARKS
    this.storedMarks = null
    return this
  }

  // :: bool
  // Whether the selection was explicitly updated by this transaction.
  get selectionSet() {
    return (this.updated & UPDATED_SEL) > 0
  }

  // :: (?[Mark]) → Transaction
  // Set the current stored marks.
  setStoredMarks(marks) {
    this.storedMarks = marks
    this.updated |= UPDATED_MARKS
    return this
  }

  // :: ([Mark]) → Transaction
  // Make sure the current stored marks or, if that is null, the marks
  // at the selection, match the given set of marks. Does nothing if
  // this is already the case.
  ensureMarks(marks) {
    if (!Mark.sameSet(this.storedMarks || this.selection.$from.marks(), marks))
      this.setStoredMarks(marks)
    return this
  }

  // :: (Mark) → Transaction
  // Add a mark to the set of stored marks.
  addStoredMark(mark) {
    return this.ensureMarks(mark.addToSet(this.storedMarks || this.selection.$head.marks()))
  }

  // :: (union<Mark, MarkType>) → Transaction
  // Remove a mark or mark type from the set of stored marks.
  removeStoredMark(mark) {
    return this.ensureMarks(mark.removeFromSet(this.storedMarks || this.selection.$head.marks()))
  }

  // :: bool
  // Whether the stored marks were explicitly set for this transaction.
  get storedMarksSet() {
    return (this.updated & UPDATED_MARKS) > 0
  }

  addStep(step, doc) {
    super.addStep(step, doc)
    this.updated = this.updated & ~UPDATED_MARKS
    this.storedMarks = null
  }

  // :: (number) → Transaction
  // Update the timestamp for the transaction.
  setTime(time) {
    this.time = time
    return this
  }

  // :: (Slice) → Transaction
  // Replace the current selection with the given slice.
  replaceSelection(slice) {
    this.selection.replace(this, slice)
    return this
  }

  // :: (Node, ?bool) → Transaction
  // Replace the selection with the given node. When `inheritMarks` is
  // true and the content is inline, it inherits the marks from the
  // place where it is inserted.
  replaceSelectionWith(node, inheritMarks) {
    let selection = this.selection
    if (inheritMarks !== false)
      node = node.mark(this.storedMarks || (selection.empty ? selection.$from.marks() : (selection.$from.marksAcross(selection.$to) || Mark.none)))
    selection.replaceWith(this, node)
    return this
  }

  // :: () → Transaction
  // Delete the selection.
  deleteSelection() {
    this.selection.replace(this)
    return this
  }

  // :: (string, from: ?number, to: ?number) → Transaction
  // Replace the given range, or the selection if no range is given,
  // with a text node containing the given string.
  insertText(text, from, to = from) {
    let schema = this.doc.type.schema
    if (from == null) {
      if (!text) return this.deleteSelection()
      return this.replaceSelectionWith(schema.text(text), true)
    } else {
      if (!text) return this.deleteRange(from, to)
      let marks = this.storedMarks
      if (!marks) {
        let $from = this.doc.resolve(from)
        marks = to == from ? $from.marks() : $from.marksAcross(this.doc.resolve(to))
      }
      return this.replaceRangeWith(from, to, schema.text(text, marks))
    }
  }

  // :: (union<string, Plugin, PluginKey>, any) → Transaction
  // Store a metadata property in this transaction, keyed either by
  // name or by plugin.
  setMeta(key, value) {
    this.meta[typeof key == "string" ? key : key.key] = value
    return this
  }

  // :: (union<string, Plugin, PluginKey>) → any
  // Retrieve a metadata property for a given name or plugin.
  getMeta(key) {
    return this.meta[typeof key == "string" ? key : key.key]
  }

  // :: bool
  // Returns true if this transaction doesn't contain any metadata,
  // and can thus safely be extended.
  get isGeneric() {
    for (let _ in this.meta) return false
    return true
  }

  // :: () → Transaction
  // Indicate that the editor should scroll the selection into view
  // when updated to the state produced by this transaction.
  scrollIntoView() {
    this.updated |= UPDATED_SCROLL
    return this
  }

  get scrolledIntoView() {
    return (this.updated & UPDATED_SCROLL) > 0
  }
}
<commit_msg>Make insertText, when given start/end positions, collapse the selection<commit_after>import {Transform} from "prosemirror-transform"
import {Mark} from "prosemirror-model"
import {Selection} from "./selection"

const UPDATED_SEL = 1, UPDATED_MARKS = 2, UPDATED_SCROLL = 4

// ::- An editor state transaction, which can be applied to a state to
// create an updated state. Use
// [`EditorState.tr`](#state.EditorState.tr) to create an instance.
//
// Transactions track changes to the document (they are a subclass of
// [`Transform`](#transform.Transform)), but also other state changes,
// like selection updates and adjustments of the set of [stored
// marks](#state.EditorState.storedMarks). In addition, you can store
// metadata properties in a transaction, which are extra pieces of
// information that client code or plugins can use to describe what a
// transacion represents, so that they can update their [own
// state](#state.StateField) accordingly.
//
// The [editor view](#view.EditorView) uses a few metadata properties:
// it will attach a property `"pointer"` with the value `true` to
// selection transactions directly caused by mouse or touch input, and
// a `"uiEvent"` property of that may be `"paste"`, `"cut"`, or `"drop"`.
export class Transaction extends Transform {
  constructor(state) {
    super(state.doc)
    // :: number
    // The timestamp associated with this transaction, in the same
    // format as `Date.now()`.
    this.time = Date.now()
    this.curSelection = state.selection
    // The step count for which the current selection is valid.
    this.curSelectionFor = 0
    // :: ?[Mark]
    // The stored marks set by this transaction, if any.
    this.storedMarks = state.storedMarks
    // Bitfield to track which aspects of the state were updated by
    // this transaction.
    this.updated = 0
    // Object used to store metadata properties for the transaction.
    this.meta = Object.create(null)
  }

  // :: Selection
  // The transaction's current selection. This defaults to the editor
  // selection [mapped](#state.Selection.map) through the steps in the
  // transaction, but can be overwritten with
  // [`setSelection`](#state.Transaction.setSelection).
  get selection() {
    if (this.curSelectionFor < this.steps.length) {
      this.curSelection = this.curSelection.map(this.doc, this.mapping.slice(this.curSelectionFor))
      this.curSelectionFor = this.steps.length
    }
    return this.curSelection
  }

  // :: (Selection) → Transaction
  // Update the transaction's current selection. Will determine the
  // selection that the editor gets when the transaction is applied.
  setSelection(selection) {
    this.curSelection = selection
    this.curSelectionFor = this.steps.length
    this.updated = (this.updated | UPDATED_SEL) & ~UPDATED_MARKS
    this.storedMarks = null
    return this
  }

  // :: bool
  // Whether the selection was explicitly updated by this transaction.
  get selectionSet() {
    return (this.updated & UPDATED_SEL) > 0
  }

  // :: (?[Mark]) → Transaction
  // Set the current stored marks.
  setStoredMarks(marks) {
    this.storedMarks = marks
    this.updated |= UPDATED_MARKS
    return this
  }

  // :: ([Mark]) → Transaction
  // Make sure the current stored marks or, if that is null, the marks
  // at the selection, match the given set of marks. Does nothing if
  // this is already the case.
  ensureMarks(marks) {
    if (!Mark.sameSet(this.storedMarks || this.selection.$from.marks(), marks))
      this.setStoredMarks(marks)
    return this
  }

  // :: (Mark) → Transaction
  // Add a mark to the set of stored marks.
  addStoredMark(mark) {
    return this.ensureMarks(mark.addToSet(this.storedMarks || this.selection.$head.marks()))
  }

  // :: (union<Mark, MarkType>) → Transaction
  // Remove a mark or mark type from the set of stored marks.
  removeStoredMark(mark) {
    return this.ensureMarks(mark.removeFromSet(this.storedMarks || this.selection.$head.marks()))
  }

  // :: bool
  // Whether the stored marks were explicitly set for this transaction.
  get storedMarksSet() {
    return (this.updated & UPDATED_MARKS) > 0
  }

  addStep(step, doc) {
    super.addStep(step, doc)
    this.updated = this.updated & ~UPDATED_MARKS
    this.storedMarks = null
  }

  // :: (number) → Transaction
  // Update the timestamp for the transaction.
  setTime(time) {
    this.time = time
    return this
  }

  // :: (Slice) → Transaction
  // Replace the current selection with the given slice.
  replaceSelection(slice) {
    this.selection.replace(this, slice)
    return this
  }

  // :: (Node, ?bool) → Transaction
  // Replace the selection with the given node. When `inheritMarks` is
  // true and the content is inline, it inherits the marks from the
  // place where it is inserted.
  replaceSelectionWith(node, inheritMarks) {
    let selection = this.selection
    if (inheritMarks !== false)
      node = node.mark(this.storedMarks || (selection.empty ? selection.$from.marks() : (selection.$from.marksAcross(selection.$to) || Mark.none)))
    selection.replaceWith(this, node)
    return this
  }

  // :: () → Transaction
  // Delete the selection.
  deleteSelection() {
    this.selection.replace(this)
    return this
  }

  // :: (string, from: ?number, to: ?number) → Transaction
  // Replace the given range, or the selection if no range is given,
  // with a text node containing the given string.
  insertText(text, from, to = from) {
    let schema = this.doc.type.schema
    if (from == null) {
      if (!text) return this.deleteSelection()
      return this.replaceSelectionWith(schema.text(text), true)
    } else {
      if (!text) return this.deleteRange(from, to)
      let marks = this.storedMarks
      if (!marks) {
        let $from = this.doc.resolve(from)
        marks = to == from ? $from.marks() : $from.marksAcross(this.doc.resolve(to))
      }
      this.replaceRangeWith(from, to, schema.text(text, marks))
      if (!this.selection.empty) this.setSelection(Selection.near(this.selection.$to))
      return this
    }
  }

  // :: (union<string, Plugin, PluginKey>, any) → Transaction
  // Store a metadata property in this transaction, keyed either by
  // name or by plugin.
  setMeta(key, value) {
    this.meta[typeof key == "string" ? key : key.key] = value
    return this
  }

  // :: (union<string, Plugin, PluginKey>) → any
  // Retrieve a metadata property for a given name or plugin.
  getMeta(key) {
    return this.meta[typeof key == "string" ? key : key.key]
  }

  // :: bool
  // Returns true if this transaction doesn't contain any metadata,
  // and can thus safely be extended.
  get isGeneric() {
    for (let _ in this.meta) return false
    return true
  }

  // :: () → Transaction
  // Indicate that the editor should scroll the selection into view
  // when updated to the state produced by this transaction.
  scrollIntoView() {
    this.updated |= UPDATED_SCROLL
    return this
  }

  get scrolledIntoView() {
    return (this.updated & UPDATED_SCROLL) > 0
  }
}
<|endoftext|>


<commit_before><commit_msg>added functions to the board class<commit_after><|endoftext|>


<commit_before><commit_msg>Added 'use strict';<commit_after><|endoftext|>


<commit_before><commit_msg>expose Parser class<commit_after><|endoftext|>


<commit_before><commit_msg>jsdoc<commit_after><|endoftext|>