Skip to content

Instantly share code, notes, and snippets.

@JonathanMontane
Last active February 17, 2020 20:39
Show Gist options
  • Save JonathanMontane/05aa9c50c42908bac07faf7b6c0e97bd to your computer and use it in GitHub Desktop.
Save JonathanMontane/05aa9c50c42908bac07faf7b6c0e97bd to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
// 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