vuex-stateshot
Version:
A state snapshot plugin on actions/mutations for Vuex3.1+.
752 lines (634 loc) • 23.7 kB
JavaScript
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "fae3");
/******/ })
/************************************************************************/
/******/ ({
/***/ "61e5":
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, '__esModule', { value: true });
// See https://github.com/garycourt/murmurhash-js
var murmurHash2 = function murmurHash2(str) {
var seed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var l = str.length;
var h = seed ^ l;
var i = 0;
var k = void 0;
while (l >= 4) {
k = str.charCodeAt(i) & 0xff | (str.charCodeAt(++i) & 0xff) << 8 | (str.charCodeAt(++i) & 0xff) << 16 | (str.charCodeAt(++i) & 0xff) << 24;
k = (k & 0xffff) * 0x5bd1e995 + (((k >>> 16) * 0x5bd1e995 & 0xffff) << 16);
k ^= k >>> 24;
k = (k & 0xffff) * 0x5bd1e995 + (((k >>> 16) * 0x5bd1e995 & 0xffff) << 16);
h = (h & 0xffff) * 0x5bd1e995 + (((h >>> 16) * 0x5bd1e995 & 0xffff) << 16) ^ k;
l -= 4;
++i;
}
/* eslint-disable no-fallthrough */
switch (l) {
case 3:
h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
case 2:
h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
case 1:
h ^= str.charCodeAt(i) & 0xff;
h = (h & 0xffff) * 0x5bd1e995 + (((h >>> 16) * 0x5bd1e995 & 0xffff) << 16);
}
h ^= h >>> 13;
h = (h & 0xffff) * 0x5bd1e995 + (((h >>> 16) * 0x5bd1e995 & 0xffff) << 16);
h ^= h >>> 15;
return h >>> 0;
};
var hashFunc = murmurHash2;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var defaultRule = {
// StateNode => { Chunks, Children }
toRecord: function toRecord(node) {
return {
chunks: [_extends({}, node, { children: undefined })], children: node.children
};
},
// { Chunks, Children } => StateNode
fromRecord: function fromRecord(_ref) {
var chunks = _ref.chunks,
children = _ref.children;
return _extends({}, chunks[0], { children: children });
}
};
var state2Record = function state2Record(stateNode, chunkPool) {
var rules = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
var prevRecord = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
var pickIndex = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : -1;
var ruleIndex = rules.findIndex(function (_ref2) {
var match = _ref2.match;
return match(stateNode);
});
var rule = rules[ruleIndex] || defaultRule;
var _rule$toRecord = rule.toRecord(stateNode),
chunks = _rule$toRecord.chunks,
children = _rule$toRecord.children;
var recordChildren = children;
var hashes = [];
for (var i = 0; i < chunks.length; i++) {
var chunkStr = JSON.stringify(chunks[i]);
var hashKey = hashFunc(chunkStr);
hashes.push(hashKey);
chunkPool[hashKey] = chunkStr;
}
if (pickIndex !== -1 && Array.isArray(prevRecord && prevRecord.children)) {
var childrenCopy = [].concat(_toConsumableArray(prevRecord.children));
childrenCopy[pickIndex] = state2Record(recordChildren[pickIndex], chunkPool, rules);
return { hashes: hashes, ruleIndex: ruleIndex, children: childrenCopy };
} else {
return {
hashes: hashes,
ruleIndex: ruleIndex,
children: children && children.map(function (node) {
return state2Record(node, chunkPool, rules);
})
};
}
};
var record2State = function record2State(recordNode, chunkPool) {
var rules = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
var hashes = recordNode.hashes,
ruleIndex = recordNode.ruleIndex,
children = recordNode.children;
var chunks = hashes.map(function (hash) {
return JSON.parse(chunkPool[hash]);
});
var rule = rules[ruleIndex] || defaultRule;
return rule.fromRecord({
chunks: chunks,
children: children && children.map(function (node) {
return record2State(node, chunkPool, rules);
})
});
};
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var noop = function noop() {};
var History = function () {
function History() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
rules: [],
delay: 50,
maxLength: 100,
onChange: noop,
useChunks: true
};
_classCallCheck(this, History);
this.rules = options.rules || [];
this.delay = options.delay || 50;
this.maxLength = options.maxLength || 100;
this.useChunks = options.useChunks === undefined ? true : options.useChunks;
this.onChange = options.onChange || noop;
this.$index = -1;
this.$records = [];
this.$chunks = {};
this.$pending = {
state: null, pickIndex: null, onResolves: [], timer: null
};
this.$debounceTime = null;
}
// : boolean
_createClass(History, [{
key: 'get',
// void => State
value: function get() {
var currentRecord = this.$records[this.$index];
var resultState = void 0;
if (!currentRecord) {
resultState = null;
} else if (!this.useChunks) {
resultState = currentRecord;
} else {
resultState = record2State(currentRecord, this.$chunks, this.rules);
}
this.onChange(resultState);
return resultState;
}
// (State, number?) => History
}, {
key: 'pushSync',
value: function pushSync(state) {
var _this = this;
var pickIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
var latestRecord = this.$records[this.$index] || null;
var record = this.useChunks ? state2Record(state, this.$chunks, this.rules, latestRecord, pickIndex) : state;
this.$index++;
this.$records[this.$index] = record;
// Clear redo records.
for (var i = this.$index + 1; i < this.$records.length; i++) {
this.$records[i] = null;
}
// Clear first valid record if max length reached.
if (this.$index >= this.maxLength) {
this.$records[this.$index - this.maxLength] = null;
}
// Clear pending state.
if (this.$pending.timer) {
clearTimeout(this.$pending.timer);
this.$pending.state = null;
this.$pending.pickIndex = null;
this.$pending.timer = null;
this.$debounceTime = null;
this.$pending.onResolves.forEach(function (resolve) {
return resolve(_this);
});
this.$pending.onResolves = [];
}
this.onChange(state);
return this;
}
// (State, number?) => Promise<History>
}, {
key: 'push',
value: function push(state) {
var _this2 = this;
var pickIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
var currentTime = +new Date();
var setupPending = function setupPending() {
_this2.$pending.state = state;
_this2.$pending.pickIndex = pickIndex;
_this2.$debounceTime = currentTime;
var promise = new Promise(function (resolve, reject) {
_this2.$pending.onResolves.push(resolve);
_this2.$pending.timer = setTimeout(function () {
_this2.pushSync(_this2.$pending.state, _this2.$pending.pickIndex);
}, _this2.delay);
});
return promise;
};
// First time called.
if (this.$pending.timer === null) {
return setupPending();
} else if (currentTime - this.$debounceTime < this.delay) {
// Has been called without resolved.
clearTimeout(this.$pending.timer);
this.$pending.timer = null;
return setupPending();
} else return Promise.reject(new Error('Invalid push ops'));
}
// void => History
}, {
key: 'undo',
value: function undo() {
if (this.hasUndo) this.$index--;
return this;
}
// void => History
}, {
key: 'redo',
value: function redo() {
if (this.hasRedo) this.$index++;
return this;
}
// void => History
}, {
key: 'reset',
value: function reset() {
var _this3 = this;
this.$index = -1;
this.$records.forEach(function (tree) {
tree = null;
});
Object.keys(this.$chunks).forEach(function (key) {
_this3.$chunks[key] = null;
});
this.$records = [];
this.$chunks = {};
clearTimeout(this.$pending.timer);
this.$pending = {
state: null, pickIndex: null, onResolves: [], timer: null
};
this.$debounceTime = null;
return this;
}
}, {
key: 'hasRedo',
get: function get() {
// No redo when pointing to last record.
if (this.$index === this.$records.length - 1) return false;
// Only has redo if there're valid records after index.
// There can be no redo even if index less than records' length,
// when we undo multi records then push a new one.
var hasRecordAfterIndex = false;
for (var i = this.$index + 1; i < this.$records.length; i++) {
if (this.$records[i] !== null) hasRecordAfterIndex = true;
}
return hasRecordAfterIndex;
}
// : boolean
}, {
key: 'hasUndo',
get: function get() {
// Only has undo if we have records before index.
var lowerBound = Math.max(this.$records.length - this.maxLength, 0);
return this.$index > lowerBound;
}
// : number
}, {
key: 'length',
get: function get() {
return Math.min(this.$records.length, this.maxLength);
}
}]);
return History;
}();
/**
* Stateshot.js
* (c) 2018 Yifeng Wang
*/
exports.History = History;
//# sourceMappingURL=stateshot.js.map
/***/ }),
/***/ "8bbf":
/***/ (function(module, exports) {
module.exports = require("vue");
/***/ }),
/***/ "f6fd":
/***/ (function(module, exports) {
// document.currentScript polyfill by Adam Miller
// MIT license
(function(document){
var currentScript = "currentScript",
scripts = document.getElementsByTagName('script'); // Live NodeList collection
// If browser needs currentScript polyfill, add get currentScript() to the document object
if (!(currentScript in document)) {
Object.defineProperty(document, currentScript, {
get: function(){
// IE 6-10 supports script readyState
// IE 10+ support stack trace
try { throw new Error(); }
catch (err) {
// Find the second match for the "at" string to get file src url from stack.
// Specifically works with the format of stack traces in IE.
var i, res = ((/.*at [^\(]*\((.*):.+:.+\)$/ig).exec(err.stack) || [false])[1];
// For all scripts on the page, if src matches or if ready state is interactive, return the script tag
for(i in scripts){
if(scripts[i].src == res || scripts[i].readyState == "interactive"){
return scripts[i];
}
}
// If no match, return null
return null;
}
}
});
}
})(document);
/***/ }),
/***/ "fae3":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js
// This file is imported into lib/wc client bundles.
if (typeof window !== 'undefined') {
if (true) {
__webpack_require__("f6fd")
}
var i
if ((i = window.document.currentScript) && (i = i.src.match(/(.+\/)[^/]+\.js(\?.*)?$/))) {
__webpack_require__.p = i[1] // eslint-disable-line
}
}
// Indicate to webpack that this file can be concatenated
/* harmony default export */ var setPublicPath = (null);
// EXTERNAL MODULE: external {"commonjs":"vue","commonjs2":"vue","root":"Vue"}
var external_commonjs_vue_commonjs2_vue_root_Vue_ = __webpack_require__("8bbf");
var external_commonjs_vue_commonjs2_vue_root_Vue_default = /*#__PURE__*/__webpack_require__.n(external_commonjs_vue_commonjs2_vue_root_Vue_);
// EXTERNAL MODULE: ./node_modules/stateshot/dist/stateshot.js
var stateshot = __webpack_require__("61e5");
// CONCATENATED MODULE: ./src/vuex-stateshot.js
const SCOPE_NAME = 'vuexstateshot'
const SYNC_STATE = '__SYNC_STATE__'
const STATESHOT_UNDO = 'STATESHOT_UNDO'
const STATESHOT_REDO = 'STATESHOT_REDO'
class vuex_stateshot_VuexStateshot {
constructor (store, modules, options) {
const {
// Max length saving history states, 100 by default.
maxLength = 100,
// Debounce time for push in milliseconds, 50 by default.
delay = 50,
...others
} = options
const history = new stateshot["History"]({
maxLength: maxLength + 1,
delay,
...others
})
// store state
this.store = store
this.modules = modules
this.moduleNames = Object.keys(modules)
this.rootModule = store._modules.root
// subscribe state
this.actions = this.getSubscribeTypes('actions')
this.mutations = this.getSubscribeTypes('mutations')
this.unsubscribeAction = null
this.unsubscribe = null
// history state
this.history = history
// console.log(this.store)
}
getHistoryLength () {
/**
* There is some wrong with the stateshot.js
* The truely history length is $records we need filter the record
* Otherwise when you call the undo().get(), the history.length is wrong
*/
// const historyLength = this.history.length
return this.history.$records.filter(record => record).length
}
/**
* Get the wanted subscribe types with namespace
* @param {*} context 'actions/mutations'
*/
getSubscribeTypes (context) {
let types = []
for (const namespace of this.moduleNames) {
const subscribe = this.modules[namespace][context]
const mapedTypes = subscribe && subscribe.map(type => {
return namespace === 'rootModule' ? type : `${namespace}/${type}`
})
types = [...types, mapedTypes]
}
const deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)))
return deepFlatten(types.filter(v => v))
}
findNamespacedModule (namespace, moduleTree = this.rootModule) {
if (namespace === 'root') return this.rootModule
const parts = namespace.split('/')
if (!parts.length) return false
const subtree = moduleTree._children[parts[0]]
const subpath = parts.slice(1).join('/')
if (subtree && subtree.namespaced) {
return parts.length === 1 ? subtree : this.findNamespacedModule(subpath, subtree)
} else {
for (const name in moduleTree._children) {
const namespacedMoudle = this.findNamespacedModule(namespace, moduleTree._children[name])
if (namespacedMoudle) return namespacedMoudle
}
}
return false
}
registerPluginMoudle () {
this.store.registerModule(SCOPE_NAME, {
namespaced: true,
state: {
prevState: {},
nextState: {},
hasUndo: false,
hasRedo: false,
historyLength: 0,
undoCount: 0,
redoCount: 0
},
actions: {
// snap a state shot of rootState
snapshot: ({ commit, dispatch, state, rootState }, payload) => {
this.history.pushSync(rootState)
const historyLength = this.getHistoryLength()
commit(SYNC_STATE, {
hasUndo: historyLength - 1 > 0,
hasRedo: false,
historyLength,
undoCount: historyLength - 1,
redoCount: 0
})
},
undo: ({ commit, state }, payload) => {
const { hasUndo } = state
const prevState = this.history.undo().get()
if (prevState && hasUndo) commit('STATESHOT_UNDO', prevState)
return prevState
},
redo: ({ commit, state }, payload) => {
const { hasRedo } = state
const nextState = this.history.redo().get()
if (nextState && hasRedo) commit('STATESHOT_REDO', nextState)
return nextState
},
reset: ({ commit }, payload) => {
this.history.reset()
commit(SYNC_STATE, {
nextState: {},
prevState: {},
hasUndo: false,
hasRedo: false,
historyLength: 0,
undoCount: 0,
redoCount: 0
})
}
},
mutations: {
[SYNC_STATE] (state, payload) {
Object.assign(state, payload)
},
[STATESHOT_UNDO] (state, prevState) {
state.prevState = prevState
state.undoCount -= 1
if (state.undoCount === 0) state.hasUndo = false
state.redoCount += 1
state.hasRedo = state.redoCount > 0
},
[STATESHOT_REDO] (state, nextState) {
state.nextState = nextState
state.redoCount -= 1
if (state.redoCount === 0) state.hasRedo = false
state.undoCount += 1
state.hasUndo = state.undoCount > 0
}
},
getters: {
hasUndo: state => state.hasUndo,
hasRedo: state => state.hasRedo,
undoCount: state => state.undoCount,
redoCount: state => state.redoCount,
historyLength: state => state.historyLength
}
})
}
syncState (namespace = 'root') {
const { state } = this.findNamespacedModule(namespace, this.rootModule)
this.store.dispatch(`${SCOPE_NAME}/snapshot`, state)
}
stateshotFromAction (store) {
return {
before: (action, state) => {
console.log(
'%cAction',
'background: blue; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;',
`${action.type}`
)
},
after: (action, state) => {
if (this.actions.includes(action.type)) {
this.syncState()
console.log(
'%cSync',
'background: green; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;',
`Sync State... length is: ${this.history.length}`
)
}
}
}
}
stateshotFromMutation (mutation, state) {
if (this.mutations.includes(mutation.type)) {
this.syncState()
console.log(
'%cMutation',
'background: green; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;',
`Sync State... length is: ${this.history.length}`
)
}
}
subscribeAction () {
this.unsubscribeAction = this.store.subscribeAction(this.stateshotFromAction(this.store))
}
subscribe () {
this.unsubscribe = this.store.subscribe((mutation, state) => this.stateshotFromMutation(mutation, state))
}
}
function createPlugin (modules, options) {
return store => {
const plugin = new vuex_stateshot_VuexStateshot(store, modules, options)
plugin.registerPluginMoudle()
plugin.syncState()
plugin.subscribeAction()
plugin.subscribe()
external_commonjs_vue_commonjs2_vue_root_Vue_default.a.prototype.$stateshot = plugin
}
}
// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/entry-lib-no-default.js
/* concated harmony reexport createPlugin */__webpack_require__.d(__webpack_exports__, "createPlugin", function() { return createPlugin; });
/***/ })
/******/ });