Home Reference Source

src/js/githubOauth.js

  1. import Molecule from './molecules/molecule.js'
  2. import GlobalVariables from './globalvariables.js'
  3. import { licenses } from './licenseOptions.js'
  4. import { extractBomTags, convertLinks } from './BOM.js'
  5. import { OAuth } from 'oauthio-web'
  6.  
  7. /**
  8. * This function works like a class to sandbox interaction with GitHub.
  9. */
  10. export default function GitHubModule(){
  11. const { Octokit } = require("@octokit/rest")
  12. /**
  13. * The octokit instance which allows authenticated interaction with GitHub.
  14. * @type {object}
  15. */
  16. var octokit = new Octokit()
  17. /**
  18. * The HTML element which is the popup.
  19. * @type {object}
  20. */
  21. var popup = document.getElementById('projects-popup')
  22. /**
  23. * The name of the current repo.
  24. * @type {string}
  25. */
  26. var currentRepoName = null
  27. /**
  28. * The name of the currently logged in user.
  29. * @type {string}
  30. */
  31. var currentUser = null
  32. /**
  33. * The text to display at the top of the bill of materials.
  34. * @type {string}
  35. */
  36. var bomHeader = "###### Note: Do not edit this file directly, it is automatically generated from the CAD model \n# Bill Of Materials \n |Part|Number Needed|Price|Source| \n |----|----------|-----|-----|"
  37. /**
  38. * The text to display at the top of the ReadMe file.
  39. * @type {string}
  40. */
  41. var readmeHeader = "###### Note: Do not edit this file directly, it is automatically generated from the CAD model"
  42.  
  43. /**
  44. * The timer used to trigger saving of the file.
  45. * @type {object}
  46. */
  47. var intervalTimer
  48.  
  49. /**
  50. * The timer used to trigger saving of the file.
  51. * @type {object}
  52. */
  53. var page = 1
  54.  
  55. //Github pop up event listeners
  56. document.getElementById("loginButton").addEventListener("mousedown", () => {
  57. this.tryLogin()
  58. })
  59. /**
  60. * Try to login using the oauth popup.
  61. */
  62. this.tryLogin = function(){
  63. // Initialize with OAuth.io app public key
  64. if(window.location.href.includes('private')){
  65. OAuth.initialize('6CQQE8MMCBFjdWEjevnTBMCQpsw') //app public key for repo scope
  66. }
  67. else{
  68. OAuth.initialize('BYP9iFpD7aTV9SDhnalvhZ4fwD8') //app public key for public_repo scope
  69. }
  70. // Use popup for oauth
  71. OAuth.popup('github').then(github => {
  72. /**
  73. * Oktokit object to access github
  74. * @type {object}
  75. */
  76. octokit = new Octokit({
  77. auth: github.access_token
  78. })
  79. //Test the authentication
  80. octokit.users.getAuthenticated({}).then(result => {
  81. currentUser = result.data.login
  82. this.showProjectsToLoad()
  83. })
  84. })
  85. }
  86. /**
  87. * Display projects which can be loaded in the popup.
  88. */
  89. this.showProjectsToLoad = function(){
  90. //Remove everything in the popup now
  91. while (popup.firstChild) {
  92. popup.removeChild(popup.firstChild)
  93. }
  94.  
  95. //Close button (Mac style)
  96. if(GlobalVariables.topLevelMolecule && GlobalVariables.topLevelMolecule.name != "Maslow Create"){ //Only offer a close button if there is a project to go back to
  97. var closeButton = document.createElement("button")
  98. closeButton.setAttribute("class", "closeButton")
  99. closeButton.addEventListener("click", () => {
  100. popup.classList.add('off')
  101. })
  102. popup.appendChild(closeButton)
  103. }
  104. //Welcome title
  105. var welcome = document.createElement("div")
  106. welcome.setAttribute("style", " display: flex; margin: 10px; align-items: center;")
  107. popup.appendChild(welcome)
  108.  
  109. var welcome1 = document.createElement("IMG")
  110. welcome1.setAttribute("src", "/imgs/maslow-logo.png" )
  111. welcome1.setAttribute("style", " height:25px; border-radius:50%;")
  112. welcome.appendChild(welcome1)
  113. var welcome2 = document.createElement("IMG")
  114. welcome2.setAttribute("src", "/imgs/maslowcreate.svg" )
  115. welcome2.setAttribute("style", "height:20px; padding: 10px;")
  116. welcome.appendChild(welcome2)
  117. var middleBrowseDiv = document.createElement("div")
  118. if (currentUser == null){
  119.  
  120. var githubSign = document.createElement("button")
  121. githubSign.setAttribute("id", "loginButton2" )
  122. githubSign.setAttribute("class", "form browseButton githubSign")
  123. githubSign.setAttribute("style", "width: 90px; font-size: .7rem; margin-left: auto;")
  124. githubSign.textContent = "Login"
  125. welcome.appendChild(githubSign)
  126.  
  127. var githubSignUp = document.createElement("button")
  128. githubSignUp.setAttribute("class", "form browseButton githubSign")
  129. githubSignUp.setAttribute("onclick", "window.open('https://github.com/join')")
  130. githubSignUp.setAttribute("style", "width: 130px; font-size: .7rem;margin-left: 5px;")
  131. githubSignUp.textContent = "Create an account"
  132. welcome.appendChild(githubSignUp)
  133.  
  134. //Welcome title
  135. var welcome3 = document.createElement("div")
  136. welcome3.innerHTML = "Maslow Create User Projects"
  137. welcome3.setAttribute("style", "justify-content: flex-start; display: inline; width: 100%; font-size: 18px;")
  138. popup.appendChild(welcome3)
  139.  
  140. middleBrowseDiv.setAttribute("style","margin-top:25px")
  141.  
  142. githubSign.addEventListener("mousedown", () => {
  143. this.tryLogin()
  144. })
  145. }
  146. popup.classList.remove('off')
  147. popup.setAttribute("style", "padding: 0;text-align: center; background-color: #f9f6f6; border: 10px solid #3e3d3d;")
  148. var tabButtons = document.createElement("DIV")
  149. tabButtons.setAttribute("class", "tab")
  150. popup.appendChild(tabButtons)
  151. middleBrowseDiv.setAttribute("class", "middleBrowse")
  152. popup.appendChild(middleBrowseDiv)
  153.  
  154. var searchIcon = document.createElement("IMG")
  155. searchIcon.setAttribute("src", '/imgs/search_icon.svg')
  156. searchIcon.setAttribute("style", "width: 20px; float: right; color: white; position: relative;right: 3px; opacity: 0.5;")
  157. middleBrowseDiv.appendChild(searchIcon)
  158.  
  159. var searchBar = document.createElement("input")
  160. searchBar.setAttribute("type", "text")
  161. searchBar.setAttribute("contenteditable", "true")
  162. searchBar.setAttribute("placeholder", "Search for project..")
  163. searchBar.setAttribute("class", "menu_search")
  164. searchBar.setAttribute("id", "project_search")
  165. middleBrowseDiv.appendChild(searchBar)
  166.  
  167. //Display option buttons
  168. var browseDisplay1 = document.createElement("div")
  169. browseDisplay1.setAttribute("class", "browseDisplay")
  170. var listPicture = document.createElement("IMG")
  171. listPicture.setAttribute("src", '/imgs/list-with-dots.svg') //https://www.freeiconspng.com/img/1454
  172. listPicture.setAttribute("style", "height: 75%;padding: 3px;")
  173. browseDisplay1.appendChild(listPicture)
  174. middleBrowseDiv.appendChild(browseDisplay1)
  175. var browseDisplay2 = document.createElement("div")
  176. browseDisplay2.setAttribute("class", "browseDisplay active_filter")
  177. browseDisplay2.setAttribute("id", "thumb")
  178. var listPicture2 = document.createElement("IMG")
  179. listPicture2.setAttribute("src", '/imgs/thumb_icon.png')
  180. listPicture2.setAttribute("style", "height: 80%;padding: 3px;")
  181. browseDisplay2.appendChild(listPicture2)
  182. middleBrowseDiv.appendChild(browseDisplay2)
  183.  
  184. //Input to search for projects
  185.  
  186. searchBar.addEventListener('keydown', (e) => {
  187. this.loadProjectsBySearch("yoursButton",e, searchBar.value, "updated")
  188. this.loadProjectsBySearch("githubButton",e, searchBar.value, "stars") // updated just sorts content by most recently updated
  189. })
  190.  
  191. this.projectsSpaceDiv = document.createElement("DIV")
  192. this.projectsSpaceDiv.setAttribute("class", "float-left-div")
  193. this.projectsSpaceDiv.setAttribute("style", "overflow-x: hidden; margin-top: 10px;")
  194. popup.appendChild(this.projectsSpaceDiv)
  195. const pageChange = document.createElement("div")
  196. const pageBack = document.createElement("button")
  197. pageBack.setAttribute("id", "back")
  198. pageBack.setAttribute("class", "page_change")
  199. pageBack.innerHTML = "‹"
  200.  
  201. const pageForward = document.createElement("button")
  202. pageChange.appendChild(pageBack)
  203. pageChange.appendChild(pageForward)
  204. pageForward.setAttribute("id", "forward")
  205. pageForward.setAttribute("class", "page_change")
  206. pageForward.innerHTML = "›"
  207.  
  208. popup.appendChild(pageChange)
  209.  
  210. this.openTab(page)
  211.  
  212. //Event listeners
  213.  
  214. browseDisplay1.addEventListener("click", () => {
  215. // titlesDiv.style.display = "flex"
  216. browseDisplay2.classList.remove("active_filter")
  217. this.openTab(page)
  218. })
  219. browseDisplay2.addEventListener("click", () => {
  220. // titlesDiv.style.display = "none"
  221. browseDisplay2.classList.add("active_filter")
  222. this.openTab(page)
  223. })
  224. pageForward.addEventListener("click", () => {
  225. if (page >=1){ page +=1 }
  226. this.openTab(page)
  227. })
  228. pageBack.addEventListener("click", () => {
  229. if (page >1){page -=1}
  230. this.openTab(page)
  231. })
  232.  
  233. }
  234.  
  235. /**
  236. * Search for the name of a project and then return results which match that search.
  237. */
  238. this.loadProjectsBySearch = async function(tabName, ev, searchString, sorting, pageNumber, clear = true){
  239. if(ev.key == "Enter"){
  240. //Remove projects shown now
  241. if(clear){
  242. while (this.projectsSpaceDiv.firstChild) {
  243. this.projectsSpaceDiv.removeChild(this.projectsSpaceDiv.firstChild)
  244. }
  245. }
  246. // add initial projects to div
  247.  
  248. //New project div
  249. if (currentUser !== null && clear){
  250. var browseDiv = document.createElement("div")
  251. browseDiv.setAttribute("class", "browseDiv")
  252. this.projectsSpaceDiv.appendChild(browseDiv)
  253. var createNewProject = document.createElement("div")
  254. createNewProject.setAttribute("class", "newProject")
  255.  
  256. browseDiv.appendChild(createNewProject)
  257. this.NewProject("New Project", null, true, "")
  258. }
  259. //header for project list style display
  260. var titlesDiv = document.createElement("div")
  261. titlesDiv.setAttribute("id","titlesDiv")
  262. var titles = document.createElement("div")
  263. titles.innerHTML = ""
  264. titles.setAttribute("class","browseColumn")
  265. titlesDiv.appendChild(titles)
  266. var titles2 = document.createElement("div")
  267. titles2.innerHTML = "Project"
  268. titles2.setAttribute("class","browseColumn")
  269. titlesDiv.appendChild(titles2)
  270. var titles3 = document.createElement("div")
  271. titles3.innerHTML = "Creator"
  272. titles3.setAttribute("class","browseColumn")
  273. titlesDiv.appendChild(titles3)
  274. var titles4 = document.createElement("div")
  275. titles4.innerHTML = "Created on"
  276. titles4.setAttribute("class","browseColumn")
  277. titlesDiv.appendChild(titles4)
  278. var titles5 = document.createElement("div")
  279. titles5.innerHTML = "Last Modified"
  280. titles5.setAttribute("class","browseColumn")
  281. titlesDiv.appendChild(titles5)
  282.  
  283. if (!document.getElementById("thumb").classList.contains("active_filter")){
  284. titlesDiv.style.display = "flex"
  285. titlesDiv.style.marginTop = "10px"
  286. browseDiv.style.width = "100%"
  287. createNewProject.style.height = "80px"
  288. }
  289. this.projectsSpaceDiv.appendChild(titlesDiv)
  290. //Load projects
  291. var query
  292. var owned
  293.  
  294. var sortMethod = sorting //drop down input. temporarily inactive until we figure some better way to sort
  295. if(tabName == "yoursButton"){
  296. owned = true
  297. query = searchString + ' ' + 'fork:true user:' + currentUser + ' topic:maslowcreate'
  298. }
  299. else{
  300. owned = false
  301. query = searchString + ' topic:maslowcreate -user:' + currentUser
  302. }
  303. //Figure out how many repos this user has, search will throw an error if they have 0;
  304. // octokit.repos.list({
  305. // affiliation: 'owner',
  306. // })
  307. return octokit.search.repos({
  308. q: query,
  309. sort: sortMethod,
  310. per_page: 50,
  311. page: pageNumber,
  312. headers: {
  313. accept: 'application/vnd.github.mercy-preview+json'
  314. }
  315. }).then(result => {
  316. result.data.items.forEach(repo => {
  317. const thumbnailPath = "https://raw.githubusercontent.com/"+repo.full_name+"/master/project.svg?sanitize=true"
  318. this.addProject(repo.name, repo.id, repo.owner.login, repo.created_at, repo.updated_at, owned, thumbnailPath)
  319. })
  320. })
  321. }
  322. }
  323. /**
  324. * Adds a new project to the load projects display.
  325. */
  326. this.NewProject = function(projectName, id, owned, thumbnailPath){
  327. //create a project element to display
  328. var project = document.createElement("DIV")
  329. project.classList.add("newProjectdiv")
  330. var projectPicture = document.createElement("IMG")
  331. projectPicture.setAttribute("src", thumbnailPath)
  332. projectPicture.setAttribute("onerror", "this.src='/defaultThumbnail.svg'")
  333. projectPicture.setAttribute("style", "height: 80%; float: left;")
  334. project.appendChild(projectPicture)
  335. var projectText = document.createElement("span")
  336. projectText.innerHTML = "Start a new project"
  337. projectText.setAttribute("style","align-self: center")
  338. project.appendChild(projectText)
  339.  
  340. document.querySelector(".newProject").appendChild(project)
  341. project.addEventListener('click', () => {
  342. this.projectClicked(projectName, id, owned)
  343. })
  344.  
  345. }
  346. /**
  347. * Adds a new project to the load projects display.
  348. */
  349. this.addProject = function(projectName, id, owner, createdAt, updatedAt, owned, thumbnailPath){
  350. this.projectsSpaceDiv.classList.remove("float-left-div-thumb")
  351. var project = document.createElement("DIV")
  352. var projectPicture = document.createElement("IMG")
  353. projectPicture.setAttribute("src", thumbnailPath)
  354. projectPicture.setAttribute("onerror", "this.src='/defaultThumbnail.svg'")
  355. project.appendChild(projectPicture)
  356. project.setAttribute("id", projectName)
  357. project.classList.add("project")
  358.  
  359. if (owned){
  360. project.classList.add("mine")
  361. }
  362.  
  363. //create a project element to display
  364. if (document.getElementById("thumb").classList.contains("active_filter")){
  365. projectPicture.setAttribute("style", "width: 100%; height: 80%;")
  366. project.appendChild(document.createElement("BR"))
  367.  
  368. var shortProjectName
  369. if(projectName.length > 13){
  370. shortProjectName = document.createTextNode(projectName.substr(0,9)+"..")
  371. }
  372. else{
  373. shortProjectName = document.createTextNode(projectName)
  374. }
  375. project.setAttribute("title",projectName)
  376. project.appendChild(shortProjectName)
  377. }
  378. else{
  379. project.setAttribute("style", "display:flex; flex-direction:row; flex-wrap:wrap; width: 100%; border-bottom: 1px solid darkgrey;")
  380. projectPicture.setAttribute("class", "browseColumn")
  381. shortProjectName = document.createElement("DIV")
  382. shortProjectName.innerHTML = projectName
  383. shortProjectName.setAttribute("class", "browseColumn")
  384. project.appendChild(shortProjectName)
  385.  
  386. var ownerName = document.createElement("DIV")
  387. var ownerNameIn = document.createTextNode(owner)
  388. ownerName.appendChild(ownerNameIn)
  389. ownerName.setAttribute("class", "browseColumn")
  390. project.appendChild(ownerName)
  391.  
  392. var date = new Date(createdAt)
  393. var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
  394. var createdTime = document.createElement("DIV")
  395. createdTime.setAttribute("class", "browseColumn")
  396. var createdTimeIn = document.createTextNode(months[date.getMonth()] + " " + date.getFullYear())
  397. createdTime.appendChild(createdTimeIn)
  398. project.appendChild(createdTime)
  399.  
  400. var updated = new Date(updatedAt)
  401. var updatedTime = document.createElement("DIV")
  402. var updatedTimeIn = document.createTextNode(months[updated.getMonth()] + " " + date.getFullYear())
  403. updatedTime.appendChild(updatedTimeIn)
  404. updatedTime.setAttribute("class", "browseColumn")
  405. project.appendChild(updatedTime)
  406. }
  407.  
  408. this.projectsSpaceDiv.appendChild(project)
  409.  
  410. project.addEventListener('click', () => {
  411. this.projectClicked(projectName, id, owned)
  412. })
  413. }
  414. /**
  415. * Runs when you click on a project.
  416. */
  417. this.projectClicked = function(projectName, projectID, owned){
  418. //runs when you click on one of the projects
  419. if(projectName == "New Project"){
  420. this.createNewProjectPopup()
  421. }
  422. else if(owned){
  423. this.loadProject(projectName)
  424. }
  425. else{
  426. window.open('/run?'+projectID)
  427. }
  428. }
  429. /**
  430. * Runs owned search first and then full github search
  431. */
  432. this.openTab = function(page) {
  433.  
  434. // Show the current tab, and add an "active" class to the button that opened the tab
  435. //Click on the search bar so that when you start typing it shows updateCommands
  436. document.getElementById('menuInput').focus()
  437. this.loadProjectsBySearch("yoursButton", {key: "Enter"}, document.getElementById("project_search").value, "updated", page, true)
  438. .then( () => {
  439. this.loadProjectsBySearch("githubButton", {key: "Enter"}, document.getElementById("project_search").value, "stars", page, false)
  440. })
  441. }
  442. /**
  443. * The popup to create a new project (giving it a name and whatnot).
  444. */
  445. this.createNewProjectPopup = function(){
  446. //Clear the popup and populate the fields we will need to create the new repo
  447. while (popup.firstChild) {
  448. popup.removeChild(popup.firstChild)
  449. }
  450. popup.setAttribute("style", "padding:2% 0")
  451. //Project name
  452. // <div class="form">
  453. var createNewProjectDiv = document.createElement("DIV")
  454. createNewProjectDiv.setAttribute("class", "form")
  455. createNewProjectDiv.setAttribute("style", "color:whitesmoke")
  456. //Add a title
  457. var header = document.createElement("H1")
  458. var title = document.createTextNode("Create a new project")
  459. header.appendChild(title)
  460. createNewProjectDiv.appendChild(header)
  461. //Create the form object
  462. var form = document.createElement("form")
  463. form.setAttribute("class", "login-form")
  464. createNewProjectDiv.appendChild(form)
  465. //Create the name field
  466. var name = document.createElement("input")
  467. name.setAttribute("id","project-name")
  468. name.setAttribute("type","text")
  469. name.setAttribute("placeholder","Project name")
  470. form.appendChild(name)
  471. //Add the description field
  472. var description = document.createElement("input")
  473. description.setAttribute("id", "project-description")
  474. description.setAttribute("type", "text")
  475. description.setAttribute("placeholder", "Project description")
  476. form.appendChild(description)
  477. //Grab all of the available licenses
  478. var licenseOptions = document.createElement('select')
  479. licenseOptions.setAttribute("id", "license-options")
  480. Object.keys(licenses).forEach( key => {
  481. var option = document.createElement('option')
  482. option.value = key
  483. option.text = key
  484. licenseOptions.appendChild(option)
  485. })
  486. form.appendChild(licenseOptions)
  487. //Add the button
  488. var createButton = document.createElement("button")
  489. createButton.setAttribute("type", "button")
  490. createButton.setAttribute("style", "height: 50px; border: 1px solid whitesmoke;")
  491. createButton.addEventListener('click', () => {
  492. this.createNewProject()
  493. })
  494. var buttonText = document.createTextNode("Create Project")
  495. createButton.appendChild(buttonText)
  496. form.appendChild(createButton)
  497. popup.appendChild(createNewProjectDiv)
  498.  
  499. }
  500. /**
  501. * Open a new tab with a sharable copy of the project.
  502. */
  503. this.shareOpenedProject = function(){
  504. alert("A page with a shareable url to this project will open in a new window. Share the link to that page with anyone you would like to share the project with.")
  505.  
  506. octokit.repos.get({
  507. owner: currentUser,
  508. repo: currentRepoName
  509. }).then(result => {
  510. var ID = result.data.id
  511. window.open('/run?'+ID)
  512. })
  513. }
  514. /**
  515. * Open a new tab with the github page for the project.
  516. */
  517. this.openGitHubPage = function(){
  518. //Open the github page for the current project in a new tab
  519. octokit.repos.get({
  520. owner: currentUser,
  521. repo: currentRepoName
  522. }).then(result => {
  523. var url = result.data.html_url
  524. window.open(url)
  525. })
  526. }
  527. /**
  528. * Open a new tab with the README page for the project.
  529. */
  530. this.openREADMEPage = function(){
  531. //Open the github page for the current project in a new tab
  532. octokit.repos.get({
  533. owner: currentUser,
  534. repo: currentRepoName
  535. }).then(result => {
  536. var url = result.data.html_url + '/blob/master/README.md'
  537. window.open(url)
  538. })
  539. }
  540. /**
  541. * Open a new tab with the Bill Of Materials page for the project.
  542. */
  543. this.openBillOfMaterialsPage = function(){
  544. //Open the github page for the current project in a new tab
  545. octokit.repos.get({
  546. owner: currentUser,
  547. repo: currentRepoName
  548. }).then(result => {
  549. var url = result.data.html_url + '/blob/master/BillOfMaterials.md'
  550. window.open(url)
  551. })
  552. }
  553. /**
  554. * Search github for projects which match a string.
  555. */
  556. this.searchGithub = async (searchString,owned) => {
  557. //Load projects
  558. var query
  559.  
  560. if(owned){
  561. query = searchString + ' ' + 'user:' + currentUser + ' topic:maslowcreate'
  562. }
  563. else{
  564. query = searchString + ' topic:maslowcreate -user:' + currentUser
  565. }
  566.  
  567. return await octokit.search.repos({
  568. q: query,
  569. sort: 'stars',
  570. per_page: 10,
  571. page: 1,
  572. headers: {
  573. accept: 'application/vnd.github.mercy-preview+json'
  574. }
  575. })
  576. }
  577. /**
  578. * Send user to GitHub settings page to delete project.
  579. */
  580. this.deleteProject = function(){
  581. //Open the github page for the current project in a new tab
  582. octokit.repos.get({
  583. owner: currentUser,
  584. repo: currentRepoName
  585. }).then(result => {
  586. var url = result.data.html_url + '/settings'
  587. window.open(url)
  588. })
  589. }
  590.  
  591. /**
  592. * Open pull request if it's a forked project.
  593. */
  594. this.makePullRequest = function(){
  595. //Open the github page for making a pull request to the current project in a new tab
  596. octokit.repos.get({
  597. owner: currentUser,
  598. repo: currentRepoName
  599. }).then(result => {
  600. const webString = "https://github.com/" + result.data.parent.full_name + "/compare/" + result.data.parent.default_branch + "..." + result.data.owner.login + ":" + result.data.default_branch
  601. window.open(webString)
  602. })
  603. }
  604. /**
  605. * Creates a new blank project.
  606. */
  607. this.createNewProject = function(){
  608.  
  609. if(typeof intervalTimer != undefined){
  610. clearInterval(intervalTimer) //Turn of auto saving
  611. }
  612. //Get name and description
  613. const name = document.getElementById('project-name').value
  614. const description = document.getElementById('project-description').value
  615. const licenseText = licenses[document.getElementById('license-options').value]
  616. //Load a blank project
  617. GlobalVariables.topLevelMolecule = new Molecule({
  618. x: 0,
  619. y: 0,
  620. topLevel: true,
  621. name: name,
  622. atomType: "Molecule",
  623. uniqueID: GlobalVariables.generateUniqueID()
  624. })
  625. GlobalVariables.currentMolecule = GlobalVariables.topLevelMolecule
  626. //Create a new repo
  627. octokit.repos.createForAuthenticatedUser({
  628. name: name,
  629. description: description
  630. }).then(result => {
  631. //Once we have created the new repo we need to create a file within it to store the project in
  632. currentRepoName = result.data.name
  633. var jsonRepOfProject = GlobalVariables.topLevelMolecule.serialize()
  634. jsonRepOfProject.filetypeVersion = 1
  635. jsonRepOfProject.circleSegmentSize = GlobalVariables.circleSegmentSize
  636. const projectContent = window.btoa(JSON.stringify(jsonRepOfProject, null, 4))
  637. octokit.repos.createOrUpdateFileContents({
  638. owner: currentUser,
  639. repo: currentRepoName,
  640. path: "project.maslowcreate",
  641. message: "initialize repo",
  642. content: projectContent
  643. }).then(() => {
  644. //Then create the BOM file
  645. var content = window.btoa(bomHeader) // create a file with just the header in it and base64 encode it
  646. octokit.repos.createOrUpdateFileContents({
  647. owner: currentUser,
  648. repo: currentRepoName,
  649. path: "BillOfMaterials.md",
  650. message: "initialize BOM",
  651. content: content
  652. }).then(() => {
  653. //Then create the README file
  654. content = window.btoa(readmeHeader) // create a file with just the word "init" in it and base64 encode it
  655. octokit.repos.createOrUpdateFileContents({
  656. owner: currentUser,
  657. repo: currentRepoName,
  658. path: "README.md",
  659. message: "initialize README",
  660. content: content
  661. }).then(() => {
  662. octokit.repos.createOrUpdateFileContents({
  663. owner: currentUser,
  664. repo: currentRepoName,
  665. path: "project.svg",
  666. message: "SVG Picture",
  667. content: ""
  668. }).then(()=>{
  669. octokit.repos.createOrUpdateFileContents({
  670. owner: currentUser,
  671. repo: currentRepoName,
  672. path: ".gitattributes",
  673. message: "Create gitattributes",
  674. content: window.btoa("data binary")
  675. }).then(()=>{
  676. octokit.repos.createOrUpdateFileContents({
  677. owner: currentUser,
  678. repo: currentRepoName,
  679. path: "data.json",
  680. message: "Data file",
  681. content: ""
  682. }).then(()=>{
  683. octokit.repos.createOrUpdateFileContents({
  684. owner: currentUser,
  685. repo: currentRepoName,
  686. path: "LICENSE.txt",
  687. message: "Establish license",
  688. content: window.btoa(licenseText)
  689. }).then(()=>{
  690. intervalTimer = setInterval(() => { this.saveProject() }, 1200000) //Save the project regularly
  691. })
  692. })
  693. })
  694. })
  695. })
  696. })
  697. })
  698. //Update the project topics
  699. octokit.repos.replaceAllTopics({
  700. owner: currentUser,
  701. repo: currentRepoName,
  702. names: ["maslowcreate", "maslowcreate-project"],
  703. headers: {
  704. accept: 'application/vnd.github.mercy-preview+json'
  705. }
  706. })
  707. })
  708. GlobalVariables.currentMolecule.backgroundClick()
  709. //Clear and hide the popup
  710. while (popup.firstChild) {
  711. popup.removeChild(popup.firstChild)
  712. }
  713. popup.classList.add('off')
  714. }
  715.  
  716. /**
  717. * Save the current project to github.
  718. */
  719. this.saveProject = function(){
  720. //Save the current project into the github repo
  721. if(currentRepoName != null){
  722. //Store the target repo incase a new project is loaded during the save
  723. const saveRepoName = currentRepoName
  724. const saveUser = currentUser
  725. if(typeof intervalTimer != undefined){
  726. clearInterval(intervalTimer) //Turn off auto saving to prevent it from saving again during this save
  727. }
  728. this.progressSave(0)
  729. // var shape = null
  730.  
  731. // if(GlobalVariables.topLevelMolecule.value != null && typeof GlobalVariables.topLevelMolecule.value != 'number'){
  732. // shape = GlobalVariables.topLevelMolecule.value
  733. // }
  734. const passBOMOn = (bomItems) => {
  735. const values = {op: "svg", readPath: GlobalVariables.topLevelMolecule.path}
  736. const {answer} = window.ask(values)
  737. answer.then( answer => {
  738. this.progressSave(10)
  739. var contentSvg = answer //Would compute the svg picture here
  740. var bomContent = bomHeader
  741. var totalParts = 0
  742. var totalCost = 0
  743. if(bomItems != undefined){
  744. bomItems.forEach(item => {
  745. totalParts += item.numberNeeded
  746. totalCost += item.costUSD
  747. bomContent = bomContent + "\n|" + item.BOMitemName + "|" + item.numberNeeded + "|$" + item.costUSD.toFixed(2) + "|" + convertLinks(item.source) + "|"
  748. })
  749. }
  750. bomContent = bomContent + "\n|" + "Total: " + "|" + totalParts + "|$" + totalCost.toFixed(2) + "|" + " " + "|"
  751. bomContent = bomContent+"\n\n 3xCOG MSRP: $" + (3*totalCost).toFixed(2)
  752. var readmeContent = readmeHeader + "\n\n" + "# " + saveRepoName + "\n\n![](/project.svg)\n\n"
  753. GlobalVariables.topLevelMolecule.requestReadme().forEach(item => {
  754. readmeContent = readmeContent + item + "\n\n\n"
  755. })
  756. var jsonRepOfProject = GlobalVariables.topLevelMolecule.serialize()
  757. jsonRepOfProject.filetypeVersion = 1
  758. jsonRepOfProject.circleSegmentSize = GlobalVariables.circleSegmentSize
  759. const projectContent = JSON.stringify(jsonRepOfProject, null, 4)
  760. var decoder = new TextDecoder('utf8')
  761. var finalSVG = decoder.decode(contentSvg)
  762. const askJsonVals = {op: "getJSON", readPath: GlobalVariables.topLevelMolecule.path}
  763. const {answer: answer2} = window.ask(askJsonVals)
  764. answer2.then( JSONData => {
  765. this.createCommit(octokit,{
  766. owner: saveUser,
  767. repo: saveRepoName,
  768. changes: {
  769. files: {
  770. 'BillOfMaterials.md': bomContent,
  771. 'README.md': readmeContent,
  772. 'project.svg': finalSVG,
  773. 'project.maslowcreate': projectContent,
  774. 'data.json': JSONData
  775. },
  776. commit: 'Autosave'
  777. }
  778. })
  779. })
  780.  
  781. intervalTimer = setInterval(() => this.saveProject(), 1200000)
  782. })
  783. }
  784. extractBomTags(GlobalVariables.topLevelMolecule.path, passBOMOn)
  785. }
  786. }
  787. /**
  788. * Creates saving/saved pop up
  789. */
  790. this.progressSave = function (progress, saving = true) {
  791. progress = Math.max(0, progress) //Make it so the progress can't be displayed negitive
  792. var popUp = document.getElementById("popUp")
  793. let popUpBox = document.querySelector('#Progress_Status')
  794. //var width = 1;
  795. popUp.setAttribute("style","display:block")
  796. popUpBox.setAttribute("style","display:block")
  797. if (progress >= 100) {
  798. popUp.style.width = progress + '%'
  799. if(saving){
  800. popUp.textContent = "Project Saved"
  801. setTimeout(function() {
  802. popUp.setAttribute("style","display:none")
  803. popUpBox.setAttribute("style","display:none")
  804. }, 4000)
  805. }
  806. else{
  807. popUp.setAttribute("style","display:none")
  808. popUpBox.setAttribute("style","display:none")
  809. }
  810. } else {
  811. if(saving){
  812. popUp.textContent = "Saving..."
  813. }
  814. else{
  815. popUp.textContent = "Loading..."+progress.toFixed(1)+"%"
  816. }
  817. popUp.style.width = progress + '%'
  818. }
  819. }
  820. /**
  821. * Create a commit as part of the saving process.
  822. */
  823. this.createCommit = async function(octokit, { owner, repo, base, changes }) {
  824. this.progressSave(30)
  825. let response
  826. if (!base) {
  827. response = await octokit.repos.get({ owner, repo })
  828. base = response.data.default_branch
  829. }
  830. this.progressSave(40)
  831. response = await octokit.repos.listCommits({
  832. owner,
  833. repo,
  834. sha: base,
  835. per_page: 1
  836. })
  837. let latestCommitSha = response.data[0].sha
  838. const treeSha = response.data[0].commit.tree.sha
  839. this.progressSave(60)
  840. response = await octokit.git.createTree({
  841. owner,
  842. repo,
  843. base_tree: treeSha,
  844. tree: Object.keys(changes.files).map(path => {
  845. if(changes.files[path] != null){
  846. return {
  847. path,
  848. mode: '100644',
  849. content: changes.files[path]
  850. }
  851. }
  852. else{
  853. return {
  854. path,
  855. mode: '100644',
  856. sha: null
  857. }
  858. }
  859. })
  860. })
  861. const newTreeSha = response.data.sha
  862. this.progressSave(80)
  863.  
  864. response = await octokit.git.createCommit({
  865. owner,
  866. repo,
  867. message: changes.commit,
  868. tree: newTreeSha,
  869. parents: [latestCommitSha]
  870. })
  871. latestCommitSha = response.data.sha
  872.  
  873. this.progressSave(90)
  874. await octokit.git.updateRef({
  875. owner,
  876. repo,
  877. sha: latestCommitSha,
  878. ref: "heads/" + base,
  879. force: true
  880. })
  881. this.progressSave(100)
  882. console.warn("Project saved")
  883. }
  884. /**
  885. * Loads a project from github by name.
  886. */
  887. this.loadProject = async function(projectName){
  888. this.totalAtomCount = 0
  889. this.numberOfAtomsToLoad = 0
  890.  
  891. GlobalVariables.startTime = new Date().getTime()
  892. if(typeof intervalTimer != undefined){
  893. clearInterval(intervalTimer) //Turn off auto saving
  894. }
  895.  
  896. //Clear and hide the popup
  897. while (popup.firstChild) {
  898. popup.removeChild(popup.firstChild)
  899. }
  900. popup.classList.add('off')
  901. currentRepoName = projectName
  902. //Load a blank project
  903. GlobalVariables.topLevelMolecule = new Molecule({
  904. x: 0,
  905. y: 0,
  906. topLevel: true,
  907. atomType: "Molecule"
  908. })
  909. GlobalVariables.currentMolecule = GlobalVariables.topLevelMolecule
  910. octokit.repos.getContent({
  911. owner: currentUser,
  912. repo: projectName,
  913. path: 'project.maslowcreate'
  914. }).then(result => {
  915. //content will be base64 encoded
  916. let rawFile = JSON.parse(atob(result.data.content))
  917. if(rawFile.circleSegmentSize){
  918. GlobalVariables.circleSegmentSize = rawFile.circleSegmentSize
  919. }
  920. if(rawFile.filetypeVersion == 1){
  921. GlobalVariables.topLevelMolecule.deserialize(rawFile)
  922. }
  923. else{
  924. GlobalVariables.topLevelMolecule.deserialize(this.convertFromOldFormat(rawFile))
  925. }
  926. })
  927. octokit.repos.get({
  928. owner: currentUser,
  929. repo: currentRepoName
  930. }).then(result => {
  931. GlobalVariables.fork = result.data.fork
  932. if(!GlobalVariables.fork){
  933. document.getElementById("pull_top").style.display = "none"
  934. }
  935. else{
  936. document.getElementById("pull_top").style.display = "inline"
  937. }
  938. })
  939. }
  940. this.convertFromOldFormat = function(json){
  941. var listOfMoleculeAtoms = json.molecules
  942. //Find the top level molecule
  943. var projectObject = listOfMoleculeAtoms.filter((molecule) => { return molecule.topLevel == true })[0]
  944. //Remove that element from the listOfMoleculeAtoms
  945. listOfMoleculeAtoms.splice(listOfMoleculeAtoms.findIndex(e => e.topLevel == true),1)
  946. //Recursive function to walk the tree and find molecule placeholders
  947. function walkForMolecules(projectObject){
  948. projectObject.allAtoms.forEach(function(atom, allAtomsIndex, allAtomsObject) {
  949. if(atom.atomType == "Molecule"){
  950. if(atom.allAtoms != undefined){ //If this molecule has allAtoms
  951. walkForMolecules(atom)//Walk it
  952. }
  953. else{ //Else replace it with a version which does have allAtoms from the list
  954. //Find the version in molecules list and plug it in
  955. allAtomsObject[allAtomsIndex] = listOfMoleculeAtoms.filter((molecule) => { return molecule.uniqueID == atom.uniqueID })[0]
  956. //Remove that element from the listOfMoleculeAtoms
  957. listOfMoleculeAtoms.splice(listOfMoleculeAtoms.findIndex(e => e.uniqueID == atom.uniqueID),1)
  958. }
  959. }
  960. })
  961. }
  962. //Find any placeholder molecules in there (this needs to be a full tree walk for everything to work)
  963. while(listOfMoleculeAtoms.length > 0){
  964. walkForMolecules(projectObject)
  965. }
  966. return projectObject
  967. }
  968. /**
  969. * Begins the automatic process of saving the project
  970. */
  971. this.beginAutosave = function(){
  972. intervalTimer = setInterval(() => this.saveProject(), 120000) //Save the project regularly
  973. }
  974. /**
  975. * Loads a project from github by its github ID.
  976. */
  977. this.getProjectByID = async function(id, saveUserInfo){
  978. let repo = await octokit.request('GET /repositories/:id', {id})
  979. //Find out the owners info;
  980. const user = repo.data.owner.login
  981. const repoName = repo.data.name
  982. const description = repo.data.description
  983. //Get the file contents
  984. let result = await octokit.repos.getContent({
  985. owner: user,
  986. repo: repoName,
  987. path: 'project.maslowcreate'
  988. })
  989. //If this is the top level we will save the rep info at the top level
  990. if(saveUserInfo){
  991. currentUser = user
  992. currentRepoName = repoName
  993. }
  994. let rawFile = JSON.parse(atob(result.data.content))
  995. rawFile.description = description
  996. if(rawFile.filetypeVersion == 1){
  997. return rawFile
  998. }
  999. else{
  1000. return this.convertFromOldFormat(rawFile)
  1001. }
  1002. }
  1003. /**
  1004. * Loads a project's data from github by its github ID.
  1005. */
  1006. this.getProjectDataByID = async function(id){
  1007. let repo = await octokit.request('GET /repositories/:id', {id})
  1008. //Find out the owners info;
  1009. const user = repo.data.owner.login
  1010. const repoName = repo.data.name
  1011. try{
  1012. let jsonData = await octokit.repos.getContent({
  1013. owner: user,
  1014. repo: repoName,
  1015. path: 'data.json'
  1016. })
  1017. jsonData = atob(jsonData.data.content)
  1018. return jsonData
  1019. }catch(err){
  1020. console.warn("Unable to load project data from github...using full model")
  1021. return false
  1022. }
  1023. }
  1024. /**
  1025. * Export a molecule as a new github project.
  1026. */
  1027. this.exportCurrentMoleculeToGithub = function(molecule){
  1028. //Get name and description
  1029. var name = molecule.name
  1030. var description = "A stand alone molecule exported from Maslow Create"
  1031. //Create a new repo
  1032. octokit.repos.createForAuthenticatedUser({
  1033. name: name,
  1034. description: description
  1035. }).then(result => {
  1036. //Once we have created the new repo we need to create a file within it to store the project in
  1037. var repoName = result.data.name
  1038. var id = result.data.id
  1039. var path = "project.maslowcreate"
  1040. var content = window.btoa("init") // create a file with just the word "init" in it and base64 encode it
  1041. octokit.repos.createOrUpdateFileContents({
  1042. owner: currentUser,
  1043. repo: repoName,
  1044. path: path,
  1045. message: "initialize repo",
  1046. content: content
  1047. }).then(() => {
  1048. //Save the molecule into the newly created repo
  1049. var path = "project.maslowcreate"
  1050. molecule.topLevel = true //force the molecule to export in the long form as if it were the top level molecule
  1051. var content = window.btoa(JSON.stringify(molecule.serialize({molecules: []}), null, 4)) //Convert the passed molecule object to a JSON string and then convert it to base64 encoding
  1052. //Get the SHA for the file
  1053. octokit.repos.getContent({
  1054. owner: currentUser,
  1055. repo: repoName,
  1056. path: path
  1057. }).then(result => {
  1058. var sha = result.data.sha
  1059. //Save the repo to the file
  1060. octokit.repos.updateFile({
  1061. owner: currentUser,
  1062. repo: repoName,
  1063. path: path,
  1064. message: "export Molecule",
  1065. content: content,
  1066. sha: sha
  1067. }).then(() => {
  1068. //Replace the existing molecule now that we just exported
  1069. molecule.replaceThisMoleculeWithGithub(id)
  1070. })
  1071. })
  1072.  
  1073. })
  1074. //Update the project topics
  1075. octokit.repos.replaceTopics({
  1076. owner: currentUser,
  1077. repo: repoName,
  1078. names: ["maslowcreate", "maslowcreate-molecule"],
  1079. headers: {
  1080. accept: 'application/vnd.github.mercy-preview+json'
  1081. }
  1082. })
  1083. })
  1084. }
  1085.  
  1086. /**
  1087. * Like a project on github by unique ID.
  1088. */
  1089. this.starProject = function(id){
  1090. //Authenticate - Initialize with OAuth.io app public key
  1091. OAuth.initialize('BYP9iFpD7aTV9SDhnalvhZ4fwD8')
  1092. // Use popup for oauth
  1093. OAuth.popup('github').then(github => {
  1094.  
  1095. octokit = new Octokit({
  1096. auth: github.access_token
  1097. })
  1098. octokit.request('GET /repositories/:id', {id}).then(result => {
  1099. //Find out the information of who owns the project we are trying to like
  1100. var user = result.data.owner.login
  1101. var repoName = result.data.name
  1102. octokit.repos.listTopics({
  1103. owner: user,
  1104. repo: repoName,
  1105. headers: {
  1106. accept: 'application/vnd.github.mercy-preview+json'
  1107. }
  1108. }).then(()=> {
  1109. //Find out if the project has been starred and unstar if it is
  1110. octokit.activity.checkStarringRepo({
  1111. owner:user,
  1112. repo: repoName
  1113. }).then(() => {
  1114. var button= document.getElementById("Star-button")
  1115. button.setAttribute("class","browseButton")
  1116. button.innerHTML = "Star"
  1117. octokit.activity.unstarRepo({
  1118. owner: user,
  1119. repo: repoName
  1120. })
  1121. })
  1122. }).then(() =>{
  1123. var button= document.getElementById("Star-button")
  1124. button.setAttribute("class","liked")
  1125. button.innerHTML = "Starred"
  1126. octokit.activity.starRepo({
  1127. owner: user,
  1128. repo: repoName
  1129. })
  1130. })
  1131. })
  1132. })
  1133. }
  1134. /**
  1135. * Fork a project on github by unique ID.
  1136. */
  1137. this.forkByID = function(id){
  1138. //Authenticate - Initialize with OAuth.io app public key
  1139. OAuth.initialize('BYP9iFpD7aTV9SDhnalvhZ4fwD8')
  1140. // Use popup for oauth
  1141. OAuth.popup('github').then(github => {
  1142.  
  1143. octokit = new Octokit({
  1144. auth: github.access_token
  1145. })
  1146. octokit.request('GET /repositories/:id', {id}).then(result => {
  1147. //Find out the information of who owns the project we are trying to fork
  1148. var user = result.data.owner.login
  1149. var repoName = result.data.name
  1150. octokit.repos.listTopics({
  1151. owner: user,
  1152. repo: repoName,
  1153. headers: {
  1154. accept: 'application/vnd.github.mercy-preview+json'
  1155. }
  1156. }).then(result => {
  1157. var topics = result.data.names
  1158. //Create a fork of the project with the found user name and repo name under your account
  1159. octokit.repos.createFork({
  1160. owner: user,
  1161. repo: repoName,
  1162. headers: {
  1163. accept: 'application/vnd.github.mercy-preview+json'
  1164. }
  1165. }).then(result => {
  1166. var repoName = result.data.name
  1167. //Manually copy over the topics which are lost in forking
  1168. octokit.repos.replaceTopics({
  1169. owner: result.data.owner.login,
  1170. repo: result.data.name,
  1171. names: topics,
  1172. headers: {
  1173. accept: 'application/vnd.github.mercy-preview+json'
  1174. }
  1175. }).then(() => {
  1176. //Remove everything in the popup now
  1177. while (popup.firstChild) {
  1178. popup.removeChild(popup.firstChild)
  1179. }
  1180. popup.classList.remove('off')
  1181. popup.setAttribute("style", "text-align: center")
  1182.  
  1183. var subButtonDiv = document.createElement('div')
  1184. subButtonDiv.setAttribute("class", "form")
  1185. //Add a title
  1186. var title = document.createElement("H3")
  1187. title.appendChild(document.createTextNode("A copy of the project '" + repoName + "' has been copied and added to your projects. You can view it by clicking the button below."))
  1188. subButtonDiv.setAttribute('style','color:white;')
  1189. subButtonDiv.appendChild(title)
  1190. subButtonDiv.appendChild(document.createElement("br"))
  1191. var form = document.createElement("form")
  1192. subButtonDiv.appendChild(form)
  1193. var button = document.createElement("button")
  1194. button.setAttribute("type", "button")
  1195. button.setAttribute("style", "border:2px solid white;")
  1196. button.appendChild(document.createTextNode("View Projects"))
  1197. button.addEventListener("click", () => {
  1198. window.location.href = '/'
  1199. })
  1200. form.appendChild(button)
  1201. popup.appendChild(subButtonDiv)
  1202. })
  1203. })
  1204. })
  1205. })
  1206. })
  1207. }
  1208. /**
  1209. * Upload or remove files from github. Files with null content will be deleted.
  1210. * @param {object} files A dictionary with paths as keys and the content as the answer.
  1211. */
  1212. this.uploadAFile = async function(files){
  1213. await this.createCommit(octokit,{
  1214. owner: currentUser,
  1215. repo: currentRepoName,
  1216. changes: {
  1217. files: files,
  1218. commit: 'Upload file'
  1219. }
  1220. })
  1221. }
  1222. /**
  1223. * Get a file from github. Calback is called after the retrieved.
  1224. */
  1225. this.getAFile = async function(filePath){
  1226. const result = await octokit.repos.getContent({
  1227. owner: currentUser,
  1228. repo: currentRepoName,
  1229. path: filePath
  1230. })
  1231. //content will be base64 encoded
  1232. let rawFile = atob(result.data.content)
  1233. return rawFile
  1234. }
  1235. /**
  1236. * Get a link to the raw version of a file on GitHub
  1237. */
  1238. this.getAFileRawPath = function(filePath){
  1239. const rawPath = "https://raw.githubusercontent.com/" + currentUser + "/" + currentRepoName + "/main/" + filePath
  1240. return rawPath
  1241. }
  1242. }