-
Notifications
You must be signed in to change notification settings - Fork 8
Project Architecture
netnet.studio is a "Single Page Application" (SPA), but unlike most modern SPA's which are developed using front end frameworks for creating SPA's (React, Vue, Angular, etc) netnet.studio is vanilla HTML/CSS/JS (...for now anyways, let's see how this develops). The literal "single page" is www/index.html, the www
folder also contains directories for css
, images
and js
. The index page's HTML structure is simply:
<section id="netnet">
<div id="nn-output"></div>
<div id="nn-window">
<canvas id="nn-bg-canvas"></canvas>
<div id="nn-menu"></div>
<div id="nn-editor"></div>
</div>
</section>
The global styles for all the above elements (as well as those which can later be instantiated like text-bubbles and widgets) can be found in www/css/styles.css as you would expect.
The parent <section id="netnet">
element contains two children, the first of which is the <div id="nn-output">
. netnet's code editor is an instance of netizen's netitor which can (optionally) render the output of it's code into an <iframe>
, this <div id="nn-output">
is the parent element for the <iframe>
.
The second child inside <section id="netnet">
is the <div id="nn-window">
, this is netnet's main "window". It contains 3 children, the <canvas id="nn-bg-canvas">
which is the windows background (the gradient that follows your cursor), the <div id="nn-menu">
the top horizontal bar which includes netnet's face (ie. the menu/dialogue system) and lastly the <div id="nn-editor"></div>
which is where the netitor instance's code editor goes.
The main JavaScript file is www/js/main.js, this file does a couple of things. First, it creates instances of the 3 main global variables:
const NNE = new Netitor({ /* configuration details */})
const NNW = new WindowManager({ /* configuration details */})
const NNM = new MenuManager({ /* configuration details */})
This is the instance of the "Netitor", which is what handles all of the code editor logic as well as the rendered output. It communicates with netnet's menu/dialogue system through it's events (see events below). For more info see the README.md in the Netitor's repository.
transitioning between layout types in order: 'welcome', 'separate-window', 'dock-left', 'dock-bottom' and 'full-screen'
This is the instance of netent's window manager. It's main concern is handling netnet.studio's layout (including updating the menu/dialogue system's layout to match the current window layout). It take's an optional options object as it's only argument, which can looks like this:
const NNW = new WindowManager({
layout: 'separate-window', // the window's default layout
opacity: 0.5 // the window's default opacity
})
Both the layout and opacity can be edited after via the instance's properties. The opacity property, which takes a number between 0 and 1 as it's value, for example: NNW.opacity = 0.5
, while the layout property takes a string, for example NNW.layout = 'separate-window'
, the other options are: 'welcome', 'separate-window', 'dock-left', 'dock-bottom' and 'full-screen'. This is also the layout order, which comes into play when using the NNW.nextLayout()
and NNW.prevLayout()
to switch between them.
The window manager also handles loading all of netnet's widgets, essentially any files it findes inside the www/js/widgets
directory. For more on widgets refer to the Creating Widgets wiki page.
The window manager also handles theme changes, both for the netitor but also for the page at large, as the windows, widgets, menu items, alerts and text bubbles inherit their foreground and background colors from the editor's theme. To update the theme use the NNW.updateTheme('theme-name')
method.
Additional documentation can be found at the top of the www/js/WindowManager.js file itself.
This is the instance of the netnet's menu/dialogue system. Though technically the entire SPA is netnet, it's presence is really in the menu/dialogue system made most apparent by the fact that this is where netnet's face lives. Clicking on netnet's face opens it's "main menu", a circular/radial menu with options set when it's initially instantiated:
const NNM = new MenuManager({
ele: '#nn-menu', // id of the element to inject the menu system into
radius: 100, // radius of the radial circle of menu options when opened
items: { // the menu's options
menuOptionName: { // the item key's should be the menu item names
path: 'path/to/icon.png', // icon to display in menu item
click: () => { /* function to call when this item is clicked */ }
},
anotherOptionName: {
path: 'path/to/icon.png',
click: () => { }
}
}
})
We can check if the menu/dialogue system is currently open by calling it's NNM.opened
(read-only) property. This will return false
when ti's closed, but when it's open it will return an object which looks like { type, content }
. This is because the menu/dialogue systems is used for more than just the "main menu", it is also where "alerts" and "textbubbles" appear. So, if the "mainmenu" is currently opened then NNM.opened.type
would return 'mainmenu'
, but if an alert or text bubble was currently opened then nm.opened.type
would return 'alert'
or 'textbubble'
. When the type is an 'alert' there is an additional nm.openedtype.sub
property which returns the alert's sub-type: 'error', 'warning' or 'information'.
netnet's face: netnet's face changes depedning on what's going on in the menu, but there will be presumably other reasons to change netnet's expression (when using widgets or going through tutorials), for this there's a NNM.getFace()
method which returns an array with the current unicode chars making up netnet's face (default is [ "◕", "◞", "◕" ]
), as well as NNM.setFace('◕', '◞', '◕')
which takes 3 required arguments (the left eye, mouth and right eye) as well as an additional (optional) fourth argument: a boolean used to determine whether or not netnet's eyes should spin around to follow the cursor (default is true
).
general menu display: there are three methods for handling general menu display, these are the NNM.fadeIn(ms)
which takes an optional argument (number for the fade-in time in milliseconds) as well as NNM.fadeIn(ms, callback)
which takes two optional arguments, the first is the fade-out time (in milliseconds) and the second is a function which gets called when the fade out is complete
NOTE: these general display methods are used to hide/show and reposition the menu when the window manager is changing it's layout, it's not very likely other parts of the code base will need to make use of these... but there there if we ever need to show/hide the menu (say for example during a tutorial where we don't want the user messing with the menu at a particular moment).
There are three methods for displaying the main menu, NNM.showMainMenu()
to open it, NNM.hideMainMenu()
to close it and NNM.toggleMenu()
which toggles between the two states (this is what gets called when netnet's face is clicked).
When there is an error in the netitor and/or when the user double clicks a piece of syntax for which netitor has some educational information about the menu will call the NNM.showAlert(t,c)
method to create an alert bubble. This method takes two required arugments, first a string specify the type of alert ('error', 'warning' or 'information') and second some "content" to display in the text bubble that will appear when the alert bubble is clicked (see the Menu Dialogue System wiki page for more info). If an alert is currently displayed (which can be checked via NNM.opened.type
as mentioned before) it can be closed by calling NNM.hideAlert()
.
A Note on Menu State Management: The menu/dialogue system includes a simple state manager which keeps track of previously queued up menu items. For example if there is an error in the editor the menu will call NNM.showAlert()
which will in tern display an alert bubble. but say the user decides to click on netnet's face to open the main menu (rather than on the alert bubble to view the message) this will replace the alert bubble with the main menu. If the user then closes the main menu, the menu/dialogue system should know to re-open the alert bubble previously displayed, this "queue" can be viewed at any moment by calling the NNM.history
property which returns an array of objects (the same type of objects returned by NNM.opened
, which looks like { type, sub, content }
). Now, because the netitor may occasionally fire more than one error at a time, when the user corrects the errors the menu/dialogue system needs a way to remove all the errors from it's queue without removing the other object types from it's history, for that there's the NNM.clearAlerts()
.
To have netnet say something we can simply call NNM.showTextBubble('hi')
and to later hide that text bubble call NNM.hideTextBubble()
. The content argument passed to NNM.showTextBubble(content)
can be an html string (ex: 'hi there, <a href="http://site.com" target="_blank">click me</a>'
) but it can also be an instance of HTMLElement
. Additionally, for more control, the content argument can be an object which contains additional parameters.
For example if we'd like to create a text bubble which draws attention to a particular line in the editor we could do this:
NNM.showTextBubble({
content: 'I like what you did here.',
highlight: 5
})
If we'd like to use a different color to highlight the line we can include an additional highlightColor
property (just make sure the color string includes transparency/alpha).
We might also want to give the user a way of responding to a question, for this there's the options
property, which is an object of key/value pairs where the key is the text for that option and the value is a function to call when the user clicks it, for example:
NNM.showTextBubble({
content: 'do you like music?',
options: {
'of course!': () => { playMusic() },
'no thnx': () => { stopMusic() }
}
})
NOTE: when no options
property is set, the menu/dialogue system will inject a default 'ok' option which will close the dialogue bubble, which is essentially the equivalent of:
NNM.showTextBubble({
content: 'do you like music?',
options: {
ok: () => { NNM.hideTextBubble() }
}
})
Keep in mind that if we create our own options
at least one of them should call NNM.hideTextBubble()
if not the user will have no way of closing the text bubble.
There's a bit more to this dialogue system, but for that you should refer to the Menu Dialogue System wiki.