├── .gitignore ├── LICENSE ├── README.md ├── colada-extension ├── .eslintrc.js ├── README.md ├── app-frontend │ └── src │ │ ├── App.vue │ │ ├── assets │ │ └── style.scss │ │ ├── components │ │ ├── ChartNode.vue │ │ ├── ChartsContainer.vue │ │ ├── CurrentNode.vue │ │ ├── CurrentStore.vue │ │ ├── Navbar.vue │ │ ├── StoreInfo.vue │ │ ├── TimelineNode.vue │ │ └── VertTimeline.vue │ │ ├── main.js │ │ ├── router │ │ └── index.js │ │ └── views │ │ ├── ChartView.vue │ │ ├── MenuView.vue │ │ └── TimelineView.vue ├── devtools-panel.js ├── index.html ├── package-lock.json ├── package.json ├── public │ ├── assets │ │ ├── chrome-dev-tools.png │ │ ├── icon-128.png │ │ ├── icon-16.png │ │ └── icon-48.png │ ├── content-script.js │ ├── devtools-background.html │ ├── devtools-background.js │ ├── manifest.json │ └── popup.html └── vite.config.js ├── colada-plugin ├── .eslintrc.js ├── .npmignore ├── README.md ├── __tests__ │ ├── handleStoreChange.test.js │ └── stateHandler.test.js ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── src │ ├── ColadaDevToolsPlugin │ │ ├── index.ts │ │ ├── inspector.ts │ │ ├── stateHandler.ts │ │ └── timeline.ts │ ├── PiniaColadaPlugin │ │ └── index.ts │ ├── index.ts │ └── types.ts ├── tsconfig.json └── vite.config.js ├── demo-project ├── .eslintrc.js ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public │ └── favicon.ico ├── reload.sh ├── src │ ├── App.vue │ ├── assets │ │ ├── base.css │ │ ├── logo.svg │ │ └── main.css │ ├── components │ │ ├── Counter.vue │ │ ├── DoubleStore.vue │ │ ├── Header.vue │ │ ├── Main.vue │ │ ├── WelcomeItem.vue │ │ └── __tests__ │ │ │ └── HelloWorld.spec.js │ ├── main.js │ └── stores │ │ ├── counter.js │ │ └── store.js └── vite.config.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | dist.* 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | 107 | #.DS_Store 108 | .DS_Store 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | Colada logo 6 | 7 |

8 |
9 |

10 | 11 | license MIT 12 | npm package 13 | chrome web store 14 | typescript 15 | 16 |

17 |
18 | 19 | # What is [Colada](https://colada.dev/)? 20 | 21 | ## *Time-travel debugging for [Pinia🍍](https://pinia.vuejs.org/ "Pinia homepage and documentation"), Vue's official state management library* 22 |
23 | 24 | ## Colada offers a suite of tools for Vue developers working with the [Pinia state management library](https://pinia.vuejs.org/): 25 | 1. [Chrome DevTool Extension](https://chrome.google.com/webstore/detail/colada-devtools/icdbaobbeemmhlmjolbkedcneadkfpdl) 26 | 2. [NPM package](https://www.npmjs.com/package/colada-plugin) that serves as a [plugin](https://devtools.vuejs.org/plugin/plugins-guide.html) for the [Vue.js DevTools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=en) Chrome Extension 27 | 3. [Pinia🍍](https://pinia.vuejs.org/) plugin to directly access and mutate your app's store 28 | 29 |
30 | 31 | ## Core Features 32 | 33 | - ✅ Minimal installation and automatic detection of Vue app in [Vue.js DevTools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=en) 34 | - 🔄 Direct integration into [Vue.js DevTools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=en), so developers can use Colada without leaving their existing devtool configuration 35 | - 🕰️ Time travel debugging 36 | - 🔎 Inspector panel for viewing Pinia state within your Vue app 37 | - 💻 A [Chrome DevTool Extension](https://chrome.google.com/webstore/detail/colada-devtools/icdbaobbeemmhlmjolbkedcneadkfpdl) providing enhanced features, including: 38 | - 🕰️ Time travel debugging 39 | - 🔎 Inspector panel for viewing Pinia state within your Vue app 40 | 41 |
42 | 43 | 44 | demo screenshot 45 | 46 |
47 |
48 | 49 | # Getting Started 50 | 51 | ## Installation: **Vue DevTools Plugin** 52 | 0. Ensure the [Vue.js DevTools Chrome Extension](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=en) is installed 53 | 1. Install the [Colada npm package](https://www.npmjs.com/package/colada-plugin) in your app's root directory 54 | ```bash 55 | npm install colada-plugin --save-dev 56 | ``` 57 | 58 | 2. Add Colada to your Vue app 59 | ```js 60 | // main.js 61 | 62 | import { createApp } from 'vue'; 63 | import { createPinia } from 'pinia'; 64 | // import Colada Plugin 65 | import Colada, { PiniaColadaPlugin } from 'colada-plugin'; 66 | import App from './App.vue'; 67 | 68 | const app = createApp(App); 69 | const pinia = createPinia(); 70 | 71 | app.use(pinia); 72 | pinia.use(PiniaColadaPlugin); 73 | app.use(Colada); 74 | 75 | app.mount('#app'); 76 | ``` 77 |
78 | 79 | ## Installation: **Chrome DevTools Extension** 80 | 81 | ### *NOTE: Ensure the [Vue.js DevTools Chrome Extension](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=en) is installed before installing the Colada DevTool Chrome Extension* 82 |
83 | 84 | ### There are two ways to install the Colada Chrome Extension: 85 | 86 | 87 | ### 1. **Install from the Chrome Web Store** 88 | 1. Navigate to [Colada on the Chrome Web Store](https://chrome.google.com/webstore/detail/colada-devtools/icdbaobbeemmhlmjolbkedcneadkfpdl), and click "Add to Chrome" 89 | 90 | demo screenshot 91 | 92 | ### 2. **Install from source** 93 | 94 | 1. Clone this repository 95 | 2. Run the following commands 96 | ``` 97 | cd colada-extension 98 | npm install 99 | npm run build 100 | ``` 101 | 3. This will create a new `/dist` directory in `/colada-extension` 102 | 4. In Chrome, navigate to [chrome://extensions](chrome://extensions). 103 | 5. In the top right of the Extensions page, there is a toggle for "Developer Mode." Make sure this is toggled **ON**. 104 | 6. On the top left of the page, select "Load Unpacked", and select the `colada/colada-extension/dist` directory. 105 | 106 |
107 |
108 | 109 | # How to Use Colada 110 | 111 | ## Using the Colada **Vue DevTools Plugin** 112 | - Navigate to the Vue.js DevTools 113 |
114 | 115 | ### **Time Travel Debugging** 116 | - Select the "Colada" timeline in the timeline view 117 | - Turn off screenshots
118 | 119 | - Changes in your app's store and state will automatically be tracked on the timeline 120 | - Click on timeline events to travel through time and update your app's state 121 |

122 | 123 | ### **Inspector Panel** - View Your App's Stores and State in Real Time 124 | - Select "Colada" in the component menu drop down 125 | - Click on your Pinia store to view state, actions, and getters updated in real time 126 |
127 | 128 | 129 | 130 |
131 |
132 |
133 | 134 | ## Using the Colada **Chrome DevTool Extension** 135 | - Navigate to Colada DevTools in Chrome 136 |
137 | 138 | 139 | - Changes in your app's store and state will automatically be tracked on the timeline 140 | - Click on a timestamp or use the arrows to travel through time and update your app's state 141 | - View your app's state as you time travel in the inspector panel on the right 142 |
143 | 144 | 145 |
146 |
147 | 148 | # How to Give Colada a Test Run With Our Demo App 149 | 150 | 1. Clone this repository 151 | 2. Navigate to the ```demo-project``` directory 152 | ``` 153 | cd demo-project 154 | ``` 155 | 3. Install packages and run application 156 | ```bash 157 | npm install 158 | npm run dev 159 | ``` 160 | 4. Interact with the app to watch the app's state update in real-time! 161 | 162 |
163 |
164 | 165 | # Contributing and Issues 166 | Interested in conributing to Colada? Reach out to our [core team](https://colada.dev/#contributors)
167 | Feature requests or issues/bugs to report? [Let us know!](https://github.com/oslabs-beta/colada/issues) 168 | 169 |
170 | 171 | # Release Notes 172 | ## Contributors 173 | - Parker Steinberg • [LinkedIn](https://www.linkedin.com/in/parker-steinberg/) • [Github](https://github.com/parkersteinberg) 174 | - Jonathan Chen • [LinkedIn](https://www.linkedin.com/in/jonathan-hp-chen/) • [Github](https://github.com/JonHPC) 175 | - Vaughn Sulit • [LinkedIn](https://www.linkedin.com/in/bvaughnsulit/) • [Github](https://github.com/bvaughnsulit) 176 | - Dan Steinbrook • [LinkedIn](https://www.linkedin.com/in/daniel-steinbrook/) • [Github](https://github.com/dsteinbrook) 177 | 178 |
179 | 0.1.1 | Initial release of Colada, more to come! 180 | 181 | 182 |
183 | 184 | # License 185 | [MIT](http://opensource.org/licenses/MIT) -------------------------------------------------------------------------------- /colada-extension/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2021': true 5 | }, 6 | 'extends': [ 7 | 'eslint:recommended', 8 | 'plugin:vue/vue3-essential' 9 | ], 10 | 'overrides': [ 11 | ], 12 | 'parserOptions': { 13 | 'ecmaVersion': 'latest', 14 | 'sourceType': 'module' 15 | }, 16 | 'globals' : { 17 | 'chrome' : 'readonly' 18 | }, 19 | 'ignorePatterns' : ['dist/'], 20 | 'plugins': [ 21 | 'vue' 22 | ], 23 | 'rules': { 24 | 'indent': [ 25 | 'warn', 26 | 2 27 | ], 28 | 'linebreak-style': [ 29 | 'warn', 30 | 'unix' 31 | ], 32 | 'quotes': [ 33 | 'warn', 34 | 'single' 35 | ], 36 | 'semi': [ 37 | 'warn', 38 | 'always' 39 | ] 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /colada-extension/README.md: -------------------------------------------------------------------------------- 1 | #colada-extension 2 | ``` 3 | cd colada-extension 4 | npm install 5 | ``` 6 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 27 | 28 | 32 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/assets/style.scss: -------------------------------------------------------------------------------- 1 | //VARIABLES 2 | $primary-color:rgb(96, 202, 140); 3 | $background-color: rgb(13,16,21); 4 | $nav-icon:white; 5 | $nav-hover:rgb(48,61,78); 6 | $nav-click:rgb(71, 91, 118); 7 | $border-color:rgb(71, 91, 118); 8 | $store-color:rgb(199, 165, 243); 9 | $pinia-color:rgb(250,217,111); 10 | $red-500: #ef4444; 11 | $slate-400: #94a3b8; 12 | $slate-800:#1e293b; 13 | 14 | //****************** Below is App.vue styling *************** 15 | 16 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400&display=swap'); 17 | * { 18 | box-sizing: border-box; 19 | margin: 0; 20 | padding: 0; 21 | } 22 | html{ 23 | width:100vw; 24 | height:100vh; 25 | } 26 | body { 27 | // font-family: 'Poppins', sans-serif; 28 | font-family:monospace; 29 | font-size: 100%; //override the chrome bug that sets font size to 75% 30 | display:flex; 31 | flex-direction:column; 32 | justify-content:flex-start; 33 | align-items:center; 34 | background-color:$background-color; 35 | color:rgb(182,193,201); 36 | overflow:hidden; 37 | } 38 | .main{ 39 | display:flex; 40 | flex-direction:column; 41 | justify-content: flex-start; 42 | align-items:center; 43 | } 44 | .container { 45 | max-width: 500px; 46 | margin: 30px auto; 47 | overflow: auto; 48 | min-height: 300px; 49 | border: 1px solid steelblue; 50 | padding: 30px; 51 | border-radius: 5px; 52 | } 53 | //****************** Above is App.vue styling *************** 54 | 55 | //****************** Below is Button styling *************** 56 | .btn { 57 | display: inline-block; 58 | background: $background-color; 59 | color: $primary-color; 60 | border: none; 61 | padding: 3px 14px; 62 | margin: 3px; 63 | border-radius: 5px; 64 | cursor: pointer; 65 | text-decoration: none; 66 | font-size: 16px; 67 | font-family: inherit; 68 | transition:0.25s; 69 | border:2px solid $primary-color; 70 | } 71 | 72 | .btn:hover{ 73 | background-color:$primary-color; 74 | color: $background-color; 75 | } 76 | 77 | .btn:focus { 78 | outline: none; 79 | } 80 | .btn:active { 81 | transform: scale(0.9); 82 | } 83 | .btn-block { 84 | display: block; 85 | width: 100%; 86 | } 87 | 88 | .clear-btn { 89 | display: inline-block; 90 | background: $background-color; 91 | color: $red-500; 92 | border: none; 93 | padding: 3px 14px; 94 | margin: 3px; 95 | border-radius: 5px; 96 | cursor: pointer; 97 | text-decoration: none; 98 | font-size: 16px; 99 | font-family: inherit; 100 | transition:0.25s; 101 | border:2px solid $red-500; 102 | } 103 | 104 | .clear-btn:hover{ 105 | background-color:$red-500; 106 | color: $background-color; 107 | } 108 | 109 | .clear-btn:focus { 110 | outline: none; 111 | } 112 | .clear-btn:active { 113 | transform: scale(0.9); 114 | } 115 | .clear-btn-block { 116 | display: block; 117 | width: 100%; 118 | } 119 | 120 | .timestamp-btn { 121 | display: inline-block; 122 | background: $background-color; 123 | color: $primary-color; 124 | border: none; 125 | padding: 3px 6px; 126 | margin: 3px; 127 | border-radius: 5px; 128 | cursor: pointer; 129 | text-decoration: none; 130 | font-size: 12px; 131 | font-family: inherit; 132 | transition:0.25s; 133 | border:2px solid $primary-color; 134 | } 135 | 136 | .timestamp-btn:hover{ 137 | background-color:$primary-color; 138 | color: $background-color; 139 | } 140 | 141 | .timestamp-btn:focus { 142 | outline: none; 143 | } 144 | .timestamp-btn:active { 145 | transform: scale(0.9); 146 | } 147 | .timestamp-btn-block { 148 | display: block; 149 | width: 100%; 150 | } 151 | //****************** Above is Button styling *************** 152 | 153 | //****************** Below is Navbar.vue styling *************** 154 | .navbar{ 155 | display:flex; 156 | justify-content:space-between; 157 | align-items:center; 158 | border-bottom: 3px double $border-color; 159 | width:100vw; 160 | padding: 0rem 0.5rem; 161 | } 162 | 163 | .navbar-icons{ 164 | display:flex; 165 | justify-content:space-evenly; 166 | align-items:center; 167 | } 168 | 169 | .nav-icon{ 170 | width:48px; 171 | height:48px; 172 | color:$nav-icon; 173 | transition:0.2s; 174 | border-radius:8px; 175 | display:flex; 176 | justify-content:center; 177 | align-items:center; 178 | } 179 | 180 | .nav-icon:hover{ 181 | background-color:$nav-hover; 182 | } 183 | 184 | .nav-icon:active{ 185 | background-color:$nav-click; 186 | } 187 | //****************** Above is Navbar.vue styling *************** 188 | 189 | //****************** Below is VertTimeline.vue styling *************** 190 | .timeline-container{ 191 | display:flex; 192 | flex-direction:row; 193 | justify-content:space-between; 194 | align-items:flex-start; 195 | gap:1rem; 196 | width: 100vw; 197 | height: 100vh; 198 | } 199 | 200 | .vertical-left{ 201 | display:flex; 202 | justify-content:flex-start; 203 | align-items:flex-start; 204 | width:50%; 205 | height:100%; 206 | padding-top:1rem; 207 | flex-grow:1; 208 | font-family:monospace; 209 | } 210 | 211 | .vert-timeline{ 212 | height:90%; 213 | width:100%; 214 | display:flex; 215 | flex-direction:column; 216 | justify-content:flex-start; 217 | align-items:center; 218 | overflow-x: hidden; 219 | overflow-y: auto; 220 | 221 | } 222 | 223 | .timeline { 224 | list-style-type: none; 225 | display: flex; 226 | flex-direction:column; 227 | align-items: flex-start; 228 | justify-content: flex-start; 229 | } 230 | 231 | .timeline-nodes{ 232 | display:flex; 233 | width:100%; 234 | flex-direction:column; 235 | align-items:flex-start; 236 | justify-content:space-evenly; 237 | border-left: 2px solid #D6DCE0; 238 | border-top: 1px solid $border-color; 239 | position:relative; 240 | transition: all 200ms ease-in; 241 | z-index:1; 242 | &:before{ 243 | content: ''; 244 | width: 8px; 245 | height: 8px; 246 | background-color: $slate-400; 247 | border-radius: 25px; 248 | border: 1px solid $slate-400; 249 | position: absolute; 250 | top: 25%; 251 | left: -6px; 252 | transition: all 200ms ease-in; 253 | } 254 | } 255 | 256 | // .timeline-nodes:hover{ 257 | // background-color:$nav-hover; 258 | // cursor:pointer; 259 | // } 260 | 261 | // .timeline-nodes:focus, .timeline-nodes:active{ 262 | // background-color:$nav-click; 263 | // } 264 | 265 | // .timeline-nodes.clicked{ 266 | // background-color:$nav-click; 267 | // } 268 | 269 | .node{ 270 | display:flex; 271 | flex-direction:column; 272 | } 273 | 274 | .timeline-node{ 275 | display:flex; 276 | justify-content:space-evenly; 277 | height:100%; 278 | } 279 | 280 | .timestamp{ 281 | padding: 0px 20px; 282 | display: flex; 283 | flex-direction: column; 284 | justify-content:center; 285 | align-items: center; 286 | font-weight: 100; 287 | } 288 | .status{ 289 | padding: 0px 20px; 290 | display: flex; 291 | justify-content: center; 292 | align-items:center; 293 | position: relative; 294 | transition: all 200ms ease-in; 295 | h4{ 296 | font-weight:600; 297 | } 298 | } 299 | 300 | .timeline-nodes.complete{ 301 | background-color:$slate-800; 302 | border-left: 2px solid $primary-color; 303 | color: $store-color; 304 | &:before{ 305 | background-color: $primary-color; 306 | transition: all 200ms ease-in ; 307 | border: 1px solid $primary-color; 308 | } 309 | h4 { 310 | color: $primary-color; 311 | } 312 | } 313 | 314 | //****************** Above is VertTimeline.vue styling *************** 315 | 316 | //****************** Below is Animations *************** 317 | 318 | .slide-right-enter-active { 319 | transition: all 0.25s ease-out; 320 | } 321 | 322 | .slide-right-leave-active { 323 | opacity:0; 324 | } 325 | 326 | .slide-right-enter-from, .slide-right-leave-to { 327 | transform: translateX(-200px); 328 | opacity: 0; 329 | } 330 | 331 | .slide-left-enter-active { 332 | transition: all 0.25s ease-out; 333 | } 334 | 335 | .slide-left-leave-active { 336 | opacity:0; 337 | } 338 | 339 | .slide-left-enter-from, .slide-left-leave-to { 340 | transform: translateX(200px); 341 | opacity: 0; 342 | } 343 | 344 | .fade-enter-active, .fade-leave-active { 345 | transition: opacity 0.5s ease; 346 | } 347 | 348 | .fade-enter-from, .fade-leave-to { 349 | opacity: 0; 350 | } 351 | 352 | //****************** Above is Animations *************** 353 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/components/ChartNode.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/components/ChartsContainer.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/components/CurrentNode.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 34 | 35 | 59 | 60 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/components/CurrentStore.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 36 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/components/StoreInfo.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 59 | 60 | 100 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/components/TimelineNode.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 77 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/components/VertTimeline.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | 5 | 6 | const app = createApp(App); 7 | app.use(router); 8 | app.mount('#app'); 9 | 10 | // export async function initDevTools(){ 11 | // const app = createApp(App) 12 | // app.mount('#app') 13 | // } 14 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import {createRouter, createWebHistory} from 'vue-router'; 2 | import TimelineView from '../views/TimelineView.vue'; 3 | import ChartView from '../views/ChartView.vue'; 4 | import MenuView from '../views/MenuView.vue'; 5 | 6 | const routes = [ 7 | { 8 | path: '/index.html', 9 | redirect: '/' 10 | }, 11 | { 12 | path: '/', 13 | name: 'TimelineView', 14 | component: TimelineView, 15 | meta: {transition: 'slide-right'} 16 | }, 17 | { 18 | path: '/chart', 19 | name: 'ChartView', 20 | component: ChartView, 21 | meta: {transition: 'slide-left'} 22 | }, 23 | { 24 | path: '/menu', 25 | name: 'MenuView', 26 | component: MenuView, 27 | meta: {transition: 'slide-left'} 28 | } 29 | ]; 30 | 31 | const router = createRouter({ 32 | history: createWebHistory(), 33 | routes 34 | }); 35 | 36 | export default router; 37 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/views/ChartView.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/views/MenuView.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 42 | 43 | -------------------------------------------------------------------------------- /colada-extension/app-frontend/src/views/TimelineView.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 205 | 206 | 215 | -------------------------------------------------------------------------------- /colada-extension/devtools-panel.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | import App from './app-frontend/src/App.vue'; 3 | import router from './app-frontend/src/router'; 4 | 5 | const app = createApp(App); 6 | app.use(router); 7 | app.mount('#app'); 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /colada-extension/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /colada-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "vite build --watch", 4 | "build": "vite build" 5 | }, 6 | "devDependencies": { 7 | "@vitejs/plugin-vue": "^3.1.0", 8 | "@vitejs/plugin-vue-jsx": "^2.0.1", 9 | "cross-env": "^7.0.3", 10 | "eslint": "^8.23.1", 11 | "eslint-plugin-vue": "^9.5.1", 12 | "laravel-mix": "^6.0.49", 13 | "sass": "^1.54.9", 14 | "sass-loader": "^12.6.0", 15 | "vite": "^3.1.3", 16 | "vue": "^3.2.39", 17 | "vue-loader": "^17.0.0", 18 | "vue-router": "^4.1.5", 19 | "vue-template-compiler": "^2.7.10" 20 | }, 21 | "dependencies": { 22 | "bootstrap-icons": "^1.9.1", 23 | "dotenv": "^16.0.2", 24 | "swiper": "^8.4.2", 25 | "vue-json-pretty": "^2.2.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /colada-extension/public/assets/chrome-dev-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/colada/6253de89595e6a19443fc45b6527aa0432e48321/colada-extension/public/assets/chrome-dev-tools.png -------------------------------------------------------------------------------- /colada-extension/public/assets/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/colada/6253de89595e6a19443fc45b6527aa0432e48321/colada-extension/public/assets/icon-128.png -------------------------------------------------------------------------------- /colada-extension/public/assets/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/colada/6253de89595e6a19443fc45b6527aa0432e48321/colada-extension/public/assets/icon-16.png -------------------------------------------------------------------------------- /colada-extension/public/assets/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/colada/6253de89595e6a19443fc45b6527aa0432e48321/colada-extension/public/assets/icon-48.png -------------------------------------------------------------------------------- /colada-extension/public/content-script.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | function saveMessage(event) { 3 | const parsed = typeof event.data === 'string' ? JSON.parse(event.data) : ''; 4 | if (parsed && parsed.source === 'colada') { 5 | const date = Object.keys(parsed.payload)[0]; 6 | chrome.storage.local.set({ [date]: parsed.payload }, () => { }); 7 | } 8 | } 9 | window.addEventListener('message', saveMessage); 10 | window.addEventListener('load', () => { chrome.storage.local.clear(); }); 11 | chrome.runtime.onMessage.addListener((message) => { 12 | if (message.source === 'colada-extension') { 13 | window.postMessage(JSON.stringify(message), window.location.href); 14 | } 15 | }); 16 | })(); -------------------------------------------------------------------------------- /colada-extension/public/devtools-background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /colada-extension/public/devtools-background.js: -------------------------------------------------------------------------------- 1 | // loaded by index.html, which is the the "background" page for the devtools-panel 2 | // this is declared in manifest.json. note that this does not actually represent the UI, that is the devtools "panel") 3 | 4 | chrome.devtools.panels.create('Colada DevTools', '', './index.html'); 5 | 6 | -------------------------------------------------------------------------------- /colada-extension/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Colada DevTools", 3 | "description": "Time-travel debugging for Pinia, Vue's official state management library.", 4 | "version": "0.1.2", 5 | "manifest_version": 3, 6 | "icons": { 7 | "16": "./assets/icon-16.png", 8 | "48": "./assets/icon-48.png", 9 | "128": "./assets/icon-128.png" 10 | }, 11 | "permissions": ["storage"], 12 | "action": { 13 | "default_popup": "popup.html" 14 | }, 15 | "devtools_page": "devtools-background.html", 16 | "content_scripts": [ 17 | { 18 | "matches": [ 19 | "*://*/*" 20 | ], 21 | "js": ["./content-script.js"] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /colada-extension/public/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Colada 5 | 14 | 15 | 16 | 17 |

18 | To use Colada DevTools, open the Chrome Developer Tools, and select Colada DevTools from the main toolbar. 19 |

20 |

21 | For more information, please visit colada.dev 22 |

23 | Chrome DevTools menu 24 | 25 | 26 | -------------------------------------------------------------------------------- /colada-extension/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import vueJsx from '@vitejs/plugin-vue-jsx'; 4 | 5 | export default defineConfig({ 6 | plugins: [vue(), vueJsx()], 7 | base: '', 8 | build: { minify: false } 9 | }); 10 | -------------------------------------------------------------------------------- /colada-plugin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2021': true 5 | }, 6 | 'extends': [ 7 | 'eslint:recommended', 8 | 'plugin:vue/vue3-essential', 9 | 'plugin:@typescript-eslint/recommended' 10 | ], 11 | 'overrides': [ 12 | ], 13 | 'parser': '@typescript-eslint/parser', 14 | 'parserOptions': { 15 | 'ecmaVersion': 'latest', 16 | 'sourceType': 'module' 17 | }, 18 | 'plugins': [ 19 | 'vue', 20 | '@typescript-eslint' 21 | ], 22 | 'rules': { 23 | 'indent': [ 24 | 'warn', 25 | 2 26 | ], 27 | 'linebreak-style': [ 28 | 'warn', 29 | 'unix' 30 | ], 31 | 'quotes': [ 32 | 'warn', 33 | 'single' 34 | ], 35 | 'semi': [ 36 | 'warn', 37 | 'always' 38 | ] 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /colada-plugin/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | rollup.config.mjs 3 | tsconfig.json 4 | .eslintrc.js -------------------------------------------------------------------------------- /colada-plugin/README.md: -------------------------------------------------------------------------------- 1 | ## How to add Colada to your Vue app... 2 | ``` 3 | //main.js 4 | import { createApp } from 'vue'; 5 | import { createPinia } from 'pinia'; 6 | import Colada, { PiniaColadaPlugin } from 'colada-plugin'; 7 | import App from './App.vue'; 8 | 9 | const app = createApp(App); 10 | const pinia = createPinia(); 11 | 12 | app.use(pinia); 13 | pinia.use(PiniaColadaPlugin); 14 | app.use(Colada); 15 | 16 | app.mount('#app'); 17 | -------------------------------------------------------------------------------- /colada-plugin/__tests__/handleStoreChange.test.js: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from 'vitest'; 2 | import { handleStoreChange } from '../src/ColadaDevToolsPlugin/stateHandler'; 3 | import * as _ from 'lodash'; 4 | 5 | 6 | describe('handleStoreChange does not mutate inputs', () => { 7 | 8 | it('does not mutate snapshsot argument', () => { 9 | const firstSnapshot = 10 | { 11 | timestamp: 1663784676523, 12 | type: 'Store: counter', 13 | key: 'counter', 14 | value: { 'count': 0 }, 15 | state: ['state'], 16 | getters: {}, 17 | actions: {}, 18 | editable: true 19 | }; 20 | 21 | handleStoreChange(firstSnapshot); 22 | expect(firstSnapshot).toEqual({ 23 | timestamp: 1663784676523, 24 | type: 'Store: counter', 25 | key: 'counter', 26 | value: { 'count': 0 }, 27 | state: ['state'], 28 | getters: {}, 29 | actions: {}, 30 | editable: true 31 | }); 32 | 33 | }); 34 | 35 | }); -------------------------------------------------------------------------------- /colada-plugin/__tests__/stateHandler.test.js: -------------------------------------------------------------------------------- 1 | // @vitest-environment jsdom 2 | 3 | import { 4 | setAppState, 5 | } from './src/ColadaDevToolsPlugin/stateHandler'; 6 | import {describe, expect, it } from 'vitest'; 7 | 8 | 9 | 10 | describe('setAppState tests', () => { 11 | window.store = { 12 | 'counter': { 13 | $state: {} 14 | }, 15 | 'store': { 16 | $state: {} 17 | } 18 | }; 19 | 20 | const mockSnapshot = { 21 | '1663717295074': 22 | { 23 | counter: 24 | { 25 | timestamp: Date.now(), 26 | type: 'Store: counter', 27 | key: 'counter', 28 | value: { 'count': 0 }, 29 | state: ['state'], 30 | getters: {}, 31 | actions: {}, 32 | editable: true 33 | }, 34 | store: { 35 | timestamp: Date.now(), 36 | type: 'Store: store', 37 | key: 'store', 38 | value: {myStr: '', elements: []}, 39 | state: ['state'], 40 | getters: {}, 41 | actions: {}, 42 | editable: true 43 | } 44 | } 45 | }; 46 | 47 | 48 | it('updates window.store', () => { 49 | setAppState(mockSnapshot); 50 | 51 | expect(window.store['counter'].$state).toEqual({ count: 0 }); 52 | expect(window.store['store'].$state).toEqual({ myStr: '', elements: [] }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /colada-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colada-plugin", 3 | "version": "0.1.0", 4 | "description": "A plugin to support Pinia", 5 | "author": { 6 | "name": "Jonathan Chen, Parker Steinberg, Vaughn Sulit, Dan Steinbrook" 7 | }, 8 | "main": "dist/colada-plugin.cjs.js", 9 | "module": "dist/colada-plugin.esm-bundler.js", 10 | "unpkg": "dist/colada-plugin.global.js", 11 | "jsdelivr": "dist/colada-plugin.global.js", 12 | "types": "dist/index.d.ts", 13 | "exports": { 14 | ".": { 15 | "require": "./dist/colada-plugin.cjs.js", 16 | "browser": "./dist/colada-plugin.esm-browser.js", 17 | "import": "./dist/colada-plugin.esm-bundler.js", 18 | "module": "./dist/colada-plugin.esm-bundler.js" 19 | }, 20 | "./package.json": "./package.json" 21 | }, 22 | "sideEffects": false, 23 | "scripts": { 24 | "prepublish": "npm run dev", 25 | "dev": "rimraf dist && rollup -c rollup.config.mjs", 26 | "ts": "tsc --watch -d", 27 | "test": "vitest", 28 | "coverage": "vitest run --coverage" 29 | }, 30 | "dependencies": { 31 | "@rollup/plugin-commonjs": "^22.0.2", 32 | "@rollup/plugin-node-resolve": "^13.3.0", 33 | "@rollup/plugin-replace": "^4.0.0", 34 | "@types/node": "^18.7.14", 35 | "@vue/devtools-api": "^6.2.1", 36 | "lodash.clonedeep": "^4.5.0", 37 | "lodash.debounce": "^4.0.8", 38 | "lodash.isempty": "^4.4.0", 39 | "pascalcase": "^2.0.0", 40 | "pinia": "^2.0.22", 41 | "rimraf": "^3.0.2", 42 | "rollup": "^2.79.0", 43 | "rollup-plugin-terser": "^7.0.2", 44 | "rollup-plugin-typescript2": "^0.33.0", 45 | "rollup-plugin-vue": "^6.0.0", 46 | "typescript": "^4.8.2", 47 | "vue": "^3.2.38" 48 | }, 49 | "devDependencies": { 50 | "@types/lodash": "^4.14.185", 51 | "@types/lodash.clonedeep": "^4.5.7", 52 | "@types/lodash.debounce": "^4.0.7", 53 | "@types/lodash.isempty": "^4.4.7", 54 | "@typescript-eslint/eslint-plugin": "^5.37.0", 55 | "@typescript-eslint/parser": "^5.37.0", 56 | "eslint": "^8.23.1", 57 | "eslint-plugin-vue": "^9.5.1", 58 | "jsdom": "^20.0.0", 59 | "vitest": "^0.23.4" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /colada-plugin/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import vuePlugin from 'rollup-plugin-vue'; 2 | import replace from '@rollup/plugin-replace'; 3 | import resolve from '@rollup/plugin-node-resolve'; 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | import pascalcase from 'pascalcase'; 6 | import pkg from './package.json' assert {type:'json'}; 7 | import {terser} from 'rollup-plugin-terser'; 8 | import ts from 'rollup-plugin-typescript2'; 9 | import * as path from 'path'; 10 | import { fileURLToPath } from 'url'; 11 | 12 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 13 | 14 | //const pkg = require('./package.json') 15 | const name = pkg.name; 16 | 17 | function getAuthors (pkg) { 18 | const { contributors, author } = pkg; 19 | 20 | const authors = new Set(); 21 | if (contributors && contributors) { 22 | contributors.forEach((contributor) => { 23 | authors.add(contributor.name); 24 | }); 25 | } 26 | if (author) authors.add(author.name); 27 | 28 | return Array.from(authors).join(', '); 29 | } 30 | 31 | const banner = `/*! 32 | * ${pkg.name} v${pkg.version} 33 | * (c) ${new Date().getFullYear()} ${getAuthors(pkg)} 34 | * @license MIT 35 | */`; 36 | 37 | // ensure TS checks only once for each build 38 | let hasTSChecked = false; 39 | 40 | const outputConfigs = { 41 | // each file name has the format: `dist/${name}.${format}.js` 42 | // format being a key of this object 43 | 'esm-bundler': { 44 | file: pkg.module, 45 | format: 'es' 46 | }, 47 | cjs: { 48 | file: pkg.main, 49 | format: 'cjs' 50 | }, 51 | global: { 52 | file: pkg.unpkg, 53 | format: 'iife' 54 | }, 55 | esm: { 56 | file: pkg.module.replace('bundler', 'browser'), 57 | format: 'es' 58 | } 59 | }; 60 | 61 | const allFormats = Object.keys(outputConfigs); 62 | const packageFormats = allFormats; 63 | const packageConfigs = packageFormats.map((format) => 64 | createConfig(format, outputConfigs[format]) 65 | ); 66 | 67 | // only add the production ready if we are bundling the options 68 | packageFormats.forEach((format) => { 69 | if (format === 'cjs') { 70 | packageConfigs.push(createProductionConfig(format)); 71 | } else if (format === 'global') { 72 | packageConfigs.push(createMinifiedConfig(format)); 73 | } 74 | }); 75 | 76 | export default packageConfigs; 77 | 78 | function createConfig (format, output, plugins = []) { 79 | if (!output) { 80 | console.log(`invalid format: "${format}"`); 81 | process.exit(1); 82 | } 83 | 84 | output.sourcemap = !!process.env.SOURCE_MAP; 85 | output.banner = banner; 86 | output.externalLiveBindings = false; 87 | output.globals = { vue: 'Vue', '@vue/composition-api': 'vueCompositionApi' }; 88 | 89 | const isProductionBuild = /\.prod\.js$/.test(output.file); 90 | const isGlobalBuild = format === 'global'; 91 | const isRawESMBuild = format === 'esm'; 92 | const isNodeBuild = format === 'cjs'; 93 | const isBundlerESMBuild = /esm-bundler/.test(format); 94 | 95 | if (isGlobalBuild) output.name = pascalcase(pkg.name); 96 | 97 | 98 | // ************** 99 | const shouldEmitDeclarations = !hasTSChecked; 100 | 101 | const tsPlugin = ts({ 102 | check: !hasTSChecked, 103 | tsconfig: path.resolve(__dirname, 'tsconfig.json'), 104 | cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'), 105 | tsconfigOverride: { 106 | compilerOptions: { 107 | sourceMap: output.sourcemap, 108 | declaration: shouldEmitDeclarations, 109 | declarationMap: shouldEmitDeclarations, 110 | }, 111 | exclude: ['__tests__', 'test-dts'], 112 | }, 113 | }); 114 | // we only need to check TS and generate declarations once for each build. 115 | // it also seems to run into weird issues when checking multiple times 116 | // during a single build. 117 | hasTSChecked = true; 118 | 119 | const external = ['vue', '@vue/composition-api']; 120 | if (!isGlobalBuild) { 121 | external.push('@vue/devtools-api'); 122 | } 123 | 124 | const nodePlugins = [resolve(), commonjs()]; 125 | 126 | return { 127 | input: 'src/index.ts', 128 | //input: 'src/index.js', 129 | // Global and Browser ESM builds inlines everything so that they can be 130 | // used alone. 131 | external, 132 | plugins: [ 133 | vuePlugin(), 134 | tsPlugin, 135 | createReplacePlugin( 136 | isProductionBuild, 137 | isBundlerESMBuild 138 | ), 139 | ...nodePlugins, 140 | ...plugins 141 | ], 142 | output 143 | }; 144 | } 145 | 146 | function createReplacePlugin ( 147 | isProduction, 148 | isBundlerESMBuild 149 | ) { 150 | const replacements = { 151 | 'process.env.NODE_ENV': isBundlerESMBuild 152 | ? // preserve to be handled by bundlers 153 | 'process.env.NODE_ENV' 154 | : // hard coded dev/prod builds 155 | JSON.stringify(isProduction ? 'production' : 'development'), 156 | __VUE_PROD_DEVTOOLS__: isBundlerESMBuild 157 | ? '__VUE_PROD_DEVTOOLS__' 158 | : 'false' 159 | }; 160 | // allow inline overrides like 161 | // __RUNTIME_COMPILE__=true yarn build 162 | Object.keys(replacements).forEach((key) => { 163 | if (key in process.env) { 164 | replacements[key] = process.env[key]; 165 | } 166 | }); 167 | return replace({ 168 | preventAssignment: true, 169 | values: replacements 170 | }); 171 | } 172 | 173 | function createProductionConfig (format) { 174 | return createConfig(format, { 175 | file: `dist/${name}.${format}.prod.js`, 176 | format: outputConfigs[format].format 177 | }); 178 | } 179 | 180 | function createMinifiedConfig (format) { 181 | //const { terser } = require('rollup-plugin-terser') 182 | 183 | return createConfig( 184 | format, 185 | { 186 | file: `dist/${name}.${format}.prod.js`, 187 | format: outputConfigs[format].format 188 | }, 189 | [ 190 | terser({ 191 | module: /^esm/.test(format), 192 | compress: { 193 | ecma: 2015, 194 | pure_getters: true 195 | } 196 | }) 197 | ] 198 | ); 199 | } -------------------------------------------------------------------------------- /colada-plugin/src/ColadaDevToolsPlugin/index.ts: -------------------------------------------------------------------------------- 1 | import { setupDevtoolsPlugin } from '@vue/devtools-api'; 2 | import { addPiniaStoreLabels, addPiniaStoreData } from './inspector'; 3 | import { handleInspectTimelineEvent } from './timeline'; 4 | import { 5 | initializeState, 6 | setAppState, 7 | getSnapshotbyTimestamp 8 | } from './stateHandler'; 9 | 10 | // declare type for application window 11 | declare const window: any; 12 | 13 | 14 | //*************************************************************************** */ 15 | //************* global variables that are used throughout file ************** */ 16 | //*************************************************************************** */ 17 | const inspectorId = 'colada-plugin'; 18 | const timelineLayerId = 'colada-plugin'; 19 | 20 | 21 | export function setupDevtools(app: any) { 22 | 23 | 24 | setupDevtoolsPlugin({ 25 | id: inspectorId, 26 | label: 'Colada 🥥', 27 | packageName: 'colada-plugin', 28 | homepage: 'https://colada.dev/', 29 | logo: 'https://user-images.githubusercontent.com/34523493/191631808-4dee4315-2638-4214-9c4f-c074316d969e.png', 30 | app, 31 | enableEarlyProxy: true, 32 | settings: {} 33 | }, api => { 34 | //********************************************************************** */ 35 | // ************ EVENT LISTENERS ***************************************** 36 | //********************************************************************** */ 37 | 38 | window.addEventListener('message', (event: any) => { 39 | // parse data from message 40 | const parsed = typeof event.data === 'string' ? JSON.parse(event.data) : ''; 41 | 42 | // if source is colada extension, set app's state to snapshot that correspodns with payload's timestamp 43 | if (parsed.source === 'colada-extension') { 44 | const timestamp = parseInt(parsed.payload); 45 | setAppState(getSnapshotbyTimestamp(timestamp)); 46 | } 47 | }) 48 | 49 | window.addEventListener('DOMContentLoaded', () => { 50 | setTimeout(initializeState, 1000); 51 | }); 52 | 53 | //add event listener to the window for 'addTimeLineEvent' 54 | window.addEventListener('addTimelineEvent', (e: any) => { 55 | const eventToAdd = e.detail; 56 | 57 | // grab timestamp from eventToAdd 58 | const currentTimestamp = parseInt(Object.keys(eventToAdd)[0]); 59 | const currentStores = Object.values(eventToAdd[currentTimestamp]); 60 | // iterate over snapshot associated with that timestamp 61 | currentStores.forEach((store: any) => { 62 | // add timelineEvent for each store 63 | api.addTimelineEvent({ 64 | layerId: timelineLayerId, 65 | event: { 66 | time: currentTimestamp, 67 | title: store.key, 68 | data: { 69 | state: store.value 70 | }, 71 | groupId: store.key 72 | } 73 | }); 74 | 75 | }); 76 | // refresh inspector state after adding element to timeline 77 | api.sendInspectorState(inspectorId); 78 | //END OF window.addEventListener 79 | }); 80 | 81 | api.on.inspectTimelineEvent(handleInspectTimelineEvent); 82 | 83 | 84 | //********************************************************************** */ 85 | // ************ COMPONENT SETTINGS ***************************************** 86 | //********************************************************************** */ 87 | 88 | //Adds a tag to next to the component in the Inspector -> Components Tree 89 | api.on.visitComponentTree((payload) => { 90 | //console.log('context is', context); 91 | const node = payload.treeNode; 92 | if (payload.componentInstance.type.meow) { 93 | node.tags.push({ 94 | label: 'colada', 95 | textColor: 0x000000, 96 | backgroundColor: 0xff984f 97 | }); 98 | } 99 | }); 100 | 101 | 102 | //********************************************************************** */ 103 | // ************ INSPECTOR SETTINGS ***************************************** 104 | //********************************************************************** */ 105 | //adds the Colada label into the Inspector bar 106 | api.addInspector({ 107 | id: inspectorId, 108 | label: 'Colada 🥥', 109 | icon: 'code', 110 | treeFilterPlaceholder: 'Searching...', 111 | }); 112 | 113 | api.on.getInspectorTree((payload: any) => { 114 | // if payload's inspector id matches our custom Colada inspectorId, add our store labels to the inspector 115 | if (payload.inspectorId === inspectorId) { 116 | addPiniaStoreLabels(payload); 117 | } 118 | }); 119 | 120 | 121 | api.on.getInspectorState((payload: any) => { 122 | // if payload inspectorId matches the Colada inspectorId, add the relevant Pinia store data to the inspector panel 123 | if (payload.inspectorId === inspectorId) { 124 | addPiniaStoreData(payload); 125 | } 126 | }); 127 | 128 | 129 | //********************************************************************** */ 130 | // ************ TIMELINE SETTINGS ***************************************** 131 | //********************************************************************** */ 132 | //Register a timeline layer 133 | api.addTimelineLayer({ 134 | id: timelineLayerId, 135 | color: 0xff984f, 136 | label: 'Colada 🥥', 137 | skipScreenshots: true, // doesn't work :( 138 | }); 139 | 140 | }); 141 | } 142 | -------------------------------------------------------------------------------- /colada-plugin/src/ColadaDevToolsPlugin/inspector.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentStores } from './stateHandler'; 2 | import { StateObject } from '../types'; 3 | 4 | // add elements (names of stores) to inspector 5 | const addPiniaStoreLabels = (payload: any) => { 6 | // iterate over stores and grab each label 7 | const currentStores = getCurrentStores(); 8 | // initialize root node for Colada inspector tree 9 | payload.rootNodes = [ 10 | { 11 | id: 'root', 12 | label: '🥥 Root', 13 | children: [], 14 | } 15 | ]; 16 | 17 | // iterate over currentStores to add children stores to root 18 | Object.values(currentStores).forEach((store: any) => { 19 | const currentLabel = store.key; 20 | // push current store label to children array in payload.rootNodes 21 | payload.rootNodes[0].children.push({ 22 | id: currentLabel, 23 | label: `store: ${currentLabel}` 24 | }); 25 | }); 26 | }; 27 | 28 | // add state to inspector 29 | // API: api.on.getInspectorState 30 | // triggered by: when the devtools needs to load the state for the currently selected node in a custom inspector 31 | // iterate over most recent versions of stores and add all that data to the inspector 32 | // make use of getter function exported from stateHandler.ts that grabs the most recent stores from storeHistory 33 | const addPiniaStoreData = (payload: any) => { 34 | // use getCurrentStore from stateHandler to get most recent versions stores 35 | const currentStores = getCurrentStores(); 36 | 37 | // initialize a state array 38 | const stateArr: StateObject[] = []; 39 | // initialize a getters array 40 | const gettersArr: any[] = []; 41 | // initialize a actions array 42 | const actionsArr: any[] = []; 43 | 44 | // iterate over currentStores 45 | Object.values(currentStores).forEach((store: any) => { 46 | 47 | const { key, value, getters, actions } = store; 48 | // add state to stateArry, getters to gettersArray, and actions to actionsArray 49 | const stateObj: StateObject = { 50 | key: key, 51 | value: value, 52 | editable: false 53 | }; 54 | 55 | const gettersObj: any = { 56 | key: key, 57 | value: getters, 58 | editable: false 59 | }; 60 | 61 | const actionsObj: any = { 62 | key: key, 63 | value: actions, 64 | editable: false 65 | }; 66 | 67 | stateArr.push(stateObj); 68 | gettersArr.push(gettersObj); 69 | actionsArr.push(actionsObj); 70 | 71 | }); 72 | 73 | // if nodeId is root --> add all the data for all stores 74 | if (payload.nodeId === 'root') { 75 | payload.state = { 76 | 'state': stateArr, 77 | 'getters': gettersArr, 78 | 'actions': actionsArr, 79 | }; 80 | } else { // if nodeId is not root 81 | // use filter to get state, getters, and actionsArr to match the current node id being selected (which is the selected store) 82 | const filteredStateArr = stateArr.filter((state) => state.key === payload.nodeId); 83 | const filteredGettersArr = gettersArr.filter((getters) => getters.key === payload.nodeId); 84 | const filteredActionsArr = actionsArr.filter((actions) => { 85 | return actions.key === payload.nodeId; 86 | }); 87 | 88 | payload.state = { 89 | 'state': filteredStateArr, 90 | 'getters': filteredGettersArr, 91 | 'actions': filteredActionsArr 92 | }; 93 | } 94 | 95 | }; 96 | 97 | export { 98 | addPiniaStoreLabels, 99 | addPiniaStoreData 100 | }; -------------------------------------------------------------------------------- /colada-plugin/src/ColadaDevToolsPlugin/stateHandler.ts: -------------------------------------------------------------------------------- 1 | import { piniaStores } from '../PiniaColadaPlugin/index'; 2 | import * as _ from 'lodash'; 3 | import debounce from 'lodash.debounce'; 4 | import cloneDeep from 'lodash.clonedeep'; 5 | import isEmpty from 'lodash.isempty'; 6 | 7 | // delcare global variables 8 | const storeHistory: any = []; 9 | declare const window: any 10 | let combinedSnapshot: any = {}; 11 | const storeLabels: any = []; 12 | 13 | /* 14 | * Add missing stores to combinedSnapshot 15 | * Add property hasBeenUpdated to combinedSnapshot and set to false 16 | * push combinedSnapshot to storeHistory 17 | * emit custom addTimelineEvent event with combinedSnapshot as payload 18 | * send data to extension via window.postMessage with combinedSnapshot as payload 19 | */ 20 | const outputCombinedSnapshot = debounce(() => { 21 | // delcare variable missing stores, which will have the labels for the missing stores from snapShot 22 | const missingStores = storeLabels.filter((label: any) => { 23 | return !Object.keys(combinedSnapshot[Object.keys(combinedSnapshot)[0]]).includes(label); 24 | }); 25 | // iterate over missing stores, find the corresponding most recent snapshot, and add to combinedSnapshot 26 | missingStores.forEach((store: any) => { 27 | // can replace this with getter function below 28 | // need to make a deep clone, otherwise we will be udpated the mostRecentSnapshot inadvertently 29 | const mostRecentSnapshot = getCurrentStores(true); 30 | const mostRecentSnapshotClone: any = cloneDeep(mostRecentSnapshot); 31 | // get correspong store and have to const 32 | const mostRecentStore = mostRecentSnapshotClone[Object.keys(mostRecentSnapshotClone)[0]][store]; 33 | // add hasBeenUpdated = false property to snapshot we're adding 34 | mostRecentStore.hasBeenUpdated = false; 35 | // add to combinedSnapshot 36 | combinedSnapshot[Object.keys(combinedSnapshot)[0]][store] = mostRecentStore; 37 | }); 38 | 39 | // pushing combinedSnap to storeHistory, triggering custom event, and posting message to window 40 | storeHistory.push(combinedSnapshot); 41 | //emit a custom event with the proxyObj as a payload 42 | const event: any = new CustomEvent('addTimelineEvent', { detail: combinedSnapshot }); 43 | window.dispatchEvent(event); 44 | 45 | //send a messsage to the window for the extension to make use of 46 | const messageObj: any = { 47 | source: 'colada', 48 | payload: combinedSnapshot 49 | }; 50 | 51 | window.postMessage(JSON.stringify(messageObj), window.location.href); 52 | 53 | // reset combinedSnapshot to empty object 54 | combinedSnapshot = {}; 55 | }, 10); 56 | 57 | const handleStoreChange = (snapshot: any) => { 58 | 59 | const snapshotClone = cloneDeep(snapshot) 60 | 61 | // add hasBeenUpdated property to true on snapshotClone 62 | snapshotClone.hasBeenUpdated = true; 63 | 64 | // add snapshots's label ('key' proprety) to storeLabels if it's not already in there 65 | if (!storeLabels.includes(snapshotClone.key)) { 66 | storeLabels.push(snapshotClone.key); 67 | } 68 | 69 | // if finalSnaphsot has no properites, add initial timestamp property along with associated snapshotClone 70 | if (isEmpty(combinedSnapshot)) { 71 | combinedSnapshot[snapshotClone.timestamp] = { 72 | [snapshotClone.key]: snapshotClone 73 | }; 74 | } 75 | // else, add a new key to combinedSnapshot at existing timestamp property 76 | else { 77 | combinedSnapshot[Object.keys(combinedSnapshot)[0]][snapshotClone.key] = snapshotClone; 78 | } 79 | 80 | // invoke debounced outputCombinedSnapshot 81 | outputCombinedSnapshot(); 82 | }; 83 | 84 | 85 | // import the subscribe method and implement associated functionality 86 | const initializeState = () => { 87 | piniaStores.subscribe(handleStoreChange, true); 88 | }; 89 | 90 | const resubscribe = () => { 91 | piniaStores.subscribe(handleStoreChange, false); 92 | }; 93 | 94 | const unsubscribe = () => { 95 | piniaStores.unsubscribe(); 96 | }; 97 | 98 | 99 | // NOTE: currently 0(n) ... consider refactoring to use binary search 100 | const getSnapshotbyTimestamp = (timestamp: number) => { 101 | for (const e of storeHistory) { 102 | if (parseInt(Object.keys(e)[0]) === timestamp) return e; 103 | } 104 | }; 105 | 106 | const setAppState = (snapshot: any) => { 107 | unsubscribe(); 108 | const stores: any = Object.values(snapshot)[0]; 109 | for (const key in stores) { 110 | window.store[key].$state = stores[key].value; 111 | } 112 | resubscribe(); 113 | }; 114 | 115 | /* 116 | @param {boolean} [includeTimestamps=false] - To retrieve data with timestamps. Defaults to false. 117 | */ 118 | const getCurrentStores = (includeTimestamps = false) => { 119 | if (includeTimestamps) { 120 | return storeHistory[storeHistory.length - 1]; 121 | } 122 | else { 123 | return Object.values(storeHistory[storeHistory.length - 1])[0]; 124 | } 125 | }; 126 | 127 | export { 128 | initializeState, 129 | getCurrentStores, 130 | getSnapshotbyTimestamp, 131 | setAppState, 132 | resubscribe, 133 | unsubscribe, 134 | handleStoreChange 135 | }; 136 | -------------------------------------------------------------------------------- /colada-plugin/src/ColadaDevToolsPlugin/timeline.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getSnapshotbyTimestamp, 3 | setAppState 4 | } from './stateHandler'; 5 | 6 | const handleInspectTimelineEvent = (payload: any): void => { 7 | if (payload.layerId === 'colada-plugin') { 8 | const selectedEventTimestamp: number = payload.event.time; 9 | setAppState(getSnapshotbyTimestamp(selectedEventTimestamp)); 10 | return; 11 | } 12 | }; 13 | 14 | export { handleInspectTimelineEvent }; 15 | -------------------------------------------------------------------------------- /colada-plugin/src/PiniaColadaPlugin/index.ts: -------------------------------------------------------------------------------- 1 | import { ProxyObject } from '../types'; 2 | 3 | declare const window: any; 4 | 5 | const unsubscribeMethods: Array<() => void> = []; 6 | const stores: Array = []; 7 | const piniaProxies: ProxyObject[] = []; 8 | 9 | // declare object where methods will be stored and exported 10 | const piniaStores: any = {}; 11 | 12 | // add empty store object to window which will get filled with Pinia stores 13 | window.store = {}; 14 | 15 | piniaStores.unsubscribe = () => { 16 | unsubscribeMethods.forEach(e => e()); 17 | }; 18 | 19 | piniaStores.getPiniaStores = (): any => { 20 | return piniaProxies; 21 | }; 22 | 23 | piniaStores.subscribe = (callback: any, setInitialState = false) => { 24 | stores.forEach(store => { 25 | const snapshot: ProxyObject = { 26 | timestamp: Date.now(), 27 | type: `Store: ${store.$id}`, 28 | key: `${store.$id}`, 29 | value: store.$state, 30 | state: store._hmrPayload.state, 31 | getters: store._hmrPayload.getters, 32 | actions: store._hmrPayload.actions, 33 | editable: true 34 | }; 35 | const unsubscribeMethod = store.$subscribe(() => { 36 | snapshot.timestamp = Date.now(); 37 | // we can also access mutation and state here 38 | callback(snapshot); 39 | }); 40 | unsubscribeMethods.push(unsubscribeMethod); 41 | if (setInitialState) callback(snapshot); 42 | }); 43 | }; 44 | 45 | 46 | const PiniaColadaPlugin = (context: any) => { 47 | const store: any = context.store; 48 | stores.push(store); 49 | window.store[store.$id] = store; 50 | }; 51 | 52 | export { 53 | piniaStores, 54 | PiniaColadaPlugin 55 | }; 56 | -------------------------------------------------------------------------------- /colada-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import { setupDevtools } from './ColadaDevToolsPlugin/index'; 2 | import { PiniaColadaPlugin } from './PiniaColadaPlugin/index'; 3 | 4 | export default { 5 | install(app: any) { 6 | setupDevtools(app); 7 | } 8 | }; 9 | 10 | export { PiniaColadaPlugin }; 11 | 12 | 13 | -------------------------------------------------------------------------------- /colada-plugin/src/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Types to define for the ProxyObject (and to be used elsewhere): 3 | - value (the state data) 4 | - state (an array of labels for the state within a store) 5 | - getters (an object where keys are labels for the getters within the store and the values are their function definitions) 6 | - actions (object where keys are action labels and values are associated function definitions) 7 | */ 8 | 9 | // Other types to define: 10 | // - type for piniaStore 11 | 12 | export type ProxyObject = { 13 | timestamp?: any, 14 | type: string, 15 | key: string, 16 | value: any, 17 | state: any, 18 | getters: any, 19 | actions: any, 20 | editable: boolean 21 | } 22 | 23 | export type StateObject = { 24 | store_id?: string, 25 | key: string, 26 | value: any, 27 | editable: boolean 28 | } 29 | 30 | -------------------------------------------------------------------------------- /colada-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/**/*.ts" 4 | ], 5 | "compilerOptions": { 6 | "outDir": "dist", 7 | "rootDir": "src", 8 | "sourceMap": false, 9 | 10 | "target": "ES6", 11 | "module": "ESNext", 12 | "moduleResolution": "node", 13 | "allowJs": true, 14 | "skipLibCheck": true, 15 | 16 | "noUnusedLocals": true, 17 | "strictNullChecks": true, 18 | "noImplicitAny": true, 19 | "noImplicitThis": true, 20 | "noImplicitReturns": false, 21 | "strict": true, 22 | "isolatedModules": true, 23 | 24 | "experimentalDecorators": false, 25 | "resolveJsonModule": true, 26 | "esModuleInterop": true, 27 | "removeComments": false, 28 | "lib": [ 29 | "es6", 30 | "dom" 31 | ], 32 | "types": [ 33 | "node" 34 | ] 35 | } 36 | } -------------------------------------------------------------------------------- /colada-plugin/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | environment: 'jsdom' 7 | }, 8 | }) -------------------------------------------------------------------------------- /demo-project/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2021': true 5 | }, 6 | 'extends': [ 7 | 'eslint:recommended', 8 | 'plugin:vue/vue3-essential', 9 | 'plugin:@typescript-eslint/recommended' 10 | ], 11 | 'overrides': [ 12 | ], 13 | 'parser': '@typescript-eslint/parser', 14 | 'parserOptions': { 15 | 'ecmaVersion': 'latest', 16 | 'sourceType': 'module' 17 | }, 18 | 'plugins': [ 19 | 'vue', 20 | '@typescript-eslint' 21 | ], 22 | 'rules': { 23 | 'indent': [ 24 | 'warn', 25 | 2 26 | ], 27 | 'linebreak-style': [ 28 | 'warn', 29 | 'unix' 30 | ], 31 | 'quotes': [ 32 | 'warn', 33 | 'single' 34 | ], 35 | 'semi': [ 36 | 'warn', 37 | 'always' 38 | ] 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /demo-project/README.md: -------------------------------------------------------------------------------- 1 | # vue-project 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 8 | 9 | ## Customize configuration 10 | 11 | See [Vite Configuration Reference](https://vitejs.dev/config/). 12 | 13 | ## Project Setup 14 | 15 | ```sh 16 | npm install 17 | ``` 18 | 19 | ### Compile and Hot-Reload for Development 20 | 21 | ```sh 22 | npm run dev 23 | ``` 24 | 25 | ### Compile and Minify for Production 26 | 27 | ```sh 28 | npm run build 29 | ``` 30 | 31 | ### Run Unit Tests with [Vitest](https://vitest.dev/) 32 | 33 | ```sh 34 | npm run test:unit 35 | ``` 36 | 37 | ### Lint with [ESLint](https://eslint.org/) 38 | 39 | ```sh 40 | npm run lint 41 | ``` 42 | -------------------------------------------------------------------------------- /demo-project/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /demo-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-project", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "preview": "vite preview --port 4173", 8 | "test:unit": "vitest --environment jsdom", 9 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore" 10 | }, 11 | "dependencies": { 12 | "colada-plugin": "file:../colada-plugin", 13 | "pinia": "^2.0.17", 14 | "vue": "^3.2.37" 15 | }, 16 | "devDependencies": { 17 | "@rushstack/eslint-patch": "^1.1.4", 18 | "@typescript-eslint/eslint-plugin": "^5.37.0", 19 | "@typescript-eslint/parser": "^5.37.0", 20 | "@vitejs/plugin-vue": "^3.0.1", 21 | "@vitejs/plugin-vue-jsx": "^2.0.0", 22 | "@vue/eslint-config-prettier": "^7.0.0", 23 | "@vue/test-utils": "^2.0.2", 24 | "eslint": "^8.21.0", 25 | "eslint-plugin-vue": "^9.5.1", 26 | "jsdom": "^20.0.0", 27 | "prettier": "^2.7.1", 28 | "vite": "^3.0.4", 29 | "vitest": "^0.21.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demo-project/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/colada/6253de89595e6a19443fc45b6527aa0432e48321/demo-project/public/favicon.ico -------------------------------------------------------------------------------- /demo-project/reload.sh: -------------------------------------------------------------------------------- 1 | cd ../colada-plugin 2 | npm run dev 3 | cd ../demo-project 4 | npm install ../colada-plugin 5 | npm run dev -------------------------------------------------------------------------------- /demo-project/src/App.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | 35 | -------------------------------------------------------------------------------- /demo-project/src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | position: relative; 59 | font-weight: normal; 60 | } 61 | 62 | body { 63 | min-height: 100vh; 64 | color: var(--color-text); 65 | background: var(--color-background); 66 | transition: color 0.5s, background-color 0.5s; 67 | line-height: 1.6; 68 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, 69 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 70 | font-size: 15px; 71 | text-rendering: optimizeLegibility; 72 | -webkit-font-smoothing: antialiased; 73 | -moz-osx-font-smoothing: grayscale; 74 | } 75 | 76 | 77 | -------------------------------------------------------------------------------- /demo-project/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo-project/src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import "./base.css"; 2 | 3 | #app { 4 | max-width: 1280px; 5 | margin: 0 auto; 6 | padding: 2rem; 7 | 8 | font-weight: normal; 9 | } 10 | 11 | .wrapper{ 12 | display:flex; 13 | flex-direction:column; 14 | justify-content:flex-start; 15 | align-items:center; 16 | gap:0.5rem; 17 | } 18 | 19 | .content-container{ 20 | display:flex; 21 | justify-content:space-around; 22 | align-items:flex-start; 23 | gap:0.5rem; 24 | width:90%; 25 | } 26 | 27 | .btn-container{ 28 | display:flex; 29 | flex-direction:column; 30 | justify-content:space-evenly; 31 | align-items:center; 32 | width:100%; 33 | } 34 | 35 | .text-container{ 36 | width:100%; 37 | display:flex; 38 | flex-direction:column; 39 | justify-content:flex-start; 40 | align-items:flex-start; 41 | } 42 | 43 | a, 44 | .green { 45 | text-decoration: none; 46 | color: hsla(160, 100%, 37%, 1); 47 | transition: 0.4s; 48 | } 49 | 50 | .yellow{ 51 | text-decoration:none; 52 | color:rgb(226, 206, 52); 53 | transition: 0.4s; 54 | } 55 | 56 | .btn{ 57 | background-color:rgb(130, 99, 151); 58 | color:white; 59 | border:none; 60 | border-radius:0.5rem; 61 | padding:0.5rem 1rem; 62 | margin:0.5rem; 63 | } 64 | 65 | .btn:hover{ 66 | background-color:rgb(151, 120, 172); 67 | cursor:pointer; 68 | } 69 | 70 | .btn:active{ 71 | background-color:rgb(96, 72, 111); 72 | } 73 | 74 | 75 | 76 | 77 | 78 | @media (hover: hover) { 79 | a:hover { 80 | background-color: hsla(160, 100%, 37%, 0.2); 81 | } 82 | } 83 | 84 | /* @media (min-width: 1024px) { 85 | body { 86 | display: flex; 87 | place-items: center; 88 | } 89 | 90 | #app { 91 | display: grid; 92 | grid-template-columns: 1fr 1fr; 93 | padding: 0 2rem; 94 | } 95 | } */ 96 | -------------------------------------------------------------------------------- /demo-project/src/components/Counter.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /demo-project/src/components/DoubleStore.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /demo-project/src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /demo-project/src/components/Main.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /demo-project/src/components/WelcomeItem.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /demo-project/src/components/__tests__/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest"; 2 | 3 | import { mount } from "@vue/test-utils"; 4 | import HelloWorld from "../HelloWorld.vue"; 5 | 6 | describe("HelloWorld", () => { 7 | it("renders properly", () => { 8 | const wrapper = mount(HelloWorld, { props: { msg: "Hello Vitest" } }); 9 | expect(wrapper.text()).toContain("Hello Vitest"); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /demo-project/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import { createPinia } from "pinia"; 3 | import Colada, {PiniaColadaPlugin} from 'colada-plugin'; 4 | import App from "./App.vue"; 5 | 6 | import "./assets/main.css"; 7 | 8 | const app = createApp(App); 9 | const pinia = createPinia() 10 | app.use(pinia); 11 | pinia.use(PiniaColadaPlugin) 12 | app.use(Colada); 13 | app.mount("#app"); 14 | 15 | -------------------------------------------------------------------------------- /demo-project/src/stores/counter.js: -------------------------------------------------------------------------------- 1 | // stores/counter.js 2 | import { defineStore } from 'pinia' 3 | 4 | export const useCounterStore = defineStore('counter', { 5 | state: () => { 6 | return { count: 0 } 7 | }, 8 | getters: { 9 | totalCount: (state) => state.elements.length, 10 | }, 11 | actions: { 12 | increment() { 13 | this.count++ 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /demo-project/src/stores/store.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | 3 | export const useStore = defineStore({ 4 | id: "store", 5 | state: () => ({ 6 | myStr: "", 7 | elements: [ 8 | ], 9 | }), 10 | getters: { 11 | totalPeople: (state) => state.elements.length, 12 | }, 13 | actions: { 14 | addPerson(val) { 15 | this.elements.push({ name: val }); 16 | this.myStr = "" 17 | }, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /demo-project/vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "node:url"; 2 | 3 | import { defineConfig } from "vite"; 4 | import vue from "@vitejs/plugin-vue"; 5 | import vueJsx from "@vitejs/plugin-vue-jsx"; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [vue(), vueJsx()], 10 | resolve: { 11 | alias: { 12 | "@": fileURLToPath(new URL("./src", import.meta.url)), 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colada", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | {} 2 | --------------------------------------------------------------------------------