app.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. import { Lang } from '/web/lib/polyglot-lang.js';
  2. var options = {
  3. query: 'pathname=' + window.location.pathname.slice(1,
  4. window.location.pathname.lastIndexOf("/")),
  5. secure: window.location.protocol === "https:",
  6. reconnection: false,
  7. transports: ['websocket']
  8. };
  9. const langPhrases = {
  10. "en":{
  11. "start": "Start new",
  12. "users": "Users online: "
  13. },
  14. "ru": {
  15. "start": "Создать",
  16. "users": "Пользователей онлайн: "
  17. }
  18. }
  19. const prepareLang = () => {
  20. let langID = localStorage.getItem('krestianstvo_locale');
  21. let phrases = langPhrases[langID];
  22. let lang = new Lang;
  23. return new lang.polyglot({ phrases });
  24. }
  25. const language = prepareLang();
  26. const translations = {
  27. "titleText":{
  28. "en": '<h1 class="mdc-typography--display3 mdc-theme--text-secondary-on-background mdc-typography"><a class="mdc-typography link-in-text" style="cursor: pointer;" onclick="window.location.reload(true)"><strong>LiveCoding</strong>.space</a><!--<strong>LiveCoding</strong>.space --></h1>',
  29. "ru":'<h1 class="mdc-typography--display3 mdc-theme--text-secondary-on-background mdc-typography"><a class="mdc-typography link-in-text" style="cursor: pointer;" onclick="window.location.reload(true)"><strong>LiveCoding</strong>.пространство</a><!--<strong>LiveCoding</strong>.space --></h1>'
  30. },
  31. "headerText": {
  32. 'en': '<h1 class="mdc-typography mdc-typography--headline mdc-typography--adjust-margin mdc-theme--text-hint-on-background">Collaborative Live Coding Space with support of user-defined languages and WebVR ready 3D graphics.<br> Based on: <strong>Virtual World Framework | A-Frame | Ohm language | OSC.js | and more... </strong> by <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="https://www.krestianstvo.org"><strong>Krestianstvo.org</strong></a> </h1>',
  33. "ru":'<h1 class="mdc-typography mdc-typography--headline mdc-typography--adjust-margin mdc-theme--text-hint-on-background">Виртуальное обучающее пространство в веб-браузере с функциями живого кодирования, возможностью создания собственных языков программирования, технологий виртуальной/дополненной/смешанной реальности WebVR.<br> На основе: <strong>Virtual World Framework | A-Frame | Ohm language | OSC.js | ... </strong> проект <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="https://www.krestianstvo.org"><strong>Krestianstvo.org</strong></a> </h1>'
  34. },
  35. "featuresText": {
  36. "en": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Features</h1> <ul class="featureList mdc-typography mdc-typography--title mdc-theme--text-hint-on-background"> <li><strong>Decentralized network model</strong> for <strong>A-Frame</strong> components and entities based on <strong>VWF</strong> replicated computation architecture</li> <li><strong>Ohm</strong> language driver for sharing user-defined grammars, parsers, tokenisers inside virtual space</li><li>In browser <strong>Code and Properties editor</strong> based on Cell.js</li><li><strong>OSC </strong>messaging through <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="https://github.com/NikolaySuslov/osc-relay-lcs">OSC relay</a> on the client</li><li><strong>Avatars</strong> (Simple and GLTF models with animation)</li><li><strong>Multi-window</strong> or multi-monitor/multi-machine setups with view <strong>offset cameras</strong></li><li><strong>WebRTC</strong> for video/audio streaming,<strong>3D positional audio</strong> support</li><li><strong>GearVR, Windows MixedReality</strong> motion controllers</li> </ul>',
  37. "ru": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">О программе</h1> <ul class="featureList mdc-typography mdc-typography--title mdc-theme--text-hint-on-background"> <li><strong> Децентрализованная модель приложения </strong> на основе <strong>A-Frame</strong> компонентов и <strong>VWF</strong> архитектуры распределенных вычислений (репликация и виртуальное время) в сети. </li> <li><strong>Ohm</strong> драйвер для совместного создания пользовательских языков программирования, грамматик, парсеров, токенайзеров внутри виртуального пространства</li><li><strong>Редактор кода и параметров объектов</strong> прямо в веб-браузере на основе Cell.js</li><li>Работа с <strong>OSC </strong>сообщениями через <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="https://github.com/NikolaySuslov/osc-relay-lcs">OSC relay</a></li><li><strong>Аватары</strong> (простые или GLTF модели с анимацией)</li><li><strong>Мульти-оконные</strong> и мульти-мониторные/компьютерные/телефонные проекции с применением виртуальных камер со <strong>смещением вида</strong></li><li><strong>WebRTC</strong> для видео/аудио потоковой передачи данных P2P, с функциями <strong>звукового 3D позиционирования</strong> в виртуальном пространстве</li><li><strong>GearVR, Windows MixedReality</strong> контроллеры движения</li> </ul>'
  38. },
  39. "worldInfo":{
  40. "en": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Virtual Worlds</h1><h1 class="mdc-typography mdc-typography--headline mdc-theme--text-hint-on-background">To begin collaborative coding in virtual space, just start one of the listed prototypes and connect to it from another browser window using the generated link. The link will apper near the <strong>Start new</strong> button.</h1>',
  41. "ru":'<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Виртуальные миры</h1><h1 class="mdc-typography mdc-typography--headline mdc-theme--text-hint-on-background">Чтобы начать работу в виртуальном обучающем пространстве, выберите один из прототипов миров и запустите его нажав на кнопку <strong>Создать</strong>. Для вновь созданного мира сгенерируется уникальная ссылка, которая отобразиться под его описанием. Для совместной работы, войдите с помощью этой ссылки с другого компьютера или окна браузера. Из прототипа можно создавать неограниченное количество миров. Рядом с ссылками так же указывается количество пользователей, находящихся онлайн в указанном мире.</h1>'
  42. },
  43. "demoText":{
  44. "en": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Demo videos</h1>',
  45. "ru": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Видео демонстрации</h1>'
  46. }
  47. }
  48. const initLocale = () => {
  49. if (!localStorage.getItem('krestianstvo_locale'))
  50. localStorage.setItem('krestianstvo_locale', 'en');
  51. }
  52. const setLocale = (langID) => {
  53. localStorage.setItem('krestianstvo_locale', langID);
  54. }
  55. const getTranslationFor = (aString) => {
  56. let locale = localStorage.getItem('krestianstvo_locale');
  57. return translations[aString][locale]
  58. }
  59. const setLanguage = (langID) => {
  60. setLocale(langID);
  61. generateFrontPage();
  62. }
  63. function generateFrontPage(){
  64. let allTextEl = ["titleText", "headerText", "featuresText", "worldInfo", "demoText"];
  65. allTextEl.forEach(el => {
  66. let textEl = document.querySelector("#" + el);
  67. textEl.innerHTML = getTranslationFor(el);
  68. })
  69. }
  70. var socket = io.connect(window.location.protocol + "//" + window.location.host, options);
  71. socket.on('getWebAppUpdate', function (msg) {
  72. parseAppInstancesData(msg)
  73. //console.log(msg);
  74. });
  75. function parseAppInstancesData(data) {
  76. var needToUpdate = true;
  77. if (data == "{}") {
  78. var el = document.querySelector(".instance");
  79. if (el) {
  80. var topEl = el.parentNode;
  81. topEl.removeChild(el);
  82. }
  83. // let removeElements = elms => Array.from(elms).forEach(el => el.remove());
  84. }
  85. let jsonObj = JSON.parse(data);
  86. var parsed = {};
  87. for (var prop in jsonObj) {
  88. var name = prop.split('/')[1];
  89. if (parsed[name]) {
  90. parsed[name][prop] = jsonObj[prop];
  91. } else {
  92. parsed[name] = {};
  93. parsed[name][prop] = jsonObj[prop];
  94. }
  95. }
  96. //console.log(parsed);
  97. document.querySelector("#main")._emptyLists();
  98. for (var prop in parsed) {
  99. var name = prop;
  100. let element = document.getElementById(name + 'List');
  101. if (element) {
  102. element._setListData(parsed[prop]);
  103. }
  104. //needToUpdate = true
  105. }
  106. // console.log(data)
  107. }
  108. function getAllAppInstances() {
  109. let allInatances = httpGetJson('allinstances.json')
  110. .then(res => {
  111. parseAppInstancesData(res);
  112. });
  113. }
  114. function getSiteHeader(){
  115. return {
  116. $type: "div",
  117. class: "mdc-layout-grid",
  118. $components: [
  119. {
  120. $type: "div",
  121. class: "mdc-layout-grid__inner",
  122. $components: [
  123. {
  124. $cell: true,
  125. $type: "div",
  126. class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
  127. $components: [
  128. {
  129. $cell: true,
  130. $type: "h1",
  131. class: "mdc-typography--display1 mdc-theme--text-hint-on-background",
  132. $text: "Virtual Worlds"
  133. }
  134. ]
  135. }
  136. ]
  137. }
  138. ]
  139. }
  140. }
  141. function parseWebAppDataForCell(data) {
  142. document.querySelector("#main").$cell({
  143. $cell: true,
  144. $type: "div",
  145. id: "main",
  146. _jsonData: {},
  147. _emptyLists: function () {
  148. Object.entries(this._jsonData).forEach(function (element) {
  149. //console.log(element);
  150. document.getElementById(element[0] + 'List')._setListData({});
  151. });
  152. },
  153. $init: function () {
  154. this._jsonData = JSON.parse(data);
  155. },
  156. _makeWorldCard: function (m) {
  157. let langID = localStorage.getItem('krestianstvo_locale');
  158. var appInfo = m
  159. if(langID) {
  160. if(m[1][langID]){
  161. appInfo = [m[0], m[1][langID]]
  162. }
  163. }
  164. return {
  165. $cell: true,
  166. $type: "div",
  167. class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-4",
  168. $components: [
  169. this._worldCardDef(appInfo)
  170. ]
  171. }
  172. },
  173. $update: function () {
  174. this.$components = [
  175. //getSiteHeader(),// siteHeader,
  176. {
  177. $type: "div",
  178. class: "mdc-layout-grid",
  179. $components: [
  180. {
  181. $type: "div",
  182. class: "mdc-layout-grid__inner",
  183. $components: Object.entries(this._jsonData).map(this._makeWorldCard)
  184. }
  185. ]
  186. },
  187. ]
  188. },
  189. _worldCardDef: function (desc) {
  190. return {
  191. $cell: true,
  192. $type: "div",
  193. class: "mdc-card world-card",
  194. $components: [
  195. {
  196. $type: "section",
  197. class: "mdc-card__media world-card__16-9-media",
  198. $init: function () {
  199. if (desc[1].imgUrl !== "") {
  200. this.style.backgroundImage = 'linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3) ), url(' + desc[1].imgUrl + ')';
  201. }
  202. }
  203. },
  204. {
  205. $type: "section",
  206. class: "mdc-card__primary",
  207. $components: [
  208. {
  209. $type: "h1",
  210. class: "mdc-card__title mdc-card__title--large",
  211. $text: desc[1].title
  212. },
  213. {
  214. $type: "h2",
  215. class: "mdc-card__subtitle mdc-theme--text-secondary-on-background",
  216. $text: desc[1].text
  217. }
  218. ]
  219. },
  220. {
  221. $type: "section",
  222. class: "mdc-card__actions",
  223. $components: [
  224. {
  225. $type: "a",
  226. class: "mdc-button mdc-button--compact mdc-card__action mdc-button--stroked",
  227. $text: language.t('start'),//"Start new",
  228. target: "_blank",
  229. href: "/" + desc[0],
  230. onclick: function (e) {
  231. refresh();
  232. }
  233. }
  234. ]
  235. },
  236. {
  237. $type: "section",
  238. class: "mdc-card__actions",
  239. $components: [
  240. {
  241. $type: "ul",
  242. _listData: {},
  243. _setListData: function (data) {
  244. this._listData = data;
  245. },
  246. class: "mdc-list mdc-list--two-line",
  247. id: desc[0] + 'List',
  248. $update: function () {
  249. var connectText = {}
  250. if (Object.entries(this._listData).length !== 0) {
  251. connectText = {
  252. // $type: "span",
  253. // class: "mdc-theme--text-secondary",
  254. // $text: "...or connect to:"
  255. }
  256. }
  257. this.$components = [
  258. {
  259. $type: "hr",
  260. class: "mdc-list-divider"
  261. }
  262. ].concat(Object.entries(this._listData).map(this._worldListItem))
  263. // [connectText]
  264. // }].concat(Object.entries(this._listData).map(this._worldListItem))
  265. },
  266. _worldListItem: function (m) {
  267. return {
  268. $type: "li",
  269. class: "mdc-list-item",
  270. $components: [
  271. {
  272. $type: "span",
  273. class: "world-link mdc-list-item__text",
  274. $components: [
  275. {
  276. $type: "a",
  277. $text: m[1].instance,
  278. target: "_blank",
  279. href: window.location.protocol + "//" + m[1].instance,
  280. onclick: function (e) {
  281. refresh();
  282. }
  283. },
  284. {
  285. $type: "span",
  286. class: "mdc-list-item__text__secondary",
  287. $text: language.t('users') + m[1].clients
  288. }
  289. ]
  290. }
  291. ]
  292. }
  293. }
  294. }
  295. ]
  296. }
  297. ]
  298. }
  299. }
  300. })
  301. }
  302. function getAppDetails(val) {
  303. let appDetails = httpGetJson(val)
  304. .then(res => {
  305. parseWebAppDataForCell(res)
  306. })
  307. .then(res => refresh());
  308. }
  309. function refresh() {
  310. // socket.emit('getWebAppUpdate', "");
  311. }
  312. function httpGet(url) {
  313. return new Promise(function (resolve, reject) {
  314. // do the usual Http request
  315. let request = new XMLHttpRequest();
  316. request.open('GET', url);
  317. request.onload = function () {
  318. if (request.status == 200) {
  319. resolve(request.response);
  320. } else {
  321. reject(Error(request.statusText));
  322. }
  323. };
  324. request.onerror = function () {
  325. reject(Error('Network Error'));
  326. };
  327. request.send();
  328. });
  329. }
  330. async function httpGetJson(url) {
  331. // check if the URL looks like a JSON file and call httpGet.
  332. let regex = /\.(json)$/i;
  333. if (regex.test(url)) {
  334. // call the async function, wait for the result
  335. return await httpGet(url);
  336. } else {
  337. throw Error('Bad Url Format');
  338. }
  339. }
  340. export {getAppDetails, generateFrontPage, setLanguage, initLocale};