@raclettejs/workbench 0.1.31 → 0.1.32-nightly

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/.raclette/backend/raclette.config.js +515 -0
  2. package/.raclette/backend/yarn.lock +3636 -0
  3. package/.raclette/frontend/raclette.config.js +43 -0
  4. package/.raclette/frontend/yarn.lock +3388 -0
  5. package/.raclette/virtual/backend/README.md +172 -0
  6. package/.raclette/virtual/backend/raclette.config.js +515 -0
  7. package/.raclette/virtual/backend/yarn.lock +3636 -0
  8. package/.raclette/virtual/frontend/README.md +511 -0
  9. package/.raclette/virtual/frontend/raclette.config.js +43 -0
  10. package/.raclette/virtual/frontend/src/core/lib/eggs/readme.md +75 -0
  11. package/.raclette/virtual/frontend/yarn.lock +3388 -0
  12. package/CHANGELOG.md +6 -10
  13. package/i18n/de-DE.json +1 -10
  14. package/i18n/en-EU.json +1 -10
  15. package/i18n/sk-SK.json +1 -10
  16. package/package.json +5 -3
  17. package/plugins/pacifico__compositions/frontend/widgets/CompositionListWidget.vue +61 -105
  18. package/plugins/pacifico__interactionLinks/frontend/widgets/InteractionLinkListWidget.vue +64 -120
  19. package/plugins/pacifico__plugins/frontend/widgets/PluginListWidget.vue +2 -2
  20. package/plugins/pacifico__tags/frontend/widgets/TagListWidget.vue +45 -102
  21. package/plugins/pacifico__users/frontend/widgets/UserListWidget.vue +24 -16
  22. package/services/frontend/src/app/lib/jsonTools.ts +69 -3
  23. package/services/frontend/src/app/components/BaseDataTable.vue +0 -303
  24. package/services/frontend/src/app/components/FileUpload.vue +0 -98
  25. package/services/frontend/src/app/components/SelectionDialog.vue +0 -182
  26. package/services/frontend/src/app/components/SummaryDialog.vue +0 -140
@@ -0,0 +1,511 @@
1
+ # Pacifico Frontend
2
+
3
+ ## Docker Compose
4
+
5
+ Service Name: `frontend`
6
+ Dev Container Name: `portal-frontend`
7
+
8
+ ## Dev
9
+
10
+ This template should help get you started developing with Vue 3 in Vite. The
11
+ template uses Vue 3 `<script setup>` SFCs, check out the [script setup
12
+ docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn
13
+ more.
14
+
15
+ ## Mini-RX-Store
16
+
17
+ [Here](https://github.com/spierala/mini-rx-store) you find the documentation of
18
+ the Mini-ry-Store store Framework. If you feel fancy and want to brag about cool
19
+ stuff you know, [visit the redux documentation](https://redux.js.org/api/api-reference) to learn cool stuff which
20
+ you might want to use after a while using this framework
21
+
22
+ ## Vuetify3
23
+
24
+ [Here](https://next.vuetifyjs.com/en/) you find the documentation of the Vuetify3 UI-Component Library
25
+
26
+ ## Developer How To
27
+
28
+ ### Basic Idea
29
+
30
+ The whole application, based on vue3, vuetify and the mini-rx-store, is loosely
31
+ coupled and communicates just via state changes triggered by the mini-rx-store and a global eventbus.
32
+ All data which is solely needed for a certain component within it's lifecycle you may use vue props or data variables,
33
+ every data which is shared between components and is not impacted by their lifecycle should always be stored via the store.
34
+
35
+ ### Important to Know
36
+
37
+ Whilst you are basically pretty free to setup configurations for you pages and
38
+ widgets as you see fit, there are some required ones which are usually preset to
39
+ default values. Page configurations could either be "manually" provided by using the page/add store reducer and providing a config object.
40
+ But we encourage to always store page and widget configs on the backend in the projects.config object.
41
+ Widgets can be preconfigured by using their conf.js and then overriding those settings if needed per page config in which those widgets are used
42
+
43
+ ### Creating you own page
44
+
45
+ ### Folder structure
46
+
47
+ To locate all the current pages and to create you own, navigate to
48
+
49
+ ```
50
+ src/components/pages
51
+ ```
52
+
53
+ The best starting point from there is to take a look into the boilerplate
54
+ folder.All Pages are structured in the same way which must not be violated. A
55
+ page always contains out of the following files
56
+
57
+ ```
58
+ ├── myPageName
59
+ │ ├── component.cy.js // contains the cypress tests
60
+ │ ├── page.vue // the actual code
61
+ └── └── story.js // a storyfile for storybook
62
+ ```
63
+
64
+ #### page.vue
65
+
66
+ Here we see the most important things you definitely need to do to get your page
67
+ working.
68
+
69
+ ```
70
+ <v-container v-if="state" data-test-container="pages/boilerplate" fluid pa-0>
71
+ <Grid v-if="state.slots" />
72
+ ```
73
+
74
+ At first note that, whatever main wrapping element you choose, you must have a
75
+ v-if="state" in there to prevent false rendering. We also encourage you to use
76
+ the same naming scheme and concept as we do. Every component, may it be
77
+ template, widget, page or whatnot should provide a "data-test-container"
78
+ attribute in it's main DOM element. The value of this attribute should be
79
+ according to it's file path as from "components/"
80
+
81
+ We also encourage you to use the Vue3 Compose API over the Options API
82
+
83
+ The "Grid" component will provide you a blackbox you should not think about too
84
+ much, it simply allows the user to customize the appearance of the pages
85
+ contents i.E grid colum count, column background, or widget sorting.
86
+ You on the other hand may just provide additional information or
87
+ functionallity within the page file which is unique to this page and cannot be a
88
+ widget
89
+
90
+ The rest of the code within the boilerplate/page.vue must be kept in order to
91
+ have a working base for you page. Note that the subscriber variables alway end
92
+ on an $ which signalizes that the content of this variable is a redux observable
93
+ and therefore needs to be unsubscribed whenever you leave the page to prevent
94
+ memory leaks.
95
+
96
+ #### Page config
97
+
98
+ The following section describes how you can configure you pages by either using the page/add reducer
99
+ and providing those configs, or by properly storing page configs backend side to be applied when a user enters a given project
100
+
101
+ You can define what kind of grid you need with Grid Key.
102
+
103
+ ```javascript
104
+ {
105
+ // grid defines the used grid
106
+ grid: {
107
+ type: "default",
108
+ items: "card",
109
+ columns: 2
110
+ },
111
+ // this is by default false and will result in you not having the 2nd tool section called inspectorTools
112
+ hasInspector: true,
113
+ // this will determine if we always scroll to the last page position or to the top whenever we enter this page
114
+ scrollTop: false,
115
+ // title defines the title of the page
116
+ title: 'My Title',
117
+ // slots array defines the used widgets within the grid
118
+ slots: [
119
+ // first slot
120
+ {
121
+ column: 6,
122
+ widget: {
123
+ name: "myCategory",
124
+ props: {
125
+ title: "widget title"
126
+ }
127
+ }
128
+ },
129
+ ]
130
+ }
131
+ ```
132
+
133
+ If you don't want to use the grid but instead place widgets manually in your
134
+ page you can set you config like so
135
+
136
+ ```javascript
137
+ {
138
+ grid: false,
139
+ slots: false,
140
+ hasInspector: false, // every page will have the navigation toolset, but not all the pages have the inspector toolset. Set to true if you want it
141
+ widgets: [{ // you can also bypass the grid or use widgets directly in the page via the pageloader. This is fully optional
142
+ uuid: "mySpecialPageWidget"
143
+ name: "myCategory",
144
+ props: {
145
+ title: "widget title"
146
+ }
147
+ }],
148
+ widget: { // you can also add just one widget if you are as autistic as I am and don't wan't to store one thing in an array
149
+ uuid: "mySpecialPageWidget"
150
+ name: "myCategory",
151
+ props: {
152
+ title: "widget title"
153
+ }
154
+ },
155
+ }
156
+ ```
157
+
158
+ #### component.cy.js
159
+
160
+ The component.cy.js contains the test Code for your page, it is also used to
161
+ trigger the test for all widgets present on your Page. Your Test code must
162
+ provide a `prepareTest`function, it is used to navigate to the page you want to
163
+ test.
164
+
165
+ How you navigate to your test case depends on the test environment, there is the
166
+ case component or integration test. In the case of the component test, the tests
167
+ run against Storybook, in the case of the integration test, runs against the
168
+ application directly.
169
+
170
+ The [Boilerplate](./src/components/pages/boilerplate/component.cy.js) prepareTest function looks like this.
171
+
172
+ ```javascript
173
+ const source = "pages/boilerplate"
174
+ const prepareTest = () => {
175
+ if (isComonentTest()) {
176
+ // visit Storybook see story.js
177
+ cy.visitStorybook(source, "Headless")
178
+ } else {
179
+ // visit the path where the boilerplate is located
180
+ cy.visit("/boilerplate") // eine route anlegen ?
181
+ }
182
+ }
183
+ ```
184
+
185
+ Your Test code for the Page is inside the 'describe' block.
186
+
187
+ In the [Boilerplate](./components/pages/boilerplate/component.cy.js) Example the
188
+ Test that `This is a Page Boilerplate` text is present on your page.
189
+
190
+ ```javascript
191
+ it("Boilerplate has Text ", () => {
192
+ cy.get('[data-test-container="pages/boilerplate"]').should(
193
+ "contain.text",
194
+ "This is a Page Boilerplate",
195
+ )
196
+ })
197
+ ```
198
+
199
+ Once your Pages tests are done, the `testWidgets(source, prepareTest)` will
200
+ trigger the Widget Test.
201
+
202
+ The testWidgets function will then use the `data-test-container`passed by source
203
+ to identify your page and run the widgets found on your page.
204
+
205
+ ## TODO Umgang mit visit.json
206
+
207
+ **Notes: **
208
+
209
+ - The **`source`** ( in our example `pages/boilerplate` ) has to match your Pages container id !
210
+ - The **`source`** should match your Storybook title.
211
+
212
+ #### story.js
213
+
214
+ In the `story.js` the environment for the components for the storybook is
215
+ created, which are to be used in the component tests. A title must be defined in
216
+ the standard export block. The component will then appear in the storybook under
217
+ this title. Please look up the example in the `pages/boilerplate/story.js`
218
+
219
+ ```javascript
220
+ export default {
221
+ title: "pages/boilerplate",
222
+ argTypes: {},
223
+ }
224
+ ```
225
+
226
+ Afterwards, a template must be created with which Storybook should initialize the component.
227
+
228
+ ```javascript
229
+ const HeadlessTemplate = (args) => ({
230
+ components: { comp },
231
+ setup() {
232
+ initStore()
233
+ prepareStore("BOILERPLATE", args)
234
+ return { args }
235
+ },
236
+ template: wrapComponent("comp", "v-card"),
237
+ })
238
+ ```
239
+
240
+ In order for a component to appear, the Temperate must be initialized.
241
+
242
+ ```javascript
243
+ export const Headless = HeadlessTemplate.bind({})
244
+ Headless.args = conf
245
+ ```
246
+
247
+ To be able to address the component in a test, the title and the variable name are used.
248
+
249
+ **Note: The title and the variable name are case sensitive.**
250
+
251
+ ```javascript
252
+ // inside your component.cy.js
253
+ cy.visitStorybook("pages/boilerplate", "Headless") // cy.visitStorybook(title, variable name)
254
+ ```
255
+
256
+ ### Store
257
+
258
+ #### Configuration
259
+
260
+ Take me to [the Page default config](#pageConfig)
261
+
262
+ ## Creating you own widget
263
+
264
+ ### Folder structure
265
+
266
+ To locate all the current widgets and to create you own, navigate to
267
+
268
+ ```
269
+ src/components/widgets
270
+ ```
271
+
272
+ The best starting point from there is to take a look into the boilerplate folder.
273
+ All Widgets are structured in the same way which must not be violated. A widget always contains out of the following files
274
+
275
+ ```
276
+ ├── myWidgetName
277
+ │ ├── default.vue // the actual code of the default widget
278
+ │ ├── default.component.cy.js // contains the cypress tests for the default widget
279
+ │ ├── FACENAME.vue // optional, the actual code of specific widget face
280
+ │ ├── FACENAME.component.cy.js // optional, contains the cypress tests for the FACE widget
281
+ │ ├── conf.js // optional, contains page specific config which override default conf
282
+ └───└── story.js // a storyfile for storybook
283
+
284
+ ```
285
+
286
+ ### default.vue/FACE.vue
287
+
288
+ Here we see the most important things you definitely need to do to get your widget working.
289
+
290
+ ```
291
+ <v-card v-if="state && props.uuid" data-test-container="widgets/boilerplate/default" :data-test-container-uuid=props.uuid>
292
+ ```
293
+
294
+ At first note that, whatever main rapping element you choose, you must have a
295
+ v-if="state && props.uuid" in there to prevent false rendering. We also
296
+ encourage you to use the same naming scheme and concept as we do. Every
297
+ component, may it be template, widget, page or whatnot should provide a
298
+ "data-test-container" attribute in it's main DOM element. The value of this
299
+ attribute should be according to it's file path as from "components/"
300
+
301
+ To be able to test a specific widget we also need to add a
302
+ "data-test-container-uuid" which contains the widgets uuid which by default we
303
+ obtain from the props
304
+ We also encourage you to use the Vue3 Compose API over the Options API
305
+
306
+ The rest of the code within the boilerplate/default/FACE.vue must be kept in
307
+ order to have a working base for your widget (the iLike and title vars and form
308
+ elements are just for showcase purposes and may be removed). Note that the
309
+ subscriber variables alway end on an $ which signalizes that the content of this
310
+ variable is a redux observable and therefore needs to be unsubscribed whenever
311
+ you leave the page to prevent memory leaks.
312
+
313
+ ### Store
314
+
315
+ Take me to [the Widget default config](#widgetConfig)
316
+
317
+ ## Default Configurations for application concepts
318
+
319
+ ### <a name="pageConfig"></a>Required page configuration:
320
+
321
+ ```json
322
+ {
323
+ // will be set based on the name of the vue router route you've used, or whatever you configure in your route file
324
+ "routeName": "Vue3routeName",
325
+ // will usually be set automatically, if set manually, be aware that duplicated uuid lead to shared page store
326
+ "uuid": false,
327
+ // obvious
328
+ "loading": true,
329
+ // grid config for a page
330
+ "grid": {
331
+ // defines the wrapping html template for the whole grid
332
+ "type": "default",
333
+ // defines the wrapping html template for the single item within the grid
334
+ "items": "card",
335
+ // defines the amount of columns per row
336
+ "columns": 3
337
+ },
338
+ // the array containing the configs for the 1-n slots
339
+ "slots": [
340
+ {
341
+ // defines a css class which will be used for the v-col the slot widget is housed in
342
+ "class": false,
343
+ // defines the width of the widget by using flexbox grid col numbers 1-12
344
+ "column": 12,
345
+ // defines the widget configuration, see widget storePattern
346
+ "widget": {}
347
+ }
348
+ ]
349
+ }
350
+ ```
351
+
352
+ Note that you can add as many parameters as you like, apart from ones the above.
353
+
354
+ ### <a name="widgetConfig"></a>Required widget configuration:
355
+
356
+ ```json
357
+ {
358
+ // should usually be a i18n key
359
+ "title": false,
360
+ // will usually be set automatically, if set manually, be aware that duplicated uuid lead to shared widget store
361
+ "uuid": false,
362
+ // the type of widget which represents a folder name under widgets and a config
363
+ "name": false,
364
+ // the specific .vue file for the widget, by default a file called default is needed
365
+ "face": false,
366
+ // a deeper abstractation level for widgets which are used as tools. ie. type:'add' create a folder called 'add' and create a default.vue or FACE.vue containing your add logic for a given widget
367
+ "type": false
368
+ }
369
+ ```
370
+
371
+ Note that you can add as much parameters, apart from the above, as you like.
372
+
373
+ ## Toolbar
374
+
375
+ Every widget you create can also be used as a tool lauched from the toolbar. To
376
+ do so simply go to the file which shall provide said function. Usually I'd
377
+ recommend you to do this in the page.vue file or if you wan't it global die
378
+ App.vue.
379
+
380
+ To add a widget as a tool use the following command in your source File
381
+
382
+ ```js
383
+ // either $store or this.$store depending if you use options or compose API
384
+ $store.dispatch({
385
+ type: "toolbar/add",
386
+ payload: {
387
+ // the title to use in the toolbar next to the icon. Should be an i18n key
388
+ title: "newJournal",
389
+ // a whitlist field. If set the tool will just be visible on a page with the configured routeName, if false or not set the tool will be visible everywhere. You can also add as many route names as you like, seperated by whatever
390
+ page: "app.journal;app.kanbanboard",
391
+ // a mdi icon you want for the inactive state
392
+ icon: "mdi-content-save-edit-outline",
393
+ // a mdi icon you want for the active state
394
+ iconActive: "mdi-content-save-edit",
395
+ // a normal widget config object which follows the same principle as all the widget configs.
396
+ widget: {
397
+ name: "journal",
398
+ type: "add",
399
+ // make sure to provide a uuid if you don't want to have multiple instances of a given tool (multiple instances might be buggy atm)
400
+ uuid: "journal_add",
401
+ },
402
+ },
403
+ })
404
+ ```
405
+
406
+ ## Test preparation
407
+
408
+ If you want to run 2 non-headless sessions simultaneously make sure to select different Browsers
409
+
410
+ ```
411
+ docker-compose up --build -d
412
+ cd services/frontend
413
+ ```
414
+
415
+ ## Integration Test non-headless for Development or Debugging
416
+
417
+ Runs a Test against the RAW app via with Browser For Test Development/Debugging
418
+
419
+ 1. -> Test Preparation
420
+
421
+ ```
422
+ yarn test:integration
423
+ ```
424
+
425
+ ## Integration Test Headless
426
+
427
+ Runs a Test against the RAW app with your favorite Terminal
428
+
429
+ 1. -> Test Preparation
430
+
431
+ ```
432
+ yarn test:integration:ci
433
+ ```
434
+
435
+ ## Component Storybook Test non-headless for Development or Debugging
436
+
437
+ Runs a Test against the Storybook RAW app with your favorite Terminal
438
+
439
+ ```
440
+ yarn test:component
441
+ ```
442
+
443
+ ## Component Storybook Test Headless
444
+
445
+ ```
446
+ yarn test:component:ci
447
+ ```
448
+
449
+ Work flow Create Dynamic Imported Widget tests for new page called lets call it`MYPAGE`.
450
+ We start with the Cypress for integration.
451
+
452
+ 1. copy and rename the page boilerplates (`_boilerplate.vue` and `_boilerplate.component.cy.js `)
453
+ 2. adapt the URL/endpoints in the prepareTest() function in `MYPAGE.component.cy.js`
454
+ 3. create an empty definition in your "API/Database" (now visit.json)
455
+ 4. restart the Cypress for integration `->` Test will fail, if there are widgets in the definition but not on the page.
456
+ 5. add your widgets to the definition in your "API/Database" (now visit.json)
457
+ 6. restart the Cypress for integration `->` Test will fail if there is no test template for the widget.
458
+ 7. copy and rename the widget template `_boilerplate.component.template.cy.js`
459
+ 8. writing the test template
460
+ 9. redo Cypress E2E and fix your Errors until tests passes.
461
+ 10. restart the Cypress for Storybook `->` Test will fail, if `MYPAGE.stories.js` is missing, fix it.
462
+ 11. restart the Cypress with Integration
463
+
464
+ ## Recommended IDE Setup
465
+
466
+ ### VSCode
467
+
468
+ - [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
469
+
470
+ Full recommended extensionlist
471
+
472
+ ```
473
+ code --install-extension aaron-bond.better-comments
474
+ code --install-extension bierner.color-info
475
+ code --install-extension christian-kohler.npm-intellisense
476
+ code --install-extension christian-kohler.path-intellisense
477
+ code --install-extension dbaeumer.vscode-eslint
478
+ code --install-extension ecmel.vscode-html-css
479
+ code --install-extension esbenp.prettier-vscode
480
+ code --install-extension formulahendry.auto-close-tag
481
+ code --install-extension formulahendry.auto-rename-tag
482
+ code --install-extension Marko-JS.marko-vscode
483
+ code --install-extension mikestead.dotenv
484
+ code --install-extension MisterJ.vue-volar-extention-pack
485
+ code --install-extension ms-azuretools.vscode-docker
486
+ code --install-extension MS-CEINTL.vscode-language-pack-de
487
+ code --install-extension ms-vscode-remote.remote-containers
488
+ code --install-extension ms-vscode-remote.remote-ssh
489
+ code --install-extension ms-vscode-remote.remote-ssh-edit
490
+ code --install-extension ms-vscode.remote-explorer
491
+ code --install-extension Natizyskunk.sftp
492
+ code --install-extension naumovs.color-highlight
493
+ code --install-extension oliversturm.fix-json
494
+ code --install-extension redhat.java
495
+ code --install-extension sdras.vue-vscode-snippets
496
+ code --install-extension sibiraj-s.vscode-scss-formatter
497
+ code --install-extension steoates.autoimport
498
+ code --install-extension stylelint.vscode-stylelint
499
+ code --install-extension syler.sass-indented
500
+ code --install-extension VisualStudioExptTeam.intellicode-api-usage-examples
501
+ code --install-extension VisualStudioExptTeam.vscodeintellicode
502
+ code --install-extension vscjava.vscode-java-debug
503
+ code --install-extension vscjava.vscode-java-dependency
504
+ code --install-extension vscjava.vscode-java-pack
505
+ code --install-extension vscjava.vscode-java-test
506
+ code --install-extension vscjava.vscode-maven
507
+ code --install-extension Vue.volar
508
+ code --install-extension Wscats.vue
509
+ code --install-extension xabikos.JavaScriptSnippets
510
+ code --install-extension yzhang.markdown-all-in-one
511
+ ```
@@ -0,0 +1,43 @@
1
+ // Generated Raclette frontend Configuration
2
+ // This file is auto-generated, do not edit directly
3
+
4
+ export const racletteConfig = {
5
+ "name": "playground-workbench",
6
+ "env": {
7
+ "development": {
8
+ "RACLETTE_DEBUG_MODE": true,
9
+ "RACLETTE_APP_PATH": "/home/thesilverfisch/Work/Projects/pacifico/PublicRaclette/core-dev/@raclettejs/playground"
10
+ },
11
+ "production": {}
12
+ },
13
+ "global": {
14
+ "requireAuthentication": true,
15
+ "requiresAuthentication": true
16
+ },
17
+ "service": "frontend",
18
+ "framework": "vue",
19
+ "i18n": {
20
+ "locales": [
21
+ "en-EU"
22
+ ],
23
+ "priorities": {
24
+ "app": 10,
25
+ ".": 30,
26
+ "app-workbench": 5,
27
+ "app-app": 7
28
+ },
29
+ "vue": {
30
+ "plugins": []
31
+ }
32
+ },
33
+ "meta": {
34
+ "title": "Workbench"
35
+ },
36
+ "preventNotifications": {
37
+ "checkToken": {
38
+ "status": 500
39
+ }
40
+ }
41
+ };
42
+
43
+ export default racletteConfig;
@@ -0,0 +1,75 @@
1
+ smoke usage examples
2
+
3
+ ```
4
+ <v-btn id="pneis" @click="addSmoke">smoke me</v-btn>
5
+
6
+ import smoke from "#src/lib/eggs/smoke"
7
+
8
+ const smokeEffect = ref(null)
9
+
10
+ const addSmoke = (e) => {
11
+ const element = document.querySelector("#pneis")
12
+
13
+ if (smokeEffect.value === null) {
14
+ smokeEffect.value = smoke(element, {
15
+ // Emission pattern control
16
+ emissionWidth: 0.8, // Use 80% of element width for emission
17
+ emissionPattern: "gaussian", // 'uniform', 'gaussian', or 'centered'
18
+
19
+ // Dispersion control
20
+ disperseAngle: 45, // 45 degree spread angle
21
+ turbulence: 0.1, // Add random movement
22
+ swayFrequency: 0.003, // How often the smoke sways
23
+ swayAmplitude: 0.1, // How far the smoke sways
24
+
25
+ // Basic appearance
26
+ color: [50, 50, 50],
27
+ baseOpacity: 1,
28
+ particlesPerEmission: 2,
29
+ })
30
+ smokeEffect.value.start()
31
+ // smokeEffect.value.addSmoke(0, 0, 2)
32
+ } else {
33
+ smokeEffect.value.stop()
34
+ smokeEffect.value.destroy()
35
+ smokeEffect.value = null
36
+ }
37
+ }
38
+ ```
39
+
40
+ ```
41
+ // Create a smoke effect for an element
42
+ const element = document.getElementById('myElement');
43
+ const smokeEffect = createSmokeEffect(element, {
44
+ // Emission pattern control
45
+ emissionWidth: 0.8, // Use 80% of element width for emission
46
+ emissionPattern: 'gaussian', // 'uniform', 'gaussian', or 'centered'
47
+
48
+ // Dispersion control
49
+ disperseAngle: 45, // 45 degree spread angle
50
+ turbulence: 0.4, // Add random movement
51
+ swayFrequency: 0.003, // How often the smoke sways
52
+ swayAmplitude: 0.6, // How far the smoke sways
53
+
54
+ // Basic appearance
55
+ color: [50, 50, 50],
56
+ baseOpacity: 0.2,
57
+ particlesPerEmission: 2
58
+ });
59
+
60
+ // Start the effect
61
+ smokeEffect.start();
62
+
63
+
64
+ // Stop the effect
65
+ smokeEffect.stop();
66
+
67
+ // Update configuration on the fly
68
+ smokeEffect.updateConfig({
69
+ color: [50, 50, 50],
70
+ windForce: 0.08
71
+ });
72
+
73
+ // Clean up when done
74
+ smokeEffect.destroy();
75
+ ```