├── .gitattributes
├── README.md
├── awesome-menu.js
├── db-init.js
├── db-populate.js
├── fonts
├── ABeeZee-Regular.ttf
├── OFL-ABeeZee.txt
├── OFL-Oswald.txt
├── Oswald-Regular.ttf
├── icomoon.eot
├── icomoon.svg
├── icomoon.ttf
└── icomoon.woff
├── gencode.js
├── index.php
├── licenses
├── license-taffydb.txt
└── public-domain-studygamedev.txt
├── search.js
├── section-toggle.js
├── style.css
└── taffy-min.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StudyGameDev.com
2 | This is a simple online resource database, categorized according to branch of study and then further organized by topics/skill sets under the field that one can learn. The fields are programming, writing, sound design, music production, art, 3D modelling, game design, user experience, quality assurance, production & management, marketing, finanace, and business & legal. The resources are primarily intended for United States game developers, although those who can read and speak english will find numerous resources that are of value to them. You can view the site at https://studygamedev.com/.
3 |
4 | ## Basic Usage of the Site
5 | This website is primarily a portal to other websites with information. To navigate the portal, either use the menu or the featured content links at the welcoming section of the page. The entire website is keyboard accessible with the use of tab, shift+tab, and spacebar. The keyboard shortcuts `Ctrl+Z` or `Ctrl+/` will bookmark your current navigation spot and select the menu again. Accessibility controls can help increase the font size, change the theme from dark to bright, and disable content folding for users who can use the mouse for navigation and want to navigate the website faster. Additionally, there is a search bar at the top of the website for looking for entries according to your exact string of input.
6 |
7 | ## Guide to the Files on StudyGameDev.
8 | StudyGameDev is a relatively simple website project, facilitating the use of HTML, CSS, and JavaScript. Here is a list of the file names and the general functions for each:
9 |
10 | * index.php - This is the project's "starting point." It is the page where all of the components come together. Mostly HTML.
11 | * style.css - Used to detail the website with a responsive, high contrast theme.
12 | * section-toggle.js - Script for accessing different website sections and turning them off and on.
13 | * taffy-min.js - A JS library for object-based libraries that perform faster than arrays.
14 | * db-init.js - The database of game development resources and all their entries.
15 | * db-populate.js - (Most of) the functionality for adding topics, sections, and resources to the site.
16 | * awesome-menu.js - Modular script for the A.W.E.S.O.M.E. menu, a keyboard-accessible, screen reader-tested, slide-in menu system.
17 | * gencode.js - A quick tool for generating database entries for existing sections and topics.
18 | * search.js - The code for reading and responding to user input in the search bar.
19 |
20 | ## Licenses
21 | * This version of Taffy DB is under the [MIT license](https://tldrlegal.com/license/mit-license).
22 | * The font licenses (Open Font License) for ABeeZee and Oswald are contained within the font directory.
23 |
24 | # How a Beginner Can Contribute to StudyGameDev.com
25 | ## Step One: Set Up Your Git Repository Software.
26 | I recommend that beginners download https://desktop.github.com/. Simply install it.
27 | ## Step Two: Launch the Github Desktop Software
28 | The installer should do this step for you, once it is done installing. But if it doesn't go ahead and launch it, yourself.
29 | ## Step Three: Clone the StudyGameDev.com repository to your Github Desktop software.
30 | This action is performed in a few simple steps, but depends on your familiarity with the software.
31 | 1. This step depends on whether you have used Github Desktop to work with a repository, before, or not.
32 | * If this is your first time using the software, click the button that is labelled with "Clone a repository from the Internet."
33 | * If you have used this software before and do not see this button, then open the File menu at the top of the window of the Github Desktop software and select "Clone repository." The shortcut key combination for this action is `Ctrl+Shift+O`.
34 | 2. The software will prompt you for the location of the repository. Type in `nitronova/StudyGameDev.com` and define the installation path on your hard drive where you'd like the StudyGameDev.com project files to be placed.
35 | ## Step Four: Look for a Potential Issue to Fix
36 | On this page, you can find StudyGameDev.com's current Issues under the Issues tab near the top of this website. See if there is an issue that you believe you can fix using code.
37 |
38 | If there are not any issues that you can fix with code, that's okay! You can still make a contribution to the project!
39 | 1. Think of a website where you learned information that could be included on StudyGameDev.com.
40 | 2. Copy the URL to that resource and fill out the form on StudyGameDev.com's Code Generator. This tool will generate a small piece of code that you can add to the db-init.js file. Remember to specify the category topic of this resource as well as the media type that it is in. Write a title and a brief summary of the content. Press the copy button to copy the resulting code.
41 | ## Step Five: Create a New Branch for Your Own Modifications
42 | This will group your changes so that you can later request they be made to the original version. Let's assume the above example of using the code generator to edit the db-init.js file.
43 |
44 | In your Github Desktop software, navigate to the Branch menu at the top of the screen, and select "New Branch." The keyboard shortcut for this is `Ctrl+Shift+N`. Alternatively, you can use the "Current Branch" submenu to create a new branch. You can give this branch whatever name you wish to use. You could name it something like "db New Resources," if you want a suggestion.
45 | ## Step Six: Publish the Branch to Github
46 | This will make your branch visible on the Github page for the StudyGameDev.com project.
47 | 1. Ensure that your newly created branch is selected by looking at the Current Branch dropdown menu. If your branch's name is visible, you can proceed to step two. Otherwise, use the dropdown menu to select your branch.
48 | 2. To publish the branch, click the button just to the right of the Current Branch dropdown menu that says "Publish Branch to Github".
49 | ## Step Seven: Begin Making Modifications
50 | 1. Ensure that your newly created branch is selected by looking at the Current Branch dropdown menu. If your branch's name is visible, you can proceed to step two. Otherwise, use the dropdown menu to select your branch.
51 | 2. Simply open the db-init.js file in any plaintext editor (such as Notepad or Atom, but not Microsoft Word or OpenOffice Writer, those are rich text editors!) and paste the code in a reasonable location (usually amongst similarly categorized information.
52 | 3. Github Desktop will detect your changes and show them in its user interface.
53 | 4. Remember to test your changes to ensure that your resource appears in the right place and that your changes haven't caused a glitch somewhere!
54 | You can repeat this process and end up with 2 or even more new blocks of code to be added to db-init.js.
55 | ## Step Eight: Commit and Push the Changes to Your Branch
56 | 1. Commit: To update your branch with your changes, navigate to the bottom-left corner of the window where you can see the form to commit your edits. Name your edit in the title (example: "DB Add: `Name of resource goes here`"). Add a brief description or summary of what changes you made to which files. Then click "Commit to `Name of branch`" to commit the changes.
57 | 2. Push: Next to the Current Branch dropdown menu, the button has changed to be labelled with "Push Origin." Press this button. Your branch is now a newer version of the project than the original.
58 | ## Step Nine: Perform a Pull Request
59 | Performing a pull request means that you wish to add your changes to the original project. This has to be requested of me, nitronova, the project maintainer.
60 | 1. Navigate to the Branch menu at the top of the window of your Github Desktop software and select "Create Pull Request." The keyboard shortcut to this is `Ctrl+R`. This will open a new tab in your web browser where you can go into detail with the issues or additions you wish to fix.
61 | 2. If you are following the db-init.js example, select the label that says "New Resource for db-init.js".
62 | 3. When everything looks good, click the green button that says "Create Pull Request." You can make edits if you realize you forgot anything in particular.
63 |
64 | I will at that point review your submission. If the request is approved, we will work on merging it. For simple database additions, this is as simple as merging the pull request with the master. Once this is done, you can at that point delete the branch you have created by going back to Github Desktop, navigating to the Branch menu, and select "Delete," checking "Yes" for deleting the remote. If you forget to check it then see this [guide to deleting a branch on Github](https://help.github.com/en/articles/creating-and-deleting-branches-within-your-repository).
65 |
66 | If the original version of the site changes, make sure to keep your source code up to date. Make sure that the Current Branch is selecting the master and press Fetch Origin. This may check the github project for updates. If there are, press the button again (it should say "Pull Origin," this time). You are ready to begin making another change. Begin again at Step Four for new updates.
67 |
--------------------------------------------------------------------------------
/awesome-menu.js:
--------------------------------------------------------------------------------
1 | /*
2 | I hereby release this code into the public domain.
3 | Let it be freely used and improved by all.
4 | I highly recommend that forks of this project,
5 | should they be licensed, be licensed with a
6 | libre license for free software.
7 |
8 | Important point to make: If you use this, and something breaks, yeah that sucks.
9 | But I will legally have to say: This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 | Please seek the consultation of a professional developer before using foreign code.
12 |
13 | I based a lot of my code from these two projects:
14 | https://codepen.io/marcysutton/pen/JoQqVw
15 | https://codepen.io/rjmccollam/pen/ZYNEXd
16 | */
17 | var oAwesomeMenu = {};
18 | oAwesomeMenu.fMenuApplication = ( function()
19 | {
20 | var bIsFirefox = typeof InstallTrigger !== "undefined",
21 | hLastFocusedID = null;//document.getElementById( "text-site-title" ).children[ 0 ],
22 | bShortcutToSearch = false;
23 | bAccessibilityDialogOpen = false,
24 | kTab = { 9: 1 };
25 | kMenuShortcut = { 90: 1, 191: 1 }, // Z and slash
26 | kSearchShortcut = { 88: 1, 190: 1 } // X and .
27 | kScroll = { 32: 1, 37: 1, 38: 1, 39: 1, 40: 1 }, // ARROWS
28 | kMouse = { 0: 1, 1: 1, 2: 1 },
29 | hMainMenuButton = document.getElementById( "button-main-menu" ),
30 | hMainMenuWrapper = document.getElementById( "wrapper-main-menu" ),
31 | aByClassButtonToggleSubmenu = hMainMenuWrapper.getElementsByClassName( "button-toggle-submenu" ),
32 | hBlackBackground = document.getElementById( "background-black-alpha" ),
33 | hAccessibilityDialogButton = document.getElementById( "button-settings-accessibility" ),
34 | hAccessibilityDialog = document.getElementById( "wrapper-settings-accessibility" );
35 |
36 |
37 | /* Set Cookie Settings: 1. dark/bright, 2. default/larger/largest 3. yes/no to unfolding */
38 | function fSetCookie( sTheme, sFontSize, sUnfoldContent, nLifespanDays, sValidDomain )
39 | {
40 | // https://www.thesitewizard.com/javascripts/cookies.shtml (Thank youuu)
41 | var sDomain = sValidDomain ? ( "; domain=" + sValidDomain ) : '';
42 | document.cookie = "theme" + "=" + encodeURIComponent( sTheme ) +
43 | "; max-age=" + 60 * 60 * 24 * nLifespanDays +
44 | "; path=/" + sDomain ;
45 | document.cookie = "fontsize" + "=" + encodeURIComponent( sFontSize ) +
46 | "; max-age=" + 60 * 60 * 24 * nLifespanDays +
47 | "; path=/" + sDomain ;
48 | document.cookie = "showcontent" + "=" + encodeURIComponent( sUnfoldContent ) +
49 | "; max-age=" + 60 * 60 * 24 * nLifespanDays +
50 | "; path=/" + sDomain ;
51 | }
52 |
53 |
54 | /* Get Cookie once it has been set. */
55 | function fGetCookie( sCookieName )
56 | {
57 | // https://www.thesitewizard.com/javascripts/cookies.shtml
58 | var sCookie = document.cookie;
59 | if( sCookie.length != 0 )
60 | {
61 | var aCookie = sCookie.split( '; ' );
62 | for( let nCookieIndex = 0; nCookieIndex < aCookie.length; nCookieIndex++ )
63 | {
64 | let aCookieValue = aCookie[ nCookieIndex ].match( sCookieName + '=(.*)' );
65 | if( aCookieValue != null )
66 | {
67 | return decodeURIComponent( aCookieValue[ 1 ] ); // The value in () above.
68 | }
69 | }
70 | }
71 | return '';
72 | }
73 |
74 |
75 | /// Site settings, set by cookies and forms.
76 | function fApplySettings( sSettingTheme, sSettingFontSize, sSettingShowContent )
77 | {
78 | document.body.className = sSettingTheme;
79 | document.getElementsByTagName( "html" )[ 0 ].className = sSettingFontSize;
80 | document.getElementById( "setting-unfolding-marker" ).className = sSettingShowContent;
81 | }
82 |
83 |
84 | /// Focus after a small timer.
85 | function fDelayFocusOnElement( hElement )
86 | {
87 | setTimeout( function(){ hElement.focus(); }, 100 ); // For readability.
88 | }
89 |
90 |
91 | /// Scroll Disabling (mouse)
92 | function preventDefault( hEvent )
93 | {
94 | hEvent = hEvent || window.event;
95 | if( hEvent.preventDefault )
96 | {
97 | hEvent.preventDefault();
98 | }
99 | hEvent.returnValue = false;
100 | }
101 | function fPreventScrollKeys( hEvent )
102 | { /// Keyboard
103 | if( kScroll[ hEvent.keyCode ] )
104 | {
105 | preventDefault( hEvent );
106 | return false;
107 | }
108 | }
109 | function fEnableScroll()
110 | { /// Re-enabling scroll
111 | document.onkeydown = null;
112 | }
113 | function fDisableScroll()
114 | { /// Re-enabling keys
115 | document.onkeydown = fPreventScrollKeys;
116 | }
117 |
118 |
119 | /// Tabindex and ARIA-Hidden
120 | function fTabScreenReaderOn( hElement )
121 | {
122 | hElement.removeAttribute( "tabindex" );
123 | hElement.removeAttribute( "aria-hidden" );
124 | }
125 | function fTabScreenReaderOff( hElement )
126 | {
127 | hElement.setAttribute( "tabindex", "-1" );
128 | hElement.setAttribute( "aria-hidden", "true" );
129 | }
130 | function fSubmenuTabScreenOn( hElement )
131 | {
132 | for( let nSubmenuChildrenIndex = 0; nSubmenuChildrenIndex < hElement.children.length; nSubmenuChildrenIndex++ )
133 | {
134 | fTabScreenReaderOn( hElement.children[ nSubmenuChildrenIndex ].children[ 0 ] );
135 | }
136 | }
137 | function fSubmenuTabScreenOff( hElement )
138 | {
139 | for( let nSubmenuChildrenIndex = 0; nSubmenuChildrenIndex < hElement.children.length; nSubmenuChildrenIndex++ )
140 | {
141 | fTabScreenReaderOff( hElement.children[ nSubmenuChildrenIndex ].children[ 0 ] );
142 | }
143 | }
144 | function fAllTabScreenOn()
145 | { // Links lose their tabIndex="-1" thing.
146 | fTabScreenReaderOn( hMainMenuWrapper );
147 | let aMainMenuChildren = hMainMenuWrapper.children[ 1 ].children;
148 | for( let nMenuChildrenIndex = 0; nMenuChildrenIndex < aMainMenuChildren.length; nMenuChildrenIndex++ )
149 | {
150 | let hButton = aMainMenuChildren[ nMenuChildrenIndex ].children[ 0 ];
151 | fTabScreenReaderOn( hButton );
152 | if( aMainMenuChildren[ nMenuChildrenIndex ].classList.contains( "wrapper-submenu-parent" ) )
153 | {
154 | let hSiblingList = hButton.nextElementSibling;
155 | fSubmenuTabScreenOn( hSiblingList );
156 | }
157 | }
158 | }
159 | function fAllTabScreenOff()
160 | { // Links lose their tabIndex="-1" thing.
161 | fTabScreenReaderOff( hMainMenuWrapper );
162 | var aMainMenuChildren = hMainMenuWrapper.children[ 1 ].children;
163 | for( let nMenuChildrenIndex = 0; nMenuChildrenIndex < aMainMenuChildren.length; nMenuChildrenIndex++ )
164 | {
165 | var hButton = aMainMenuChildren[ nMenuChildrenIndex ].children[ 0 ];
166 | fTabScreenReaderOff( hButton );
167 | if( aMainMenuChildren[ nMenuChildrenIndex ].classList.contains( "wrapper-submenu-parent" ) )
168 | {
169 | let hSiblingList = hButton.nextElementSibling;
170 | fSubmenuTabScreenOff( hSiblingList );
171 | }
172 | }
173 | }
174 |
175 | /// Opening/Closing Navigation
176 | function fMoveMenuButtonToOpen()
177 | {
178 | document.getElementById( "wrapper-button-menu-closed" ).removeChild( hMainMenuButton );
179 | document.getElementById( "wrapper-button-menu-open" ).appendChild( hMainMenuButton );
180 | }
181 | function fMoveMenuButtonToClosed()
182 | {
183 | document.getElementById( "wrapper-button-menu-open" ).removeChild( hMainMenuButton );
184 | document.getElementById( "wrapper-button-menu-closed" ).appendChild( hMainMenuButton );
185 | }
186 | function fOpenMainMenu()
187 | {
188 | hBlackBackground.className = "state-open";
189 | hMainMenuButton.className = "state-open";
190 | hMainMenuWrapper.className = "state-open";
191 | hMainMenuButton.setAttribute( "aria-label", "Close the menu " );
192 | fDisableScroll();
193 | fAllTabScreenOn();
194 | fDelayFocusOnElement( hMainMenuWrapper.children[ 1 ].children[ 0 ].children[ 0 ] );
195 | fMoveMenuButtonToOpen();
196 | hMainMenuWrapper.children[ 1 ].children[ 0 ].children[ 0 ].focus();
197 | }
198 | function fCloseMainMenu()
199 | {
200 | if( bAccessibilityDialogOpen )
201 | {
202 | return;
203 | }
204 | let aSlicedMenuOpenChildren = Array.prototype.slice.call( hMainMenuWrapper.getElementsByClassName( "state-open" ) );
205 | for( let hMenuOpenChild of aSlicedMenuOpenChildren )
206 | {
207 | hMenuOpenChild.classList.remove( "state-open" );
208 | }
209 | hMainMenuWrapper.classList.remove( "state-open" );
210 | hBlackBackground.classList.remove( "state-open" );
211 | hMainMenuButton.classList.remove( "state-open" );
212 | hMainMenuButton.setAttribute( "aria-label", "Open the menu " );
213 | fEnableScroll();
214 | fAllTabScreenOff();
215 | fMoveMenuButtonToClosed();
216 | }
217 |
218 |
219 | /// Is child a descendent of parent?
220 | function fIsDescendent( hParent, hChild )
221 | {
222 | let hNode = hChild.parentNode;
223 | while( hNode != null )
224 | {
225 | if( hNode == hParent )
226 | {
227 | return true;
228 | }
229 | hNode = hNode.parentNode;
230 | }
231 | return false;
232 | }
233 |
234 |
235 | /// Are we focused on something in the menu?
236 | function fIsNavFocused()
237 | {
238 | return fIsDescendent( hMainMenuWrapper, document.activeElement );
239 | }
240 |
241 |
242 | /// Wait, you mean INNERTEXT ISN'T CROSS-BROWSER, YET?!
243 | function fRetrieveText( hElement )
244 | {
245 | if( typeof hElement.textContent != null )
246 | {
247 | return hElement.textContent;
248 | }
249 | return hElement.innerText;
250 | }
251 |
252 |
253 | /// Toggle Opening/Closing of Submenus
254 | function fOpenSubmenu( hSubmenu )
255 | {
256 | hSubmenu.classList.add( "state-open" );
257 | fSubmenuTabScreenOn( hSubmenu.nextElementSibling );
258 | let hFirstSubmenuItem = hSubmenu.nextElementSibling.children[ 0 ].children[ 0 ];
259 | hFirstSubmenuItem.focus();
260 | }
261 | function fCloseSubmenu( hSubmenu )
262 | {
263 | hSubmenu.classList.remove( "state-open" );
264 | fSubmenuTabScreenOff( hSubmenu.nextElementSibling );
265 | }
266 | function fCloseAllSubmenus()
267 | {
268 | let aSlicedSubmenuButtons = Array.prototype.slice.call( aByClassButtonToggleSubmenu );
269 | for( let hSubmenuButton of aSlicedSubmenuButtons )
270 | {
271 | if( fIsDescendent( hSubmenuButton.parentNode, document.activeElement ) == false )
272 | {
273 | fCloseSubmenu( hSubmenuButton );
274 | }
275 | }
276 | }
277 | function fToggleSubmenu( hSubmenu )
278 | {
279 | if( hSubmenu.classList.contains( "state-open" ) )
280 | {
281 | fCloseSubmenu( hSubmenu );
282 | hSubmenu.setAttribute( "aria-label", "Open the " + fRetrieveText( hSubmenu ) + " sub-menu" );
283 | }
284 | else
285 | {
286 | fOpenSubmenu( hSubmenu );
287 | hSubmenu.setAttribute( "aria-label", "Close the " + fRetrieveText( hSubmenu ) + " sub-menu" );
288 | }
289 | }
290 |
291 |
292 | /// Events
293 | function fEventClickSubmenu()
294 | {
295 | fCloseAllSubmenus();
296 | fToggleSubmenu( this );
297 | }
298 | function fEventClickMainMenuButton( hEvent )
299 | {
300 | if( this.className == "state-open" )
301 | {
302 | fCloseMainMenu();
303 | if( hLastFocusedID != null )
304 | {
305 | hLastFocusedID.focus();
306 | hLastFocusedID == null;
307 | }
308 | }
309 | else
310 | {
311 | fOpenMainMenu();
312 | }
313 | }
314 |
315 |
316 | function fCloseAccessibilityDialog()
317 | {
318 | hBlackBackground.classList.remove( "state-accessibility-settings" );
319 | hAccessibilityDialog.classList.remove( "state-open" );
320 | fTabScreenReaderOff( hAccessibilityDialog );
321 | // Do the same for all selects and buttons in hAccessibilityDialog
322 | let aByTagNameButton = hAccessibilityDialog.getElementsByTagName( "button" );
323 | let aByTagNameSelect = hAccessibilityDialog.getElementsByTagName( "select" );
324 | aByTagNameButton = Array.prototype.slice.call( aByTagNameButton );
325 | aByTagNameSelect = Array.prototype.slice.call( aByTagNameSelect );
326 |
327 | for( let hButtonArrayIndex = 0; hButtonArrayIndex < aByTagNameButton.length; hButtonArrayIndex++ )
328 | {
329 | fTabScreenReaderOff( aByTagNameButton[ hButtonArrayIndex ] );
330 | }
331 | for( let hSelectArrayIndex = 0; hSelectArrayIndex < aByTagNameSelect.length; hSelectArrayIndex++ )
332 | {
333 | fTabScreenReaderOff( aByTagNameSelect[ hSelectArrayIndex ] );
334 | }
335 | fTabScreenReaderOff( document.getElementById( "setting-content-unfold" ) );
336 | hAccessibilityDialog.children[ 0 ].setAttribute( "tabindex", "-1" );
337 | bAccessibilityDialogOpen = false;
338 | hAccessibilityDialogButton.focus();
339 | }
340 |
341 |
342 | function fEventKeyUpGlobal( hEvent )
343 | {
344 | if( bAccessibilityDialogOpen )
345 | {
346 | if( fIsDescendent( hAccessibilityDialog, document.activeElement ) == false )
347 | {
348 | fCloseAccessibilityDialog();
349 | }
350 | }
351 | else if( fIsNavFocused() )
352 | {
353 | fCloseAllSubmenus(); // Automatically close non-focused submenus.
354 | }
355 | else if( document.activeElement != hMainMenuButton )
356 | {
357 | if( hMainMenuWrapper.className == "state-open" )
358 | {
359 | fCloseMainMenu();
360 | if( hLastFocusedID != null )
361 | {
362 | hLastFocusedID.focus();
363 | hLastFocusedID == null;
364 | }
365 | }
366 | else
367 | {
368 | if( kTab[ hEvent.keyCode ] )
369 | {
370 | if( hEvent.shiftKey == false )
371 | {
372 | if( bShortcutToSearch )
373 | {
374 | if( hLastFocusedID != null )
375 | {
376 | hLastFocusedID.focus();
377 | bShortcutToSearch = false;
378 | hLastFocusedID == null;
379 | }
380 | }
381 | }
382 | if( hLastFocusedID != null )
383 | {
384 | if( document.activeElement == document.getElementById( "line-input-search" ) )
385 | {
386 | bShortcutToSearch = true;
387 | }
388 | else
389 | {
390 | bShortcutToSearch = false;
391 | }
392 | }
393 | }
394 | else if( hEvent.ctrlKey && kMenuShortcut[ hEvent.keyCode ] )
395 | {
396 | let sActiveNodeName = document.activeElement.nodeName;
397 | if( sActiveNodeName != "INPUT" && sActiveNodeName != "TEXTAREA" )
398 | {
399 | hLastFocusedID = document.activeElement;
400 | hMainMenuButton.focus();
401 | }
402 | }
403 | else if( hEvent.ctrlKey && kSearchShortcut[ hEvent.keyCode ] )
404 | {
405 | hLastFocusedID = document.activeElement;
406 | bShortcutToSearch = true;
407 | document.getElementById( "line-input-search" ).focus();
408 | }
409 | }
410 | }
411 | }
412 | function fEventBlurLastMenuItem()
413 | {
414 | var hLastMenuItem = this;
415 | setTimeout( function()
416 | {
417 | if( fIsNavFocused() == false )
418 | {
419 | if( hLastMenuItem.classList.contains( "state-open" ) == false )
420 | {
421 | fCloseMainMenu();
422 | hLastFocusedID.focus();
423 | }
424 | }
425 | }, 1 );
426 | }
427 |
428 | function fEventBlurAccessibility( hEvent )
429 | {
430 | setTimeout( function()
431 | {
432 | if( fIsDescendent( hAccessibilityDialog, document.activeElement ) == false )
433 | {
434 | if( document.activeElement == document.body )
435 | { // The mouse was probably clicked.
436 | return;
437 | }
438 | fCloseAccessibilityDialog();
439 | }
440 | }, 100 );
441 | }
442 | function fEventClickBlackBackground()
443 | {
444 | if( this.classList.contains( "state-accessibility-settings" ) )
445 | {
446 | fCloseAccessibilityDialog();
447 | }
448 | else if( hMainMenuWrapper.className == "state-open" )
449 | {
450 | fCloseMainMenu();
451 | }
452 | }
453 | function fEventClickAccessibilityDialogButton()
454 | {
455 | hAccessibilityDialog.classList.add( "state-open" );
456 | hBlackBackground.classList.add( "state-accessibility-settings" );
457 | fTabScreenReaderOn( hAccessibilityDialog );
458 | let aByTagNameButton = hAccessibilityDialog.getElementsByTagName( "button" );
459 | aByTagNameButton = Array.prototype.slice.call( aByTagNameButton );
460 | let aByTagNameSelect = hAccessibilityDialog.getElementsByTagName( "select" );
461 | aByTagNameSelect = Array.prototype.slice.call( aByTagNameSelect );
462 | for( let hButtonArrayIndex = 0; hButtonArrayIndex < aByTagNameButton.length; hButtonArrayIndex++ )
463 | {
464 | fTabScreenReaderOn( aByTagNameButton[ hButtonArrayIndex ] );
465 | }
466 | for( let hSelectArrayIndex = 0; hSelectArrayIndex < aByTagNameSelect.length; hSelectArrayIndex++ )
467 | {
468 | fTabScreenReaderOn( aByTagNameSelect[ hSelectArrayIndex ] );
469 | }
470 | TabScreenReaderOn( document.getElementById( "setting-content-unfold" ) );
471 | hAccessibilityDialog.children[ 0 ].setAttribute( "tabindex", "0" );
472 | // Do the same for all selects and buttons in hAccessibilityDialog
473 | bAccessibilityDialogOpen = true;
474 | hAccessibilityDialog.children[ 0 ].focus();
475 | }
476 |
477 | function fEventClickAccessibilitySaveButton()
478 | {
479 | let hUnfoldCheckbox = document.querySelector( "#setting-content-unfold:checked" );
480 | let sUnfoldSetting = "null";
481 | if( hUnfoldCheckbox != null )
482 | {
483 | sUnfoldSetting = hUnfoldCheckbox.value;
484 | }
485 | var aOptions = [
486 | document.getElementById( "setting-theme" ).value,
487 | document.getElementById( "setting-font-size" ).value,
488 | sUnfoldSetting
489 | ];
490 | fApplySettings( aOptions[ 0 ], aOptions[ 1 ], aOptions[ 2 ] );
491 | fSetCookie( aOptions[ 0 ], aOptions[ 1 ], aOptions[ 2 ], 30, "studygamedev.com" );
492 | }
493 |
494 | function fEventKeyDownOnButton( hEvent )
495 | {
496 | if( hEvent.keyCode == 32 )
497 | {
498 | this.click();
499 | }
500 | }
501 |
502 | /// Custom function for when the menu items are clicked in studygamedev.
503 | function fHandleMenuClick( hEvent )
504 | {
505 | ///
506 | let aByClassTopic = document.getElementsByClassName( "topic" );
507 | let sToAttribute = hEvent.target.getAttribute( "to" );
508 | // In this part, we set all of the attributes appropriately.
509 | fRemoveResourceNodes(); // Clear out nodes we are no longer using (reduce RAM usage)
510 | fPopulateViaMenu( sToAttribute );
511 |
512 | var iTimer = setTimeout( function()
513 | {
514 | var hTopic = document.getElementById( sToAttribute );
515 | if( hMainMenuWrapper.className == "state-open" )
516 | {
517 | fCloseMainMenu();
518 | }
519 | document.getElementById( "wrapper-home-page-content" ).className = "state-hidden";
520 | let hTopicChapterButton = hTopic.getElementsByTagName( "button" )[ 0 ];
521 | hTopicChapterButton.focus();
522 |
523 | }, 26 ); // 5 to give enough of a buffer
524 | }
525 |
526 | function fInitApplication()
527 | { // Bind actions to functions.
528 | document.body.focus();
529 | hMainMenuButton.addEventListener( "click", fEventClickMainMenuButton, false );
530 | hMainMenuButton.setAttribute( "aria-label", "Open the menu" );
531 | let aSlicedSubmenuButtons = Array.prototype.slice.call( aByClassButtonToggleSubmenu );
532 |
533 | for( let hSubmenuButton = 0; hSubmenuButton < aSlicedSubmenuButtons.length; hSubmenuButton++ )
534 | {
535 | aSlicedSubmenuButtons[ hSubmenuButton ].addEventListener( "click", fEventClickSubmenu, false );
536 | aSlicedSubmenuButtons[ hSubmenuButton ].setAttribute( "aria-label", "Open the " +
537 | fRetrieveText( aSlicedSubmenuButtons[ hSubmenuButton ] ) + " sub-menu" );
538 | }
539 |
540 | let hLastMenuItem = hMainMenuWrapper.children[ 1 ].children;
541 | hLastMenuItem = hLastMenuItem[ hLastMenuItem.length - 1 ].children[ 0 ];
542 | hLastMenuItem.addEventListener( "blur", fEventBlurLastMenuItem, false ); // button or a
543 | if( hLastMenuItem.parentNode.classList.contains("wrapper-submenu-parent") )
544 | {
545 | hLastMenuItem = hLastMenuItem.nextElementSibling.children;
546 | hLastMenuItem = hLastMenuItem[ hLastMenuItem.length - 1 ].children[0]; // ul ul a
547 | hLastMenuItem.addEventListener( "blur", fEventBlurLastMenuItem, false );
548 | }
549 |
550 | hBlackBackground.addEventListener( "click", fEventClickBlackBackground, false );
551 | document.body.addEventListener( "keyup", fEventKeyUpGlobal, false );
552 |
553 | fAllTabScreenOff();
554 |
555 | hAccessibilityDialogButton.addEventListener( "click", fEventClickAccessibilityDialogButton, false );
556 | document.getElementById( "button-settings-accessibility-close" ).addEventListener( "click", fCloseAccessibilityDialog, false );
557 | document.getElementById( "button-settings-accessibility-close" ).addEventListener( "blur", fEventBlurAccessibility, false );
558 | hAccessibilityDialog.children[ 0 ].addEventListener( "blur", fEventBlurAccessibility, false );
559 | document.getElementById( "button-settings-accessibility-save" ).addEventListener( "click", fEventClickAccessibilitySaveButton, false );
560 |
561 | if( bIsFirefox == false )
562 | {
563 | let aByTagNameButton = document.getElementsByTagName( "button" );
564 | for( let nButtonIndex = 0; nButtonIndex < aByTagNameButton.length; nButtonIndex++ )
565 | {
566 | aByTagNameButton[ nButtonIndex ].addEventListener( "keyup", fEventKeyDownOnButton, false );
567 | }
568 | }
569 | let aMenuAnchors = document.querySelectorAll(".wrapper-submenu-parent a");
570 | for( let nMenuAnchorIndex = 0; nMenuAnchorIndex < aMenuAnchors.length; nMenuAnchorIndex++ )
571 | {
572 | aMenuAnchors[ nMenuAnchorIndex ].addEventListener( "click", fHandleMenuClick, false );
573 | aMenuAnchors[ nMenuAnchorIndex ].addEventListener( "keyup", fEventKeyDownOnButton, false );
574 | }
575 | let aRecommendedTopicLinks = document.querySelectorAll("#text-recommended-topics a");
576 | for( let nRecommendedIndex = 0; nRecommendedIndex < aRecommendedTopicLinks.length; nRecommendedIndex++ )
577 | {
578 | aRecommendedTopicLinks[ nRecommendedIndex ].addEventListener( "click", fHandleMenuClick, false );
579 | aRecommendedTopicLinks[ nRecommendedIndex ].addEventListener( "keyup", fEventKeyDownOnButton, false );
580 | }
581 | fCloseAccessibilityDialog();
582 | fApplySettings( fGetCookie("theme"), fGetCookie("fontsize"), fGetCookie("showcontent") );
583 | }
584 |
585 |
586 | return {
587 | init: function()
588 | {
589 | fInitApplication();
590 | }
591 | };
592 | } )();
593 |
594 | window.addEventListener( "load", function()
595 | {
596 | new oAwesomeMenu.fMenuApplication.init();
597 | });
598 |
--------------------------------------------------------------------------------
/db-populate.js:
--------------------------------------------------------------------------------
1 | /* dLinks-populate.js
2 | This script provides controls for populating the content in the site.
3 | Other parts only need to refer to the functions provided here.
4 | */
5 |
6 | /// This global variable is dual-purposed for removing nodes and adding them.
7 | var dResourceStack = TAFFY([]); // Stacks made from queries of other databases.
8 | var bInterrupt = false; // Interruption flag for canceling async operations.
9 | var nCurrentTask = 0;
10 | var nTotalTasks = 0;
11 | var nBarWidthPercentage = 0; // For drawing the loading bar.
12 |
13 | /* Remove node and its children. */
14 | function fRemoveElement( hElement )
15 | {
16 | let hRange = document.createRange();
17 | hRange.selectNodeContents( hElement );
18 | hRange.deleteContents();
19 | hRange = null;
20 | let hParentElement = hElement.parentNode;
21 | hParentElement.removeChild( hElement );
22 | hParentElement = null;
23 | }
24 |
25 | /* Erase contents of stack database. */
26 | function fClearDResourceStack()
27 | {
28 | dResourceStack().remove();
29 | }
30 |
31 | /* Appends a resource link from the stack to the website. */
32 | function fAddNewResource()
33 | {
34 | let hDiv = document.createElement( "div" );
35 | let hTag = document.createElement( "span" );
36 | let hLink = document.createElement( "a" );
37 | let hDetails = document.createElement( "span" );
38 | let oResource = dResourceStack().first();
39 |
40 | // Containing div that acts similar to details.
41 | hDiv.setAttribute( "aria-label", "Expand description" );
42 | hDiv.setAttribute( "tabindex", "0" );
43 | hDiv.className = "resource";
44 | hDiv.addEventListener( "keyup", fHandleKeyUpResource, false );
45 | hDiv.addEventListener( "click", fHandleClickResource, false );
46 | // The tag in each summary.
47 | hTag.classList.add( "tag" );
48 | hTag.classList.add( oResource.sTagColor );
49 | hTag.appendChild( document.createTextNode( oResource.sTag ) );
50 | // The link in the summary.
51 | hLink.href = oResource.sURL;
52 | hLink.setAttribute( "target", "_BLANK" );
53 | hLink.className = "icon-external-link";
54 | hLink.appendChild( document.createTextNode( oResource.sSummary ) );
55 | // The description.
56 | hDetails.className = "resource-details";
57 | hDetails.appendChild( document.createTextNode( oResource.sDetails ) );
58 | // Assemble it.
59 | hDiv.appendChild( hTag );
60 | hDiv.appendChild( hLink );
61 | hDiv.appendChild( hDetails );
62 |
63 | document.getElementById( oResource.sSection ).appendChild( hDiv );
64 |
65 | dResourceStack().limit( 1 ).remove();
66 | fUpdateLoadingBar();
67 | }
68 |
69 | /* Creates and returns a new empty chapter */
70 | function fCreateNewChapter( sChapterID, sChapterName )
71 | {
72 | let hDiv = document.createElement( "div" );
73 | let hButton = document.createElement( "button" );
74 | let hSection = document.createElement( "section" );
75 |
76 | hDiv.className = "chapter";
77 |
78 | hButton.className = "button-chapter-name";
79 | hButton.setAttribute( "type", "button" );
80 | hButton.setAttribute( "aria-label", "Expand description" );
81 | hButton.appendChild( document.createTextNode( sChapterName ) );
82 |
83 | hSection.id = sChapterID;
84 | // Assemble it.
85 | hDiv.appendChild( hButton );
86 | hDiv.appendChild( hSection );
87 |
88 | return hDiv;
89 | }
90 |
91 | /* Adds a child to a topic in response to a menu click. */
92 | function fAddChapterFromMenu( hTopic, nChapterArrayIndex, aChapters )
93 | {
94 | let sChapterID = aChapters[ nChapterArrayIndex ][ 0 ];
95 | let sChapterName = aChapters[ nChapterArrayIndex ][ 1 ];
96 | let hChapter = fCreateNewChapter( sChapterID, sChapterName );
97 |
98 | hTopic.appendChild( hChapter );
99 | return nChapterArrayIndex + 1;
100 | }
101 |
102 | /* Creates, appends to site, and returns a topic element. */
103 | function fCreateAndAddNewTopic( sTopicID, sTopicName )
104 | {
105 | let hDiv = document.createElement( "div" );
106 | let hH2 = document.createElement( "h2" );
107 |
108 | hDiv.className = "topic";
109 | hDiv.id = sTopicID;
110 |
111 | hH2.appendChild( document.createTextNode( sTopicName ) );
112 |
113 | /// Assemble.
114 | hDiv.appendChild( hH2 );
115 |
116 | document.getElementById( "wrapper-resource-content" ).appendChild( hDiv );
117 | return hDiv;
118 | }
119 |
120 | /* Someone very kind has provided a basic way to do this.
121 | https://stackoverflow.com/questions/41366438/how-to-output-to-console-in-real-time-in-javascript
122 | */
123 |
124 | /* Updates loading bar with increased amount. */
125 | function fUpdateLoadingBar()
126 | {
127 | // And to do that, we want one action, like this, repeated
128 | nBarWidthPercentage = ++nCurrentTask / nTotalTasks * 100;
129 | // And for the browser to update like so,
130 | var hLoadingBar = document.getElementById( "dynamic-loading-bar" );
131 | hLoadingBar.style.width = nBarWidthPercentage + '%';
132 | // but without processing the increase all at once.
133 | hLoadingBar.setAttribute('loading', nCurrentTask + ' / ' + nTotalTasks );
134 | if( nBarWidthPercentage == 100 )
135 | {
136 | hLoadingBar.className = 'loading-complete';
137 | }
138 | }
139 |
140 | /* This function initiates an asynchronously performed task. */
141 | function fStartAsyncTask( oParameters )
142 | {
143 | let nMillisecondsAllotted = oParameters.nMillisecondsAllotted;
144 | let nTasksAllotted = oParameters.nTasksAllotted;
145 | let nTasksPerTick = oParameters.nTasksPerTick;
146 | let nCompletedTasks = 0;
147 | let nTotalTicks = Math.ceil( nTasksAllotted / nTasksPerTick );
148 | let iCurrentAsyncTask = null;
149 | let hLoadingBar = document.getElementById( "dynamic-loading-bar" );
150 |
151 | nCurrentTask = 0;
152 | hLoadingBar.classList.remove( "loading-complete" );
153 | hLoadingBar.setAttribute( "loading", nCurrentTask + " / " + nTotalTasks );
154 |
155 | if( nTotalTicks === 0 )
156 | {
157 | return;
158 | }
159 |
160 | var fTickTask = function()
161 | {
162 | let nCompletedTasksByTickEnd = Math.min( nCompletedTasks + nTasksPerTick, nTasksAllotted );
163 | do
164 | {
165 | oParameters.fTask( nCompletedTasks++ );
166 | } while( nCompletedTasks < nCompletedTasksByTickEnd );
167 |
168 | if( nCompletedTasks >= nTasksAllotted || bInterrupt )
169 | {
170 | if( bInterrupt == false )
171 | {
172 | oParameters.fUponAsyncTaskCompletion();
173 | }
174 | clearInterval( iCurrentAsyncTask );
175 | }
176 | };
177 | // Tick once immediately, and then as many times as needed using setInterval
178 | fTickTask();
179 | if( nTotalTicks > 1 )
180 | {
181 | iCurrentAsyncTask = setInterval( fTickTask, nMillisecondsAllotted / nTotalTicks );
182 | }
183 | }
184 |
185 | /* Remove all active topics. */
186 | function fRemoveTopicNodes()
187 | {
188 | let aTopics = Array.prototype.slice.call( document.getElementsByClassName( "topic" ) );
189 |
190 | for( let nTopicsIndex = aTopics.length - 1; nTopicsIndex >= 0; nTopicsIndex-- )
191 | {
192 | fRemoveElement( aTopics[ nTopicsIndex ] );
193 | }
194 | }
195 | /* Remove all Resources */
196 | function fRemoveResourceNodes()
197 | {
198 | let aResources = Array.prototype.slice.call( document.getElementsByClassName( "resource" ) );
199 | for( let nResourcesIndex = aResources.length - 1; nResourcesIndex >= 0; nResourcesIndex-- )
200 | {
201 | fRemoveElement( aResources[ nResourcesIndex ] );
202 | }
203 | }
204 |
205 | /* Copies a record entry (an array made of a dLinks entry) to the stack. */
206 | function fCopyRecordToStack( aRecord )
207 | {
208 | dResourceStack.insert(
209 | {
210 | "sTag" : aRecord[ "sTag" ], "sTagColor" : aRecord[ "sTagColor" ],
211 | "sSection" : aRecord[ "sSection" ], "sSummary" : aRecord[ "sSummary" ],
212 | "sURL" : aRecord[ "sURL" ], "sDetails" : aRecord[ "sDetails" ]
213 | }
214 | );
215 | }
216 |
217 | /* Populate the dResourceStack object according to section (handled by menu clicks) */
218 | function fPopulateStackBySection( aSectionNames )
219 | {
220 | for( let nSectionArrayIndex = 0; nSectionArrayIndex < aSectionNames.length; nSectionArrayIndex++ )
221 | {
222 | dLinks( { "sSection" : aSectionNames[ nSectionArrayIndex ] } ).each( function( record, recordnumber )
223 | {
224 | fCopyRecordToStack( record );
225 | } );
226 | }
227 | }
228 |
229 | /* Populate dResourceStack with results from search input string. */
230 | function fPopulateStackBySearch( sSearch )
231 | {
232 | dLinks( function()
233 | {
234 | if( this.sSection.includes( sSearch ) || this.sSummary.includes( sSearch ) ||
235 | this.sDetails.includes( sSearch ) || this.sURL.includes( sSearch ) ||
236 | this.sTag.includes( sSearch ) )
237 | {
238 | return true;
239 | }
240 | return false;
241 | } ).each( function( record, recordnumber )
242 | {
243 | fCopyRecordToStack( record );
244 | } );
245 | }
246 |
247 | /* After database entries are appended to the site, we add event listeners. */
248 | function fAddAllEventListeners()
249 | {
250 | let aByClassChapter = document.getElementsByClassName( "chapter" );
251 | for( let nChapterIndex = 0; nChapterIndex < aByClassChapter.length; nChapterIndex++ )
252 | {
253 | aByClassChapter[ nChapterIndex ].addEventListener( "click", fHandleClickChapter, false );
254 | aByClassChapter[ nChapterIndex ].addEventListener( "keyup", fHandleKeyUpChapter, false );
255 | }
256 | let aByClassChapterButtonName = document.getElementsByClassName( "button-chapter-name" );
257 | for( let nButtonIndex = 0; nButtonIndex < aByClassChapterButtonName.length; nButtonIndex++ )
258 | {
259 | fSetNewARIALabel( aByClassChapterButtonName[ nButtonIndex ], "Expand" );
260 | aByClassChapterButtonName[ nButtonIndex ].addEventListener( "click", fHandleClickChapterButton, false );
261 | }
262 | }
263 |
264 | /* Retrieves an array of chapters from the queried topic. */
265 | function fGetChapterArray( sQueriedTopicID )
266 | {
267 | return dTopics( { "sTopicID" : sQueriedTopicID } ).first().aChapters;
268 | }
269 |
270 | /* Asynchronously Populates a stack and then populates a topic with the stack. */
271 | function fPopulateTopicWithStack( sQueriedTopicID )
272 | {
273 | bInterrupt = false;
274 | var aChaptersFromQuery = fGetChapterArray( sQueriedTopicID );
275 | fPopulateStackBySection( aChaptersFromQuery );
276 | nTotalTasks = dResourceStack().count();
277 |
278 | fStartAsyncTask(
279 | { // And supply a bunch of arguments in the form of an object.
280 | nMillisecondsAllotted: 25,
281 | nTasksAllotted: nTotalTasks,
282 | nTasksPerTick: 1,
283 | fTask: function() // In here we attach a function.
284 | {
285 | fAddNewResource();
286 | },
287 | fUponAsyncTaskCompletion: function()
288 | {
289 | fAddAllEventListeners();
290 | }
291 | }
292 | );
293 | }
294 |
295 | /* Handled via menu click, make a topic and append chapters to it. */
296 | function fPopulateTopicsViaSection( sQueriedTopicID )
297 | {
298 | var aChaptersFromQuery = fGetChapterArray( sQueriedTopicID );
299 | var nChaptersQueryIndex = 0;
300 | nTotalTasks = aChaptersFromQuery.length;
301 | bInterrupt = false;
302 |
303 | /// Create the topic element.
304 | let sTopicName = dTopics( { "sTopicID" : sQueriedTopicID } ).first().sTopic;
305 | var hTopic = fCreateAndAddNewTopic( sQueriedTopicID, sTopicName );
306 |
307 | fStartAsyncTask(
308 | {
309 | nMillisecondsAllotted: 25,
310 | nTasksAllotted: nTotalTasks,
311 | nTasksPerTick: 1,
312 | fTask: function()
313 | {
314 | nChaptersQueryIndex = fAddChapterFromMenu( hTopic, nChaptersQueryIndex, aChaptersFromQuery );
315 | },
316 | fUponAsyncTaskCompletion: function()
317 | {
318 | hTopic = null;
319 | fPopulateTopicWithStack( sQueriedTopicID );
320 | }
321 | }
322 | );
323 | }
324 |
325 | /* Wrapper function for the search function to create topics. */
326 | function fPrepareTopicViaSearch( sQueriedTopicID )
327 | {
328 | let aChaptersFromQuery = fGetChapterArray( sQueriedTopicID );
329 | nTotalTasks = aChaptersFromQuery.length;
330 | bInterrupt = false;
331 | //let i = 0;
332 |
333 | let sTopicName = dTopics( { "sTopicID" : sQueriedTopicID } ).first().sTopic;
334 | fCreateAndAddNewTopic( sQueriedTopicID, sTopicName );
335 | }
336 |
337 | /* Using a search term, append topics to the website. */
338 | function fPopulateTopicsViaSearch( nSectionIndex, nTopicsIndex, aSections, aTopics )
339 | {
340 | let sChapterName = "";
341 | let sCurrentSection = aSections[ nSectionIndex ];
342 | let oSelectedTopic = dTopics( function()
343 | {
344 | for( let nChaptersIndex = 0; nChaptersIndex < this.aChapters.length; nChaptersIndex++ )
345 | {
346 | if( this.aChapters[ nChaptersIndex ][ 0 ] == sCurrentSection )
347 | {
348 | sChapterName = this.aChapters[ nChaptersIndex ][ 1 ];
349 | return true;
350 | }
351 | }
352 | return false;
353 | }
354 | ).first();
355 | let bAlreadyMadeTopic = false;
356 |
357 | // This groups the topics into one place.
358 | for( let nAlreadyTopicsIndex = 0; nAlreadyTopicsIndex < aTopics.length; nAlreadyTopicsIndex++ )
359 | {
360 | if( aTopics[ nAlreadyTopicsIndex ] == oSelectedTopic.sTopic )
361 | {
362 | nAlreadyTopicsIndex = aSections.length;
363 | bAlreadyMadeTopic = true;
364 | }
365 | }
366 |
367 | if( bAlreadyMadeTopic == false )
368 | {
369 | aTopics[ nTopicsIndex ] = oSelectedTopic.sTopic;
370 | nTopicsIndex++;
371 | fPrepareTopicViaSearch( oSelectedTopic.sTopicID );
372 | }
373 | var node_chapter = fCreateNewChapter( aSections[ nSectionIndex ], sChapterName );
374 | document.getElementById( oSelectedTopic.sTopicID ).appendChild( node_chapter );
375 |
376 | return { nSectionIndexValue : nSectionIndex + 1, nTopicsIndexValue: nTopicsIndex };
377 | }
378 |
379 |
380 | /* This iterates through all of the appended db elements and removes event listeners. */
381 | function fEraseEventListeners()
382 | {
383 | let aByClassChapterButtonName = document.getElementsByClassName( "button-chapter-name" );
384 | for( let nButtonIndex = 0; nButtonIndex < aByClassChapterButtonName.length; nButtonIndex++ )
385 | {
386 | aByClassChapterButtonName[ nButtonIndex ].removeEventListener( "click", fHandleClickChapterButton, false );
387 | }
388 | let aByClassChapter = document.getElementsByClassName( "chapter" );
389 | for( let nChapterIndex = 0; nChapterIndex < aByClassChapter.length; nChapterIndex++ )
390 | {
391 | aByClassChapter[ nChapterIndex ].removeEventListener( "click", fHandleClickChapter, false );
392 | aByClassChapter[ nChapterIndex ].removeEventListener( "keyup", fHandleKeyUpChapter, false );
393 | }
394 | let aByClassTopic = document.getElementsByClassName( "topic" );
395 | for( let nTopicIndex = aByClassTopic.length - 1; nTopicIndex >= 0 ; nTopicIndex-- )
396 | {
397 | fRemoveElement( aByClassTopic[ nTopicIndex ] );
398 | }
399 | }
400 |
401 | /* Wrapper function for erasing event listeners and populating topics. */
402 | function fPopulateViaMenu( sQueriedTopicID )
403 | {
404 | bInterrupt = true; // First, cancel all existing timers.
405 | fEraseEventListeners();
406 | setTimeout( fPopulateTopicsViaSection( sQueriedTopicID ), 26 );
407 | }
408 |
--------------------------------------------------------------------------------
/fonts/ABeeZee-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/programnoir/StudyGameDev-website/b94d830b6b32cedf00cdfac16e8bf52515d2e9b8/fonts/ABeeZee-Regular.ttf
--------------------------------------------------------------------------------
/fonts/OFL-ABeeZee.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 by Anja Meiners, with Reserved Font Name 'ABeeZee'
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/fonts/OFL-Oswald.txt:
--------------------------------------------------------------------------------
1 | Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/fonts/Oswald-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/programnoir/StudyGameDev-website/b94d830b6b32cedf00cdfac16e8bf52515d2e9b8/fonts/Oswald-Regular.ttf
--------------------------------------------------------------------------------
/fonts/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/programnoir/StudyGameDev-website/b94d830b6b32cedf00cdfac16e8bf52515d2e9b8/fonts/icomoon.eot
--------------------------------------------------------------------------------
/fonts/icomoon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/programnoir/StudyGameDev-website/b94d830b6b32cedf00cdfac16e8bf52515d2e9b8/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/programnoir/StudyGameDev-website/b94d830b6b32cedf00cdfac16e8bf52515d2e9b8/fonts/icomoon.woff
--------------------------------------------------------------------------------
/gencode.js:
--------------------------------------------------------------------------------
1 | /*
2 | Init:
3 | gather all .xc IDs and h2s underneath
4 | add each as an option to #section select.
5 |
6 | Button: Make Code
7 | .background-black-alpha -- .open .gen
8 | #wrapper-code-generator -- .active, remove tabIndex="-1"
9 | focus on h1.
10 | Inputs -> Textarea
11 | keyup or value change updates textarea.
12 | Copy -> Copy
13 | copies to clipboard.
14 | Exit: Exit Make Code
15 | self remove .active, set tabIndex = -1
16 | .background-black-alpha clear of classes.
17 | focus the button again
18 | */
19 |
20 | oAwesomeMenu.fCodeGeneratorApplication = ( function()
21 | {
22 | var hCodeGeneratorWrapper = document.getElementById( "wrapper-code-generator" );
23 |
24 | /// Is child a descendent of parent?
25 |
26 | function fIsDescendent( hParent, hChild )
27 | {
28 | var hNode = hChild.parentNode;
29 | while( hNode != null )
30 | {
31 | if( hNode == hParent )
32 | {
33 | return true;
34 | }
35 | hNode = hNode.parentNode;
36 | }
37 | return false;
38 | }
39 |
40 | /// Access enablement and disablement.
41 |
42 | function fTabScreenReaderOn( hElement )
43 | {
44 | hElement.removeAttribute("tabindex");
45 | hElement.removeAttribute("aria-hidden");
46 | }
47 | function fTabScreenReaderOff( hElement )
48 | {
49 | hElement.setAttribute( "tabindex", "-1" );
50 | hElement.setAttribute( "aria-hidden", "true" );
51 | }
52 |
53 | /// Populate one of the inputs with options.
54 |
55 | function fPopulateSectionParameter()
56 | {
57 | let hSelectSection = document.getElementById( "parameter-select-section" );
58 | dTopics().each( function( record, recordNumber )
59 | {
60 | for( let nTopicArrayIndex = 0; nTopicArrayIndex < record[ "aChapters" ].length; nTopicArrayIndex++ )
61 | {
62 | let hOption = document.createElement( "option" );
63 | hOption.value = record[ "aChapters" ][ nTopicArrayIndex ][ 0 ];
64 | hOption.innerHTML = record[ "aChapters" ][ nTopicArrayIndex ][ 1 ];
65 | hSelectSection.appendChild( hOption );
66 | }
67 | }
68 | );
69 | }
70 |
71 | /// Creates a generated string of code to use in db.js.
72 |
73 | function fCreateCodeString()
74 | {
75 | let sCode = "dLinks.insert( { \"sSection\" : \"";
76 | sCode += document.getElementById( "parameter-select-section" ).value;
77 | sCode += "\", \"sTag\" : \"";
78 | var aTagValues = document.getElementById( "parameter-select-tag" ).value.split(",");
79 | sCode += aTagValues[ 1 ];
80 | sCode += "\", \"sTagColor\" : \"";
81 | sCode += aTagValues[ 0 ];
82 | sCode += "\",\n \"sURL\" : \"";
83 | sCode += document.getElementById( "parameter-resource-url" ).value;
84 | sCode += "\",\n \"sSummary\" : \"";
85 | sCode += document.getElementById( "parameter-resource-summary" ).value;
86 | sCode += "\",\n \"sDetails\" : \"";
87 | sCode += document.getElementById( "parameter-resource-details" ).value;
88 | sCode += "\"\n} );";
89 | return sCode;
90 | }
91 |
92 | /// Utilize the fCreateCodeString function to update it.
93 |
94 | function fUpdateOutputText()
95 | {
96 | document.getElementById( "text-output-code-generator" ).value = fCreateCodeString();
97 | }
98 |
99 | /// Close the code generation dialog.
100 |
101 | function fCloseCodeGenerator()
102 | {
103 | if( document.getElementById( "background-black-alpha" ).classList.contains( "state-code-generator" ) )
104 | {
105 | document.getElementById( "background-black-alpha" ).classList.remove( "state-open" );
106 | document.getElementById( "background-black-alpha" ).classList.remove( "state-code-generator" );
107 | document.getElementById( "wrapper-code-generator" ).classList.remove( "state-active" );
108 | fTabScreenReaderOff( hCodeGeneratorWrapper );
109 |
110 | let aByClassFocusableFields = hCodeGeneratorWrapper.getElementsByClassName("group-focusable-code-generator");
111 | for( var nFocusableFieldsIndex = 0; nFocusableFieldsIndex < aByClassFocusableFields.length; nFocusableFieldsIndex++ )
112 | {
113 | fTabScreenReaderOff( aByClassFocusableFields[ nFocusableFieldsIndex ] );
114 | }
115 | document.getElementById( "button-code-generator" ).focus();
116 | }
117 | }
118 |
119 | function fEventBlueCodeGenerator( hEvent )
120 | {
121 | setTimeout( function()
122 | {
123 | if( fIsDescendent( hCodeGeneratorWrapper, document.activeElement ) == false )
124 | {
125 | if( document.activeElement == document.body )
126 | { // The mouse was probably clicked.
127 | return;
128 | }
129 | fCloseCodeGenerator();
130 | }
131 | }, 100 );
132 | }
133 |
134 | /// Assign multiple event listeners at once.
135 |
136 | function fApplyEventListeners()
137 | {
138 | document.getElementById( "button-code-generator" ).addEventListener( "click", function()
139 | {
140 | document.getElementById( "background-black-alpha" ).classList.add( "state-open" );
141 | document.getElementById( "background-black-alpha" ).classList.add( "state-code-generator" );
142 | document.getElementById( "wrapper-code-generator" ).classList.add( "state-active" );
143 | fTabScreenReaderOn( hCodeGeneratorWrapper );
144 |
145 | let aByClassFocusableFields = hCodeGeneratorWrapper.getElementsByClassName("group-focusable-code-generator");
146 | for( var nFocusableFieldsIndex = 0; nFocusableFieldsIndex < aByClassFocusableFields.length; nFocusableFieldsIndex++ )
147 | {
148 | fTabScreenReaderOn( aByClassFocusableFields[ nFocusableFieldsIndex ] );
149 | }
150 | document.getElementById( "text-code-generator-title" ).setAttribute( "tabIndex", "0" );
151 | document.getElementById( "text-code-generator-title" ).focus();
152 | }, false );
153 |
154 | let aByClassLineInput = document.getElementsByClassName("form-line-input-code-generator");
155 | for( var nLineInputsIndex = 0; nLineInputsIndex < aByClassLineInput.length; nLineInputsIndex++ )
156 | {
157 | aByClassLineInput[ nLineInputsIndex ].addEventListener( "keyup", fUpdateOutputText, false );
158 | }
159 |
160 | let aByClassSelect = document.getElementsByClassName("form-select-code-generator");
161 | for( var nSelectsIndex = 0; nSelectsIndex < aByClassSelect.length; nSelectsIndex++ )
162 | {
163 | aByClassSelect[ nSelectsIndex ].addEventListener( "change", fUpdateOutputText, false );
164 | }
165 |
166 | let hCopyButton = document.getElementById('button-code-generator-copy-output');
167 | hCopyButton.addEventListener( "click", function( hEvent )
168 | { // Copy button
169 | document.getElementById( "text-output-code-generator" ).select();
170 | document.execCommand( "copy" );
171 | }
172 | );
173 |
174 | document.getElementById( "text-code-generator-title" ).addEventListener( "blur", fEventBlueCodeGenerator, false );
175 | document.getElementById( "button-code-generator-close" ).addEventListener( "blur", fEventBlueCodeGenerator, false );
176 | document.getElementById( "button-code-generator-close" ).addEventListener( "click", fCloseCodeGenerator, false );
177 | document.getElementById( "background-black-alpha" ).addEventListener( "click", fCloseCodeGenerator, false );
178 | }
179 |
180 | function fInitApplication()
181 | {
182 | fPopulateSectionParameter();
183 | fApplyEventListeners();
184 | fTabScreenReaderOff( hCodeGeneratorWrapper );
185 |
186 | let aByClassFocusableFields = hCodeGeneratorWrapper.getElementsByClassName("group-focusable-code-generator");
187 | for( let nFocusableFieldsIndex = 0; nFocusableFieldsIndex < aByClassFocusableFields.length; nFocusableFieldsIndex++ )
188 | {
189 | fTabScreenReaderOff( aByClassFocusableFields[ nFocusableFieldsIndex ] );
190 | }
191 | }
192 |
193 | return {
194 | init: function()
195 | {
196 | fInitApplication();
197 | }
198 | };
199 | })();
200 |
201 | window.addEventListener( "load", function()
202 | {
203 | new oAwesomeMenu.fCodeGeneratorApplication.init();
204 | });
205 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 |
2 |
We list resources that explain topics of game development. Under each category, we try to arrange information in an academic order. There are a lot of resources to dive into, here, so let's get you started with a few recommended subjects.
258 |
These resources are coming soon, stay tuned while we continue to populate the site.