@peter.naydenov/cuts 1.0.0

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.
package/Changelog.md ADDED
@@ -0,0 +1,7 @@
1
+ # Release History
2
+
3
+
4
+ ### 1.0.0 (2024-05-08)
5
+ - [x] Initial release;
6
+
7
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Peter Naydenov
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.
@@ -0,0 +1 @@
1
+ # Migration Guides
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Cuts (@peter.naydenov/cuts)
2
+
3
+ ![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/peterNaydenov/cuts/main)
4
+ ![GitHub License](https://img.shields.io/github/license/peterNaydenov/cuts)
5
+ ![npm package minimized gzipped size](https://img.shields.io/bundlejs/size/%40peter.naydenov%2Fcuts)
6
+
7
+
8
+ In filmmaking, a change of scene is typically referred to as a "**cut**". Cuts are an essential part of film editing and are used to create a cohesive narrative by connecting different scenes together.
9
+
10
+ Let's define SPA application as a set of scenes (pages/visual states) and each scene discribes a visual elements on the page and possible user interaction ( mouse, keyboard, etc.). **Cuts** controls the flow among scenes in SPA application.
11
+
12
+ When all interactions can be described in the **Scene** data models, means that the components (react,vue, svelte or any other framework) can be used as simple template engines - to render only html.
13
+
14
+
15
+
16
+
17
+ ## Installation
18
+
19
+ ```
20
+ npm install @peter.naydenov/cuts
21
+ ```
22
+
23
+
24
+
25
+ ## Usage
26
+
27
+ ```js
28
+ import cuts from '@peter.naydenov/cuts'
29
+
30
+ const script = cuts ();
31
+
32
+ script.setScenes () // provide list of Scenes to the app
33
+ script.setDependencies () // add object to the "dependencies" object. This object will be passed to the Scene "show" method
34
+ // script is ready to use
35
+ script.show ({ scene : 'sceneName'}) // change the current Scene
36
+
37
+ ```
38
+
39
+
40
+
41
+
42
+
43
+ ## Screen Cuts Methods Overview
44
+
45
+ ```js
46
+ setScenes : 'Provide list of scenes to the app'
47
+ , show : 'Change the current scene'
48
+ , hide : 'Hide the current scene'
49
+ , listScenes : 'List of loaded Scene names'
50
+ , listShortcuts : 'List shortcuts per Scene. Provide the name of the Scene'
51
+ , setDependencies : 'Add object to the "dependencies" object. This object will be passed to the Scene "show" method'
52
+ , getDependencies : 'Returns the "dependencies" object'
53
+ , enablePlugin : 'Enable a shortcut plugin. Available after version 1.1.0'
54
+ , disablePlugin : 'Disable a shortcut plugin. Available after version 1.1.0'
55
+ , emit : 'Emit an event. Available after version 2.0.0'
56
+ ```
57
+
58
+
59
+
60
+ ## Scene
61
+
62
+ Scene is a data model that describes how to render the scene and how the user will interact with it ( mouse, keyboard, etc.):
63
+
64
+ ```js
65
+ {
66
+ show // method. Returns a promise
67
+ , hide // method. Returns a promise
68
+ , parents // list of parent Scene names.
69
+ // ... shortcuts for all user interactions with the screen ( mouse, keyboard, etc.)
70
+ // Shortcuts are implemented with @peter.naydenov/shortcuts
71
+ }
72
+ ```
73
+
74
+
75
+ Scenes can be `visible`(show) and `unvisible`(hide), also some scenes can be a child of another scene. Every scene can be treated as a visual state of the application. All provided to script `Scenes` are available on demand by calling them with a simple instruction `show({scene:sceneName})`. What should be visible and what should be unvisible is managed by the library.
76
+
77
+
78
+
79
+
80
+
81
+ ## Links
82
+ - [History of changes](https://github.com/PeterNaydenov/cuts/blob/main/Changelog.md)
83
+ - [Learn how shortcuts are implemented](https://github.com/PeterNaydenov/shortcuts)
84
+
85
+
86
+
87
+
88
+ ## Credits
89
+ '@peter.naydenov/cuts' was created and supported by Peter Naydenov.
90
+
91
+
92
+
93
+ ## License
94
+ '@peter.naydenov/cuts' is released under the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "cypress";
2
+
3
+ export default defineConfig({
4
+ component: {
5
+ supportFile: false,
6
+ devServer: {
7
+ framework: "vue",
8
+ bundler: "vite",
9
+ },
10
+ },
11
+ });
package/dist/cuts.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var e=require("@peter.naydenov/shortcuts"),n=require("ask-for-promise"),r=require("@peter.naydenov/log");function*t(e,n,r,t){let s=null;if(null===e)return yield*t.map((e=>[e,"show"])),void(yield[r,"show"]);if(!t||0===t.length)return n?(yield[e,"hide"],n.reverse(),yield*n.map((e=>[e,"hide"])),void(yield[r,"show"])):(yield[e,"hide"],void(yield[r,"show"]));if(t.includes(e)){for(let n=t.indexOf(e)+1;n<t.length;n++)yield[t[n],"show"];yield[r,"show"]}else{if(!n)return yield[e,"hide"],void(yield*t.map((e=>[e,"show"])));if(t.every(((e,r)=>n[r]===t[r]&&(s=r,!0))),null==s)return yield[e,"hide"],n.reverse(),yield*n.map((e=>[e,"hide"])),yield*t.map((e=>[e,"show"])),void(yield[r,"show"]);yield[e,"hide"];for(let e=n.length;e>s+1;e--)yield[n[e-1],"hide"];for(let e=s+1;e<t.length;e++)yield[t[e],"show"];yield[r,"show"]}}function s(e){return function(n,...r){const{askForPromise:t,deps:s}=e;return function(){const e=t();return n({task:e,dependencies:s()},...r),e.promise}}}var o={hide:function(e,n){return function(r=1){const{askForPromise:t,shortcutMngr:s,setInstruction:o}=e,{currentScene:i,currentParents:c,scenes:u}=n,l=t(),d=[],a=c.length>0;let h;return s.pause(),d.push(o(u[i].hide)),a||(n.currentScene=null),a&&"*"===r&&[...c].reverse().forEach((e=>{h=n.currentParents.pop(),n.currentScene=h,d.push(o(u[e].hide))})),a&&!["*",1].includes(r)&&c.slice("-"+(r-1)).reverse().map((e=>{h=n.currentParents.pop(),n.currentScene=h,d.push(o(u[e].hide))})),(r=t.sequence(d)).onComplete((()=>{s.changeContext(n.currentScene),l.done()})),l.promise}},listShortcuts:function(e,n){return function(e){const{scenes:r,sceneNames:t}=n;if(!t.has(e))return null;const{show:s,hide:o,parents:i,...c}=r[e];return Object.keys(c)}},setScenes:function(e,n){return function(r){const{shortcutMngr:t}=e;r.forEach((({name:e,scene:r})=>{r.parents||(r.parents=[]),n.scenes[e]=r,n.sceneNames.add(e);const{show:s,hide:o,parents:i,...c}=r,u={};u[e]=c,t.load(u)}))}},show:function(e,n){return function({scene:r,options:t={ssr:!1}},...s){const{shortcutMngr:o,askForPromise:i,log:c,findInstructions:u,setInstruction:l}=e,d=i(),{opened:a,scenes:h,sceneNames:p}=n;if(!p.has(r)&&c)return c({message:`Scene ${r} is not available.`,level:1,type:"error"}),d.done(),d.promise;if(!a&&t.ssr)return n.opened=!0,n.currentPage=r,o.changeContext(r),d.done(),d.promise;const{show:f,parents:m}=h[r];if("*"===m[0])return f().then((()=>d.done())),n.currentParents.push(n.currentPage),n.currentPage=r,d.promise;if(!(m.forEach((e=>p.has(e)))||!0))return c&&c({message:`Some of '${r}' parent scenes are not set.`,level:1,type:"error"}),d.done(),d.promise;function g([e,r]){if("show"===r)n.currentScene&&n.currentParents.push(n.currentScene),n.currentScene=e;else{let e=n.currentParents.pop();n.currentScene=e}return h[e][r]}n.currentParents||(n.currentParents=[]);const y=u(n.currentScene,n.currentParents,r,m),P=[];for(let e of y)[e].map(g).map((e=>P.push(l(e),...s)));return i.sequence(P).onComplete((()=>{n.opened=!0,o.changeContext(r),d.done()})),d.promise}}};module.exports=function(i={logLevel:0}){const c=e.shortcuts(),u=i.logLevel||0,l=r({level:u}),d={currentScene:null,currentParents:null,sceneNames:new Set,scenes:{},opened:!1},a={},h={},p={shortcutMngr:c,API:a,inAPI:h,findInstructions:t,askForPromise:n,setInstruction:s({askForPromise:n,deps:c.getDependencies}),log:l};return Object.entries(o).forEach((([e,n])=>{e.startsWith("_")?h[e]=n(p,d):a[e]=n(p,d)})),a.setDependencies=e=>c.setDependencies(e),a.getDependencies=()=>c.getDependencies(),a.setNote=n=>e.shortcuts.setNote(n),a.listScenes=()=>[...d.sceneNames],a.enablePlugin=(e,n)=>c.enablePlugin(e,n),a.disablePlugin=e=>c.disablePlugin(e),a.emit=(e,...n)=>c.emit(e,...n),a};
@@ -0,0 +1 @@
1
+ import{shortcuts as e}from"@peter.naydenov/shortcuts";import n from"ask-for-promise";import r from"@peter.naydenov/log";function*t(e,n,r,t){let s=null;if(null===e)return yield*t.map((e=>[e,"show"])),void(yield[r,"show"]);if(!t||0===t.length)return n?(yield[e,"hide"],n.reverse(),yield*n.map((e=>[e,"hide"])),void(yield[r,"show"])):(yield[e,"hide"],void(yield[r,"show"]));if(t.includes(e)){for(let n=t.indexOf(e)+1;n<t.length;n++)yield[t[n],"show"];yield[r,"show"]}else{if(!n)return yield[e,"hide"],void(yield*t.map((e=>[e,"show"])));if(t.every(((e,r)=>n[r]===t[r]&&(s=r,!0))),null==s)return yield[e,"hide"],n.reverse(),yield*n.map((e=>[e,"hide"])),yield*t.map((e=>[e,"show"])),void(yield[r,"show"]);yield[e,"hide"];for(let e=n.length;e>s+1;e--)yield[n[e-1],"hide"];for(let e=s+1;e<t.length;e++)yield[t[e],"show"];yield[r,"show"]}}function s(e){return function(n,...r){const{askForPromise:t,deps:s}=e;return function(){const e=t();return n({task:e,dependencies:s()},...r),e.promise}}}var o={hide:function(e,n){return function(r=1){const{askForPromise:t,shortcutMngr:s,setInstruction:o}=e,{currentScene:i,currentParents:c,scenes:u}=n,l=t(),d=[],a=c.length>0;let h;return s.pause(),d.push(o(u[i].hide)),a||(n.currentScene=null),a&&"*"===r&&[...c].reverse().forEach((e=>{h=n.currentParents.pop(),n.currentScene=h,d.push(o(u[e].hide))})),a&&!["*",1].includes(r)&&c.slice("-"+(r-1)).reverse().map((e=>{h=n.currentParents.pop(),n.currentScene=h,d.push(o(u[e].hide))})),(r=t.sequence(d)).onComplete((()=>{s.changeContext(n.currentScene),l.done()})),l.promise}},listShortcuts:function(e,n){return function(e){const{scenes:r,sceneNames:t}=n;if(!t.has(e))return null;const{show:s,hide:o,parents:i,...c}=r[e];return Object.keys(c)}},setScenes:function(e,n){return function(r){const{shortcutMngr:t}=e;r.forEach((({name:e,scene:r})=>{r.parents||(r.parents=[]),n.scenes[e]=r,n.sceneNames.add(e);const{show:s,hide:o,parents:i,...c}=r,u={};u[e]=c,t.load(u)}))}},show:function(e,n){return function({scene:r,options:t={ssr:!1}},...s){const{shortcutMngr:o,askForPromise:i,log:c,findInstructions:u,setInstruction:l}=e,d=i(),{opened:a,scenes:h,sceneNames:p}=n;if(!p.has(r)&&c)return c({message:`Scene ${r} is not available.`,level:1,type:"error"}),d.done(),d.promise;if(!a&&t.ssr)return n.opened=!0,n.currentPage=r,o.changeContext(r),d.done(),d.promise;const{show:f,parents:m}=h[r];if("*"===m[0])return f().then((()=>d.done())),n.currentParents.push(n.currentPage),n.currentPage=r,d.promise;if(!(m.forEach((e=>p.has(e)))||!0))return c&&c({message:`Some of '${r}' parent scenes are not set.`,level:1,type:"error"}),d.done(),d.promise;function g([e,r]){if("show"===r)n.currentScene&&n.currentParents.push(n.currentScene),n.currentScene=e;else{let e=n.currentParents.pop();n.currentScene=e}return h[e][r]}n.currentParents||(n.currentParents=[]);const y=u(n.currentScene,n.currentParents,r,m),P=[];for(let e of y)[e].map(g).map((e=>P.push(l(e),...s)));return i.sequence(P).onComplete((()=>{n.opened=!0,o.changeContext(r),d.done()})),d.promise}}};function i(i={logLevel:0}){const c=e(),u=i.logLevel||0,l=r({level:u}),d={currentScene:null,currentParents:null,sceneNames:new Set,scenes:{},opened:!1},a={},h={},p={shortcutMngr:c,API:a,inAPI:h,findInstructions:t,askForPromise:n,setInstruction:s({askForPromise:n,deps:c.getDependencies}),log:l};return Object.entries(o).forEach((([e,n])=>{e.startsWith("_")?h[e]=n(p,d):a[e]=n(p,d)})),a.setDependencies=e=>c.setDependencies(e),a.getDependencies=()=>c.getDependencies(),a.setNote=n=>e.setNote(n),a.listScenes=()=>[...d.sceneNames],a.enablePlugin=(e,n)=>c.enablePlugin(e,n),a.disablePlugin=e=>c.disablePlugin(e),a.emit=(e,...n)=>c.emit(e,...n),a}export{i as default};
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).cuts=t()}(this,(function(){"use strict";var e={_normalizeWithPlugins:function(e,t){return function(e){const n=t.shortcuts;Object.keys(n).forEach((t=>{Object.entries(n[t]).forEach((([r,o])=>{const s=e(r);s!==r&&(delete n[t][r],n[t][s]=o)}))}))}},_readShortcutWithPlugins:function(e,t){return function(n){const{inAPI:r}=e,o=n.split(":")[0],s=r._systemAction(o,"none");let c=n;return-1!==s&&(c=t.plugins[s].shortcutName(n)),c}},_systemAction:function(e,t){return function(e,n,r=null){return t.plugins.findIndex((t=>t.getPrefix()===e&&(t[n]&&t[n](r),!0)))}},changeContext:function(e,t){const{shortcuts:n,currentContext:r}=t,{ev:o}=e;return function(e=!1){const s=r.name;if(!e)return o.reset(),void(r.name=null);s!==e&&(n[e]?(n[s]&&o.reset(),r.name=e,t.plugins.forEach((t=>t.contextChange(e))),Object.entries(n[e]).forEach((([e,t])=>{t.forEach((t=>o.on(e,t)))})),o.on("*",((...e)=>{t.exposeShortcut&&t.exposeShortcut(...e)}))):o.emit("@shortcuts-error",`Context '${e}' does not exist`))}},listShortcuts:function(e,t){const n=t.shortcuts;return function(e=null){if(null!=e){let t=n[e];return null==t?null:Object.entries(t).map((([e,t])=>e))}return Object.keys(n).map((e=>{let t={};return t.context=e,t.shortcuts=Object.entries(n[e]).map((([e,t])=>e)),t}))}},load:function(e,t){const{shortcuts:n,plugins:r}=t,{API:{changeContext:o,getContext:s}}=e;return function(e){const t=s(),c=r.map((e=>e.getPrefix().toUpperCase()));let i=!1;Object.entries(e).forEach((([e,o])=>{e===t&&(i=!0),n[e]={},Object.entries(o).forEach((([t,o])=>{let s=t,i=t.toUpperCase().trim(),u=c.map(((e,t)=>i.startsWith(e)?t:null)).filter((e=>null!==e));if(u.length){let e=u[0];s=r[e].shortcutName(t)}o instanceof Function&&(o=[o]),n[e][s]=o}))})),i&&(o(),o(t))}},unload:function(e,t){const{currentContext:n,shortcuts:r}=t,{ev:o}=e;return function(e){n.name!==e?r[e]?delete r[e]:o.emit("shortcuts-error",`Context '${e}' does not exist`):o.emit("shortcuts-error",`Context '${e}' can't be removed during is current active context. Change the context first`)}}};function t(t={}){const n=new function(){let e={"*":[]},t={},n=[],r=!1,o="";return{on:function(t,n){e[t]||(e[t]=[]),e[t].push(n)},once:function(e,n){"*"!==e&&(t[e]||(t[e]=[]),t[e].push(n))},off:function(n,r){if(r)return e[n]&&(e[n]=e[n].filter((e=>e!==r))),t[n]&&(t[n]=t[n].filter((e=>e!==r))),e[n]&&0===e[n].length&&delete e[n],void(t[n]&&0===t[n].length&&delete e[n]);t[n]&&delete t[n],e[n]&&delete e[n]},reset:function(){e={"*":[]},t={},n=[]},emit:function(){const[s,...c]=arguments;function i(t){let r=!1;"*"!==t&&(n.includes(t)||(e[t].every((e=>{const t=e(...c);return"string"!=typeof t||"STOP"!==t.toUpperCase()||(r=!0,!1)})),r||e["*"].forEach((e=>e(s,...c)))))}if(r&&(console.log(`${o} Event "${s}" was triggered.`),c.length>0&&(console.log("Arguments:"),console.log(...c),console.log("^----"))),"*"!==s){if(t[s]){if(n.includes(s))return;t[s].forEach((e=>e(...c))),delete t[s]}e[s]&&i(s)}else Object.keys(e).forEach((e=>i(e)))},stop:function(r){if("*"!==r)n.push(r);else{const r=Object.keys(e),o=Object.keys(t);n=[...o,...r]}},start:function(e){n="*"!==e?n.filter((t=>e!=t)):[]},debug:function(e,t){r=!!e,t&&"string"==typeof t&&(o=t)}}},r={},o={},s={currentContext:{name:null,note:null},shortcuts:{},plugins:[],exposeShortcut:!(!t.onShortcut||"function"!=typeof t.onShortcut)&&t.onShortcut},c={ev:n,inAPI:r,API:o,extra:{}};return o.enablePlugin=(e,t={})=>{const n=e.name;if(-1===r._systemAction(n,"none")){let n;n=e(c,s,t),s.plugins.push(n)}},o.disablePlugin=e=>{const t=r._systemAction(e,"destroy");-1!==t&&(s.plugins=s.plugins.filter(((e,n)=>n!==t)))},o.mutePlugin=e=>r._systemAction(e,"mute"),o.unmutePlugin=e=>r._systemAction(e,"unmute"),o.getContext=()=>s.currentContext.name,o.getNote=()=>s.currentContext.note,o.setNote=(e=null)=>{"string"!=typeof e&&null!=e||(s.currentContext.note=e)},o.pause=(e="*")=>{let t=r._readShortcutWithPlugins(e);n.stop(t)},o.resume=(e="*")=>{const t=r._readShortcutWithPlugins(e);n.start(t)},o.emit=(e,...t)=>n.emit(r._readShortcutWithPlugins(e),...t),o.listContexts=()=>Object.keys(s.shortcuts),o.setDependencies=e=>c.extra={...c.extra,...e},o.getDependencies=()=>c.extra,Object.entries(e).forEach((([e,t])=>{e.startsWith("_")?r[e]=t(c,s):o[e]=t(c,s)})),o}function n(e){let t,n=!1;return e?(t=function(e){let t=e.map((e=>r())),n=t.map((e=>e.promise));return t.promises=n,t.onComplete=o(Promise.all(n)),t}(e),n=!0):t=r(),t.timeout=function(e,t){let n;return n=e?Promise.all(t.promises):t.promise,function(e,r){let s,c=new Promise(((t,o)=>{s=setTimeout((()=>{t(r),Promise.resolve(n)}),e)}));return n.then((()=>clearTimeout(s))),t.onComplete=o(Promise.race([n,c])),t}}(n,t),t}function r(){let e,t;const n=new Promise(((n,r)=>{e=n,t=r}));return{promise:n,done:e,cancel:t,onComplete:o(n)}}function o(e){return function(t){e.then((e=>t(e)))}}function s({message:e,level:t,type:n,logLevel:r}){if(0===r)return null;if(r<t)return null;const o=`[Debug]: ${e}`;switch(n){case"warn":console.warn(o);break;case"error":console.error(o);break;default:console.log(o)}return o}function c(e={},t=s){return function({message:n,type:r,level:o,...s}){const{level:c,type:i,defaultMessageLevel:u}=Object.assign({},{level:1e3,type:"log",defaultMessageLevel:1},e);return r||(r=i),o||(o=u),t({message:n,type:r,level:o,logLevel:c,...s})}}function*i(e,t,n,r){let o=null;if(null===e)return yield*r.map((e=>[e,"show"])),void(yield[n,"show"]);if(!r||0===r.length)return t?(yield[e,"hide"],t.reverse(),yield*t.map((e=>[e,"hide"])),void(yield[n,"show"])):(yield[e,"hide"],void(yield[n,"show"]));if(r.includes(e)){for(let t=r.indexOf(e)+1;t<r.length;t++)yield[r[t],"show"];yield[n,"show"]}else{if(!t)return yield[e,"hide"],void(yield*r.map((e=>[e,"show"])));if(r.every(((e,n)=>t[n]===r[n]&&(o=n,!0))),null==o)return yield[e,"hide"],t.reverse(),yield*t.map((e=>[e,"hide"])),yield*r.map((e=>[e,"show"])),void(yield[n,"show"]);yield[e,"hide"];for(let e=t.length;e>o+1;e--)yield[t[e-1],"hide"];for(let e=o+1;e<r.length;e++)yield[r[e],"show"];yield[n,"show"]}}function u(e){return function(t,...n){const{askForPromise:r,deps:o}=e;return function(){const e=r();return t({task:e,dependencies:o()},...n),e.promise}}}n.sequence=function(e,...t){const r=n(),o=[],s=function*(e){for(const t of e)yield t}(e);return function e(t,...n){t.done?r.done(o):t.value(...n).then((t=>{o.push(t),e(s.next(),...n,t)}))}(s.next(),...t),r},n.all=function(e,...t){const r=n(),o=[],s=e.map(((e,n)=>"function"==typeof e?e(...t).then((e=>o[n]=e)):e.then((e=>o[n]=e))));return Promise.all(s).then((()=>r.done(o))),r};var l={hide:function(e,t){return function(n=1){const{askForPromise:r,shortcutMngr:o,setInstruction:s}=e,{currentScene:c,currentParents:i,scenes:u}=t,l=r(),a=[],f=i.length>0;let h;return o.pause(),a.push(s(u[c].hide)),f||(t.currentScene=null),f&&"*"===n&&[...i].reverse().forEach((e=>{h=t.currentParents.pop(),t.currentScene=h,a.push(s(u[e].hide))})),f&&!["*",1].includes(n)&&i.slice("-"+(n-1)).reverse().map((e=>{h=t.currentParents.pop(),t.currentScene=h,a.push(s(u[e].hide))})),(n=r.sequence(a)).onComplete((()=>{o.changeContext(t.currentScene),l.done()})),l.promise}},listShortcuts:function(e,t){return function(e){const{scenes:n,sceneNames:r}=t;if(!r.has(e))return null;const{show:o,hide:s,parents:c,...i}=n[e];return Object.keys(i)}},setScenes:function(e,t){return function(n){const{shortcutMngr:r}=e;n.forEach((({name:e,scene:n})=>{n.parents||(n.parents=[]),t.scenes[e]=n,t.sceneNames.add(e);const{show:o,hide:s,parents:c,...i}=n,u={};u[e]=i,r.load(u)}))}},show:function(e,t){return function({scene:n,options:r={ssr:!1}},...o){const{shortcutMngr:s,askForPromise:c,log:i,findInstructions:u,setInstruction:l}=e,a=c(),{opened:f,scenes:h,sceneNames:d}=t;if(!d.has(n)&&i)return i({message:`Scene ${n} is not available.`,level:1,type:"error"}),a.done(),a.promise;if(!f&&r.ssr)return t.opened=!0,t.currentPage=n,s.changeContext(n),a.done(),a.promise;const{show:p,parents:m}=h[n];if("*"===m[0])return p().then((()=>a.done())),t.currentParents.push(t.currentPage),t.currentPage=n,a.promise;if(!(m.forEach((e=>d.has(e)))||!0))return i&&i({message:`Some of '${n}' parent scenes are not set.`,level:1,type:"error"}),a.done(),a.promise;function g([e,n]){if("show"===n)t.currentScene&&t.currentParents.push(t.currentScene),t.currentScene=e;else{let e=t.currentParents.pop();t.currentScene=e}return h[e][n]}t.currentParents||(t.currentParents=[]);const y=u(t.currentScene,t.currentParents,n,m),P=[];for(let e of y)[e].map(g).map((e=>P.push(l(e),...o)));return c.sequence(P).onComplete((()=>{t.opened=!0,s.changeContext(n),a.done()})),a.promise}}};return function(e={logLevel:0}){const r=t(),o=c({level:e.logLevel||0}),s={currentScene:null,currentParents:null,sceneNames:new Set,scenes:{},opened:!1},a={},f={},h={shortcutMngr:r,API:a,inAPI:f,findInstructions:i,askForPromise:n,setInstruction:u({askForPromise:n,deps:r.getDependencies}),log:o};return Object.entries(l).forEach((([e,t])=>{e.startsWith("_")?f[e]=t(h,s):a[e]=t(h,s)})),a.setDependencies=e=>r.setDependencies(e),a.getDependencies=()=>r.getDependencies(),a.setNote=e=>t.setNote(e),a.listScenes=()=>[...s.sceneNames],a.enablePlugin=(e,t)=>r.enablePlugin(e,t),a.disablePlugin=e=>r.disablePlugin(e),a.emit=(e,...t)=>r.emit(e,...t),a}}));
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <link rel="stylesheet" href="public/style.css" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <title>No demo yet!</title>
9
+ </head>
10
+ <body>
11
+ <div id="app"></div>
12
+ </body>
13
+ </html>
package/main.js ADDED
@@ -0,0 +1,26 @@
1
+ import './style.css'
2
+ import javascriptLogo from './javascript.svg'
3
+ import viteLogo from '/vite.svg'
4
+ // import { setupCounter } from './counter.js'
5
+
6
+ import top from './src/main.js'
7
+
8
+ document.querySelector('#app').innerHTML = `
9
+ <div>
10
+ <a href="https://vitejs.dev" target="_blank">
11
+ <img src="${viteLogo}" class="logo" alt="Vite logo" />
12
+ </a>
13
+ <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
14
+ <img src="${javascriptLogo}" class="logo vanilla" alt="JavaScript logo" />
15
+ </a>
16
+ <h1>Hello Vite!</h1>
17
+ <div class="card">
18
+ <button id="counter" data-click="count" data-quick-click type="button">0</button>
19
+ </div>
20
+ <p class="read-the-docs">
21
+ Click on the Vite logo to learn more
22
+ </p>
23
+ </div>
24
+ `
25
+
26
+ top ()
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@peter.naydenov/cuts",
3
+ "description": "SPA scene manager",
4
+ "version": "1.0.0",
5
+ "license": "MIT",
6
+ "author": "Peter Naydenov",
7
+ "main": "./dist/cuts.umd.js",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/cuts.esm.mjs",
12
+ "require": "./dist/cuts.cjs",
13
+ "default": "./dist/cuts.umd.js"
14
+ },
15
+ "./package.json": "./package.json",
16
+ "./dist/*": "./dist/*",
17
+ "./src/*": "./src/*"
18
+ },
19
+ "scripts": {
20
+ "dev": "vite",
21
+ "build": "rollup -c",
22
+ "preview": "vite preview",
23
+ "test": "mocha test-mocha/*.js",
24
+ "cypress": "cypress open --component --browser chrome"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/PeterNaydenov/screen-cuts.git"
29
+ },
30
+ "devDependencies": {
31
+ "@peter.naydenov/visual-controller-for-vue3": "^2.1.0",
32
+ "@rollup/plugin-commonjs": "^25.0.7",
33
+ "@rollup/plugin-node-resolve": "^15.2.3",
34
+ "@rollup/plugin-terser": "^0.4.4",
35
+ "@vitejs/plugin-vue": "^5.0.4",
36
+ "c8": "^9.1.0",
37
+ "chai": "^5.1.0",
38
+ "cypress": "^13.8.1",
39
+ "mocha": "^10.4.0",
40
+ "rollup": "^4.17.2",
41
+ "vite": "^5.2.11",
42
+ "vue": "^3.4.26"
43
+ },
44
+ "dependencies": {
45
+ "@peter.naydenov/log": "^1.1.0",
46
+ "@peter.naydenov/shortcuts": "^3.1.1",
47
+ "ask-for-promise": "^2.0.3"
48
+ },
49
+ "keywords": [
50
+ "script",
51
+ "visual",
52
+ "state",
53
+ "manager"
54
+ ]
55
+ }
@@ -0,0 +1,43 @@
1
+ import resolve from '@rollup/plugin-node-resolve'
2
+ import commonjs from '@rollup/plugin-commonjs'
3
+ import terser from '@rollup/plugin-terser';
4
+
5
+
6
+ export default [
7
+ // browser-friendly UMD build
8
+ {
9
+ input: 'src/main.js',
10
+ output: {
11
+ name: 'cuts',
12
+ file: 'dist/cuts.umd.js',
13
+ format: 'umd'
14
+ },
15
+ plugins: [
16
+ resolve(), // so Rollup can find `ms`
17
+ commonjs() // so Rollup can convert `ms` to an ES module
18
+ , terser()
19
+ ]
20
+ },
21
+
22
+ // CommonJS (for Node) and ES module (for bundlers) build.
23
+ // (We could have three entries in the configuration array
24
+ // instead of two, but it's quicker to generate multiple
25
+ // builds from a single configuration where possible, using
26
+ // an array for the `output` option, where we can specify
27
+ // `file` and `format` for each target)
28
+ {
29
+ input: 'src/main.js',
30
+ external: [
31
+ 'ask-for-promise',
32
+ '@peter.naydenov/log',
33
+ '@peter.naydenov/shortcuts'
34
+ ],
35
+ output: [
36
+ { file: 'dist/cuts.cjs' , format: 'cjs' },
37
+ { file: 'dist/cuts.esm.mjs', format: 'es' }
38
+ ],
39
+ plugins: [ terser() ]
40
+ }
41
+ ];
42
+
43
+
@@ -0,0 +1,75 @@
1
+ 'use strict'
2
+
3
+
4
+ function* findInstructions ( currentName, currentParents, targetName, targetParents ) {
5
+ let backToName, backIndex=null;
6
+
7
+ if ( currentName === null ) { // When the scene manager is just started
8
+ yield* targetParents.map ( name => [ name, 'show' ])
9
+ yield [ targetName, 'show' ]
10
+ return
11
+ }
12
+
13
+ if ( !targetParents || targetParents.length === 0 ) { // Need to close all current parents, then open the target
14
+ if ( !currentParents ) {
15
+ yield [ currentName, 'hide' ]
16
+ yield [ targetName, 'show' ]
17
+ return
18
+ }
19
+ yield [ currentName, 'hide' ]
20
+ currentParents.reverse ()
21
+ yield* currentParents.map ( name => [ name, 'hide' ])
22
+ yield [ targetName, 'show' ]
23
+ return
24
+ }
25
+
26
+ if ( targetParents.includes ( currentName ) ) { // Target is a subpage of the current page. No closing needed.
27
+ let id = targetParents.indexOf ( currentName );
28
+ for ( let i = id+1; i < targetParents.length; i++ ) {
29
+ yield [ targetParents[i], 'show' ]
30
+ }
31
+ yield [ targetName, 'show' ]
32
+ return
33
+ }
34
+
35
+ if ( !currentParents ) {
36
+ yield [ currentName, 'hide' ]
37
+ yield* targetParents.map ( name => [ name, 'show' ])
38
+ return
39
+ }
40
+
41
+ targetParents.every ( (name,i) => {
42
+ if ( currentParents[i] === targetParents[i] ) {
43
+ backToName = name
44
+ backIndex = i
45
+ return true
46
+ }
47
+ else {
48
+ return false
49
+ }
50
+ })
51
+
52
+ if ( backIndex == null ) { // different branches - close all current parents, then open the target branch
53
+ yield [ currentName, 'hide' ]
54
+ currentParents.reverse ()
55
+ yield* currentParents.map ( name => [ name, 'hide' ])
56
+ yield* targetParents.map ( name => [ name, 'show' ])
57
+ yield [ targetName, 'show' ]
58
+ return
59
+ }
60
+
61
+ yield [ currentName, 'hide' ]
62
+ for ( let i = currentParents.length; i > backIndex+1; i-- ) {
63
+ yield [ currentParents[i-1], 'hide' ]
64
+ }
65
+ for ( let i = backIndex+1; i < targetParents.length; i++ ) {
66
+ yield [ targetParents[i], 'show' ]
67
+ }
68
+ yield [ targetName, 'show' ]
69
+ } // findInstructions func.
70
+
71
+
72
+
73
+ export default findInstructions
74
+
75
+
package/src/main.js ADDED
@@ -0,0 +1,124 @@
1
+ 'use strict'
2
+ /**
3
+ * Cuts
4
+ * ===============
5
+ *
6
+ * SPA Scene manager.
7
+ *
8
+ * History notes:
9
+ * - Started as @peter.naydenov/screen-writer on gitHub for the first time on 2023-11-24;
10
+ * - Version 2.0.0 of screen-writer. Works with shortcuts@3.0.1. Published on March 6th, 2024;
11
+ * - Rename to @peter.naydenov/cuts
12
+ * Changes in documentation and some methods were renamed accordingly.
13
+ * Version 1.0.0.
14
+ * Published on May 8th, 2024;
15
+ *
16
+ */
17
+
18
+
19
+ import { shortcuts } from '@peter.naydenov/shortcuts' // Docs : https://github.com/PeterNaydenov/shortcuts
20
+ import askForPromise from 'ask-for-promise' // Docs : https://github.com/PeterNaydenov/ask-for-promise
21
+ import createLog from '@peter.naydenov/log' // Docs : https://github.com/PeterNaydenov/log
22
+
23
+ import findInstructions from './findInstructions.js'
24
+ import setInstruction from './setInstruction.js'
25
+ import methods from './methods/index.js'
26
+
27
+
28
+
29
+
30
+
31
+ function main ( cfg= {logLevel:0} ) {
32
+ const
33
+ shortcutMngr = shortcuts ()
34
+ , logLevel = cfg.logLevel || 0
35
+ , log = createLog ({ level:logLevel })
36
+ , state = {
37
+ currentScene : null // Current scene name;
38
+ , currentParents : null // Current scene parents if any;
39
+ , sceneNames : new Set () // Set with all loaded scenes;
40
+ , scenes : {} // Collection of all scenes;
41
+ , opened : false // Flag to indicate if the scene manager is opened
42
+ }
43
+ , API = {}
44
+ , inAPI = {}
45
+ , dependencies = {
46
+ shortcutMngr
47
+ , API
48
+ , inAPI
49
+ , findInstructions
50
+ , askForPromise
51
+ , setInstruction : setInstruction ({ askForPromise, deps: shortcutMngr.getDependencies })
52
+ , log
53
+ }
54
+ ;
55
+
56
+ Object.entries ( methods ).forEach ( ([name, fn]) => {
57
+ if ( name.startsWith ( '_' ) ) inAPI[name] = fn ( dependencies, state )
58
+ else API[name] = fn ( dependencies, state )
59
+ })
60
+
61
+ /**
62
+ * @function setDependencies
63
+ * @description Set dependencies for the Scenes
64
+ * @param {*} deps - dependencies objects
65
+ * @returns void
66
+ */
67
+ API.setDependencies = deps => shortcutMngr.setDependencies ( deps )
68
+
69
+ /**
70
+ * @function getDependencies
71
+ * @description Get dependencies for the Scenes
72
+ * @returns {*} - dependencies objects
73
+ */
74
+ API.getDependencies = () => shortcutMngr.getDependencies ()
75
+
76
+ /**
77
+ * @function setNote
78
+ * @param {string} note - note, provided to action functions
79
+ * @returns void
80
+ */
81
+ API.setNote = note => shortcuts.setNote ( note )
82
+
83
+ /**
84
+ * @function listScenes
85
+ * @description List all loaded Scene names
86
+ * @returns {Array.<string>} - list of scene names
87
+ */
88
+ API.listScenes = () => [ ...state.sceneNames ]
89
+
90
+ /**
91
+ * @function enablePlugin
92
+ * @description Enable a shortcut plugin
93
+ * @param {function} plugin - plugin library
94
+ * @param {*} options - plugin options
95
+ * @returns void
96
+ */
97
+ API.enablePlugin = (plugin,options) => shortcutMngr.enablePlugin ( plugin, options )
98
+
99
+ /**
100
+ * @function disablePlugin
101
+ * @description Disable a shortcut plugin
102
+ * @param {string} pluginName - plugin name
103
+ * @returns void
104
+ */
105
+ API.disablePlugin = ( pluginName ) => shortcutMngr.disablePlugin ( pluginName )
106
+
107
+
108
+ /**
109
+ * @function emit
110
+ * @description Emit an event
111
+ * @param {string} event - event name
112
+ * @param {*} data - event data
113
+ * @returns void
114
+ */
115
+ API.emit = ( event, ...args ) => shortcutMngr.emit ( event, ...args )
116
+
117
+ return API
118
+ } // main func.
119
+
120
+
121
+
122
+ export default main
123
+
124
+
@@ -0,0 +1,53 @@
1
+ 'use strict'
2
+
3
+
4
+
5
+ function hide ( dependencies, state ) {
6
+ /**
7
+ * @function hide
8
+ * @description Hide the current scene and returns to the parent one
9
+ * @param {number|string} endSteps - number of steps to end or '*' to end all scenes to the root
10
+ * @returns {Promise} - promise that resolves when the process 'end' is completed
11
+ */
12
+ return function hide ( endSteps=1 ) {
13
+ const
14
+ { askForPromise, shortcutMngr, setInstruction } = dependencies
15
+ , { currentScene, currentParents, scenes } = state
16
+ , hideTask = askForPromise ()
17
+ , instructions = []
18
+ , hasParents = currentParents.length > 0
19
+ ;
20
+ let el;
21
+
22
+ shortcutMngr.pause () // Stop all shortcuts
23
+ instructions.push ( setInstruction ( scenes[currentScene].hide )) // Set instruction to hide current scene
24
+
25
+ if ( !hasParents ) state.currentScene = null
26
+
27
+ if ( hasParents && endSteps === '*' ) {
28
+ [...currentParents].reverse().forEach ( name => {
29
+ el = state.currentParents.pop ();
30
+ state.currentScene = el
31
+ instructions.push ( setInstruction ( scenes[name].hide))
32
+ })
33
+ }
34
+ if ( hasParents && ![ '*', 1].includes(endSteps) ) {
35
+ currentParents.slice ( `-${endSteps-1}` ).reverse().map ( name => {
36
+ el = state.currentParents.pop ();
37
+ state.currentScene = el
38
+ instructions.push ( setInstruction (scenes[name].hide))
39
+ })
40
+ }
41
+ endSteps = askForPromise.sequence ( instructions )
42
+ endSteps.onComplete ( () => {
43
+ shortcutMngr.changeContext ( state.currentScene ) // If state.currentScene is null, then all shortcuts are switched off and currentContext is null
44
+ hideTask.done ()
45
+ })
46
+ return hideTask.promise
47
+ }} // hide func.
48
+
49
+
50
+
51
+ export default hide
52
+
53
+
@@ -0,0 +1,13 @@
1
+
2
+ import hide from './hide.js'
3
+ import listShortcuts from './listShortcuts.js'
4
+ import setScenes from './setScenes.js'
5
+ import show from './show.js'
6
+
7
+
8
+ export default {
9
+ hide
10
+ , listShortcuts
11
+ , setScenes
12
+ , show
13
+ }
@@ -0,0 +1,21 @@
1
+ 'use strict'
2
+
3
+ function listShortcuts ( dependencies, state ) {
4
+ /**
5
+ * @function listShortcuts
6
+ * @description List all shortcuts for Scene
7
+ * @param {string} sceneName - name of the scene to list shortcuts for.
8
+ * @returns {(Array.<string>|null)} - list of shortcuts for the scene or null if page not found
9
+ */
10
+ return function listShortcuts ( sceneName ) {
11
+ const { scenes, sceneNames } = state;
12
+ if ( ! sceneNames.has ( sceneName ) ) return null
13
+ const { show, hide, parents, ...shortcuts } = scenes[sceneName];
14
+ return Object.keys ( shortcuts )
15
+ }} // listShortcuts func.
16
+
17
+
18
+
19
+ export default listShortcuts
20
+
21
+
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * @type sceneDescription
5
+ * @param {string} name - name of the scene
6
+ * @param {object} scene - object should contain 'show' and 'hide' + shortcuts and action functions
7
+ */
8
+
9
+ function setScenes ( dependencies, state ) {
10
+ /**
11
+ * @function setScenes
12
+ * @param {Array} sceneDescription - list of objects with name and scene. Scene should be a Scene model.
13
+ * @returns { void }
14
+ */
15
+ return function setScenes ( list ) {
16
+ const { shortcutMngr } = dependencies;
17
+ list.forEach ( ({name, scene }) => {
18
+ if ( !scene.parents ) scene.parents = []
19
+ state.scenes[name] = scene
20
+ state.sceneNames.add ( name )
21
+
22
+ const
23
+ { show, hide, parents, ...shortcuts } = scene
24
+ , context = {}
25
+ ;
26
+
27
+ context[name] = shortcuts
28
+ shortcutMngr.load ( context )
29
+ })
30
+ }} // setScenes func.
31
+
32
+
33
+
34
+ export default setScenes
35
+
36
+
@@ -0,0 +1,105 @@
1
+ 'use strict'
2
+
3
+
4
+
5
+ function show ( dependencies, state ) {
6
+ /**
7
+ * @function show
8
+ * @description Switch to the requested scene
9
+ * @param {Object} request - request object
10
+ * @param {string} request.scene - requested scene name
11
+ * @param {Object} request.options - options object. Optional
12
+ * @param {boolean} request.options.ssr - flag to indicate if the scene is already rendered(ssr: server side rendering). Optional
13
+ * @param {Array} args - arguments to pass to the scene method. Optional
14
+ * @returns {Promise} - promise that resolves when the process to 'show' is completed
15
+ */
16
+ return function show ({scene:requestedScene, options={ssr:false}}, ...args ) {
17
+ const
18
+ { shortcutMngr, askForPromise, log, findInstructions, setInstruction } = dependencies
19
+ , showTask = askForPromise ()
20
+ , { opened, scenes, sceneNames } = state
21
+ ;
22
+
23
+ if ( !sceneNames.has ( requestedScene ) ) {
24
+ if ( log ) {
25
+ log ({
26
+ message: `Scene ${requestedScene} is not available.`
27
+ , level : 1
28
+ , type : 'error'
29
+ })
30
+ showTask.done ()
31
+ return showTask.promise
32
+ }
33
+ }
34
+
35
+ if ( !opened && options.ssr ) { // Check for Server side rendering on first scene load only
36
+ // Executes only once when the scene manager is started
37
+ state.opened = true
38
+ state.currentPage = requestedScene
39
+ shortcutMngr.changeContext ( requestedScene )
40
+ showTask.done ()
41
+ return showTask.promise
42
+ }
43
+
44
+ const { show, parents } = scenes[requestedScene];
45
+
46
+ if ( parents[0] === '*' ) {
47
+ show ().then ( () => showTask.done () )
48
+ state.currentParents.push ( state.currentPage )
49
+ state.currentPage = requestedScene
50
+ return showTask.promise
51
+ }
52
+
53
+ let checkParents = parents.forEach ( name => sceneNames.has ( name ) ) || true ;
54
+ if ( !checkParents ) {
55
+ if ( log ) {
56
+ log ({
57
+ message: `Some of '${requestedScene}' parent scenes are not set.`
58
+ , level : 1
59
+ , type : 'error'
60
+ })
61
+ }
62
+ showTask.done ()
63
+ return showTask.promise
64
+ }
65
+
66
+ function getStep ([name, action]) { // Returns a scene function(hide or show) according to instructions
67
+ if ( action === 'show' ) {
68
+ if ( state.currentScene ) state.currentParents.push ( state.currentScene )
69
+ state.currentScene = name
70
+ }
71
+ else {
72
+ let el = state.currentParents.pop ();
73
+ state.currentScene = el
74
+ }
75
+ return scenes[name][action]
76
+ } // getStep func.
77
+
78
+ if ( !state.currentParents ) state.currentParents = []
79
+ const
80
+ g = findInstructions ( state.currentScene, state.currentParents, requestedScene, parents )
81
+ , instructions = []
82
+ ;
83
+
84
+ for ( let inst of g ) {
85
+ [inst]
86
+ .map ( getStep )
87
+ .map ( step => instructions.push ( setInstruction(step),...args ))
88
+ // Note: Methods 'show' and 'hide' are async, but we need to provide them a data,
89
+ // so we need to wrap them in a another function that returns a promise
90
+ }
91
+
92
+ const goingTask = askForPromise.sequence ( instructions ); // Execute open steps(show and hide) in sequence
93
+ goingTask.onComplete ( () => {
94
+ state.opened = true
95
+ shortcutMngr.changeContext ( requestedScene )
96
+ showTask.done ()
97
+ })
98
+ return showTask.promise
99
+ }} // show func.
100
+
101
+
102
+
103
+ export default show
104
+
105
+
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ function setInstruction ( dependencies ) {
4
+ return function setInstruction ( fn, ...args ) {
5
+ const { askForPromise, deps } = dependencies;
6
+ return function applyInstruction () {
7
+ const task = askForPromise ();
8
+ fn ({ task, dependencies:deps() }, ...args )
9
+ return task.promise
10
+ } // applyInstruction func.
11
+ }} // setInstruction func.
12
+
13
+
14
+
15
+ export default setInstruction
16
+
17
+
@@ -0,0 +1,243 @@
1
+ 'use strict'
2
+
3
+ import VisualController from '@peter.naydenov/visual-controller-for-vue3' // Docs : https://github.com/PeterNaydenov/visual-controller-for-vue3
4
+ import { expect } from 'chai'
5
+ import cuts from '../src/main.js'
6
+ import { pluginClick } from '@peter.naydenov/shortcuts'
7
+
8
+ import Dummy from './dummy.vue'
9
+ import Red from './red.vue'
10
+ import Blue from './blue.vue'
11
+ import Gray from './gray.vue'
12
+
13
+
14
+
15
+ describe ( 'General', () => {
16
+
17
+ it ( 'Start a sreenWriter. ScreenWriter API', () => {
18
+ const script = cuts ();
19
+ expect ( script ).to.have.property ( 'show' )
20
+ expect ( script ).to.have.property ( 'hide' )
21
+ expect ( script ).to.have.property ( 'setScenes' )
22
+ expect ( script ).to.have.property ( 'setNote' )
23
+ expect ( script ).to.have.property ( 'listScenes' )
24
+ expect ( script ).to.have.property ( 'listShortcuts' )
25
+ expect ( script ).to.have.property ( 'setDependencies' )
26
+ expect ( script ).to.have.property ( 'getDependencies' )
27
+ }) // it start a director
28
+
29
+
30
+
31
+ it ( 'Load a top level page', done => {
32
+ let
33
+ render = false
34
+ , click = false
35
+ , html = new VisualController ()
36
+ ;
37
+ const
38
+ script = cuts ()
39
+ , d = document.querySelector ('[data-cy-root]')
40
+ , testScene = {
41
+ show : ({ task, dependencies }) => {
42
+ const { html } = dependencies;
43
+ html.publish ( Red, {}, 'container' )
44
+ render = true
45
+ task.done ()
46
+ }
47
+ , hide : ({task, dependencies}) => {
48
+ const { html } = dependencies;
49
+ html.destroy ( 'container' )
50
+ task.done ()
51
+ }
52
+ , 'click: left-1' : ({dependencies,target}) => {
53
+ click = true
54
+ }
55
+ }
56
+ ;
57
+ script.enablePlugin ( pluginClick )
58
+ d.id = 'root'
59
+ html.publish ( Dummy, {}, 'root' )
60
+
61
+ cy.wait ( 0 )
62
+ .then ( () => {
63
+ script.setDependencies ({ html })
64
+ script.setScenes ([
65
+ { name:'test', scene:testScene }
66
+ ])
67
+ script.show ({ scene : 'test' })
68
+ // cy.get ( '#target' ).click ()
69
+ })
70
+ .then ( () => {
71
+ expect ( render ).to.be.true
72
+ // expect ( click ).to.be.true
73
+ return cy.wait ( 0 )
74
+ })
75
+ .then ( () => {
76
+ return script.hide ()
77
+ })
78
+ .then ( () => {
79
+ const pageContent = document.getElementById('container').innerHTML
80
+ expect ( pageContent ).to.be.empty
81
+ done ()
82
+ })
83
+ }) // it load a top level page
84
+
85
+
86
+
87
+ it ( 'Load a deep level page', done => {
88
+ let
89
+ render = false
90
+ , click = false
91
+ , html = new VisualController ()
92
+ ;
93
+ const
94
+ script = cuts ()
95
+ , d = document.querySelector ('[data-cy-root]' )
96
+ , testScene = {
97
+ show : ({ task, dependencies }) => {
98
+ const { html } = dependencies;
99
+ html.publish ( Red, {}, 'container' )
100
+ render = true
101
+ task.done ()
102
+ }
103
+ , hide : ({task, dependencies}) => {
104
+ const { html } = dependencies;
105
+ html.destroy ( 'container' )
106
+ task.done ()
107
+ }
108
+ , parents : [ 'blue' ]
109
+ , 'click: 1-left' : ({dependencies,target}) => { // Order in the event name after the prefix is not important
110
+ click = true
111
+ }
112
+ }
113
+ , bluePage = {
114
+ show : ({ task, dependencies }) => {
115
+ const { html } = dependencies;
116
+ html.publish ( Blue, {}, 'blue' )
117
+ task.done ()
118
+ }
119
+ , hide : ({task, dependencies}) => {
120
+ const { html } = dependencies;
121
+ html.destroy ( 'blue' )
122
+ task.done ()
123
+ }
124
+ , 'click: left-1' : ({dependencies,target}) => {}
125
+ }
126
+ ;
127
+ script.enablePlugin ( pluginClick )
128
+ d.id = 'root'
129
+ html.publish ( Dummy, {}, 'root' )
130
+
131
+ cy.wait ( 0 )
132
+ .then ( () => {
133
+ script.setDependencies ({ html })
134
+ script.setScenes ([
135
+ { name:'test' , scene : testScene }
136
+ , { name: 'blue', scene : bluePage }
137
+ ])
138
+ script.show ({ scene: 'test' })
139
+ cy.get ( '#target' ).click ()
140
+ })
141
+ .then ( () => {
142
+ expect ( render ).to.be.true
143
+ expect ( click ).to.be.true
144
+ return cy.wait ( 0 )
145
+ })
146
+ .then ( () => {
147
+ return script.hide ( '*' )
148
+ })
149
+ .then ( () => {
150
+ const pageContent = document.getElementById('container').innerHTML
151
+ expect ( pageContent ).to.be.empty
152
+ done ()
153
+ })
154
+ }) // it load a deep level page
155
+
156
+
157
+
158
+ it ( 'Turn to other branch', done => {
159
+ let
160
+ render = false
161
+ , click = false
162
+ , html = new VisualController ()
163
+ ;
164
+ const
165
+ script = cuts ()
166
+ , d = document.querySelector ('[data-cy-root]' )
167
+ , testScene = {
168
+ show : ({ task, dependencies }) => {
169
+ const { html } = dependencies;
170
+ html.publish ( Red, {}, 'subgray' ) // subgray is inside 'blue' component
171
+ render = true
172
+ task.done ()
173
+ }
174
+ , hide : ({task, dependencies}) => {
175
+ const { html } = dependencies;
176
+ html.destroy ( 'subgray' )
177
+ task.done ()
178
+ }
179
+ , parents : [ 'blue' ]
180
+ , 'click: left-1' : ({dependencies,target}) => {
181
+ click = true
182
+ }
183
+ }
184
+ , bluePage = {
185
+ show : ({ task, dependencies }) => {
186
+ const { html } = dependencies;
187
+ html.publish ( Blue, {}, 'blue' )
188
+ task.done ()
189
+ }
190
+ , hide : ({task, dependencies}) => {
191
+ const { html } = dependencies;
192
+ html.destroy ( 'blue' )
193
+ task.done ()
194
+ }
195
+ , 'click: left-1' : ({dependencies,target}) => {}
196
+ }
197
+ , grayPage = {
198
+ show : ({ task, dependencies }) => {
199
+ const { html } = dependencies;
200
+ html.publish ( Gray, {}, 'subgray' )
201
+ task.done ()
202
+ }
203
+ , hide : ({task, dependencies}) => {
204
+ const { html } = dependencies;
205
+ html.destroy ( 'subgray' )
206
+ task.done ()
207
+ }
208
+ , parents : [ 'blue' ]
209
+ , 'click: left-1' : ({dependencies,target}) => {}
210
+ }
211
+ ;
212
+ script.enablePlugin ( pluginClick )
213
+ d.id = 'root'
214
+ html.publish ( Dummy, {}, 'root' )
215
+
216
+ cy.wait ( 0 )
217
+ .then ( () => {
218
+ script.setDependencies ({ html })
219
+ script.setScenes ([
220
+ { name:'test', scene : testScene }
221
+ , { name: 'blue', scene : bluePage }
222
+ , { name: 'gray', scene : grayPage }
223
+ ])
224
+ script.show ({ scene : 'test' })
225
+ cy.get ( '#target' ).click ()
226
+ })
227
+ .then ( () => {
228
+ expect ( render ).to.be.true
229
+ expect ( click ).to.be.true
230
+ return cy.wait ( 0 )
231
+ })
232
+ .then ( () => script.show ({ scene : 'gray' } ))
233
+ .then ( () => {
234
+ const
235
+ red = document.querySelectorAll ( '.red' )
236
+ , blue = document.querySelectorAll ( '.gray' )
237
+ ;
238
+ expect ( blue.length ).to.be.equal ( 1 )
239
+ expect ( red.length ).to.be.equal ( 0 )
240
+ done ()
241
+ })
242
+ }) // it turn to other branch
243
+ }) // describe
package/test/blue.vue ADDED
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <div class="blue">
3
+ <div id="subgray"></div>
4
+ </div>
5
+ </template>
6
+
7
+ <style scoped>
8
+ .blue {
9
+ width: 200px;
10
+ height: 200px;
11
+ background-color: skyblue;
12
+ }
13
+ </style>
package/test/dummy.vue ADDED
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <div>
3
+ <h1>Test</h1>
4
+ <p id="target">Test <a href="#">lorem ipsum</a></p>
5
+ <div id="container">
6
+
7
+ </div>
8
+ <div id="blue"></div>
9
+ <div id="gray"></div>
10
+ </div>
11
+ </template>
12
+
13
+ <style>
14
+ :root {
15
+ --blue: #007bff;
16
+ --gray: #6c757d;
17
+ --link-color: rgb(204, 51, 10);
18
+ }
19
+
20
+ body.dark {
21
+ --background-color: #000;
22
+ --link-color: rgb(249, 249, 249);
23
+ background-color: var(--background-color);
24
+ }
25
+
26
+ #target {
27
+ --blue: var(--link-color);
28
+ color: var(--blue);
29
+ }
30
+
31
+ #target a {
32
+ color: var(--link-color);
33
+ }
34
+ </style>
package/test/gray.vue ADDED
@@ -0,0 +1,12 @@
1
+ <template>
2
+ <div class="gray">
3
+ </div>
4
+ </template>
5
+
6
+ <style scoped>
7
+ .gray {
8
+ width: 100px;
9
+ height: 100px;
10
+ background-color: gray;
11
+ }
12
+ </style>
package/test/red.vue ADDED
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <div class="red">llm</div>
3
+ </template>
4
+
5
+ <style scoped>
6
+ .red {
7
+ width: 100px;
8
+ height: 100px;
9
+ background-color: red;
10
+ }
11
+ </style>
@@ -0,0 +1,109 @@
1
+ 'use strict'
2
+
3
+ import findInstructions from "../src/findInstructions.js"
4
+ import { expect } from 'chai'
5
+
6
+
7
+ describe ( 'Find a Position', () => {
8
+
9
+
10
+
11
+ it ( 'Switch top level pages', () => {
12
+ let result = []
13
+ let g = findInstructions ( 'old', undefined, 'new', undefined );
14
+
15
+ for ( const n of g ) { result.push ( n ) }
16
+
17
+ expect ( result.length ).to.be.equal ( 2 )
18
+ expect ( result[0][0] ).to.be.equal ( 'old' )
19
+ expect ( result[0][1] ).to.be.equal ( 'hide' )
20
+
21
+ expect ( result[1][0] ).to.be.equal ( 'new' )
22
+ expect ( result[1][1] ).to.be.equal ( 'show' )
23
+ }) // it switch top level pages
24
+
25
+
26
+
27
+ it ( 'Switch from child page to top level page', () => {
28
+ let result = [];
29
+ let g = findInstructions ( 'old', ['one','two'], 'new', undefined );
30
+
31
+ for ( const n of g ) { result.push ( n ) }
32
+
33
+ expect ( result[0]).to.be.deep.equal ( ['old', 'hide'] )
34
+ expect ( result[1]).to.be.deep.equal ( ['two', 'hide'] )
35
+ expect ( result[2]).to.be.deep.equal ( ['one', 'hide'] )
36
+ expect ( result[3]).to.be.deep.equal ( ['new', 'show'] )
37
+ }) // it switch from child page to top level page
38
+
39
+
40
+
41
+ it ( 'Switch to child page', () => {
42
+ let result = [];
43
+ let g = findInstructions ( 'old', ['bla'], 'new', ['old','two','three'] );
44
+ for ( const n of g ) { result.push ( n ) }
45
+
46
+ expect ( result ).to.have.lengthOf ( 3 )
47
+ expect ( result[0]).to.be.deep.equal ( ['two', 'show'] )
48
+ expect ( result[1]).to.be.deep.equal ( ['three', 'show'] )
49
+ expect ( result[2]).to.be.deep.equal ( ['new', 'show'] )
50
+ }) // it switch to child page
51
+
52
+
53
+
54
+ it ( 'From child page to other siblings page', () => {
55
+ let result = [];
56
+ let g = findInstructions ( 'old', ['two', 'three'], 'new', ['two','three'] );
57
+ for ( const n of g ) { result.push ( n ) }
58
+
59
+ expect ( result ).to.have.lengthOf ( 2 )
60
+ expect ( result[0]).to.be.deep.equal ( ['old', 'hide'] )
61
+ expect ( result[1]).to.be.deep.equal ( ['new', 'show'] )
62
+ }) // it from top level page to not own child page
63
+
64
+
65
+
66
+ it ( 'From top level page to not own child page', () => {
67
+ let result = [];
68
+ let g = findInstructions ( 'old', undefined, 'new', ['two','three'] );
69
+ for ( const n of g ) { result.push ( n ) }
70
+
71
+ expect ( result ).to.have.lengthOf ( 3 )
72
+ expect ( result[0]).to.be.deep.equal ( ['old', 'hide'] )
73
+ expect ( result[1]).to.be.deep.equal ( ['two', 'show'] )
74
+ expect ( result[2]).to.be.deep.equal ( ['three', 'show'] )
75
+ }) // it from top level page to not own child page
76
+
77
+
78
+
79
+ it ( 'Long chain of parents', () => {
80
+ let result = [];
81
+ let g = findInstructions ( 'old', [ 'one', 'branch' ], 'new', [ 'one','two','three'] );
82
+ for ( const n of g ) { result.push ( n ) }
83
+
84
+ expect ( result ).to.have.lengthOf ( 5 )
85
+ expect ( result[0]).to.be.deep.equal ( ['old', 'hide'] )
86
+ expect ( result[1]).to.be.deep.equal ( ['branch', 'hide'] )
87
+ expect ( result[2]).to.be.deep.equal ( ['two', 'show'] )
88
+ expect ( result[3]).to.be.deep.equal ( ['three', 'show'] )
89
+ expect ( result[4]).to.be.deep.equal ( ['new', 'show'] )
90
+ }) // it long chain of parents
91
+
92
+
93
+
94
+ it ( 'Different branches', () => {
95
+ let result = [];
96
+ let g = findInstructions ( 'old', [ 'something', 'branch' ], 'new', [ 'one','two','three'] );
97
+ for ( const n of g ) { result.push ( n ) }
98
+
99
+ expect ( result ).to.have.lengthOf ( 7 )
100
+ expect ( result[0]).to.be.deep.equal ( ['old', 'hide'] )
101
+ expect ( result[1]).to.be.deep.equal ( ['branch', 'hide'] )
102
+ expect ( result[2]).to.be.deep.equal ( ['something', 'hide'] )
103
+ expect ( result[3]).to.be.deep.equal ( ['one', 'show'] )
104
+ expect ( result[4]).to.be.deep.equal ( ['two', 'show'] )
105
+ expect ( result[5]).to.be.deep.equal ( ['three', 'show'] )
106
+ expect ( result[6]).to.be.deep.equal ( ['new', 'show'] )
107
+ }) // it long chain of parents
108
+
109
+ }) // describe
package/vite.config.js ADDED
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [vue()]
7
+ })