Last active
February 17, 2020 20:39
-
-
Save JonathanMontane/05aa9c50c42908bac07faf7b6c0e97bd to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Available variables: | |
// - Machine | |
// - interpret | |
// - assign | |
// - send | |
// - sendParent | |
// - spawn | |
// - raise | |
// - actions | |
// - XState (all XState exports) | |
/* | |
helpful content: | |
- https://gist.github.com/danharper/8364399 | |
- https://developer.chrome.com/extensions/content_scripts | |
- https://developer.chrome.com/extensions/background_pages | |
- https://developer.chrome.com/extensions/runtime#method-sendMessage | |
- svg masking for hotjar effect: https://travishorn.com/removing-parts-of-shapes-in-svg-b539a89e5649 | |
*/ | |
const injectPageActor = (context, event) => { | |
console.log('injecting page actor'); | |
/* | |
chrome.tabs.executeScript(event.tab.ib, { | |
file: 'inject.js' | |
}); | |
*/ | |
}; | |
const injectVisualBuilderActor = (context, event) => { | |
console.log('injecting builder actor'); | |
/* | |
const frameURL = chrome.runtime.getURL("build/visual-builder.html"); | |
const iframe = document.createElement("iframe"); | |
.... | |
*/ | |
}; | |
// should be a chrome.runtime.sendMessage | |
const notifyPageActorReady = send('PAGE_ACTOR_READY'); | |
const pageSupervisorMachine = Machine({ | |
initialState: 'idle', | |
states: { | |
idle: { | |
type: 'final' | |
} | |
} | |
}); | |
const spawnPageSupervisor = assign({ | |
pageSuperVisors: (context, event) => { | |
const tabId = event.tabId; | |
if (!context.pageSupervisors[tabId]) { | |
context.pageSupervisors[tabId] = spawn(pageSupervisorMachine, `page-supervisor-${tabId}`) | |
} | |
} | |
}); | |
const cvbMachine = Machine({ | |
id: 'world', | |
type: 'parallel', | |
states: { | |
'plane:extension/core': { | |
// what are the steps needed by the extension? | |
// starting event is EXTENSION_SCRIPT_LOADED which happens at then end of the script call. | |
// 1. check if we have a crawler api key to represent a user | |
// - otherwise, wait for a BROWSER_ACTION_CLICKED event | |
// 1.2 on browser action, open a new page with the crawler-admin in it. | |
// 1.3 inject the crawler-admin page actor into the page | |
// wait for the crawler-admin page actor to return an API Key, or a close event | |
// 1.3.1. on close event, update badge to red and return to idle | |
// 1.3.2. on API Key received, assign the api key to the context, save it to the storage, and go to testing | |
// 2. on entry, invoke the crawler api key testing | |
// 2.1. if it fails, update badge to red and return to idle | |
// 2.2. if it succeeds, update badge to green and go to ready. | |
// | |
// BROWSER_ACTION_CLICKED while in testing or ready will inject the regular page actor too. | |
initial: 'idle', | |
context: { | |
crawlerAPIKey: null, | |
pageSupervisors: {}, | |
}, | |
states: { | |
idle: { | |
on: { | |
BROWSER_ACTION_CLICKED: 'setup', | |
} | |
}, | |
setup: { | |
invoke: { | |
src: 'crawlerPageSupervisor', | |
onDone: { | |
target: 'active', | |
actions: assign({ user: (context, event) => event.data }) | |
}, | |
onError: { | |
target: 'idle', | |
actions: assign({ error: (context, event) => event.data }) | |
} | |
}, | |
on: { | |
BROWSER_ACTION_CLICKED: { | |
target: 'setup', | |
internal: true, | |
actions: ['spawnPageSupervisors'] | |
} | |
} | |
}, | |
active: { | |
entry: ['notifySupervisors'], | |
type: 'final', | |
on: { | |
BROWSER_ACTION_CLICKED: { | |
target: 'active', | |
internal: true, | |
actions: ['spawnPageSupervisors'] | |
} | |
} | |
} | |
} | |
}, | |
'plane:extension/supervisor': { | |
initial: 'idle', | |
states: { | |
idle: { | |
onEntry: ['injectPageActor'], | |
on: { | |
PAGE_ACTOR_READY: 'active' | |
} | |
}, | |
active: { | |
type: 'final' | |
} | |
} | |
}, | |
'plane:page': { | |
initial: 'idle', | |
states: { | |
idle: { | |
on: { | |
DOM_READY: 'starting' | |
} | |
}, | |
starting: { | |
entry: ['injectVisualBuilderActor'], | |
on: { | |
VISUAL_ACTOR_READY: 'active' | |
} | |
}, | |
active: { | |
entry: ['notifyPageActorReady'], | |
on: { | |
CRAWLER_BUILT: 'idle' | |
} | |
} | |
} | |
}, | |
'plane:iframe': { | |
initial: 'idle', | |
states: { | |
idle: { | |
on : { | |
REACT_READY: 'scope' | |
} | |
}, | |
scope: { | |
// 1. check that iframe can communicate | |
// 2. check for sitemap | |
// 3. automatic link discovery action | |
// 4. define extraction scope (pathsToMatch) | |
}, | |
definitions: { | |
// 1. define how many concepts have to be extracted from the page | |
// 2. name the different concepts | |
// 3. meta-data system | |
// 3.1. show meta-data | |
// 3.2. select which meta data is relevant for which concept | |
}, | |
extractions: { | |
// 1. inform page actor to go into dom spy mode | |
// 2. user selects a dom element | |
// 2.1. extract its selector path and mark it as selected | |
// 2.2. selector generation | |
// 2.2.1generate many simpler selectors with moz and chrome techniques + removal of brittle classes | |
// 2.2.2 user can refine themselves the raw selector if they want to | |
// 3. add more selectors | |
// 3.1. generalize simplifications | |
// 3.2. filter simplifications | |
// 4. name that attribute | |
// 5. check type (numeric or string?) | |
// 6. if multiple element, ask for relationship to object (1 elt per record vs all elt in each record) | |
}, | |
rules: { | |
// 1. size validation | |
// 2. automatic record splitting on large arrays | |
// 3. multi 1-1 array mapping finetuning | |
// 4. JS required | |
// 4.1 reload the page | |
// 4.2. test all extracted fields against their previous value | |
// 4.3. warn user for all attribute that has changed | |
// 5. robots.txt compliance | |
}, | |
validation: { | |
// 1. try on another page within the scope | |
// 2. define critical attributes that should always be there | |
// 3. | |
}, | |
indexing: { | |
// 1. if more than one concept, where do we put each concept | |
}, | |
} | |
} | |
} | |
}, { | |
actions: { | |
injectPageActor, | |
injectVisualBuilderActor, | |
notifyPageActorReady, | |
} | |
}); | |
/* frame.html */ | |
/* | |
<html> | |
<head> | |
<style> | |
body { | |
background: transparent; | |
overflow: hidden; | |
} | |
.shadow { | |
position: absolute; | |
background: rgba(0,0,0,0.25); | |
} | |
#focus { | |
box-sizing: border-box; | |
position: absolute; | |
border-radius: 2px; | |
border-color:#5468ff; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="top" class="shadow"></div> | |
<div id="left" class="shadow"></div> | |
<div id="bottom" class="shadow"></div> | |
<div id="right" class="shadow"></div> | |
<div id="focus"></div> | |
<script> | |
const updateBox = ({ top, left, width, height }) => { | |
document.getElementById('top').style.top = 0; | |
document.getElementById('top').style.left = 0; | |
document.getElementById('top').style.bottom = window.innerHeight - (top - 20); | |
document.getElementById('top').style.right = 0; | |
document.getElementById('left').style.top = top - 20; | |
document.getElementById('left').style.left = 0; | |
document.getElementById('left').style.height = height + 40; | |
document.getElementById('left').style.width = Math.max(0, left - 20); | |
document.getElementById('bottom').style.top = top + height + 20; | |
document.getElementById('bottom').style.left = 0; | |
document.getElementById('bottom').style.bottom = 0; | |
document.getElementById('bottom').style.right = 0; | |
document.getElementById('right').style.top = top - 20; | |
document.getElementById('right').style.right = 0; | |
document.getElementById('right').style.height = Math.max(0, height + 40); | |
document.getElementById('right').style.left = left + width + 20; | |
document.getElementById('focus').style.top = top - 20; | |
document.getElementById('focus').style.left = left - 20; | |
document.getElementById('focus').style.height = height + 40; | |
document.getElementById('focus').style.width = width + 40; | |
document.getElementById('focus').style.borderStyle = 'dashed'; | |
} | |
const selectBox = () => { | |
document.getElementById('focus').style.borderStyle = 'solid'; | |
} | |
window.addEventListener('message', (e) => { | |
if (event.data.type === 'BOX_UPDATED') { | |
updateBox(event.data.payload); | |
} | |
else if (event.data.type === 'BOX_SELECTED') { | |
selectBox() | |
} | |
}) | |
</script> | |
</body> | |
</html> | |
*/ | |
/* page.html */ | |
/* | |
<html> | |
<head> | |
<style> | |
.force-overflow { | |
height: 2048px; | |
box-sizing: border-box; | |
background-color: teal; | |
} | |
.rect1 { | |
box-sizing: border-box; | |
width: 512px; | |
height: 256px; | |
margin: 64px; | |
padding:32px; | |
display: flex; | |
flex-direction: row; | |
align-items: center; | |
justify-content: flex-start; | |
background-color: crimson; | |
} | |
.square { | |
box-sizing: border-box; | |
width: 128px; | |
height: 128px; | |
flex-shrink: 0; | |
flex-grow: 0; | |
background-color: deeppink; | |
} | |
.side { | |
box-sizing: border-box; | |
flex-grow: 1; | |
height: 100%; | |
margin-left: 32px; | |
display: flex; | |
flex-direction: column; | |
align-items: stretch; | |
justify-content: space-evenly; | |
background-color: lightpink; | |
} | |
.line { | |
height: 32px; | |
background-color: white; | |
} | |
</style> | |
</head> | |
<body class="body"> | |
<iframe id="ag-vcr-iframe" class="iframe" src="iframe.html" frameborder="0" | |
style="width: 100vw; height: 100vh; position: fixed; left:0px; top: 0px; background:transparent; z-index: 100000; pointer-events: none;" | |
allowTransparency="true"></iframe> | |
<div class="force-overflow"></div> | |
<div class="rect1"> | |
<div class="square"> | |
<button id="squareButton" class="button"> | |
click me | |
</button> | |
</div> | |
<div class="side"> | |
<div class="line"> | |
Some text | |
</div> | |
<div class="line"> | |
Some other text | |
</div> | |
</div> | |
</div> | |
<script type="text/javascript"> | |
let state = 'selection'; | |
let raf; | |
let currentTarget; | |
let bcrTop; | |
let bcrLeft; | |
let bcrBottom; | |
let bcrRight; | |
let iframeTarget = document.getElementById('ag-vcr-iframe').contentWindow; | |
document.querySelector('body').style.cursor = 'pointer'; | |
const handleBoxSelection = (e) => { | |
state = 'selected'; | |
iframeTarget.postMessage({ | |
type: 'BOX_SELECTED', | |
}, '*'); | |
} | |
document.querySelector('body').addEventListener('click', handleBoxSelection); | |
const logClassname = (e) => { | |
if (e.target !== currentTarget) { | |
console.log('new-target', e.target.className); | |
currentTarget = e.target; | |
} | |
} | |
document.querySelector('body').addEventListener('mouseover', (e) => { | |
e.stopPropagation(); | |
e.preventDefault(); | |
logClassname(e); | |
}); | |
const updateBoundingBox = () => { | |
if (!currentTarget || state !== 'selection') { | |
raf = window.requestAnimationFrame(updateBoundingBox) | |
return; | |
} | |
const bcr = currentTarget.getBoundingClientRect(); | |
if (bcrTop !== bcr.top || bcrLeft !== bcr.left || bcrBottom !== bcr.bottom || bcrRight !== bcr.bcrRight) { | |
bcrTop = bcr.top; | |
bcrLeft = bcr.left; | |
bcrBottom = bcr.bottom; | |
bcrRight = bcr.bcrRight; | |
console.log('raf-update', bcr); | |
iframeTarget.postMessage({ | |
type: 'BOX_UPDATED', | |
payload: { | |
top: bcr.top, | |
left: bcr.left, | |
height: bcr.height, | |
width: bcr.width | |
} | |
}, '*'); | |
} | |
raf = window.requestAnimationFrame(updateBoundingBox) | |
} | |
updateBoundingBox(); | |
</script> | |
</body> | |
</html> | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment