persistent-state

simple state manager based on Vue3.reactive and localStorage

目前為 2024-07-01 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name persistent-state
  3. // @description simple state manager based on Vue3.reactive and localStorage
  4. // @namespace http://tampermonkey.net/
  5. // @author smartacephale
  6. // @license MIT
  7. // @version 1.2.4
  8. // @match *://*/*
  9. // ==/UserScript==
  10. /* globals reactive watch parseIntegerOr */
  11.  
  12. class PersistentState {
  13. constructor(state, key = "state_acephale") {
  14. this.key = key;
  15. this.state = reactive(state);
  16. this.sync();
  17. this.watchPersistence();
  18. }
  19.  
  20. sync() {
  21. this.trySetFromLocalStorage();
  22. window.addEventListener('focus', this.trySetFromLocalStorage);
  23. }
  24.  
  25. watchPersistence() {
  26. watch(this.state, (value) => {
  27. this.saveToLocalStorage(this.key, value);
  28. });
  29. }
  30.  
  31. saveToLocalStorage(key, value) {
  32. localStorage.setItem(key, JSON.stringify(value));
  33. }
  34.  
  35. trySetFromLocalStorage = () => {
  36. const localStorageValue = localStorage.getItem(this.key);
  37. if (localStorageValue !== null) {
  38. const prevState = JSON.parse(localStorageValue);
  39. for (const prop of Object.keys(prevState)) {
  40. this.state[prop] = prevState[prop];
  41. }
  42. }
  43. }
  44. }
  45.  
  46.  
  47. class DefaultState {
  48. DEFAULT_STATE = {
  49. filterExcludeWords: "",
  50. filterExclude: false,
  51. filterIncludeWords: "",
  52. filterInclude: false,
  53. infiniteScrollEnabled: true,
  54. uiEnabled: true,
  55. };
  56.  
  57. OPTIONAL_FILTERS = {
  58. DURATION_FILTER: {
  59. filterDurationFrom: 0,
  60. filterDurationTo: 600,
  61. filterDuration: false
  62. },
  63. PRIVACY_FILTER: {
  64. filterPrivate: false,
  65. filterPublic: false
  66. }
  67. }
  68.  
  69. DEFAULT_OPTIONS = {
  70. PRIVACY_FILTER: false,
  71. DURATION_FILTER: true
  72. }
  73.  
  74. constructor(options = this.DEFAULT_OPTIONS) {
  75. const opted = Object.assign(this.DEFAULT_OPTIONS, options);
  76. Object.keys(opted).forEach(key => {
  77. if (opted[key]) {
  78. Object.assign(this.DEFAULT_STATE, this.OPTIONAL_FILTERS[key]);
  79. }
  80. });
  81.  
  82. const { state } = new PersistentState(this.DEFAULT_STATE);
  83.  
  84. this.state = state;
  85.  
  86. this.stateLocale = reactive({
  87. pagIndexLast: 1,
  88. pagIndexCur: 1,
  89. filterOptions: opted
  90. });
  91. }
  92.  
  93. setWatchers(applyFilter) {
  94. const { state, stateLocale } = this;
  95.  
  96. if (stateLocale.filterOptions.PRIVACY_FILTER) {
  97. watch(() => state.filterPrivate, () => applyFilter({ filterPrivate: true }));
  98. watch(() => state.filterPublic, () => applyFilter({ filterPublic: true }));
  99. }
  100.  
  101. if (stateLocale.filterOptions.DURATION_FILTER) {
  102. watch([() => state.filterDurationFrom, () => state.filterDurationTo], (a, b) => {
  103. state.filterDurationFrom = parseIntegerOr(a[0], b[0]);
  104. state.filterDurationTo = parseIntegerOr(a[1], b[1]);
  105. if (state.filterDuration) applyFilter({ filterDuration: true });
  106. });
  107. watch(() => state.filterDuration, () => applyFilter({ filterDuration: true }));
  108. }
  109.  
  110. watch(() => state.filterExclude, () => applyFilter({ filterExclude: true }));
  111. watch(() => state.filterExcludeWords, () => {
  112. if (state.filterExclude) applyFilter({ filterExclude: true });
  113. }, { deep: true });
  114.  
  115. watch(() => state.filterInclude, () => applyFilter({ filterInclude: true }));
  116. watch(() => state.filterIncludeWords, () => {
  117. if (state.filterInclude) applyFilter({ filterInclude: true });
  118. }, { deep: true });
  119. }
  120. }
  121.