├── .all-contributorsrc ├── .github └── workflows │ ├── build.yml │ └── prettier.yml ├── .gitignore ├── .prettierignore ├── LICENSE.md ├── README.md ├── application ├── app.js ├── assets │ ├── css │ │ ├── grid.css │ │ ├── main.css │ │ └── qr_reader.css │ ├── exclude-js │ │ ├── BitMatrix.d.ts │ │ ├── binarizer │ │ │ └── index.d.ts │ │ ├── decoder │ │ │ ├── decodeData │ │ │ │ ├── BitStream.d.ts │ │ │ │ ├── index.d.ts │ │ │ │ └── shiftJISTable.d.ts │ │ │ ├── decoder.d.ts │ │ │ ├── reedsolomon │ │ │ │ ├── GenericGF.d.ts │ │ │ │ ├── GenericGFPoly.d.ts │ │ │ │ └── index.d.ts │ │ │ └── version.d.ts │ │ ├── extractor │ │ │ └── index.d.ts │ │ ├── index.d.ts │ │ ├── jsQR.js │ │ └── locator │ │ │ └── index.d.ts │ ├── fonts │ │ ├── Lato-Black.ttf │ │ ├── Lato-BlackItalic.ttf │ │ ├── Lato-Bold.ttf │ │ ├── Lato-BoldItalic.ttf │ │ ├── Lato-Italic.ttf │ │ ├── Lato-Light.ttf │ │ ├── Lato-LightItalic.ttf │ │ ├── Lato-Regular.ttf │ │ ├── Lato-Thin.ttf │ │ ├── Lato-ThinItalic.ttf │ │ └── OFL.txt │ └── js │ │ ├── IntersectionObserver-polyfill.js │ │ ├── applait.finder.min.js │ │ ├── backend_api.js │ │ ├── helper.js │ │ ├── install.js │ │ ├── jQuery-3.1.0.js │ │ ├── jquery.autocomplete.min.js │ │ ├── lazyload.js │ │ ├── moments.min.js │ │ ├── mustache.js │ │ ├── ratings.js │ │ ├── scan.js │ │ └── search.js ├── icons │ ├── donation.svg │ ├── icon-112-112.png │ ├── icon-112-112.svg │ ├── icon-56-56.png │ ├── icon-56-56.svg │ └── icon.png ├── index.html └── manifest.webapp ├── formatting.sh ├── images ├── bitcoin_rcv.png ├── image-1.png ├── image-2.png ├── image-3.png ├── image-4.png ├── logo.png ├── paypal.png └── support.svg ├── npm-debug.log ├── package-lock.json ├── package.json └── tools ├── build.sh └── package.sh /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "farooqkz", 10 | "name": "Farooq Karimi Zadeh", 11 | "avatar_url": "https://avatars0.githubusercontent.com/u/15038218?v=4", 12 | "profile": "https://notabug.org/farooqkz", 13 | "contributions": [ 14 | "ideas" 15 | ] 16 | }, 17 | { 18 | "login": "Simon-Laux", 19 | "name": "Simon Laux", 20 | "avatar_url": "https://avatars2.githubusercontent.com/u/18725968?v=4", 21 | "profile": "https://github.com/Simon-Laux", 22 | "contributions": [ 23 | "code" 24 | ] 25 | }, 26 | { 27 | "login": "strukturart", 28 | "name": "John-David Deubl", 29 | "avatar_url": "https://avatars0.githubusercontent.com/u/5286893?v=4", 30 | "profile": "http://strukturart.com", 31 | "contributions": [ 32 | "code" 33 | ] 34 | } 35 | ], 36 | "contributorsPerLine": 7, 37 | "projectName": "bHacker-store-client", 38 | "projectOwner": "strukturart", 39 | "repoType": "github", 40 | "repoHost": "https://github.com", 41 | "skipCi": true 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload artifact 2 | on: push 3 | 4 | jobs: 5 | build: 6 | name: Build 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Set Node.js 12.x 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: 12.x 18 | 19 | - name: npm install 20 | run: npm install 21 | 22 | - name: build 23 | run: npm run package 24 | 25 | - uses: actions/upload-artifact@v2 26 | with: 27 | name: App 28 | path: build/bhacker-store.zip 29 | -------------------------------------------------------------------------------- /.github/workflows/prettier.yml: -------------------------------------------------------------------------------- 1 | name: Prettier - Check Code Formatting 2 | on: push 3 | 4 | jobs: 5 | check-formatting: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | 12 | - name: Set Node.js 12.x 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: 12.x 16 | 17 | - name: npm install 18 | run: npm install 19 | 20 | - name: check formatting 21 | run: npm run formatting:test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | tmp 3 | node_modules -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | **/jQuery-3.1.0.js 3 | **/applait.finder.min.js 4 | **/jquery.autocomplete.min.js 5 | **/moments.min.js 6 | 7 | build 8 | node_modules -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 John-David Deubl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](/images/logo.png) 2 | 3 | 4 | 5 | [![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) 6 | 7 | 8 | 9 | ## bHacker-store 10 | 11 | An alternative app store by free developers for free devices. 12 | The database of apps is hosted [here](https://gitlab.com/banana-hackers/store-db/-/tree/master), further can be added by a pull request. 13 | 14 | ### Features 15 | 16 | - Install apps from the coders source 17 | 18 | ![image-1](/images/image-1.png) 19 | ![image-2](/images/image-2.png) 20 | ![image-3](/images/image-3.png) 21 | ![image-4](/images/image-4.png) 22 | 23 | ## How to sideload the app on your device 24 | 25 | ### Omni SD 26 | 27 | Download [bHackerStore App](https://github.com/strukturart/bHacker-store/releases/latest). 28 | Copy this zip file to the apps directory of the memory card. 29 | Open "OmniSD" and install this zip. 30 | 31 | ### Web IDE 32 | 33 | Download [bHackerStore App](https://github.com/strukturart/bHacker-store/releases/latest). 34 | Extract this zip to a folder. 35 | Click "Open Packaged App" and select the folder from the previous step. 36 | Click "Install and Run" button. 37 | 38 | ## Thank you 39 | 40 | - SimonLaux and the discord [community](https://discord.gg/t2CBPb) 41 | 42 | ## Contributing 43 | 44 | ### Setup 45 | 46 | first install the dependencies 47 | 48 | ```sh 49 | npm install 50 | ``` 51 | 52 | ### Packaging 53 | 54 | This only works on linux(and maybe osx) at the moment 55 | 56 | ```sh 57 | npm run package 58 | ``` 59 | 60 | The resulting package can be found in the build folder. 61 | 62 | ### Formatting 63 | 64 | This project uses code formatting. Make sure to run the formatter before commiting, otherwise the CI will be sad 😢. 65 | 66 | ```sh 67 | # check it 68 | npm run formatting:test 69 | # run the formatter 70 | npm run formatting:fix 71 | ``` 72 | 73 | ## Contributors ✨ 74 | 75 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |

Farooq Karimi Zadeh

🤔

Simon Laux

💻

John-David Deubl

💻
87 | 88 | 89 | 90 | 91 | 92 | 93 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 94 | 95 | ## Donation 96 | 97 | If you use the app often, please donate an amount. 98 |
99 | 100 | 101 | 102 | 109 | 115 | 116 |
103 |
104 | 105 | 106 | 107 |
108 |
110 |
111 |
Bitcoin
112 | 113 |
114 |
117 | -------------------------------------------------------------------------------- /application/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let page = 0; 4 | let window_status = "article-list"; 5 | let dataSet; 6 | let panels = ["All", "ratings"]; 7 | let current_panel = 0; 8 | let app_slug; 9 | let offline = false; 10 | let apps_data = new Array(); 11 | let update_time; 12 | let apps_rating = new Array(); 13 | let co; 14 | let contributors = new Array(); 15 | let potato = ""; 16 | let after_installation = false; 17 | 18 | //background colors 19 | let col = [ 20 | "rgba(240, 221, 50,1)", 21 | "rgba(120, 120, 151,1)", 22 | "rgba(221,91,169,1)", 23 | "rgba(129,204,177,1)", 24 | "rgba(100,175,130,1)", 25 | "rgba(206,94,66,1)", 26 | "rgba(136,196,162,1)", 27 | ]; 28 | 29 | //store current article 30 | let article_id; 31 | ////////////////////////////// 32 | //fetch-database//// 33 | ////////////////////////////// 34 | 35 | function init() { 36 | BackendApi.setStatusCallback(toaster); 37 | 38 | function loadData() { 39 | dataSet = BackendApi.getData(); 40 | addAppList(addAppList_callback); 41 | document.querySelector("div#message-box").style.animationPlayState = 42 | "running"; 43 | document.querySelector( 44 | "div#message-box img.icon-2" 45 | ).style.animationPlayState = "running"; 46 | document.querySelector( 47 | "div#message-box img.icon-1" 48 | ).style.animationPlayState = "running"; 49 | } 50 | 51 | if (!BackendApi.getData()) { 52 | if (!navigator.onLine) { 53 | } else { 54 | BackendApi.update() 55 | .then(loadData) 56 | .catch((error) => { 57 | console.log(error); 58 | toaster(error instanceof Error ? error.message : error, 3000); 59 | }); 60 | } 61 | } else { 62 | if (navigator.onLine) { 63 | BackendApi.update() 64 | .then(loadData) 65 | .catch((error) => { 66 | console.log(error); 67 | toaster(error instanceof Error ? error.message : error, 3000); 68 | loadData(); 69 | }); 70 | } else { 71 | offline = true; 72 | toaster( 73 | "
your device is offline, you can view the app but you cannot install it.", 74 | 3000 75 | ); 76 | 77 | loadData(); 78 | } 79 | } 80 | } 81 | 82 | let pep = new Array(); 83 | let counter = 0; 84 | 85 | function addAppList(callback) { 86 | dataSet.apps.forEach(function (value, index) { 87 | let data = dataSet.apps[index]; 88 | let item_title = data.name; 89 | let item_summary = data.description; 90 | let item_link = data.download.url; 91 | let item_url; 92 | let item_donation = data.donation; 93 | let item_ads = data.has_ads; 94 | let item_tracking = data.has_tracking; 95 | let tag = data.meta.categories; 96 | let item_category = data.meta.categories.toString().replace(",", " "); 97 | let item_tags = tag.toString().replace(",", " "); 98 | let item_author = data.author.toString(); 99 | let item_maintainer; 100 | let item_icon = data.icon; 101 | let item_license = data.license; 102 | let item_type = data.type; 103 | let donation_icon = "none"; 104 | let item_slug = data.slug; 105 | let images = ""; 106 | 107 | //bad hack 108 | if (data.meta.categories != "undefinedutility") { 109 | pep += data.meta.categories + ","; 110 | } 111 | 112 | if (data.screenshots != "") { 113 | images = data.screenshots.toString().split(","); 114 | } 115 | 116 | if (data.git_repo != "") { 117 | item_url = data.git_repo; 118 | } 119 | 120 | //unique author list 121 | let just_author_name = item_author.split("<")[0].trim(); 122 | if (contributors.indexOf(just_author_name) === -1) { 123 | contributors.push(just_author_name); 124 | } 125 | 126 | //extract email@author 127 | // false for mustache.js logic 128 | let item_author_email = item_author.match( 129 | /([a-zA-Z0-9._+-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi 130 | ); 131 | if (item_author_email == null) { 132 | item_author_email = false; 133 | } 134 | 135 | //author 136 | if (data.author) { 137 | item_author = data.author.toString(); 138 | var regex = /(<)(.*?)(>)/g; 139 | var matches = item_author.match(regex); 140 | if (matches != null) { 141 | for (var i = 0; i < 20; i++) { 142 | item_author = item_author.replace(matches[i], ""); 143 | } 144 | } 145 | } 146 | 147 | //maintainer 148 | if (data.maintainer) { 149 | item_maintainer = data.maintainer.toString(); 150 | 151 | var regex = /(<)(.*?)(>)/g; 152 | var matches = item_maintainer.match(regex); 153 | 154 | if (matches != null) { 155 | for (var i = 0; i < 20; i++) { 156 | item_maintainer = item_maintainer.replace(matches[i], ""); 157 | } 158 | } 159 | } 160 | 161 | //donation 162 | if (item_donation == "") { 163 | donation_icon = "no"; 164 | } else { 165 | donation_icon = "yes"; 166 | } 167 | //ads 168 | if (item_ads) { 169 | item_ads = "yes"; 170 | } else { 171 | item_ads = "no"; 172 | } 173 | //tracking 174 | if (item_tracking) { 175 | item_tracking = "yes"; 176 | } else { 177 | item_tracking = "no"; 178 | } 179 | //call ratings 180 | get_ratings(item_slug, ratings_callback); 181 | 182 | counter++; 183 | apps_data.push({ 184 | images: images, 185 | title: item_title, 186 | author: item_author, 187 | maintainer: item_maintainer, 188 | author_email: item_author_email, 189 | summary: item_summary, 190 | category: item_category, 191 | link: item_link, 192 | license: item_license, 193 | ads: item_ads, 194 | donation_url: item_donation, 195 | donation: donation_icon, 196 | tracking: item_tracking, 197 | type: item_type, 198 | summarie: item_summary, 199 | icon: item_icon, 200 | slug: item_slug, 201 | tags: item_tags, 202 | url: item_url, 203 | index: counter, 204 | }); 205 | }); 206 | 207 | update_time = moment(dataSet.generated_at).format("DD.MM.YYYY, HH:mm"); 208 | co = contributors.sort().join(", "); 209 | 210 | callback("done"); 211 | } 212 | 213 | document.addEventListener("DOMContentLoaded", (event) => { 214 | init(); 215 | }); 216 | 217 | function addAppList_callback(data) { 218 | //categories - panels 219 | pep = pep.split(","); 220 | pep.forEach((c) => { 221 | if (!panels.includes(c)) { 222 | panels.push(c); 223 | } 224 | }); 225 | panels.pop(panels.length); 226 | 227 | document.querySelector("#update").textContent = update_time; 228 | document.querySelector( 229 | "div#about div#inner div#contributors" 230 | ).textContent = co; 231 | document.querySelector("article#search input").focus(); 232 | 233 | bottom_bar("scan", "select", "about"); 234 | 235 | setTimeout(() => { 236 | renderHello(); 237 | article_animation(); 238 | //panel indicator 239 | for (let i = 0; i < panels.length; i++) { 240 | let pra = document.createElement("div"); 241 | document.getElementById("panels-indicator-inner").appendChild(pra); 242 | } 243 | 244 | document.querySelector( 245 | "#panels-indicator-inner" 246 | ).firstChild.style.background = "pink "; 247 | 248 | DownloadCounter.load().then((_) => { 249 | const apps_article = document.querySelectorAll("div#apps article"); 250 | for (let i = 0; i < apps_article.length; i++) { 251 | const appId = apps_article[i].getAttribute("data-slug"); 252 | 253 | if (appId) { 254 | const dl_section = apps_article[i].querySelector("div.dl-cnt"); 255 | const count = DownloadCounter.getForApp(appId); 256 | dl_section.innerHTML = "Downloads " + count; 257 | 258 | if (dl_section && count !== -1) { 259 | dl_section.innerHTML = "Downloads " + count; 260 | } 261 | } 262 | } 263 | }); 264 | }, 1000); 265 | } 266 | 267 | //////////////////////// 268 | //Animation start////// 269 | /////////////////////// 270 | function article_animation() { 271 | let pe = document.querySelectorAll("article"); 272 | for (let i = 0; i < pe.length; i++) { 273 | setTimeout(() => { 274 | pe[i].style.opacity = "1"; 275 | }, i * 100); 276 | } 277 | } 278 | 279 | //////////////////////// 280 | ////SET TABINDEX//////// 281 | /////////////////////// 282 | 283 | function set_tabindex() { 284 | let articles_panel = document.querySelectorAll("article"); 285 | 286 | //focused = 0; 287 | 288 | var tab = 0; 289 | for (let i = 0; i < articles_panel.length; i++) 290 | if (articles_panel[i].style.display === "block") { 291 | tab++; 292 | articles_panel[i].setAttribute("tabindex", tab); 293 | } else { 294 | articles_panel[i].removeAttribute("tabindex"); 295 | } 296 | 297 | let focusme = document.querySelectorAll("article[tabindex]"); 298 | focusme[0].focus(); 299 | } 300 | 301 | ////////////////////////// 302 | ////WRITE HTML LIST/////// 303 | ////////////////////////// 304 | 305 | function renderHello() { 306 | var template = document.getElementById("template").innerHTML; 307 | var rendered = Mustache.render(template, { data: apps_data }); 308 | document.getElementById("apps").innerHTML = rendered; 309 | //searchGetData(); 310 | } 311 | 312 | ////////////////////////// 313 | //////PANELS////////////// 314 | ////////////////////////// 315 | 316 | function panels_list(panel) { 317 | let articles = document.querySelectorAll("article"); 318 | 319 | let elements = document.getElementsByClassName(panel); 320 | 321 | for (let k = 0; k < articles.length; k++) { 322 | articles[k].style.display = "none"; 323 | } 324 | 325 | for (let i = 0; i < elements.length; i++) { 326 | elements[i].style.display = "block"; 327 | } 328 | set_tabindex(); 329 | } 330 | 331 | //////////////////////// 332 | //NAVIGATION/////////// 333 | ///////////////////////// 334 | 335 | function randomNumber(min, max) { 336 | return Math.floor(Math.random() * (max - min) + min); 337 | } 338 | 339 | function nav_panels(left_right) { 340 | window.scrollTo(0, 0); 341 | focused = 0; 342 | 343 | document.getElementById("navigation").style.display = "none!important"; 344 | 345 | if (left_right == "left") { 346 | current_panel--; 347 | } 348 | 349 | if (left_right == "right") { 350 | current_panel++; 351 | } 352 | 353 | current_panel = current_panel % panels.length; 354 | if (current_panel < 0) { 355 | current_panel += panels.length; 356 | } 357 | 358 | document.querySelector("div#navigation div").textContent = 359 | panels[current_panel]; 360 | panels_list(panels[current_panel]); 361 | 362 | if (current_panel == 0) { 363 | //document.querySelector("input").focus(); 364 | 365 | let articles = document.querySelectorAll("article"); 366 | articles[1].focus(); 367 | focused = 1; 368 | document.getElementById("navigation").style.display = "none"; 369 | //document.querySelector("div#app").style.margin = "5px 0 0 0"; 370 | } 371 | //rating view 372 | if (current_panel == 1) { 373 | document.querySelector("input").focus(); 374 | document.querySelector("div#navigation").style.display = "none"; 375 | document.querySelector("div#app").style.margin = "5px 0 0 0"; 376 | 377 | let elm2 = document.querySelectorAll("div.single-article"); 378 | for (var i = 0; i < elm2.length; i++) { 379 | elm2[i].style.display = "none"; 380 | } 381 | 382 | let elm3 = document.querySelectorAll("div.article-list"); 383 | for (var i = 0; i < elm3.length; i++) { 384 | elm3[i].style.display = "none"; 385 | } 386 | 387 | let elm4 = document.querySelectorAll("div.article-list"); 388 | for (var i = 0; i < elm4.length; i++) { 389 | elm4[i].style.display = "none"; 390 | } 391 | 392 | let elm5 = document.querySelectorAll("ul.ratings"); 393 | for (var i = 0; i < elm5.length; i++) { 394 | elm5[i].style.display = "block"; 395 | } 396 | } 397 | 398 | if (current_panel > 1 || current_panel == 0) { 399 | let elm2 = document.querySelectorAll("div.single-article"); 400 | for (var i = 0; i < elm2.length; i++) { 401 | elm2[i].style.display = "none"; 402 | } 403 | 404 | let elm3 = document.querySelectorAll("div.article-list"); 405 | for (var i = 0; i < elm3.length; i++) { 406 | elm3[i].style.display = "block"; 407 | } 408 | 409 | let elm4 = document.querySelectorAll("ul.ratings"); 410 | for (var i = 0; i < elm4.length; i++) { 411 | elm4[i].style.display = "none"; 412 | } 413 | } 414 | 415 | if (current_panel > 1) { 416 | document.querySelector("div#navigation").style.display = "block"; 417 | document.querySelector("div#app").style.margin = "30px 0 0 0"; 418 | } 419 | 420 | //indicator 421 | let indicator_item = document.querySelector("#panels-indicator-inner"); 422 | let indicator_items = indicator_item.getElementsByTagName("DIV"); 423 | for (let i = 0; i < indicator_items.length; i++) { 424 | indicator_items[i].style.background = "black"; 425 | } 426 | 427 | indicator_items[current_panel].style.background = col[randomNumber(0, 5)]; 428 | 429 | random_background(); 430 | } 431 | 432 | /////////////// 433 | //up - down 434 | /////////////// 435 | let focused = 0; 436 | 437 | function nav(param) { 438 | let articles; 439 | 440 | if (window_status == "article-list" || window_status == "search") { 441 | articles = document.querySelectorAll("article[tabindex]"); 442 | } 443 | if (window_status == "options") { 444 | let k = document.activeElement.parentElement.id; 445 | articles = document.getElementById(k).children; 446 | } 447 | 448 | if (window_status == "rating") { 449 | let k = document.activeElement.parentElement.children; 450 | articles = k; 451 | } 452 | 453 | if (param == "+1" && focused < articles.length - 1) { 454 | focused++; 455 | articles[focused].focus(); 456 | 457 | var scrollDiv = articles[focused].offsetTop + 20; 458 | window.scrollTo({ top: scrollDiv, behavior: "smooth" }); 459 | } 460 | 461 | if (param == "-1" && focused > 0) { 462 | focused--; 463 | articles[focused].focus(); 464 | 465 | var scrollDiv = articles[focused].offsetTop + 30; 466 | window.scrollTo({ top: scrollDiv, behavior: "smooth" }); 467 | } 468 | 469 | random_background(); 470 | } 471 | 472 | function random_background() { 473 | function randomNumber(min, max) { 474 | return Math.floor(Math.random() * (max - min) + min); 475 | } 476 | let elem = document.querySelectorAll("article"); 477 | 478 | if (document.activeElement.tagName == "ARTICLE") { 479 | for (let i = 1; i < elem.length; i++) { 480 | elem[i].style.background = "white"; 481 | } 482 | document.activeElement.style.background = col[randomNumber(0, 5)]; 483 | } else { 484 | for (let i = 1; i < elem.length; i++) { 485 | elem[i].style.background = "white"; 486 | } 487 | } 488 | } 489 | 490 | //////////////////// 491 | ////ROUTING/// 492 | /////////////////// 493 | 494 | //to do get vars in url 495 | //to pass article ids 496 | 497 | window.addEventListener( 498 | "hashchange", 499 | function () { 500 | //home 501 | if (location.hash === "#home") { 502 | document.querySelector("div#about").style.display = "none"; 503 | document.querySelector("article#search").focus(); 504 | bottom_bar("scan", "", "about"); 505 | } 506 | //list 507 | if (location.hash === "#list") { 508 | show_article_list(); 509 | } 510 | //article 511 | if (location.hash.includes("#article")) { 512 | let getVar = location.hash.split("/"); 513 | show_article(getVar[1]); 514 | } 515 | //about 516 | if (location.hash === "#about") { 517 | document.querySelector("div#about").style.display = "block"; 518 | document.querySelector("div#about div#inner").focus(); 519 | document.getElementById("top").scrollIntoView(); 520 | article_id = document.activeElement.getAttribute("id"); 521 | bottom_bar("", "", ""); 522 | } 523 | //rating 524 | if (location.hash === "#rating") { 525 | open_rating(); 526 | } 527 | //options 528 | if (location.hash === "#options") { 529 | open_options(); 530 | } 531 | //scan 532 | if (location.hash === "#scan") { 533 | console.log("article"); 534 | } 535 | }, 536 | false 537 | ); 538 | 539 | //////////////////// 540 | ////SHOW ARTICLE/// 541 | /////////////////// 542 | 543 | function show_article(app) { 544 | window.location.hash = "#article/" + app; 545 | 546 | window_status = "single-article"; 547 | document.getElementById(app).focus(); 548 | 549 | document.getElementById("app-panels-inner").style.height = "98vh"; 550 | document.querySelector("div#app-panels-inner").scrollTo(0, 0); 551 | document.querySelector("div#app").style.margin = "0 0 0 0"; 552 | document.querySelector("div#panels-indicator").style.display = "none"; 553 | 554 | article_id = document.activeElement.getAttribute("id"); 555 | 556 | if (document.activeElement.getAttribute("class") != "About") { 557 | document.querySelector("div#navigation").style.display = "none"; 558 | 559 | let elm1 = document.querySelectorAll("article"); 560 | for (var i = 0; i < elm1.length; i++) { 561 | elm1[i].style.display = "none"; 562 | } 563 | 564 | document.activeElement.style.display = "block"; 565 | 566 | let elm2 = document.querySelectorAll("div.single-article"); 567 | for (var i = 0; i < elm2.length; i++) { 568 | elm2[i].style.display = "block"; 569 | } 570 | 571 | let elm3 = document.querySelectorAll("div.article-list"); 572 | for (var i = 0; i < elm3.length; i++) { 573 | elm3[i].style.display = "none"; 574 | } 575 | 576 | let elm4 = document.querySelectorAll("ul.ratings"); 577 | for (var i = 0; i < elm4.length; i++) { 578 | elm4[i].style.display = "block"; 579 | } 580 | 581 | if (!offline) { 582 | bottom_bar("options", "", "install"); 583 | } else { 584 | bottom_bar("", "you are offline", ""); 585 | } 586 | window_status = "single-article"; 587 | } 588 | } 589 | 590 | ///////////////////// 591 | ///SHOW ARTICLE-LIST 592 | //////////////////// 593 | 594 | function show_article_list() { 595 | after_installation = false; 596 | article_id = document.activeElement.getAttribute("id"); 597 | 598 | window.location.hash = "#list"; 599 | document.querySelector("div#panels-indicator").style.display = "block"; 600 | 601 | if (article_id) { 602 | document.getElementById(article_id).focus(); 603 | focused = document.getElementById(article_id).getAttribute("tabindex") - 1; 604 | } 605 | 606 | document.getElementById("app-panels-inner").style.height = "84vh"; 607 | if (current_panel == 0) { 608 | document.querySelector("div#app").style.margin = "5px 0 0 0"; 609 | } 610 | 611 | let elm1 = document.querySelectorAll("article"); 612 | for (var i = 0; i < elm1.length; i++) { 613 | elm1[i].style.display = "block"; 614 | } 615 | 616 | let elm2 = document.querySelectorAll("div.single-article"); 617 | for (var i = 0; i < elm2.length; i++) { 618 | elm2[i].style.display = "none"; 619 | } 620 | 621 | let elm3 = document.querySelectorAll("div.article-list"); 622 | for (var i = 0; i < elm3.length; i++) { 623 | elm3[i].style.display = "block"; 624 | } 625 | 626 | let elm4 = document.querySelectorAll("ul.ratings"); 627 | for (var i = 0; i < elm4.length; i++) { 628 | elm4[i].style.display = "none"; 629 | } 630 | 631 | if (current_panel > 1) { 632 | document.getElementById("navigation").style.display = "block"; 633 | document.querySelector("div#app").style.margin = "30px 0 0 0"; 634 | } 635 | 636 | panels_list(panels[current_panel]); 637 | bottom_bar("", "select", "about"); 638 | window_status = "article-list"; 639 | } 640 | 641 | function open_rating() { 642 | document.querySelector("div#rating-wrapper").style.display = "block"; 643 | document.querySelector("div#rating-wrapper input").focus(); 644 | rating_stars = 0; 645 | get_userId(); 646 | 647 | bottom_bar("send", "", "close"); 648 | window_status = "rating"; 649 | } 650 | 651 | function close_rating() { 652 | document.querySelector("div#rating-wrapper").style.display = "none"; 653 | document.querySelector("div#rating-wrapper input").value = ""; 654 | document.querySelector("div#rating-wrapper textarea").value = ""; 655 | rating_stars = 0; 656 | 657 | bottom_bar("", "", ""); 658 | close_options(); 659 | } 660 | 661 | function close_options() { 662 | let elm = document.querySelectorAll("div.options"); 663 | for (var i = 0; i < elm.length; i++) { 664 | elm[i].style.display = "none"; 665 | } 666 | document.querySelector("article#" + article_id).focus(); 667 | let elm1 = document.querySelectorAll("div.single-article"); 668 | for (var i = 0; i < elm1.length; i++) { 669 | elm1[i].style.display = "block"; 670 | } 671 | 672 | let elm2 = document.querySelectorAll("div.article-list"); 673 | for (var i = 0; i < elm2.length; i++) { 674 | elm2[i].style.display = "noe"; 675 | } 676 | 677 | bottom_bar("options", "", "install"); 678 | window_status = "single-article"; 679 | } 680 | 681 | function open_options() { 682 | window_status = "options"; 683 | window.location.hash = "#options"; 684 | 685 | let elm = document.querySelectorAll("div.options"); 686 | for (var i = 0; i < elm.length; i++) { 687 | elm[i].style.display = "none"; 688 | } 689 | focused = 0; 690 | document.getElementById( 691 | "options-" + document.activeElement.getAttribute("data-slug") 692 | ).style.display = "block"; 693 | document 694 | .getElementById( 695 | "options-" + document.activeElement.getAttribute("data-slug") 696 | ) 697 | .children[0].focus(); 698 | 699 | bottom_bar("", "", ""); 700 | } 701 | 702 | //search listener 703 | 704 | document.querySelector("article#search").onfocus = function () { 705 | document.querySelector("article#search input").focus(); 706 | }; 707 | 708 | const search_listener = document.querySelector("article#search input"); 709 | 710 | search_listener.addEventListener("focus", (event) => { 711 | bottom_bar("scan", "select", "about"); 712 | window.scrollTo(0, 0); 713 | window_status = "search"; 714 | document.getElementById("navigation").style.display = "none"; 715 | document.querySelector("div#app").style.margin = "0px 0 0 0"; 716 | document.querySelector("article#search input").style.border = 717 | "2px inset silver"; 718 | }); 719 | 720 | search_listener.addEventListener("blur", (event) => { 721 | bottom_bar("", "select", "about"); 722 | window_status = "article-list"; 723 | document.querySelector("article#search").style.background = "gray"; 724 | document.querySelector("article#search input").style.border = "none"; 725 | }); 726 | 727 | //////////////////// 728 | ///Read Rating///// 729 | /////////////////// 730 | 731 | function ratings_callback(data) { 732 | if (data.ratings.length > 0) { 733 | let ofind = apps_data.find((o) => o.slug === data.appid); 734 | if (ofind) ofind["rating"] = data.ratings; 735 | } 736 | } 737 | 738 | ////////////////// 739 | //download app/// 740 | ///////////////// 741 | 742 | function install_app() { 743 | if (!offline) { 744 | install.download_file( 745 | document.activeElement.getAttribute("data-download"), 746 | document.activeElement.getAttribute("data-slug") 747 | ); 748 | } 749 | } 750 | 751 | function open_url() { 752 | let url = document.activeElement.getAttribute("data-url"); 753 | if (url.startsWith("mailto:")) { 754 | var mail = new MozActivity({ 755 | name: "view", 756 | data: { 757 | type: "url", 758 | url: url, 759 | }, 760 | }); 761 | mail.onerror = () => { 762 | console.log("Error: " + this.error, 3000); 763 | }; 764 | return; 765 | } 766 | window.open(document.activeElement.getAttribute("data-url"), "_self "); 767 | } 768 | 769 | ///launch app after installation 770 | 771 | function launch_app() { 772 | var request = window.navigator.mozApps.mgmt.getAll(); 773 | request.onerror = function (e) { 774 | console.log("Error calling getInstalled: " + request.error.name); 775 | }; 776 | request.onsuccess = function (e) { 777 | var appsRecord = request.result; 778 | appsRecord[appsRecord.length - 1].launch(); 779 | }; 780 | } 781 | 782 | jQuery(function () { 783 | ////////////////////////// 784 | ////KEYPAD TRIGGER//////////// 785 | ///////////////////////// 786 | 787 | function handleKeyDown(evt) { 788 | const isInSearchField = evt.target.id == "search" && evt.target.value != ""; 789 | 790 | if ("0123456789".search(evt.key) !== -1) { 791 | potato += evt.key; 792 | if (potato.search("768286") !== -1) { 793 | alert("You are a POTATO!"); 794 | potato = ""; 795 | } 796 | if (potato.length > 12) { 797 | potato = ""; 798 | } 799 | } 800 | 801 | switch (evt.key) { 802 | case "Enter": 803 | if ( 804 | window.location.hash == "about" || 805 | window.location.hash == "search" || 806 | window.location.hash == "scan" 807 | ) { 808 | break; 809 | } 810 | if (window.location.hash == "#list" || window.location.hash == "") { 811 | window.location.hash = 812 | "#article/" + document.activeElement.getAttribute("data-slug"); 813 | } 814 | 815 | if (window.location.hash == "#options") { 816 | if (document.activeElement.hasAttribute("data-slug")) { 817 | window.location.hash = "#rating"; 818 | } 819 | if (document.activeElement.hasAttribute("data-url")) { 820 | open_url(); 821 | } 822 | } 823 | break; 824 | 825 | case "ArrowDown": 826 | if ( 827 | window.location.hash == "#about" || 828 | window.location.hash == "#scan" 829 | ) { 830 | break; 831 | } 832 | 833 | if (window.location.hash.includes("#article")) { 834 | document.querySelector("div#app-panels-inner").scrollBy(0, 15); 835 | break; 836 | } 837 | 838 | if ( 839 | window.location.hash !== "#scan" || 840 | window.location.hash !== "#about" || 841 | window.location.hash == "" 842 | ) { 843 | nav("+1"); 844 | break; 845 | } 846 | 847 | break; 848 | 849 | case "ArrowUp": 850 | if (window.location.hash === "#scan") break; 851 | if (window.location.hash === "#about") break; 852 | 853 | if (window.location.hash.includes("#article")) { 854 | document.querySelector("div#app-panels-inner").scrollBy(0, -15); 855 | break; 856 | } 857 | 858 | if ( 859 | window.location.hash !== "#scan" || 860 | window.location.hash !== "#about" || 861 | window.location.hash === "" 862 | ) { 863 | nav("-1"); 864 | break; 865 | } 866 | 867 | break; 868 | 869 | case "ArrowLeft": 870 | if (window_status == "scan") break; 871 | 872 | if (isInSearchField) { 873 | evt.preventDefault; 874 | break; 875 | } 876 | if (evt.target.id == "search" && evt.target.value == "") { 877 | nav_panels("left"); 878 | break; 879 | } 880 | 881 | if (window_status == "article-list") { 882 | nav_panels("left"); 883 | } 884 | break; 885 | 886 | case "ArrowRight": 887 | if (window_status == "scan") break; 888 | 889 | if (isInSearchField) break; 890 | 891 | if (evt.target.id == "search" && evt.target.value == "") { 892 | nav_panels("right"); 893 | break; 894 | } 895 | if (window_status == "article-list") { 896 | nav_panels("right"); 897 | } 898 | 899 | break; 900 | 901 | case "8": 902 | case "SoftLeft": 903 | if (window_status == "about") break; 904 | if (window_status == "scan") break; 905 | 906 | if (window_status == "search") { 907 | window_status = "scan"; 908 | 909 | qr.start_scan(function (callback) { 910 | let slug = callback.replace("bhackers:", ""); 911 | show_article(slug); 912 | window_status = "single-article"; 913 | }); 914 | 915 | bottom_bar("", "", ""); 916 | break; 917 | } 918 | 919 | if (window_status == "rating") { 920 | //sanitizer 921 | let body = $("div#rating-wrapper textarea").val(), 922 | temp = document.createElement("div"); 923 | temp.innerHTML = body; 924 | let comment = temp.textContent || temp.innerText; 925 | send_rating( 926 | get_userId(), 927 | get_userId(), 928 | $("#" + article_id).data("slug"), 929 | $("#" + article_id).data("name"), 930 | Number(rating_stars), 931 | comment, 932 | rating_write_callback 933 | ); 934 | 935 | break; 936 | } 937 | 938 | if (window_status == "single-article") { 939 | open_options(); 940 | break; 941 | } 942 | 943 | case "9": 944 | case "SoftRight": 945 | if (window_status == "scan") break; 946 | 947 | if (window_status == "about") break; 948 | 949 | if (after_installation == true) { 950 | launch_app(); 951 | break; 952 | } 953 | 954 | if (window_status == "article-list" || window_status == "search") { 955 | //open_about(); 956 | window.location.hash = "#about"; 957 | 958 | break; 959 | } 960 | 961 | if (window.location.hash.includes("#article")) { 962 | install_app(); 963 | break; 964 | } 965 | 966 | if ((window_status = "rating")) { 967 | close_rating(); 968 | break; 969 | } 970 | 971 | break; 972 | 973 | case "Backspace": 974 | if (window_status == "scan") { 975 | evt.preventDefault(); 976 | qr.stop_scan("search"); 977 | show_article_list(); 978 | break; 979 | } 980 | 981 | if (isInSearchField) break; 982 | if (evt.target.id == "search" && evt.target.value == "") { 983 | evt.preventDefault(); 984 | $("article:not(article#search)").css("display", "block"); 985 | } 986 | 987 | if (location.hash.includes("#article")) { 988 | evt.preventDefault(); 989 | window.location.hash = "#list"; 990 | //show_article_list(); 991 | break; 992 | } 993 | 994 | if (window.location.hash == "#about") { 995 | evt.preventDefault(); 996 | window.location.hash = "#home"; 997 | break; 998 | } 999 | 1000 | if (window.location.hash == "#options") { 1001 | evt.preventDefault(); 1002 | window.location.hash = 1003 | "#article/" + document.activeElement.getAttribute("data-slug"); 1004 | close_options(); 1005 | break; 1006 | } 1007 | if ( 1008 | window.location.hash == "#list" && 1009 | !$("input#search").is(":focus") 1010 | ) { 1011 | window.close(); 1012 | } 1013 | break; 1014 | } 1015 | } 1016 | 1017 | document.addEventListener("keydown", handleKeyDown); 1018 | }); 1019 | -------------------------------------------------------------------------------- /application/assets/css/grid.css: -------------------------------------------------------------------------------- 1 | /* G R I D */ 2 | 3 | .grid-col-1 { 4 | min-width: 10px; 5 | max-width: 10px; 6 | } 7 | 8 | .grid-col-2 { 9 | min-width: 20px; 10 | max-width: 20px; 11 | } 12 | 13 | .grid-col-3 { 14 | min-width: 30px; 15 | max-width: 30px; 16 | } 17 | 18 | .grid-col-4 { 19 | min-width: 40px; 20 | max-width: 40px; 21 | } 22 | 23 | .grid-col-5 { 24 | min-width: 50px; 25 | max-width: 50px; 26 | } 27 | 28 | .grid-col-6 { 29 | min-width: 60px; 30 | max-width: 60px; 31 | } 32 | 33 | .grid-col-7 { 34 | min-width: 70px; 35 | max-width: 70px; 36 | } 37 | 38 | .grid-col-8 { 39 | min-width: 80px; 40 | max-width: 80px; 41 | } 42 | 43 | .grid-col-9 { 44 | min-width: 90px; 45 | max-width: 90px; 46 | } 47 | 48 | .grid-col-10 { 49 | min-width: 100px; 50 | max-width: 100px; 51 | } 52 | 53 | .grid-col-11 { 54 | min-width: 110px; 55 | max-width: 110px; 56 | } 57 | 58 | .grid-col-12 { 59 | min-width: 120px; 60 | max-width: 120px; 61 | } 62 | 63 | .grid-col-13 { 64 | min-width: 130px; 65 | max-width: 130px; 66 | } 67 | 68 | .grid-col-14 { 69 | min-width: 140px; 70 | max-width: 140px; 71 | } 72 | 73 | .grid-col-15 { 74 | min-width: 150px; 75 | max-width: 150px; 76 | } 77 | 78 | .grid-col-16 { 79 | min-width: 160px; 80 | max-width: 160px; 81 | } 82 | 83 | .grid-col-17 { 84 | min-width: 170px; 85 | max-width: 170px; 86 | } 87 | 88 | .grid-col-18 { 89 | min-width: 180px; 90 | max-width: 180px; 91 | } 92 | 93 | .grid-col-19 { 94 | min-width: 190px; 95 | max-width: 190px; 96 | } 97 | 98 | .grid-col-20 { 99 | min-width: 200px; 100 | max-width: 200px; 101 | } 102 | 103 | .grid-col-21 { 104 | min-width: 210px; 105 | max-width: 210px; 106 | } 107 | 108 | .grid-col-22 { 109 | min-width: 220px; 110 | max-width: 220px; 111 | } 112 | 113 | .grid-col-23 { 114 | min-width: 230px; 115 | max-width: 230px; 116 | } 117 | 118 | .grid-col-24 { 119 | min-width: 240px; 120 | max-width: 240px; 121 | } 122 | 123 | .grid-col-25 { 124 | min-width: 250px; 125 | max-width: 250px; 126 | } 127 | 128 | .grid-col-26 { 129 | min-width: 260px; 130 | max-width: 260px; 131 | } 132 | 133 | .grid-col-27 { 134 | min-width: 270px; 135 | max-width: 270px; 136 | } 137 | 138 | .grid-col-28 { 139 | min-width: 280px; 140 | max-width: 280px; 141 | } 142 | 143 | .grid-col-29 { 144 | min-width: 290px; 145 | max-width: 290px; 146 | } 147 | 148 | .grid-col-30 { 149 | min-width: 300px; 150 | max-width: 300px; 151 | } 152 | 153 | .grid-col-31 { 154 | min-width: 310px; 155 | max-width: 310px; 156 | } 157 | 158 | .grid-col-32 { 159 | min-width: 320px; 160 | max-width: 320px; 161 | } 162 | 163 | .grid-col-33 { 164 | min-width: 330px; 165 | max-width: 330px; 166 | } 167 | 168 | .grid-col-34 { 169 | min-width: 340px; 170 | max-width: 340px; 171 | } 172 | 173 | .grid-col-35 { 174 | min-width: 350px; 175 | max-width: 350px; 176 | } 177 | 178 | .grid-col-36 { 179 | min-width: 360px; 180 | max-width: 360px; 181 | } 182 | 183 | .grid-col-37 { 184 | min-width: 370px; 185 | max-width: 370px; 186 | } 187 | 188 | .grid-col-38 { 189 | min-width: 380px; 190 | max-width: 380px; 191 | } 192 | 193 | .grid-col-39 { 194 | min-width: 390px; 195 | max-width: 390px; 196 | } 197 | 198 | .grid-col-40 { 199 | min-width: 400px; 200 | max-width: 400px; 201 | } 202 | 203 | .grid-col-41 { 204 | min-width: 410px; 205 | max-width: 410px; 206 | } 207 | 208 | .grid-col-42 { 209 | min-width: 420px; 210 | max-width: 420px; 211 | } 212 | 213 | .grid-col-43 { 214 | min-width: 430px; 215 | max-width: 430px; 216 | } 217 | 218 | .grid-col-44 { 219 | min-width: 440px; 220 | max-width: 440px; 221 | } 222 | 223 | .grid-col-45 { 224 | min-width: 450px; 225 | max-width: 450px; 226 | } 227 | 228 | .grid-col-46 { 229 | min-width: 460px; 230 | max-width: 460px; 231 | } 232 | 233 | .grid-col-47 { 234 | min-width: 470px; 235 | max-width: 470px; 236 | } 237 | 238 | .grid-col-48 { 239 | min-width: 480px; 240 | max-width: 480px; 241 | } 242 | 243 | .grid-col-49 { 244 | min-width: 490px; 245 | max-width: 490px; 246 | } 247 | 248 | .grid-col-50 { 249 | min-width: 500px; 250 | max-width: 500px; 251 | } 252 | 253 | .grid-col-51 { 254 | min-width: 510px; 255 | max-width: 510px; 256 | } 257 | 258 | .grid-col-52 { 259 | min-width: 520px; 260 | max-width: 520px; 261 | } 262 | 263 | .grid-col-53 { 264 | min-width: 530px; 265 | max-width: 530px; 266 | } 267 | 268 | .grid-col-54 { 269 | min-width: 540px; 270 | max-width: 540px; 271 | } 272 | 273 | .grid-col-55 { 274 | min-width: 550px; 275 | max-width: 550px; 276 | } 277 | 278 | .grid-col-56 { 279 | min-width: 560px; 280 | max-width: 560px; 281 | } 282 | 283 | .grid-col-57 { 284 | min-width: 570px; 285 | max-width: 570px; 286 | } 287 | 288 | .grid-col-58 { 289 | min-width: 580px; 290 | max-width: 580px; 291 | } 292 | 293 | .grid-col-59 { 294 | min-width: 590px; 295 | max-width: 590px; 296 | } 297 | 298 | .grid-col-60 { 299 | min-width: 600px; 300 | max-width: 600px; 301 | } 302 | 303 | .grid-col-61 { 304 | min-width: 610px; 305 | max-width: 610px; 306 | } 307 | 308 | .grid-col-62 { 309 | min-width: 620px; 310 | max-width: 620px; 311 | } 312 | 313 | .grid-col-63 { 314 | min-width: 630px; 315 | max-width: 630px; 316 | } 317 | 318 | .grid-col-64 { 319 | min-width: 640px; 320 | max-width: 640px; 321 | } 322 | 323 | .grid-col-65 { 324 | min-width: 650px; 325 | max-width: 650px; 326 | } 327 | 328 | .grid-col-66 { 329 | min-width: 660px; 330 | max-width: 660px; 331 | } 332 | 333 | .grid-col-67 { 334 | min-width: 670px; 335 | max-width: 670px; 336 | } 337 | 338 | .grid-col-68 { 339 | min-width: 680px; 340 | max-width: 680px; 341 | } 342 | 343 | .grid-col-69 { 344 | min-width: 690px; 345 | max-width: 690px; 346 | } 347 | 348 | .grid-col-70 { 349 | min-width: 700px; 350 | max-width: 700px; 351 | } 352 | 353 | .grid-col-71 { 354 | min-width: 710px; 355 | max-width: 710px; 356 | } 357 | 358 | .grid-col-72 { 359 | min-width: 720px; 360 | max-width: 720px; 361 | } 362 | 363 | .grid-col-73 { 364 | min-width: 730px; 365 | max-width: 730px; 366 | } 367 | 368 | .grid-col-74 { 369 | min-width: 740px; 370 | max-width: 740px; 371 | } 372 | 373 | .grid-col-75 { 374 | min-width: 750px; 375 | max-width: 750px; 376 | } 377 | 378 | .grid-col-76 { 379 | min-width: 760px; 380 | max-width: 760px; 381 | } 382 | 383 | .grid-col-77 { 384 | min-width: 770px; 385 | max-width: 770px; 386 | } 387 | 388 | .grid-col-78 { 389 | min-width: 780px; 390 | max-width: 780px; 391 | } 392 | 393 | .grid-col-79 { 394 | min-width: 790px; 395 | max-width: 790px; 396 | } 397 | 398 | .grid-col-80 { 399 | min-width: 800px; 400 | max-width: 800px; 401 | } 402 | 403 | .grid-col-81 { 404 | min-width: 810px; 405 | max-width: 810px; 406 | } 407 | 408 | .grid-col-82 { 409 | min-width: 820px; 410 | max-width: 820px; 411 | } 412 | 413 | .grid-col-83 { 414 | min-width: 830px; 415 | max-width: 830px; 416 | } 417 | 418 | .grid-col-84 { 419 | min-width: 840px; 420 | max-width: 840px; 421 | } 422 | 423 | .grid-col-85 { 424 | min-width: 850px; 425 | max-width: 850px; 426 | } 427 | 428 | .grid-col-86 { 429 | min-width: 860px; 430 | max-width: 860px; 431 | } 432 | 433 | .grid-col-87 { 434 | min-width: 870px; 435 | max-width: 870px; 436 | } 437 | 438 | .grid-col-88 { 439 | min-width: 880px; 440 | max-width: 880px; 441 | } 442 | 443 | .grid-col-88 { 444 | min-width: 880px; 445 | max-width: 880px; 446 | } 447 | 448 | .grid-col-89 { 449 | min-width: 890px; 450 | max-width: 890px; 451 | } 452 | 453 | .grid-col-90 { 454 | min-width: 900px; 455 | max-width: 900px; 456 | } 457 | 458 | .grid-col-91 { 459 | min-width: 910px; 460 | max-width: 910px; 461 | } 462 | 463 | .grid-col-92 { 464 | min-width: 920px; 465 | max-width: 920px; 466 | } 467 | 468 | .grid-col-93 { 469 | min-width: 930px; 470 | max-width: 930px; 471 | } 472 | 473 | .grid-col-94 { 474 | min-width: 940px; 475 | max-width: 940px; 476 | } 477 | 478 | .grid-col-95 { 479 | min-width: 950px; 480 | max-width: 950px; 481 | } 482 | 483 | .grid-col-96 { 484 | min-width: 960px; 485 | max-width: 960px; 486 | } 487 | 488 | .text-center { 489 | text-align: center; 490 | } 491 | 492 | .flex { 493 | display: -webkit-flex; 494 | display: flex; 495 | -webkit-flex-direction: row; 496 | flex-direction: row; 497 | -webkit-flex-wrap: wrap; 498 | flex-wrap: wrap; 499 | } 500 | 501 | .flex-column { 502 | -webkit-flex-direction: column; 503 | -ms-flex-direction: column; 504 | flex-direction: column; 505 | } 506 | 507 | .flex-shrink-1 { 508 | //flex: 1 1 509 | min-width: 50%; 510 | max-width: 50%; 511 | outline: 2px solid red; 512 | } 513 | 514 | .justify-content-start { 515 | -webkit-align-items: flex-start; 516 | align-items: flex-start; 517 | } 518 | 519 | .justify-content-end { 520 | -webkit-justify-content: flex-end; 521 | justify-content: flex-end; 522 | } 523 | 524 | .algin-item-start { 525 | -webkit-align-items: flex-start; 526 | align-items: flex-start; 527 | } 528 | 529 | .algin-item-end { 530 | -webkit-align-items: flex-end; 531 | align-items: flex-end; 532 | } 533 | 534 | .align-item-center { 535 | -webkit-align-items: center; 536 | -ms-flex-align: center; 537 | align-items: center; 538 | } 539 | 540 | .justify-content-spacebetween { 541 | -webkit-justify-content: space-between; 542 | justify-content: space-between; 543 | } 544 | 545 | .justify-content-spacearound { 546 | -webkit-justify-content: space-around; 547 | -ms-flex-pack: distribute; 548 | justify-content: space-around; 549 | -webkit-align-content: space-around; 550 | -ms-flex-line-pack: distribute; 551 | align-content: space-around; 552 | } 553 | 554 | .justify-content-center { 555 | -webkit-justify-content: center; 556 | -ms-flex-pack: center; 557 | justify-content: center; 558 | } 559 | 560 | .grow-1 { 561 | flex-grow: 1; 562 | } 563 | 564 | .width-100 { 565 | min-width: 100vw; 566 | } 567 | 568 | .height-100 { 569 | min-height: 100vh; 570 | } 571 | 572 | .width-50 { 573 | min-width: 50%; 574 | } 575 | 576 | .width-30 { 577 | min-width: 30%; 578 | max-width: 30%; 579 | } 580 | 581 | .center { 582 | transform: translate(50%, -0%); 583 | } 584 | 585 | .col-3 { 586 | -webkit-column-count: 3; 587 | -moz-column-count: 3; 588 | column-count: 3; 589 | -webkit-column-count: 3; 590 | -webkit-column-gap: 30px; 591 | -moz-column-count: 3; 592 | -moz-column-gap: 30px; 593 | column-count: 3; 594 | column-gap: 30px; 595 | } 596 | 597 | .col-dont-break-inside { 598 | -webkit-column-break-inside: avoid; 599 | /* Chrome, Safari, Opera */ 600 | page-break-inside: avoid; 601 | /* Firefox */ 602 | break-inside: avoid; 603 | /* IE 10+ */ 604 | } 605 | 606 | .block { 607 | display: block; 608 | } 609 | 610 | [class*="grid-"] { 611 | } 612 | -------------------------------------------------------------------------------- /application/assets/css/main.css: -------------------------------------------------------------------------------- 1 | /*GENERAL*/ 2 | 3 | :root { 4 | --color-one: black; 5 | --color-two: yellow; 6 | --color-three: silver; 7 | --color-four: rgb(99, 99, 99); 8 | --color-five: rgb(38, 38, 38); 9 | --color-six: rgb(78, 206, 144); 10 | --color-seven: gainsboro; 11 | scrollbar-color: #0a4c95 #c2d2e4; 12 | } 13 | 14 | @font-face { 15 | font-family: "Lato-Regular"; 16 | src: url("../fonts/Lato-Regular.ttf"); 17 | } 18 | 19 | *, 20 | *:before, 21 | *:after { 22 | border: 0px; 23 | padding: 0px; 24 | margin: 0px; 25 | box-sizing: border-box; 26 | -webkit-appearance: none; 27 | } 28 | 29 | :focus { 30 | outline: none; 31 | } 32 | 33 | ::-moz-focus-inner { 34 | border: 0; 35 | } 36 | 37 | img[src=""] { 38 | display: none; 39 | } 40 | 41 | input[type="range"]::-moz-focus-outer { 42 | border: 0; 43 | } 44 | 45 | html, 46 | body { 47 | font-family: "Lato-Regular"; 48 | font-weight: 100; 49 | width: 100vw; 50 | max-width: 100vw; 51 | max-height: 100vh; 52 | height: 100%; 53 | position: relative; 54 | margin: 0px; 55 | padding: 0px; 56 | font-size: 1rem; 57 | line-height: 1.4rem; 58 | overflow: hidden; 59 | } 60 | 61 | h1 { 62 | font-size: 1rem; 63 | } 64 | 65 | img { 66 | max-width: 90vw; 67 | height: auto; 68 | display: block; 69 | } 70 | 71 | .debug { 72 | outline: 1px solid red; 73 | } 74 | 75 | div.autocomplete-suggestions { 76 | background: gray; 77 | overflow: auto; 78 | color: black; 79 | padding: 5px 0px 0px 0; 80 | white-space: nowrap; 81 | overflow: hidden; 82 | font-size: 1.1rem; 83 | } 84 | 85 | div.no-result { 86 | position: fixed; 87 | top: 90px; 88 | border: 3px solid white; 89 | background: silver; 90 | padding: 3px; 91 | width: 90%; 92 | left: 5%; 93 | } 94 | 95 | div.autocomplete-suggestions div { 96 | border: 3px solid white; 97 | background: silver; 98 | } 99 | 100 | .autocomplete-selected { 101 | background: silver; 102 | color: black; 103 | font-weight: bold; 104 | } 105 | 106 | .autocomplete-suggestions strong { 107 | font-weight: normal; 108 | } 109 | 110 | .autocomplete-group { 111 | padding: 2px 5px; 112 | color: black; 113 | margin: 0 0 10px 0; 114 | } 115 | 116 | .autocomplete-group strong { 117 | display: block; 118 | border-bottom: 1px solid #000; 119 | } 120 | 121 | /*/////////////////////////// 122 | ///HELPER////////////////*/ 123 | 124 | div#toast { 125 | position: fixed; 126 | height: auto; 127 | overflow: none; 128 | top: -100px; 129 | background: black; 130 | color: white; 131 | z-index: 5000; 132 | min-width: 100%; 133 | padding: 5px; 134 | } 135 | 136 | /*/////////////////////////// 137 | ///APPS////////////////*/ 138 | 139 | div#panels-indicator { 140 | height: 5px; 141 | position: fixed; 142 | z-index: 3000000; 143 | background-color: black; 144 | bottom: 30px; 145 | max-width: 100%; 146 | padding: 0px 0 0 0; 147 | } 148 | 149 | div#panels-indicator div { 150 | position: relative; 151 | width: 100%; 152 | display: flex; 153 | } 154 | 155 | div#panels-indicator div div { 156 | background-color: black; 157 | width: 7px; 158 | height: 8px; 159 | flex-grow: 1; 160 | } 161 | 162 | div#panels-indicator div div:first-child { 163 | margin: 0 0 0 -10px; 164 | } 165 | 166 | div#navigation { 167 | position: fixed; 168 | height: 30px; 169 | background: black; 170 | width: 100%; 171 | top: 0px; 172 | left: 0px; 173 | display: none; 174 | } 175 | 176 | div#navigation div { 177 | height: 20px; 178 | color: yellow; 179 | text-align: center; 180 | } 181 | 182 | div#about { 183 | position: fixed; 184 | top: 0; 185 | left: 0; 186 | background: white; 187 | z-index: 1; 188 | overflow-y: scroll; 189 | height: 100%; 190 | margin: 0 0 50px 0; 191 | display: none; 192 | } 193 | 194 | div#about div#inner { 195 | position: relative; 196 | padding: 5px 5px 55px 5px; 197 | margin: 0 0 80px 0; 198 | overflow-y: scroll; 199 | height: 98%; 200 | background: silver; 201 | } 202 | 203 | div#about div#update { 204 | width: 100%; 205 | height: 20px; 206 | background: black; 207 | text-align: center; 208 | position: absolute; 209 | bottom: 0; 210 | left: 0; 211 | color: white; 212 | } 213 | 214 | div#about h1 { 215 | margin: 15px 0 0 0; 216 | text-align: center; 217 | font-size: 1rem; 218 | } 219 | 220 | div#about h2 { 221 | margin: 15px 0 0 0; 222 | text-align: center; 223 | font-size: 1rem; 224 | } 225 | 226 | div#app { 227 | position: relative; 228 | background: white; 229 | padding: 0px; 230 | color: black; 231 | height: 95vh; 232 | width: 100vw; 233 | overflow: hidden; 234 | font-weight: normal; 235 | margin: 0px 0 0 0; 236 | } 237 | 238 | div#app-panels div#app-panels-inner { 239 | height: 84vh; 240 | overflow-y: hidden; 241 | overflow-x: hidden; 242 | } 243 | 244 | div#app div#app-panels article { 245 | margin: 0px 0 10px 0px; 246 | font-weight: normal; 247 | min-width: 100vw; 248 | max-height: 97%; 249 | padding: 4px 4px 4px 4px; 250 | overflow-y: hidden; 251 | overflow-x: hidden; 252 | position: relative; 253 | opacity: 0; 254 | } 255 | 256 | article#search { 257 | font-weight: normal; 258 | min-width: 100vw; 259 | height: 47px; 260 | background: silver; 261 | overflow: hidden; 262 | text-align: center; 263 | } 264 | 265 | article#search input { 266 | margin: 5px 0 0 0; 267 | height: 30px; 268 | padding: 3px; 269 | width: 70%; 270 | overflow: hidden; 271 | border-radius: 5px; 272 | } 273 | 274 | div#app div#app-panels { 275 | margin: 0px 0 50px 0px; 276 | position: relative; 277 | } 278 | 279 | div#app div#app-panels article h1 { 280 | font-size: 1rem; 281 | font-weight: bold; 282 | } 283 | 284 | div#app div#app-panels article div.meta-data { 285 | font-size: 0.8rem; 286 | } 287 | 288 | div#app div#app-panels article div.meta-data span { 289 | font-size: 0.8rem; 290 | font-weight: bold; 291 | } 292 | 293 | div#app div#app-panels article div.icon { 294 | margin: 0 0 10px 0; 295 | } 296 | 297 | div#app div#app-panels article div.icon img { 298 | width: 30px; 299 | margin: 7px 0 7px 0 !important; 300 | } 301 | 302 | div#app div#app-panels article div.channel { 303 | font-size: 0.8rem; 304 | font-weight: bold; 305 | color: rgb(2, 2, 43); 306 | } 307 | 308 | div#app div#app-panels article div.summary { 309 | padding: 0 10px 20px 0px; 310 | } 311 | 312 | div.single-article { 313 | display: none; 314 | } 315 | 316 | div#app div#app-panels article ul.images { 317 | padding: 20px 0 20px 0; 318 | display: block; 319 | } 320 | 321 | div#app div#app-panels article ul.images li { 322 | margin: 0 0 10px 0; 323 | min-width: 100%; 324 | } 325 | 326 | div#app div#app-panels article ul.images li img { 327 | min-width: 100%; 328 | display: block; 329 | } 330 | 331 | ul.ratings { 332 | display: none; 333 | } 334 | 335 | li.rating-description { 336 | margin: 0 0 10px 0; 337 | } 338 | 339 | li.rating-points-0:after { 340 | content: ""; 341 | } 342 | 343 | li.rating-points-1:after { 344 | content: "★"; 345 | } 346 | 347 | li.rating-points-2:after { 348 | content: "★ ★"; 349 | } 350 | 351 | li.rating-points-3:after { 352 | content: "★ ★ ★"; 353 | } 354 | 355 | li.rating-points-4:after { 356 | content: "★ ★ ★ ★"; 357 | } 358 | 359 | li.rating-points-5:after { 360 | content: "★ ★ ★ ★ ★"; 361 | } 362 | 363 | /*//////////// 364 | ///options 365 | ///////////*/ 366 | 367 | div.options { 368 | width: 100vw; 369 | height: 100vh; 370 | background: black; 371 | position: fixed; 372 | top: 0px; 373 | left: 0px; 374 | display: none; 375 | padding: 10px; 376 | } 377 | 378 | div.options div { 379 | width: 90vw; 380 | height: 30px; 381 | background: white; 382 | color: black; 383 | position: relative; 384 | padding: 3px; 385 | border: 3px solid black; 386 | margin: 0px auto; 387 | display: none; 388 | } 389 | 390 | div.options div[data-url] { 391 | display: block; 392 | } 393 | 394 | div.options div[data-donation] { 395 | display: block; 396 | } 397 | 398 | div.options div[data-slug] { 399 | display: block; 400 | } 401 | 402 | div.options div[data-url=""] { 403 | display: none; 404 | } 405 | 406 | div.options div[data-donation=""] { 407 | display: none; 408 | } 409 | 410 | div.options div:focus { 411 | background: silver; 412 | } 413 | 414 | /*///////////////// 415 | ///RATING//// 416 | ////////////////*/ 417 | 418 | div#rating-wrapper { 419 | display: none; 420 | position: fixed; 421 | top: 0; 422 | left: 0; 423 | z-index: 2; 424 | background: black; 425 | color: white; 426 | padding: 10px; 427 | } 428 | 429 | div#rating-wrapper textarea.text { 430 | margin: 50px 0 0 0; 431 | padding: 5px; 432 | width: 100%; 433 | } 434 | 435 | div#rating-wrapper input { 436 | position: fixed; 437 | top: -600px; 438 | } 439 | 440 | div#rating-wrapper div#stars { 441 | position: absolute; 442 | top: 10px; 443 | } 444 | 445 | div.rating-item { 446 | margin: 0 0 30px 0; 447 | } 448 | 449 | div.rating-item div div { 450 | height: 15px; 451 | color: black; 452 | font-size: 1.1rem; 453 | margin: 0 0 5px 0; 454 | } 455 | 456 | div.rating-item { 457 | font-size: 0.8rem; 458 | } 459 | 460 | div.points-1 { 461 | content: "hello"; 462 | padding: 2px; 463 | } 464 | 465 | div.points-2 { 466 | content: "hello"; 467 | } 468 | 469 | div.points-3 { 470 | content: "•"; 471 | } 472 | 473 | div.points-4 { 474 | content: "hello"; 475 | } 476 | 477 | /*///////////////// 478 | ///BOTTOM BAR//// 479 | ////////////////*/ 480 | 481 | div#bottom-bar { 482 | position: fixed; 483 | z-index: 2000; 484 | bottom: 10px; 485 | left: 0px; 486 | height: 18px; 487 | z-index: 2; 488 | background: black; 489 | } 490 | 491 | div#bottom-bar div#inner { 492 | position: relative; 493 | } 494 | 495 | div#bottom-bar div { 496 | background: black; 497 | color: white; 498 | padding: 2px; 499 | } 500 | 501 | div#bottom-bar div#button-center { 502 | background: black; 503 | color: white; 504 | padding: 2px; 505 | width: 30%; 506 | text-align: center; 507 | } 508 | 509 | div#bottom-bar div#button-left { 510 | background: black; 511 | color: white; 512 | padding: 2px; 513 | width: 20px; 514 | text-align: left; 515 | width: 30%; 516 | } 517 | 518 | div#bottom-bar div#button-right { 519 | background: black; 520 | color: white; 521 | padding: 2px; 522 | width: 30%; 523 | text-align: right; 524 | } 525 | 526 | /*///////////////// 527 | ///INTRO//// 528 | ////////////////*/ 529 | 530 | div#message-box { 531 | animation-name: example3; 532 | display: block; 533 | position: fixed; 534 | top: 0px; 535 | left: 0px; 536 | padding: 40px 10px 10px 10px; 537 | min-width: 100vw; 538 | height: 100vh; 539 | background: black; 540 | color: white; 541 | z-index: 3; 542 | animation-duration: 2s; 543 | animation-timing-function: ease-in; 544 | animation-fill-mode: forwards; 545 | animation-iteration-count: 1; 546 | animation-play-state: paused; 547 | } 548 | 549 | div#message-box img.icon-1 { 550 | animation-name: example; 551 | animation-duration: 1s; 552 | animation-timing-function: ease-out; 553 | animation-fill-mode: forwards; 554 | animation-play-state: paused; 555 | position: absolute; 556 | width: 90px; 557 | left: 30%; 558 | top: 40%; 559 | } 560 | 561 | div#message-box img.icon-2 { 562 | animation-name: example2; 563 | animation-duration: 1s; 564 | animation-timing-function: ease-out; 565 | animation-fill-mode: forwards; 566 | position: absolute; 567 | animation-play-state: paused; 568 | transform: rotate(120deg); 569 | width: 90px; 570 | z-index: 5; 571 | left: 30%; 572 | top: 40%; 573 | } 574 | 575 | @keyframes example { 576 | from { 577 | left: 30%; 578 | top: 40%; 579 | } 580 | to { 581 | left: 200px; 582 | top: 300px; 583 | } 584 | } 585 | 586 | @keyframes example2 { 587 | from { 588 | left: 30%; 589 | top: 40%; 590 | } 591 | to { 592 | left: -100px; 593 | top: 300px; 594 | } 595 | } 596 | 597 | @keyframes example3 { 598 | from { 599 | opacity: 100; 600 | } 601 | to { 602 | opacity: 0; 603 | } 604 | } 605 | 606 | /*///Loading bar//////// 607 | //////////////////////*/ 608 | 609 | div#loading { 610 | height: 15px; 611 | width: 100vw; 612 | background: pink; 613 | position: fixed; 614 | top: 0; 615 | z-index: 10000; 616 | display: none; 617 | animation: loading 1s infinite alternate; 618 | } 619 | 620 | @keyframes loading { 621 | from { 622 | background-color: blue; 623 | width: 10px; 624 | } 625 | to { 626 | background-color: yellow; 627 | width: 100vw; 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /application/assets/css/qr_reader.css: -------------------------------------------------------------------------------- 1 | #video { 2 | bottom: 0; 3 | height: auto; 4 | min-height: 100%; 5 | min-width: 100%; 6 | position: fixed; 7 | right: 0; 8 | width: auto; 9 | } 10 | 11 | #startDecoding { 12 | background: #0095dd; 13 | border-radius: 40px; 14 | color: #fff; 15 | font-family: Arial, sans; 16 | font-size: 20px; 17 | left: 50%; 18 | margin: -30px 0 0 -95px; 19 | padding: 20px; 20 | position: fixed; 21 | text-align: center; 22 | top: 50%; 23 | width: 150px; 24 | } 25 | 26 | #corner-nw, 27 | #corner-no, 28 | #corner-so, 29 | #corner-sw { 30 | border: #fff 6px solid; 31 | height: 30px; 32 | position: fixed; 33 | width: 30px; 34 | } 35 | 36 | #corner-nw { 37 | border-bottom: 0; 38 | border-right: 0; 39 | border-top-left-radius: 20px; 40 | left: 20px; 41 | top: 20px; 42 | } 43 | 44 | #corner-no { 45 | border-bottom: 0; 46 | border-left: 0; 47 | border-top-right-radius: 20px; 48 | right: 20px; 49 | top: 20px; 50 | } 51 | 52 | #corner-so { 53 | border-bottom-right-radius: 20px; 54 | border-left: 0; 55 | border-top: 0; 56 | bottom: 20px; 57 | right: 20px; 58 | } 59 | 60 | #corner-sw { 61 | border-bottom-left-radius: 20px; 62 | border-right: 0; 63 | border-top: 0; 64 | bottom: 20px; 65 | left: 20px; 66 | } 67 | -------------------------------------------------------------------------------- /application/assets/exclude-js/BitMatrix.d.ts: -------------------------------------------------------------------------------- 1 | export declare class BitMatrix { 2 | static createEmpty(width: number, height: number): BitMatrix; 3 | width: number; 4 | height: number; 5 | private data; 6 | constructor(data: Uint8ClampedArray, width: number); 7 | get(x: number, y: number): boolean; 8 | set(x: number, y: number, v: boolean): void; 9 | setRegion( 10 | left: number, 11 | top: number, 12 | width: number, 13 | height: number, 14 | v: boolean 15 | ): void; 16 | } 17 | -------------------------------------------------------------------------------- /application/assets/exclude-js/binarizer/index.d.ts: -------------------------------------------------------------------------------- 1 | import { BitMatrix } from "../BitMatrix"; 2 | export declare function binarize( 3 | data: Uint8ClampedArray, 4 | width: number, 5 | height: number, 6 | returnInverted: boolean 7 | ): 8 | | { 9 | binarized: BitMatrix; 10 | inverted: BitMatrix; 11 | } 12 | | { 13 | binarized: BitMatrix; 14 | inverted?: undefined; 15 | }; 16 | -------------------------------------------------------------------------------- /application/assets/exclude-js/decoder/decodeData/BitStream.d.ts: -------------------------------------------------------------------------------- 1 | export declare class BitStream { 2 | private bytes; 3 | private byteOffset; 4 | private bitOffset; 5 | constructor(bytes: Uint8ClampedArray); 6 | readBits(numBits: number): number; 7 | available(): number; 8 | } 9 | -------------------------------------------------------------------------------- /application/assets/exclude-js/decoder/decodeData/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface Chunk { 2 | type: Mode; 3 | text: string; 4 | } 5 | export interface ByteChunk { 6 | type: Mode.Byte | Mode.Kanji; 7 | bytes: number[]; 8 | } 9 | export interface ECIChunk { 10 | type: Mode.ECI; 11 | assignmentNumber: number; 12 | } 13 | export declare type Chunks = Array; 14 | export interface DecodedQR { 15 | text: string; 16 | bytes: number[]; 17 | chunks: Chunks; 18 | } 19 | export declare enum Mode { 20 | Numeric = "numeric", 21 | Alphanumeric = "alphanumeric", 22 | Byte = "byte", 23 | Kanji = "kanji", 24 | ECI = "eci", 25 | } 26 | export declare function decode( 27 | data: Uint8ClampedArray, 28 | version: number 29 | ): DecodedQR; 30 | -------------------------------------------------------------------------------- /application/assets/exclude-js/decoder/decodeData/shiftJISTable.d.ts: -------------------------------------------------------------------------------- 1 | export declare const shiftJISTable: { 2 | [key: number]: number; 3 | }; 4 | -------------------------------------------------------------------------------- /application/assets/exclude-js/decoder/decoder.d.ts: -------------------------------------------------------------------------------- 1 | import { BitMatrix } from "../BitMatrix"; 2 | import { DecodedQR } from "./decodeData"; 3 | export declare function decode(matrix: BitMatrix): DecodedQR; 4 | -------------------------------------------------------------------------------- /application/assets/exclude-js/decoder/reedsolomon/GenericGF.d.ts: -------------------------------------------------------------------------------- 1 | import GenericGFPoly from "./GenericGFPoly"; 2 | export declare function addOrSubtractGF(a: number, b: number): number; 3 | export default class GenericGF { 4 | primitive: number; 5 | size: number; 6 | generatorBase: number; 7 | zero: GenericGFPoly; 8 | one: GenericGFPoly; 9 | private expTable; 10 | private logTable; 11 | constructor(primitive: number, size: number, genBase: number); 12 | multiply(a: number, b: number): number; 13 | inverse(a: number): number; 14 | buildMonomial(degree: number, coefficient: number): GenericGFPoly; 15 | log(a: number): number; 16 | exp(a: number): number; 17 | } 18 | -------------------------------------------------------------------------------- /application/assets/exclude-js/decoder/reedsolomon/GenericGFPoly.d.ts: -------------------------------------------------------------------------------- 1 | import GenericGF from "./GenericGF"; 2 | export default class GenericGFPoly { 3 | private field; 4 | private coefficients; 5 | constructor(field: GenericGF, coefficients: Uint8ClampedArray); 6 | degree(): number; 7 | isZero(): boolean; 8 | getCoefficient(degree: number): number; 9 | addOrSubtract(other: GenericGFPoly): GenericGFPoly; 10 | multiply(scalar: number): GenericGFPoly; 11 | multiplyPoly(other: GenericGFPoly): GenericGFPoly; 12 | multiplyByMonomial(degree: number, coefficient: number): GenericGFPoly; 13 | evaluateAt(a: number): number; 14 | } 15 | -------------------------------------------------------------------------------- /application/assets/exclude-js/decoder/reedsolomon/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare function decode( 2 | bytes: number[], 3 | twoS: number 4 | ): Uint8ClampedArray; 5 | -------------------------------------------------------------------------------- /application/assets/exclude-js/decoder/version.d.ts: -------------------------------------------------------------------------------- 1 | export interface Version { 2 | infoBits: number; 3 | versionNumber: number; 4 | alignmentPatternCenters: number[]; 5 | errorCorrectionLevels: Array<{ 6 | ecCodewordsPerBlock: number; 7 | ecBlocks: Array<{ 8 | numBlocks: number; 9 | dataCodewordsPerBlock: number; 10 | }>; 11 | }>; 12 | } 13 | export declare const VERSIONS: Version[]; 14 | -------------------------------------------------------------------------------- /application/assets/exclude-js/extractor/index.d.ts: -------------------------------------------------------------------------------- 1 | import { BitMatrix } from "../BitMatrix"; 2 | import { QRLocation } from "../locator"; 3 | export declare function extract( 4 | image: BitMatrix, 5 | location: QRLocation 6 | ): { 7 | matrix: BitMatrix; 8 | mappingFunction: ( 9 | x: number, 10 | y: number 11 | ) => { 12 | x: number; 13 | y: number; 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /application/assets/exclude-js/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Chunks } from "./decoder/decodeData"; 2 | import { Point } from "./locator"; 3 | export interface QRCode { 4 | binaryData: number[]; 5 | data: string; 6 | chunks: Chunks; 7 | location: { 8 | topRightCorner: Point; 9 | topLeftCorner: Point; 10 | bottomRightCorner: Point; 11 | bottomLeftCorner: Point; 12 | topRightFinderPattern: Point; 13 | topLeftFinderPattern: Point; 14 | bottomLeftFinderPattern: Point; 15 | bottomRightAlignmentPattern?: Point; 16 | }; 17 | } 18 | export interface Options { 19 | inversionAttempts?: 20 | | "dontInvert" 21 | | "onlyInvert" 22 | | "attemptBoth" 23 | | "invertFirst"; 24 | } 25 | declare function jsQR( 26 | data: Uint8ClampedArray, 27 | width: number, 28 | height: number, 29 | providedOptions?: Options 30 | ): QRCode | null; 31 | export default jsQR; 32 | -------------------------------------------------------------------------------- /application/assets/exclude-js/locator/index.d.ts: -------------------------------------------------------------------------------- 1 | import { BitMatrix } from "../BitMatrix"; 2 | export interface Point { 3 | x: number; 4 | y: number; 5 | } 6 | export interface QRLocation { 7 | topRight: Point; 8 | bottomLeft: Point; 9 | topLeft: Point; 10 | alignmentPattern: Point; 11 | dimension: number; 12 | } 13 | export declare function locate(matrix: BitMatrix): QRLocation[]; 14 | -------------------------------------------------------------------------------- /application/assets/fonts/Lato-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-Black.ttf -------------------------------------------------------------------------------- /application/assets/fonts/Lato-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-BlackItalic.ttf -------------------------------------------------------------------------------- /application/assets/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /application/assets/fonts/Lato-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-BoldItalic.ttf -------------------------------------------------------------------------------- /application/assets/fonts/Lato-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-Italic.ttf -------------------------------------------------------------------------------- /application/assets/fonts/Lato-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-Light.ttf -------------------------------------------------------------------------------- /application/assets/fonts/Lato-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-LightItalic.ttf -------------------------------------------------------------------------------- /application/assets/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /application/assets/fonts/Lato-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-Thin.ttf -------------------------------------------------------------------------------- /application/assets/fonts/Lato-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/assets/fonts/Lato-ThinItalic.ttf -------------------------------------------------------------------------------- /application/assets/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato" 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 | -------------------------------------------------------------------------------- /application/assets/js/IntersectionObserver-polyfill.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | (function (window, document) { 18 | "use strict"; 19 | 20 | // Exits early if all IntersectionObserver and IntersectionObserverEntry 21 | // features are natively supported. 22 | if ( 23 | "IntersectionObserver" in window && 24 | "IntersectionObserverEntry" in window && 25 | "intersectionRatio" in window.IntersectionObserverEntry.prototype 26 | ) { 27 | // Minimal polyfill for Edge 15's lack of `isIntersecting` 28 | // See: https://github.com/w3c/IntersectionObserver/issues/211 29 | if (!("isIntersecting" in window.IntersectionObserverEntry.prototype)) { 30 | Object.defineProperty( 31 | window.IntersectionObserverEntry.prototype, 32 | "isIntersecting", 33 | { 34 | get: function () { 35 | return this.intersectionRatio > 0; 36 | }, 37 | } 38 | ); 39 | } 40 | return; 41 | } 42 | 43 | /** 44 | * An IntersectionObserver registry. This registry exists to hold a strong 45 | * reference to IntersectionObserver instances currently observering a target 46 | * element. Without this registry, instances without another reference may be 47 | * garbage collected. 48 | */ 49 | var registry = []; 50 | 51 | /** 52 | * Creates the global IntersectionObserverEntry constructor. 53 | * https://w3c.github.io/IntersectionObserver/#intersection-observer-entry 54 | * @param {Object} entry A dictionary of instance properties. 55 | * @constructor 56 | */ 57 | function IntersectionObserverEntry(entry) { 58 | this.time = entry.time; 59 | this.target = entry.target; 60 | this.rootBounds = entry.rootBounds; 61 | this.boundingClientRect = entry.boundingClientRect; 62 | this.intersectionRect = entry.intersectionRect || getEmptyRect(); 63 | this.isIntersecting = !!entry.intersectionRect; 64 | 65 | // Calculates the intersection ratio. 66 | var targetRect = this.boundingClientRect; 67 | var targetArea = targetRect.width * targetRect.height; 68 | var intersectionRect = this.intersectionRect; 69 | var intersectionArea = intersectionRect.width * intersectionRect.height; 70 | 71 | // Sets intersection ratio. 72 | if (targetArea) { 73 | this.intersectionRatio = intersectionArea / targetArea; 74 | } else { 75 | // If area is zero and is intersecting, sets to 1, otherwise to 0 76 | this.intersectionRatio = this.isIntersecting ? 1 : 0; 77 | } 78 | } 79 | 80 | /** 81 | * Creates the global IntersectionObserver constructor. 82 | * https://w3c.github.io/IntersectionObserver/#intersection-observer-interface 83 | * @param {Function} callback The function to be invoked after intersection 84 | * changes have queued. The function is not invoked if the queue has 85 | * been emptied by calling the `takeRecords` method. 86 | * @param {Object=} opt_options Optional configuration options. 87 | * @constructor 88 | */ 89 | function IntersectionObserver(callback, opt_options) { 90 | var options = opt_options || {}; 91 | 92 | if (typeof callback != "function") { 93 | throw new Error("callback must be a function"); 94 | } 95 | 96 | if (options.root && options.root.nodeType != 1) { 97 | throw new Error("root must be an Element"); 98 | } 99 | 100 | // Binds and throttles `this._checkForIntersections`. 101 | this._checkForIntersections = throttle( 102 | this._checkForIntersections.bind(this), 103 | this.THROTTLE_TIMEOUT 104 | ); 105 | 106 | // Private properties. 107 | this._callback = callback; 108 | this._observationTargets = []; 109 | this._queuedEntries = []; 110 | this._rootMarginValues = this._parseRootMargin(options.rootMargin); 111 | 112 | // Public properties. 113 | this.thresholds = this._initThresholds(options.threshold); 114 | this.root = options.root || null; 115 | this.rootMargin = this._rootMarginValues 116 | .map(function (margin) { 117 | return margin.value + margin.unit; 118 | }) 119 | .join(" "); 120 | } 121 | 122 | /** 123 | * The minimum interval within which the document will be checked for 124 | * intersection changes. 125 | */ 126 | IntersectionObserver.prototype.THROTTLE_TIMEOUT = 100; 127 | 128 | /** 129 | * The frequency in which the polyfill polls for intersection changes. 130 | * this can be updated on a per instance basis and must be set prior to 131 | * calling `observe` on the first target. 132 | */ 133 | IntersectionObserver.prototype.POLL_INTERVAL = null; 134 | 135 | /** 136 | * Starts observing a target element for intersection changes based on 137 | * the thresholds values. 138 | * @param {Element} target The DOM element to observe. 139 | */ 140 | IntersectionObserver.prototype.observe = function (target) { 141 | // If the target is already being observed, do nothing. 142 | if ( 143 | this._observationTargets.some(function (item) { 144 | return item.element == target; 145 | }) 146 | ) { 147 | return; 148 | } 149 | 150 | if (!(target && target.nodeType == 1)) { 151 | throw new Error("target must be an Element"); 152 | } 153 | 154 | this._registerInstance(); 155 | this._observationTargets.push({ element: target, entry: null }); 156 | this._monitorIntersections(); 157 | this._checkForIntersections(); 158 | }; 159 | 160 | /** 161 | * Stops observing a target element for intersection changes. 162 | * @param {Element} target The DOM element to observe. 163 | */ 164 | IntersectionObserver.prototype.unobserve = function (target) { 165 | this._observationTargets = this._observationTargets.filter(function (item) { 166 | return item.element != target; 167 | }); 168 | if (!this._observationTargets.length) { 169 | this._unmonitorIntersections(); 170 | this._unregisterInstance(); 171 | } 172 | }; 173 | 174 | /** 175 | * Stops observing all target elements for intersection changes. 176 | */ 177 | IntersectionObserver.prototype.disconnect = function () { 178 | this._observationTargets = []; 179 | this._unmonitorIntersections(); 180 | this._unregisterInstance(); 181 | }; 182 | 183 | /** 184 | * Returns any queue entries that have not yet been reported to the 185 | * callback and clears the queue. This can be used in conjunction with the 186 | * callback to obtain the absolute most up-to-date intersection information. 187 | * @return {Array} The currently queued entries. 188 | */ 189 | IntersectionObserver.prototype.takeRecords = function () { 190 | var records = this._queuedEntries.slice(); 191 | this._queuedEntries = []; 192 | return records; 193 | }; 194 | 195 | /** 196 | * Accepts the threshold value from the user configuration object and 197 | * returns a sorted array of unique threshold values. If a value is not 198 | * between 0 and 1 and error is thrown. 199 | * @private 200 | * @param {Array|number=} opt_threshold An optional threshold value or 201 | * a list of threshold values, defaulting to [0]. 202 | * @return {Array} A sorted list of unique and valid threshold values. 203 | */ 204 | IntersectionObserver.prototype._initThresholds = function (opt_threshold) { 205 | var threshold = opt_threshold || [0]; 206 | if (!Array.isArray(threshold)) threshold = [threshold]; 207 | 208 | return threshold.sort().filter(function (t, i, a) { 209 | if (typeof t != "number" || isNaN(t) || t < 0 || t > 1) { 210 | throw new Error( 211 | "threshold must be a number between 0 and 1 inclusively" 212 | ); 213 | } 214 | return t !== a[i - 1]; 215 | }); 216 | }; 217 | 218 | /** 219 | * Accepts the rootMargin value from the user configuration object 220 | * and returns an array of the four margin values as an object containing 221 | * the value and unit properties. If any of the values are not properly 222 | * formatted or use a unit other than px or %, and error is thrown. 223 | * @private 224 | * @param {string=} opt_rootMargin An optional rootMargin value, 225 | * defaulting to '0px'. 226 | * @return {Array} An array of margin objects with the keys 227 | * value and unit. 228 | */ 229 | IntersectionObserver.prototype._parseRootMargin = function (opt_rootMargin) { 230 | var marginString = opt_rootMargin || "0px"; 231 | var margins = marginString.split(/\s+/).map(function (margin) { 232 | var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin); 233 | if (!parts) { 234 | throw new Error("rootMargin must be specified in pixels or percent"); 235 | } 236 | return { value: parseFloat(parts[1]), unit: parts[2] }; 237 | }); 238 | 239 | // Handles shorthand. 240 | margins[1] = margins[1] || margins[0]; 241 | margins[2] = margins[2] || margins[0]; 242 | margins[3] = margins[3] || margins[1]; 243 | 244 | return margins; 245 | }; 246 | 247 | /** 248 | * Starts polling for intersection changes if the polling is not already 249 | * happening, and if the page's visibilty state is visible. 250 | * @private 251 | */ 252 | IntersectionObserver.prototype._monitorIntersections = function () { 253 | if (!this._monitoringIntersections) { 254 | this._monitoringIntersections = true; 255 | 256 | // If a poll interval is set, use polling instead of listening to 257 | // resize and scroll events or DOM mutations. 258 | if (this.POLL_INTERVAL) { 259 | this._monitoringInterval = setInterval( 260 | this._checkForIntersections, 261 | this.POLL_INTERVAL 262 | ); 263 | } else { 264 | addEvent(window, "resize", this._checkForIntersections, true); 265 | addEvent(document, "scroll", this._checkForIntersections, true); 266 | 267 | if ("MutationObserver" in window) { 268 | this._domObserver = new MutationObserver(this._checkForIntersections); 269 | this._domObserver.observe(document, { 270 | attributes: true, 271 | childList: true, 272 | characterData: true, 273 | subtree: true, 274 | }); 275 | } 276 | } 277 | } 278 | }; 279 | 280 | /** 281 | * Stops polling for intersection changes. 282 | * @private 283 | */ 284 | IntersectionObserver.prototype._unmonitorIntersections = function () { 285 | if (this._monitoringIntersections) { 286 | this._monitoringIntersections = false; 287 | 288 | clearInterval(this._monitoringInterval); 289 | this._monitoringInterval = null; 290 | 291 | removeEvent(window, "resize", this._checkForIntersections, true); 292 | removeEvent(document, "scroll", this._checkForIntersections, true); 293 | 294 | if (this._domObserver) { 295 | this._domObserver.disconnect(); 296 | this._domObserver = null; 297 | } 298 | } 299 | }; 300 | 301 | /** 302 | * Scans each observation target for intersection changes and adds them 303 | * to the internal entries queue. If new entries are found, it 304 | * schedules the callback to be invoked. 305 | * @private 306 | */ 307 | IntersectionObserver.prototype._checkForIntersections = function () { 308 | var rootIsInDom = this._rootIsInDom(); 309 | var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect(); 310 | 311 | this._observationTargets.forEach(function (item) { 312 | var target = item.element; 313 | var targetRect = getBoundingClientRect(target); 314 | var rootContainsTarget = this._rootContainsTarget(target); 315 | var oldEntry = item.entry; 316 | var intersectionRect = 317 | rootIsInDom && 318 | rootContainsTarget && 319 | this._computeTargetAndRootIntersection(target, rootRect); 320 | 321 | var newEntry = (item.entry = new IntersectionObserverEntry({ 322 | time: now(), 323 | target: target, 324 | boundingClientRect: targetRect, 325 | rootBounds: rootRect, 326 | intersectionRect: intersectionRect, 327 | })); 328 | 329 | if (!oldEntry) { 330 | this._queuedEntries.push(newEntry); 331 | } else if (rootIsInDom && rootContainsTarget) { 332 | // If the new entry intersection ratio has crossed any of the 333 | // thresholds, add a new entry. 334 | if (this._hasCrossedThreshold(oldEntry, newEntry)) { 335 | this._queuedEntries.push(newEntry); 336 | } 337 | } else { 338 | // If the root is not in the DOM or target is not contained within 339 | // root but the previous entry for this target had an intersection, 340 | // add a new record indicating removal. 341 | if (oldEntry && oldEntry.isIntersecting) { 342 | this._queuedEntries.push(newEntry); 343 | } 344 | } 345 | }, this); 346 | 347 | if (this._queuedEntries.length) { 348 | this._callback(this.takeRecords(), this); 349 | } 350 | }; 351 | 352 | /** 353 | * Accepts a target and root rect computes the intersection between then 354 | * following the algorithm in the spec. 355 | * TODO(philipwalton): at this time clip-path is not considered. 356 | * https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo 357 | * @param {Element} target The target DOM element 358 | * @param {Object} rootRect The bounding rect of the root after being 359 | * expanded by the rootMargin value. 360 | * @return {?Object} The final intersection rect object or undefined if no 361 | * intersection is found. 362 | * @private 363 | */ 364 | IntersectionObserver.prototype._computeTargetAndRootIntersection = function ( 365 | target, 366 | rootRect 367 | ) { 368 | // If the element isn't displayed, an intersection can't happen. 369 | if (window.getComputedStyle(target).display == "none") return; 370 | 371 | var targetRect = getBoundingClientRect(target); 372 | var intersectionRect = targetRect; 373 | var parent = getParentNode(target); 374 | var atRoot = false; 375 | 376 | while (!atRoot) { 377 | var parentRect = null; 378 | var parentComputedStyle = 379 | parent.nodeType == 1 ? window.getComputedStyle(parent) : {}; 380 | 381 | // If the parent isn't displayed, an intersection can't happen. 382 | if (parentComputedStyle.display == "none") return; 383 | 384 | if (parent == this.root || parent == document) { 385 | atRoot = true; 386 | parentRect = rootRect; 387 | } else { 388 | // If the element has a non-visible overflow, and it's not the 389 | // or element, update the intersection rect. 390 | // Note: and cannot be clipped to a rect that's not also 391 | // the document rect, so no need to compute a new intersection. 392 | if ( 393 | parent != document.body && 394 | parent != document.documentElement && 395 | parentComputedStyle.overflow != "visible" 396 | ) { 397 | parentRect = getBoundingClientRect(parent); 398 | } 399 | } 400 | 401 | // If either of the above conditionals set a new parentRect, 402 | // calculate new intersection data. 403 | if (parentRect) { 404 | intersectionRect = computeRectIntersection( 405 | parentRect, 406 | intersectionRect 407 | ); 408 | 409 | if (!intersectionRect) break; 410 | } 411 | parent = getParentNode(parent); 412 | } 413 | return intersectionRect; 414 | }; 415 | 416 | /** 417 | * Returns the root rect after being expanded by the rootMargin value. 418 | * @return {Object} The expanded root rect. 419 | * @private 420 | */ 421 | IntersectionObserver.prototype._getRootRect = function () { 422 | var rootRect; 423 | if (this.root) { 424 | rootRect = getBoundingClientRect(this.root); 425 | } else { 426 | // Use / instead of window since scroll bars affect size. 427 | var html = document.documentElement; 428 | var body = document.body; 429 | rootRect = { 430 | top: 0, 431 | left: 0, 432 | right: html.clientWidth || body.clientWidth, 433 | width: html.clientWidth || body.clientWidth, 434 | bottom: html.clientHeight || body.clientHeight, 435 | height: html.clientHeight || body.clientHeight, 436 | }; 437 | } 438 | return this._expandRectByRootMargin(rootRect); 439 | }; 440 | 441 | /** 442 | * Accepts a rect and expands it by the rootMargin value. 443 | * @param {Object} rect The rect object to expand. 444 | * @return {Object} The expanded rect. 445 | * @private 446 | */ 447 | IntersectionObserver.prototype._expandRectByRootMargin = function (rect) { 448 | var margins = this._rootMarginValues.map(function (margin, i) { 449 | return margin.unit == "px" 450 | ? margin.value 451 | : (margin.value * (i % 2 ? rect.width : rect.height)) / 100; 452 | }); 453 | var newRect = { 454 | top: rect.top - margins[0], 455 | right: rect.right + margins[1], 456 | bottom: rect.bottom + margins[2], 457 | left: rect.left - margins[3], 458 | }; 459 | newRect.width = newRect.right - newRect.left; 460 | newRect.height = newRect.bottom - newRect.top; 461 | 462 | return newRect; 463 | }; 464 | 465 | /** 466 | * Accepts an old and new entry and returns true if at least one of the 467 | * threshold values has been crossed. 468 | * @param {?IntersectionObserverEntry} oldEntry The previous entry for a 469 | * particular target element or null if no previous entry exists. 470 | * @param {IntersectionObserverEntry} newEntry The current entry for a 471 | * particular target element. 472 | * @return {boolean} Returns true if a any threshold has been crossed. 473 | * @private 474 | */ 475 | IntersectionObserver.prototype._hasCrossedThreshold = function ( 476 | oldEntry, 477 | newEntry 478 | ) { 479 | // To make comparing easier, an entry that has a ratio of 0 480 | // but does not actually intersect is given a value of -1 481 | var oldRatio = 482 | oldEntry && oldEntry.isIntersecting 483 | ? oldEntry.intersectionRatio || 0 484 | : -1; 485 | var newRatio = newEntry.isIntersecting 486 | ? newEntry.intersectionRatio || 0 487 | : -1; 488 | 489 | // Ignore unchanged ratios 490 | if (oldRatio === newRatio) return; 491 | 492 | for (var i = 0; i < this.thresholds.length; i++) { 493 | var threshold = this.thresholds[i]; 494 | 495 | // Return true if an entry matches a threshold or if the new ratio 496 | // and the old ratio are on the opposite sides of a threshold. 497 | if ( 498 | threshold == oldRatio || 499 | threshold == newRatio || 500 | threshold < oldRatio !== threshold < newRatio 501 | ) { 502 | return true; 503 | } 504 | } 505 | }; 506 | 507 | /** 508 | * Returns whether or not the root element is an element and is in the DOM. 509 | * @return {boolean} True if the root element is an element and is in the DOM. 510 | * @private 511 | */ 512 | IntersectionObserver.prototype._rootIsInDom = function () { 513 | return !this.root || containsDeep(document, this.root); 514 | }; 515 | 516 | /** 517 | * Returns whether or not the target element is a child of root. 518 | * @param {Element} target The target element to check. 519 | * @return {boolean} True if the target element is a child of root. 520 | * @private 521 | */ 522 | IntersectionObserver.prototype._rootContainsTarget = function (target) { 523 | return containsDeep(this.root || document, target); 524 | }; 525 | 526 | /** 527 | * Adds the instance to the global IntersectionObserver registry if it isn't 528 | * already present. 529 | * @private 530 | */ 531 | IntersectionObserver.prototype._registerInstance = function () { 532 | if (registry.indexOf(this) < 0) { 533 | registry.push(this); 534 | } 535 | }; 536 | 537 | /** 538 | * Removes the instance from the global IntersectionObserver registry. 539 | * @private 540 | */ 541 | IntersectionObserver.prototype._unregisterInstance = function () { 542 | var index = registry.indexOf(this); 543 | if (index != -1) registry.splice(index, 1); 544 | }; 545 | 546 | /** 547 | * Returns the result of the performance.now() method or null in browsers 548 | * that don't support the API. 549 | * @return {number} The elapsed time since the page was requested. 550 | */ 551 | function now() { 552 | return window.performance && performance.now && performance.now(); 553 | } 554 | 555 | /** 556 | * Throttles a function and delays its executiong, so it's only called at most 557 | * once within a given time period. 558 | * @param {Function} fn The function to throttle. 559 | * @param {number} timeout The amount of time that must pass before the 560 | * function can be called again. 561 | * @return {Function} The throttled function. 562 | */ 563 | function throttle(fn, timeout) { 564 | var timer = null; 565 | return function () { 566 | if (!timer) { 567 | timer = setTimeout(function () { 568 | fn(); 569 | timer = null; 570 | }, timeout); 571 | } 572 | }; 573 | } 574 | 575 | /** 576 | * Adds an event handler to a DOM node ensuring cross-browser compatibility. 577 | * @param {Node} node The DOM node to add the event handler to. 578 | * @param {string} event The event name. 579 | * @param {Function} fn The event handler to add. 580 | * @param {boolean} opt_useCapture Optionally adds the even to the capture 581 | * phase. Note: this only works in modern browsers. 582 | */ 583 | function addEvent(node, event, fn, opt_useCapture) { 584 | if (typeof node.addEventListener == "function") { 585 | node.addEventListener(event, fn, opt_useCapture || false); 586 | } else if (typeof node.attachEvent == "function") { 587 | node.attachEvent("on" + event, fn); 588 | } 589 | } 590 | 591 | /** 592 | * Removes a previously added event handler from a DOM node. 593 | * @param {Node} node The DOM node to remove the event handler from. 594 | * @param {string} event The event name. 595 | * @param {Function} fn The event handler to remove. 596 | * @param {boolean} opt_useCapture If the event handler was added with this 597 | * flag set to true, it should be set to true here in order to remove it. 598 | */ 599 | function removeEvent(node, event, fn, opt_useCapture) { 600 | if (typeof node.removeEventListener == "function") { 601 | node.removeEventListener(event, fn, opt_useCapture || false); 602 | } else if (typeof node.detatchEvent == "function") { 603 | node.detatchEvent("on" + event, fn); 604 | } 605 | } 606 | 607 | /** 608 | * Returns the intersection between two rect objects. 609 | * @param {Object} rect1 The first rect. 610 | * @param {Object} rect2 The second rect. 611 | * @return {?Object} The intersection rect or undefined if no intersection 612 | * is found. 613 | */ 614 | function computeRectIntersection(rect1, rect2) { 615 | var top = Math.max(rect1.top, rect2.top); 616 | var bottom = Math.min(rect1.bottom, rect2.bottom); 617 | var left = Math.max(rect1.left, rect2.left); 618 | var right = Math.min(rect1.right, rect2.right); 619 | var width = right - left; 620 | var height = bottom - top; 621 | 622 | return ( 623 | width >= 0 && 624 | height >= 0 && { 625 | top: top, 626 | bottom: bottom, 627 | left: left, 628 | right: right, 629 | width: width, 630 | height: height, 631 | } 632 | ); 633 | } 634 | 635 | /** 636 | * Shims the native getBoundingClientRect for compatibility with older IE. 637 | * @param {Element} el The element whose bounding rect to get. 638 | * @return {Object} The (possibly shimmed) rect of the element. 639 | */ 640 | function getBoundingClientRect(el) { 641 | var rect; 642 | 643 | try { 644 | rect = el.getBoundingClientRect(); 645 | } catch (err) { 646 | // Ignore Windows 7 IE11 "Unspecified error" 647 | // https://github.com/w3c/IntersectionObserver/pull/205 648 | } 649 | 650 | if (!rect) return getEmptyRect(); 651 | 652 | // Older IE 653 | if (!(rect.width && rect.height)) { 654 | rect = { 655 | top: rect.top, 656 | right: rect.right, 657 | bottom: rect.bottom, 658 | left: rect.left, 659 | width: rect.right - rect.left, 660 | height: rect.bottom - rect.top, 661 | }; 662 | } 663 | return rect; 664 | } 665 | 666 | /** 667 | * Returns an empty rect object. An empty rect is returned when an element 668 | * is not in the DOM. 669 | * @return {Object} The empty rect. 670 | */ 671 | function getEmptyRect() { 672 | return { 673 | top: 0, 674 | bottom: 0, 675 | left: 0, 676 | right: 0, 677 | width: 0, 678 | height: 0, 679 | }; 680 | } 681 | 682 | /** 683 | * Checks to see if a parent element contains a child elemnt (including inside 684 | * shadow DOM). 685 | * @param {Node} parent The parent element. 686 | * @param {Node} child The child element. 687 | * @return {boolean} True if the parent node contains the child node. 688 | */ 689 | function containsDeep(parent, child) { 690 | var node = child; 691 | while (node) { 692 | if (node == parent) return true; 693 | 694 | node = getParentNode(node); 695 | } 696 | return false; 697 | } 698 | 699 | /** 700 | * Gets the parent node of an element or its host element if the parent node 701 | * is a shadow root. 702 | * @param {Node} node The node whose parent to get. 703 | * @return {Node|null} The parent node or null if no parent exists. 704 | */ 705 | function getParentNode(node) { 706 | var parent = node.parentNode; 707 | 708 | if (parent && parent.nodeType == 11 && parent.host) { 709 | // If the parent is a shadow root, return the host element. 710 | return parent.host; 711 | } 712 | return parent; 713 | } 714 | 715 | // Exposes the constructors globally. 716 | window.IntersectionObserver = IntersectionObserver; 717 | window.IntersectionObserverEntry = IntersectionObserverEntry; 718 | })(window, document); 719 | -------------------------------------------------------------------------------- /application/assets/js/applait.finder.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * EventEmitter v4.2.9 - git.io/ee 3 | * Oliver Caldwell 4 | * MIT license 5 | * @preserve 6 | */ 7 | (function(){"use strict";function EventEmitter(){}function indexOfListener(listeners,listener){for(var i=listeners.length;i--;)if(listeners[i].listener===listener)return i;return-1}function alias(name){return function(){return this[name].apply(this,arguments)}}var proto=EventEmitter.prototype,exports=this,originalGlobalValue=exports.EventEmitter;proto.getListeners=function(evt){var response,key,events=this._getEvents();if(evt instanceof RegExp){response={};for(key in events)events.hasOwnProperty(key)&&evt.test(key)&&(response[key]=events[key])}else response=events[evt]||(events[evt]=[]);return response},proto.flattenListeners=function(listeners){var i,flatListeners=[];for(i=0;i-1&&this.checkhidden(fullpath)}; 26 | -------------------------------------------------------------------------------- /application/assets/js/backend_api.js: -------------------------------------------------------------------------------- 1 | const LastUpdateFile = 2 | "https://banana-hackers.gitlab.io/store-db/lastUpdate.txt"; 3 | 4 | const server_list = [ 5 | "https://banana-hackers.gitlab.io/store-db/data.json", 6 | "https://bananahackers.github.io/store-db/data.json", 7 | ]; 8 | 9 | const SimpleRatingServerInstance = "https://bhackers.uber.space/srs/v1"; 10 | 11 | const BackendApi = (() => { 12 | const TIMEOUT_ERROR = "TimeOutError"; 13 | const FORBIDDEN_ERROR = "ForbidddenError"; 14 | 15 | let statusCallback = () => {}; 16 | 17 | function fetchData(url) { 18 | return new Promise((resolve, reject) => { 19 | let xhr = new XMLHttpRequest({ mozSystem: true }); 20 | xhr.open("GET", url); 21 | xhr.timeout = 4000; // time in milliseconds 22 | 23 | xhr.ontimeout = function (e) { 24 | reject(TIMEOUT_ERROR); 25 | }; 26 | 27 | xhr.onload = function () { 28 | if (xhr.status == 200) { 29 | resolve(xhr.responseText); 30 | } 31 | if (xhr.status == 403) { 32 | // access forbidden 33 | reject(FORBIDDEN_ERROR); 34 | } 35 | // analyze HTTP status of the response 36 | if (xhr.status != 200) { 37 | reject(new Error(`Error ${xhr.status}: ${xhr.statusText}`)); // e.g. 404: Not Found 38 | } 39 | }; 40 | 41 | xhr.onerror = function (err) { 42 | reject(err); 43 | }; 44 | xhr.send(); 45 | }); 46 | } 47 | 48 | function _saveData(dataString) { 49 | const json = JSON.parse(dataString); 50 | if (json.version !== 2) { 51 | throw "Incompatible Data, try updating the app"; 52 | } 53 | if (json.generated_at < localStorage.getItem("DATA_Timestamp")) { 54 | throw "Older Data than current data, please contact the developers this should not happen"; 55 | } 56 | localStorage.setItem("DATA", JSON.stringify(json)); 57 | localStorage.setItem("DATA_Timestamp", json.generated_at); 58 | } 59 | 60 | function _update() { 61 | return new Promise((resolve, reject) => { 62 | const done = (data) => { 63 | try { 64 | _saveData(data); 65 | console.log("Got data", data); 66 | resolve(); 67 | } catch (error) { 68 | reject(error); 69 | } 70 | }; 71 | fetchData(server_list[0]) 72 | .then(done) 73 | .catch((error) => { 74 | statusCallback("First Server wasn't reachable, trying backup"); 75 | console.error(error); 76 | fetchData(server_list[1]).then(done).catch(reject); 77 | }); 78 | }); 79 | } 80 | 81 | /** 82 | * @returns {Promise} returns a promise wrapped boolean whether there is a new update, fails if the network request fails 83 | */ 84 | function checkForNewVersion() { 85 | console.log("Checking for newer data."); 86 | return new Promise((resolve, reject) => { 87 | fetchData(LastUpdateFile) 88 | .then((response) => { 89 | const ts = Number(response); 90 | resolve(ts > localStorage.getItem("DATA_Timestamp")); 91 | }) 92 | .catch(reject); 93 | }); 94 | } 95 | 96 | /** 97 | * public function which has checks to check whether an update should be started 98 | */ 99 | function update(forceUpdate = false) { 100 | return new Promise((resolve, reject) => { 101 | if (!navigator.onLine) { 102 | reject(new Error("No internet connection")); 103 | } else if (forceUpdate) { 104 | _update().then(resolve).catch(reject); 105 | } else if (!getData()) { 106 | // update if we dont have data 107 | _update().then(resolve).catch(reject); 108 | } else { 109 | // try checking the version number 110 | checkForNewVersion() 111 | .then((newVersionExists) => { 112 | if (newVersionExists) { 113 | _update().then(resolve).catch(reject); 114 | } else { 115 | console.log("no new version of the data availible"); 116 | resolve(); 117 | } 118 | }) 119 | .catch((err) => { 120 | console.log(err); 121 | // we couldn't check for versions, 122 | // either we have no connection or the update page is blocked, so try updating anyway. 123 | _update().then(resolve).catch(reject); 124 | }); 125 | } 126 | }); 127 | } 128 | 129 | function getData() { 130 | const string = localStorage.getItem("DATA"); 131 | if (string) return JSON.parse(string); 132 | else return null; 133 | } 134 | 135 | /** 136 | * Counts a download. 137 | * @param {string} appId id/slug of the app that was downloaded 138 | */ 139 | function count_download(appId) { 140 | const url = `${SimpleRatingServerInstance}/download_counter/count/${appId}`; 141 | let xhttp = new XMLHttpRequest({ mozSystem: true }); 142 | xhttp.open("GET", url, true); 143 | xhttp.timeout = 2000; 144 | xhttp.send(null); 145 | } 146 | 147 | function getDownloadCounts() { 148 | return new Promise((resolve, reject) => { 149 | fetchData(`${SimpleRatingServerInstance}/download_counter`) 150 | .then((data) => { 151 | try { 152 | const json = JSON.parse(data); 153 | resolve(json); 154 | } catch (error) { 155 | reject(error); 156 | } 157 | }) 158 | .catch(reject); 159 | }); 160 | } 161 | 162 | return { 163 | update, 164 | getData, 165 | setStatusCallback: (cb) => (statusCallback = cb), 166 | count_download, 167 | getDownloadCounts, 168 | }; 169 | })(); 170 | -------------------------------------------------------------------------------- /application/assets/js/helper.js: -------------------------------------------------------------------------------- 1 | function notify(param_title, param_text, param_silent, requireInteraction) { 2 | var options = { 3 | body: param_text, 4 | silent: param_silent, 5 | requireInteraction: requireInteraction, 6 | }; 7 | 8 | var action = { 9 | actions: [ 10 | { 11 | action: "archive", 12 | title: "Archive", 13 | }, 14 | ], 15 | }; 16 | 17 | // Let's check if the browser supports notifications 18 | if (!("Notification" in window)) { 19 | alert("This browser does not support desktop notification"); 20 | } 21 | 22 | // Let's check whether notification permissions have already been granted 23 | else if (Notification.permission === "granted") { 24 | // If it's okay let's create a notification 25 | var notification = new Notification(param_title, options); 26 | } 27 | 28 | // Otherwise, we need to ask the user for permission 29 | else if (Notification.permission !== "denied") { 30 | Notification.requestPermission().then(function (permission) { 31 | // If the user accepts, let's create a notification 32 | if (permission === "granted") { 33 | var notification = new Notification(param_title, options, action); 34 | } 35 | }); 36 | } 37 | } 38 | 39 | //silent notification 40 | function toaster(text, time) { 41 | document.querySelector("div#toast").innerHTML = text; 42 | var elem = document.querySelector("div#toast"); 43 | var pos = -100; 44 | var id = setInterval(down, 5); 45 | var id2; 46 | 47 | function down() { 48 | if (pos == 0) { 49 | clearInterval(id); 50 | setTimeout(() => { 51 | id2 = setInterval(up, 5); 52 | }, time); 53 | } else { 54 | pos++; 55 | elem.style.top = pos + "px"; 56 | } 57 | } 58 | 59 | function up() { 60 | if (pos == -1000) { 61 | clearInterval(id2); 62 | } else { 63 | pos--; 64 | elem.style.top = pos + "px"; 65 | } 66 | } 67 | } 68 | 69 | //bottom bar 70 | function bottom_bar(left, center, right) { 71 | document.querySelector("div#bottom-bar div#button-left").textContent = left; 72 | document.querySelector( 73 | "div#bottom-bar div#button-center" 74 | ).textContent = center; 75 | document.querySelector("div#bottom-bar div#button-right").textContent = right; 76 | 77 | if (left == "" && center == "" && right == "") { 78 | document.querySelector("div#bottom-bar").style.display = "none"; 79 | } else { 80 | document.querySelector("div#bottom-bar").style.display = "block"; 81 | } 82 | } 83 | 84 | //check if internet connection 85 | function check_iconnection() { 86 | if (navigator.onLine) { 87 | } else { 88 | toaster("No Internet connection", 3000); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /application/assets/js/install.js: -------------------------------------------------------------------------------- 1 | const mozAppsWrapper = ((_) => { 2 | function _domRequest2Promise(domRequest) { 3 | return new Promise((resolve, reject) => { 4 | domRequest.onsuccess = () => resolve(domRequest.result); 5 | domRequest.onerror = () => reject(domRequest.error); 6 | }); 7 | } 8 | 9 | /** 10 | * get a list of all installed apps 11 | */ 12 | function getAll() { 13 | return _domRequest2Promise(navigator.mozApps.mgmt.getAll()); 14 | } 15 | 16 | /** 17 | * Get a list of all installed apps from this origin. 18 | * For example, if you call this on the Firefox Marketplace, 19 | * you will get the list of apps installed by the Firefox Marketplace. 20 | */ 21 | function getInstalled() { 22 | return _domRequest2Promise(navigator.mozApps.getInstalled()); 23 | } 24 | 25 | return { getAll, getInstalled }; 26 | })(); 27 | 28 | /////////////// 29 | //download file 30 | /////////////// 31 | 32 | //const { install } = ((_) => 33 | const install = (() => { 34 | function download_file(url, app_slug) { 35 | var xhttp = new XMLHttpRequest({ mozSystem: true }); 36 | document.getElementById("loading").style.display = "block"; 37 | xhttp.open("GET", url, true); 38 | xhttp.withCredentials = true; 39 | xhttp.responseType = "blob"; 40 | 41 | xhttp.onload = function () { 42 | if (xhttp.readyState === xhttp.DONE && xhttp.status === 200) { 43 | let blob = xhttp.response; 44 | let file = new Blob([blob], { type: "application/zip" }); 45 | installPkg(file); 46 | installPkg( 47 | "https://github.com/strukturart/o.map/blob/master/build/omap-test.zip" 48 | ); 49 | BackendApi.count_download(app_slug); 50 | } else { 51 | alert("can't download app"); 52 | } 53 | }; 54 | 55 | xhttp.onerror = function () { 56 | alert("can't download app"); 57 | toaster(" status: " + xhttp.status + xhttp.getAllResponseHeaders(), 3000); 58 | document.getElementById("loading").style.display = "none"; 59 | }; 60 | 61 | xhttp.send(null); 62 | } 63 | 64 | function installPkg(packageFile) { 65 | if (!navigator.mozApps.mgmt.import()) { 66 | alert("This KaiOs version do not support import()"); 67 | return false; 68 | } 69 | 70 | /* 71 | 72 | 73 | let req_install = window.navigator.mozApps.install(packageFile); 74 | 75 | req_install.onsuccess = function() { 76 | document.getElementById("loading").style.display = "none"; 77 | alert("success"); 78 | }; 79 | 80 | req_install.onerror = function(error) { 81 | document.getElementById("loading").style.display = "none"; 82 | alert(JSON.stringify(error)); 83 | }; 84 | */ 85 | 86 | navigator.mozApps.mgmt 87 | .import(packageFile) 88 | .then(function (e) { 89 | bottom_bar("options", "", "open"); 90 | after_installation = true; 91 | 92 | console.info("Installation was successfull", arguments); 93 | document.getElementById("loading").style.display = "none"; 94 | alert("App installed successfully."); 95 | }) 96 | .catch((error) => { 97 | document.getElementById("loading").style.display = "none"; 98 | 99 | if (error.name === "noMetadata") { 100 | alert("Installation error: noMetadata"); 101 | } 102 | if (error.name === "AppAlreadyInstalled") { 103 | alert("Installation error: App Already Installed."); 104 | } 105 | 106 | if (error.name === "InvalidPrivilegeLevel") { 107 | alert( 108 | "Installation error: You probably need to do the priviliged factory reset first." 109 | ); 110 | // TODO open an guide that explains it, with links to a backup guide. 111 | } 112 | }); 113 | } 114 | 115 | return { installPkg, download_file }; 116 | })(); 117 | -------------------------------------------------------------------------------- /application/assets/js/lazyload.js: -------------------------------------------------------------------------------- 1 | const lazyload = ((_) => { 2 | let ll = function () { 3 | const images = document.querySelectorAll(".lazyload"); 4 | 5 | function handleIntersection(entries) { 6 | entries.map((entry) => { 7 | if (entry.isIntersecting) { 8 | entry.target.src = entry.target.dataset.src; 9 | entry.target.classList.add("loaded"); 10 | observer.unobserve(entry.target); 11 | } 12 | }); 13 | } 14 | 15 | const observer = new IntersectionObserver(handleIntersection); 16 | 17 | for (let i = 0; i < images.length; i++) { 18 | observer.observe(images[i]); 19 | } 20 | }; 21 | 22 | let existCondition = setInterval(function () { 23 | if (document.getElementsByClassName("lazyload").length) { 24 | clearInterval(existCondition); 25 | ll(); 26 | } 27 | }, 500); 28 | 29 | return { ll }; 30 | })(); 31 | -------------------------------------------------------------------------------- /application/assets/js/mustache.js: -------------------------------------------------------------------------------- 1 | // This file has been generated from mustache.mjs 2 | (function (global, factory) { 3 | typeof exports === "object" && typeof module !== "undefined" 4 | ? (module.exports = factory()) 5 | : typeof define === "function" && define.amd 6 | ? define(factory) 7 | : ((global = global || self), (global.Mustache = factory())); 8 | })(this, function () { 9 | "use strict"; 10 | 11 | /*! 12 | * mustache.js - Logic-less {{mustache}} templates with JavaScript 13 | * http://github.com/janl/mustache.js 14 | */ 15 | 16 | var objectToString = Object.prototype.toString; 17 | var isArray = 18 | Array.isArray || 19 | function isArrayPolyfill(object) { 20 | return objectToString.call(object) === "[object Array]"; 21 | }; 22 | 23 | function isFunction(object) { 24 | return typeof object === "function"; 25 | } 26 | 27 | /** 28 | * More correct typeof string handling array 29 | * which normally returns typeof 'object' 30 | */ 31 | function typeStr(obj) { 32 | return isArray(obj) ? "array" : typeof obj; 33 | } 34 | 35 | function escapeRegExp(string) { 36 | return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); 37 | } 38 | 39 | /** 40 | * Null safe way of checking whether or not an object, 41 | * including its prototype, has a given property 42 | */ 43 | function hasProperty(obj, propName) { 44 | return obj != null && typeof obj === "object" && propName in obj; 45 | } 46 | 47 | /** 48 | * Safe way of detecting whether or not the given thing is a primitive and 49 | * whether it has the given property 50 | */ 51 | function primitiveHasOwnProperty(primitive, propName) { 52 | return ( 53 | primitive != null && 54 | typeof primitive !== "object" && 55 | primitive.hasOwnProperty && 56 | primitive.hasOwnProperty(propName) 57 | ); 58 | } 59 | 60 | // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 61 | // See https://github.com/janl/mustache.js/issues/189 62 | var regExpTest = RegExp.prototype.test; 63 | function testRegExp(re, string) { 64 | return regExpTest.call(re, string); 65 | } 66 | 67 | var nonSpaceRe = /\S/; 68 | function isWhitespace(string) { 69 | return !testRegExp(nonSpaceRe, string); 70 | } 71 | 72 | var entityMap = { 73 | "&": "&", 74 | "<": "<", 75 | ">": ">", 76 | '"': """, 77 | "'": "'", 78 | "/": "/", 79 | "`": "`", 80 | "=": "=", 81 | }; 82 | 83 | function escapeHtml(string) { 84 | return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap(s) { 85 | return entityMap[s]; 86 | }); 87 | } 88 | 89 | var whiteRe = /\s*/; 90 | var spaceRe = /\s+/; 91 | var equalsRe = /\s*=/; 92 | var curlyRe = /\s*\}/; 93 | var tagRe = /#|\^|\/|>|\{|&|=|!/; 94 | 95 | /** 96 | * Breaks up the given `template` string into a tree of tokens. If the `tags` 97 | * argument is given here it must be an array with two string values: the 98 | * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of 99 | * course, the default is to use mustaches (i.e. mustache.tags). 100 | * 101 | * A token is an array with at least 4 elements. The first element is the 102 | * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag 103 | * did not contain a symbol (i.e. {{myValue}}) this element is "name". For 104 | * all text that appears outside a symbol this element is "text". 105 | * 106 | * The second element of a token is its "value". For mustache tags this is 107 | * whatever else was inside the tag besides the opening symbol. For text tokens 108 | * this is the text itself. 109 | * 110 | * The third and fourth elements of the token are the start and end indices, 111 | * respectively, of the token in the original template. 112 | * 113 | * Tokens that are the root node of a subtree contain two more elements: 1) an 114 | * array of tokens in the subtree and 2) the index in the original template at 115 | * which the closing tag for that section begins. 116 | * 117 | * Tokens for partials also contain two more elements: 1) a string value of 118 | * indendation prior to that tag and 2) the index of that tag on that line - 119 | * eg a value of 2 indicates the partial is the third tag on this line. 120 | */ 121 | function parseTemplate(template, tags) { 122 | if (!template) return []; 123 | var lineHasNonSpace = false; 124 | var sections = []; // Stack to hold section tokens 125 | var tokens = []; // Buffer to hold the tokens 126 | var spaces = []; // Indices of whitespace tokens on the current line 127 | var hasTag = false; // Is there a {{tag}} on the current line? 128 | var nonSpace = false; // Is there a non-space char on the current line? 129 | var indentation = ""; // Tracks indentation for tags that use it 130 | var tagIndex = 0; // Stores a count of number of tags encountered on a line 131 | 132 | // Strips all whitespace tokens array for the current line 133 | // if there was a {{#tag}} on it and otherwise only space. 134 | function stripSpace() { 135 | if (hasTag && !nonSpace) { 136 | while (spaces.length) delete tokens[spaces.pop()]; 137 | } else { 138 | spaces = []; 139 | } 140 | 141 | hasTag = false; 142 | nonSpace = false; 143 | } 144 | 145 | var openingTagRe, closingTagRe, closingCurlyRe; 146 | function compileTags(tagsToCompile) { 147 | if (typeof tagsToCompile === "string") 148 | tagsToCompile = tagsToCompile.split(spaceRe, 2); 149 | 150 | if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) 151 | throw new Error("Invalid tags: " + tagsToCompile); 152 | 153 | openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + "\\s*"); 154 | closingTagRe = new RegExp("\\s*" + escapeRegExp(tagsToCompile[1])); 155 | closingCurlyRe = new RegExp( 156 | "\\s*" + escapeRegExp("}" + tagsToCompile[1]) 157 | ); 158 | } 159 | 160 | compileTags(tags || mustache.tags); 161 | 162 | var scanner = new Scanner(template); 163 | 164 | var start, type, value, chr, token, openSection; 165 | while (!scanner.eos()) { 166 | start = scanner.pos; 167 | 168 | // Match any text between tags. 169 | value = scanner.scanUntil(openingTagRe); 170 | 171 | if (value) { 172 | for (var i = 0, valueLength = value.length; i < valueLength; ++i) { 173 | chr = value.charAt(i); 174 | 175 | if (isWhitespace(chr)) { 176 | spaces.push(tokens.length); 177 | indentation += chr; 178 | } else { 179 | nonSpace = true; 180 | lineHasNonSpace = true; 181 | indentation += " "; 182 | } 183 | 184 | tokens.push(["text", chr, start, start + 1]); 185 | start += 1; 186 | 187 | // Check for whitespace on the current line. 188 | if (chr === "\n") { 189 | stripSpace(); 190 | indentation = ""; 191 | tagIndex = 0; 192 | lineHasNonSpace = false; 193 | } 194 | } 195 | } 196 | 197 | // Match the opening tag. 198 | if (!scanner.scan(openingTagRe)) break; 199 | 200 | hasTag = true; 201 | 202 | // Get the tag type. 203 | type = scanner.scan(tagRe) || "name"; 204 | scanner.scan(whiteRe); 205 | 206 | // Get the tag value. 207 | if (type === "=") { 208 | value = scanner.scanUntil(equalsRe); 209 | scanner.scan(equalsRe); 210 | scanner.scanUntil(closingTagRe); 211 | } else if (type === "{") { 212 | value = scanner.scanUntil(closingCurlyRe); 213 | scanner.scan(curlyRe); 214 | scanner.scanUntil(closingTagRe); 215 | type = "&"; 216 | } else { 217 | value = scanner.scanUntil(closingTagRe); 218 | } 219 | 220 | // Match the closing tag. 221 | if (!scanner.scan(closingTagRe)) 222 | throw new Error("Unclosed tag at " + scanner.pos); 223 | 224 | if (type == ">") { 225 | token = [ 226 | type, 227 | value, 228 | start, 229 | scanner.pos, 230 | indentation, 231 | tagIndex, 232 | lineHasNonSpace, 233 | ]; 234 | } else { 235 | token = [type, value, start, scanner.pos]; 236 | } 237 | tagIndex++; 238 | tokens.push(token); 239 | 240 | if (type === "#" || type === "^") { 241 | sections.push(token); 242 | } else if (type === "/") { 243 | // Check section nesting. 244 | openSection = sections.pop(); 245 | 246 | if (!openSection) 247 | throw new Error('Unopened section "' + value + '" at ' + start); 248 | 249 | if (openSection[1] !== value) 250 | throw new Error( 251 | 'Unclosed section "' + openSection[1] + '" at ' + start 252 | ); 253 | } else if (type === "name" || type === "{" || type === "&") { 254 | nonSpace = true; 255 | } else if (type === "=") { 256 | // Set the tags for the next time around. 257 | compileTags(value); 258 | } 259 | } 260 | 261 | stripSpace(); 262 | 263 | // Make sure there are no open sections when we're done. 264 | openSection = sections.pop(); 265 | 266 | if (openSection) 267 | throw new Error( 268 | 'Unclosed section "' + openSection[1] + '" at ' + scanner.pos 269 | ); 270 | 271 | return nestTokens(squashTokens(tokens)); 272 | } 273 | 274 | /** 275 | * Combines the values of consecutive text tokens in the given `tokens` array 276 | * to a single token. 277 | */ 278 | function squashTokens(tokens) { 279 | var squashedTokens = []; 280 | 281 | var token, lastToken; 282 | for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { 283 | token = tokens[i]; 284 | 285 | if (token) { 286 | if (token[0] === "text" && lastToken && lastToken[0] === "text") { 287 | lastToken[1] += token[1]; 288 | lastToken[3] = token[3]; 289 | } else { 290 | squashedTokens.push(token); 291 | lastToken = token; 292 | } 293 | } 294 | } 295 | 296 | return squashedTokens; 297 | } 298 | 299 | /** 300 | * Forms the given array of `tokens` into a nested tree structure where 301 | * tokens that represent a section have two additional items: 1) an array of 302 | * all tokens that appear in that section and 2) the index in the original 303 | * template that represents the end of that section. 304 | */ 305 | function nestTokens(tokens) { 306 | var nestedTokens = []; 307 | var collector = nestedTokens; 308 | var sections = []; 309 | 310 | var token, section; 311 | for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { 312 | token = tokens[i]; 313 | 314 | switch (token[0]) { 315 | case "#": 316 | case "^": 317 | collector.push(token); 318 | sections.push(token); 319 | collector = token[4] = []; 320 | break; 321 | case "/": 322 | section = sections.pop(); 323 | section[5] = token[2]; 324 | collector = 325 | sections.length > 0 326 | ? sections[sections.length - 1][4] 327 | : nestedTokens; 328 | break; 329 | default: 330 | collector.push(token); 331 | } 332 | } 333 | 334 | return nestedTokens; 335 | } 336 | 337 | /** 338 | * A simple string scanner that is used by the template parser to find 339 | * tokens in template strings. 340 | */ 341 | function Scanner(string) { 342 | this.string = string; 343 | this.tail = string; 344 | this.pos = 0; 345 | } 346 | 347 | /** 348 | * Returns `true` if the tail is empty (end of string). 349 | */ 350 | Scanner.prototype.eos = function eos() { 351 | return this.tail === ""; 352 | }; 353 | 354 | /** 355 | * Tries to match the given regular expression at the current position. 356 | * Returns the matched text if it can match, the empty string otherwise. 357 | */ 358 | Scanner.prototype.scan = function scan(re) { 359 | var match = this.tail.match(re); 360 | 361 | if (!match || match.index !== 0) return ""; 362 | 363 | var string = match[0]; 364 | 365 | this.tail = this.tail.substring(string.length); 366 | this.pos += string.length; 367 | 368 | return string; 369 | }; 370 | 371 | /** 372 | * Skips all text until the given regular expression can be matched. Returns 373 | * the skipped string, which is the entire tail if no match can be made. 374 | */ 375 | Scanner.prototype.scanUntil = function scanUntil(re) { 376 | var index = this.tail.search(re), 377 | match; 378 | 379 | switch (index) { 380 | case -1: 381 | match = this.tail; 382 | this.tail = ""; 383 | break; 384 | case 0: 385 | match = ""; 386 | break; 387 | default: 388 | match = this.tail.substring(0, index); 389 | this.tail = this.tail.substring(index); 390 | } 391 | 392 | this.pos += match.length; 393 | 394 | return match; 395 | }; 396 | 397 | /** 398 | * Represents a rendering context by wrapping a view object and 399 | * maintaining a reference to the parent context. 400 | */ 401 | function Context(view, parentContext) { 402 | this.view = view; 403 | this.cache = { ".": this.view }; 404 | this.parent = parentContext; 405 | } 406 | 407 | /** 408 | * Creates a new context using the given view with this context 409 | * as the parent. 410 | */ 411 | Context.prototype.push = function push(view) { 412 | return new Context(view, this); 413 | }; 414 | 415 | /** 416 | * Returns the value of the given name in this context, traversing 417 | * up the context hierarchy if the value is absent in this context's view. 418 | */ 419 | Context.prototype.lookup = function lookup(name) { 420 | var cache = this.cache; 421 | 422 | var value; 423 | if (cache.hasOwnProperty(name)) { 424 | value = cache[name]; 425 | } else { 426 | var context = this, 427 | intermediateValue, 428 | names, 429 | index, 430 | lookupHit = false; 431 | 432 | while (context) { 433 | if (name.indexOf(".") > 0) { 434 | intermediateValue = context.view; 435 | names = name.split("."); 436 | index = 0; 437 | 438 | /** 439 | * Using the dot notion path in `name`, we descend through the 440 | * nested objects. 441 | * 442 | * To be certain that the lookup has been successful, we have to 443 | * check if the last object in the path actually has the property 444 | * we are looking for. We store the result in `lookupHit`. 445 | * 446 | * This is specially necessary for when the value has been set to 447 | * `undefined` and we want to avoid looking up parent contexts. 448 | * 449 | * In the case where dot notation is used, we consider the lookup 450 | * to be successful even if the last "object" in the path is 451 | * not actually an object but a primitive (e.g., a string, or an 452 | * integer), because it is sometimes useful to access a property 453 | * of an autoboxed primitive, such as the length of a string. 454 | **/ 455 | while (intermediateValue != null && index < names.length) { 456 | if (index === names.length - 1) 457 | lookupHit = 458 | hasProperty(intermediateValue, names[index]) || 459 | primitiveHasOwnProperty(intermediateValue, names[index]); 460 | 461 | intermediateValue = intermediateValue[names[index++]]; 462 | } 463 | } else { 464 | intermediateValue = context.view[name]; 465 | 466 | /** 467 | * Only checking against `hasProperty`, which always returns `false` if 468 | * `context.view` is not an object. Deliberately omitting the check 469 | * against `primitiveHasOwnProperty` if dot notation is not used. 470 | * 471 | * Consider this example: 472 | * ``` 473 | * Mustache.render("The length of a football field is {{#length}}{{length}}{{/length}}.", {length: "100 yards"}) 474 | * ``` 475 | * 476 | * If we were to check also against `primitiveHasOwnProperty`, as we do 477 | * in the dot notation case, then render call would return: 478 | * 479 | * "The length of a football field is 9." 480 | * 481 | * rather than the expected: 482 | * 483 | * "The length of a football field is 100 yards." 484 | **/ 485 | lookupHit = hasProperty(context.view, name); 486 | } 487 | 488 | if (lookupHit) { 489 | value = intermediateValue; 490 | break; 491 | } 492 | 493 | context = context.parent; 494 | } 495 | 496 | cache[name] = value; 497 | } 498 | 499 | if (isFunction(value)) value = value.call(this.view); 500 | 501 | return value; 502 | }; 503 | 504 | /** 505 | * A Writer knows how to take a stream of tokens and render them to a 506 | * string, given a context. It also maintains a cache of templates to 507 | * avoid the need to parse the same template twice. 508 | */ 509 | function Writer() { 510 | this.templateCache = { 511 | _cache: {}, 512 | set: function set(key, value) { 513 | this._cache[key] = value; 514 | }, 515 | get: function get(key) { 516 | return this._cache[key]; 517 | }, 518 | clear: function clear() { 519 | this._cache = {}; 520 | }, 521 | }; 522 | } 523 | 524 | /** 525 | * Clears all cached templates in this writer. 526 | */ 527 | Writer.prototype.clearCache = function clearCache() { 528 | if (typeof this.templateCache !== "undefined") { 529 | this.templateCache.clear(); 530 | } 531 | }; 532 | 533 | /** 534 | * Parses and caches the given `template` according to the given `tags` or 535 | * `mustache.tags` if `tags` is omitted, and returns the array of tokens 536 | * that is generated from the parse. 537 | */ 538 | Writer.prototype.parse = function parse(template, tags) { 539 | var cache = this.templateCache; 540 | var cacheKey = template + ":" + (tags || mustache.tags).join(":"); 541 | var isCacheEnabled = typeof cache !== "undefined"; 542 | var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined; 543 | 544 | if (tokens == undefined) { 545 | tokens = parseTemplate(template, tags); 546 | isCacheEnabled && cache.set(cacheKey, tokens); 547 | } 548 | return tokens; 549 | }; 550 | 551 | /** 552 | * High-level method that is used to render the given `template` with 553 | * the given `view`. 554 | * 555 | * The optional `partials` argument may be an object that contains the 556 | * names and templates of partials that are used in the template. It may 557 | * also be a function that is used to load partial templates on the fly 558 | * that takes a single argument: the name of the partial. 559 | * 560 | * If the optional `tags` argument is given here it must be an array with two 561 | * string values: the opening and closing tags used in the template (e.g. 562 | * [ "<%", "%>" ]). The default is to mustache.tags. 563 | */ 564 | Writer.prototype.render = function render(template, view, partials, tags) { 565 | var tokens = this.parse(template, tags); 566 | var context = view instanceof Context ? view : new Context(view, undefined); 567 | return this.renderTokens(tokens, context, partials, template, tags); 568 | }; 569 | 570 | /** 571 | * Low-level method that renders the given array of `tokens` using 572 | * the given `context` and `partials`. 573 | * 574 | * Note: The `originalTemplate` is only ever used to extract the portion 575 | * of the original template that was contained in a higher-order section. 576 | * If the template doesn't use higher-order sections, this argument may 577 | * be omitted. 578 | */ 579 | Writer.prototype.renderTokens = function renderTokens( 580 | tokens, 581 | context, 582 | partials, 583 | originalTemplate, 584 | tags 585 | ) { 586 | var buffer = ""; 587 | 588 | var token, symbol, value; 589 | for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { 590 | value = undefined; 591 | token = tokens[i]; 592 | symbol = token[0]; 593 | 594 | if (symbol === "#") 595 | value = this.renderSection(token, context, partials, originalTemplate); 596 | else if (symbol === "^") 597 | value = this.renderInverted(token, context, partials, originalTemplate); 598 | else if (symbol === ">") 599 | value = this.renderPartial(token, context, partials, tags); 600 | else if (symbol === "&") value = this.unescapedValue(token, context); 601 | else if (symbol === "name") value = this.escapedValue(token, context); 602 | else if (symbol === "text") value = this.rawValue(token); 603 | 604 | if (value !== undefined) buffer += value; 605 | } 606 | 607 | return buffer; 608 | }; 609 | 610 | Writer.prototype.renderSection = function renderSection( 611 | token, 612 | context, 613 | partials, 614 | originalTemplate 615 | ) { 616 | var self = this; 617 | var buffer = ""; 618 | var value = context.lookup(token[1]); 619 | 620 | // This function is used to render an arbitrary template 621 | // in the current context by higher-order sections. 622 | function subRender(template) { 623 | return self.render(template, context, partials); 624 | } 625 | 626 | if (!value) return; 627 | 628 | if (isArray(value)) { 629 | for (var j = 0, valueLength = value.length; j < valueLength; ++j) { 630 | buffer += this.renderTokens( 631 | token[4], 632 | context.push(value[j]), 633 | partials, 634 | originalTemplate 635 | ); 636 | } 637 | } else if ( 638 | typeof value === "object" || 639 | typeof value === "string" || 640 | typeof value === "number" 641 | ) { 642 | buffer += this.renderTokens( 643 | token[4], 644 | context.push(value), 645 | partials, 646 | originalTemplate 647 | ); 648 | } else if (isFunction(value)) { 649 | if (typeof originalTemplate !== "string") 650 | throw new Error( 651 | "Cannot use higher-order sections without the original template" 652 | ); 653 | 654 | // Extract the portion of the original template that the section contains. 655 | value = value.call( 656 | context.view, 657 | originalTemplate.slice(token[3], token[5]), 658 | subRender 659 | ); 660 | 661 | if (value != null) buffer += value; 662 | } else { 663 | buffer += this.renderTokens( 664 | token[4], 665 | context, 666 | partials, 667 | originalTemplate 668 | ); 669 | } 670 | return buffer; 671 | }; 672 | 673 | Writer.prototype.renderInverted = function renderInverted( 674 | token, 675 | context, 676 | partials, 677 | originalTemplate 678 | ) { 679 | var value = context.lookup(token[1]); 680 | 681 | // Use JavaScript's definition of falsy. Include empty arrays. 682 | // See https://github.com/janl/mustache.js/issues/186 683 | if (!value || (isArray(value) && value.length === 0)) 684 | return this.renderTokens(token[4], context, partials, originalTemplate); 685 | }; 686 | 687 | Writer.prototype.indentPartial = function indentPartial( 688 | partial, 689 | indentation, 690 | lineHasNonSpace 691 | ) { 692 | var filteredIndentation = indentation.replace(/[^ \t]/g, ""); 693 | var partialByNl = partial.split("\n"); 694 | for (var i = 0; i < partialByNl.length; i++) { 695 | if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) { 696 | partialByNl[i] = filteredIndentation + partialByNl[i]; 697 | } 698 | } 699 | return partialByNl.join("\n"); 700 | }; 701 | 702 | Writer.prototype.renderPartial = function renderPartial( 703 | token, 704 | context, 705 | partials, 706 | tags 707 | ) { 708 | if (!partials) return; 709 | 710 | var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; 711 | if (value != null) { 712 | var lineHasNonSpace = token[6]; 713 | var tagIndex = token[5]; 714 | var indentation = token[4]; 715 | var indentedValue = value; 716 | if (tagIndex == 0 && indentation) { 717 | indentedValue = this.indentPartial(value, indentation, lineHasNonSpace); 718 | } 719 | return this.renderTokens( 720 | this.parse(indentedValue, tags), 721 | context, 722 | partials, 723 | indentedValue, 724 | tags 725 | ); 726 | } 727 | }; 728 | 729 | Writer.prototype.unescapedValue = function unescapedValue(token, context) { 730 | var value = context.lookup(token[1]); 731 | if (value != null) return value; 732 | }; 733 | 734 | Writer.prototype.escapedValue = function escapedValue(token, context) { 735 | var value = context.lookup(token[1]); 736 | if (value != null) return mustache.escape(value); 737 | }; 738 | 739 | Writer.prototype.rawValue = function rawValue(token) { 740 | return token[1]; 741 | }; 742 | 743 | var mustache = { 744 | name: "mustache.js", 745 | version: "4.0.1", 746 | tags: ["{{", "}}"], 747 | clearCache: undefined, 748 | escape: undefined, 749 | parse: undefined, 750 | render: undefined, 751 | Scanner: undefined, 752 | Context: undefined, 753 | Writer: undefined, 754 | /** 755 | * Allows a user to override the default caching strategy, by providing an 756 | * object with set, get and clear methods. This can also be used to disable 757 | * the cache by setting it to the literal `undefined`. 758 | */ 759 | set templateCache(cache) { 760 | defaultWriter.templateCache = cache; 761 | }, 762 | /** 763 | * Gets the default or overridden caching object from the default writer. 764 | */ 765 | get templateCache() { 766 | return defaultWriter.templateCache; 767 | }, 768 | }; 769 | 770 | // All high-level mustache.* functions use this writer. 771 | var defaultWriter = new Writer(); 772 | 773 | /** 774 | * Clears all cached templates in the default writer. 775 | */ 776 | mustache.clearCache = function clearCache() { 777 | return defaultWriter.clearCache(); 778 | }; 779 | 780 | /** 781 | * Parses and caches the given template in the default writer and returns the 782 | * array of tokens it contains. Doing this ahead of time avoids the need to 783 | * parse templates on the fly as they are rendered. 784 | */ 785 | mustache.parse = function parse(template, tags) { 786 | return defaultWriter.parse(template, tags); 787 | }; 788 | 789 | /** 790 | * Renders the `template` with the given `view` and `partials` using the 791 | * default writer. If the optional `tags` argument is given here it must be an 792 | * array with two string values: the opening and closing tags used in the 793 | * template (e.g. [ "<%", "%>" ]). The default is to mustache.tags. 794 | */ 795 | mustache.render = function render(template, view, partials, tags) { 796 | if (typeof template !== "string") { 797 | throw new TypeError( 798 | 'Invalid template! Template should be a "string" ' + 799 | 'but "' + 800 | typeStr(template) + 801 | '" was given as the first ' + 802 | "argument for mustache#render(template, view, partials)" 803 | ); 804 | } 805 | 806 | return defaultWriter.render(template, view, partials, tags); 807 | }; 808 | 809 | // Export the escaping function so that the user may override it. 810 | // See https://github.com/janl/mustache.js/issues/244 811 | mustache.escape = escapeHtml; 812 | 813 | // Export these mainly for testing, but also for advanced usage. 814 | mustache.Scanner = Scanner; 815 | mustache.Context = Context; 816 | mustache.Writer = Writer; 817 | 818 | return mustache; 819 | }); 820 | -------------------------------------------------------------------------------- /application/assets/js/ratings.js: -------------------------------------------------------------------------------- 1 | const DownloadCounter = (() => { 2 | let downloadCounts = {}; 3 | 4 | function load() { 5 | return new Promise((res, rej) => { 6 | BackendApi.getDownloadCounts() 7 | .then((d) => { 8 | if (typeof downloadCounts !== "object") { 9 | console.error( 10 | "DownloadCounter.load", 11 | "invalid format", 12 | downloadCounts 13 | ); 14 | rej(new Error("Invalid Format")); 15 | } 16 | downloadCounts = d; 17 | res(); 18 | }) 19 | .catch(rej); 20 | }); 21 | } 22 | 23 | function getForApp(appId) { 24 | return downloadCounts[appId] || -1; 25 | } 26 | return { 27 | load, 28 | getForApp, 29 | }; 30 | })(); 31 | 32 | //todo 33 | //create unique username 34 | //if the user open the app first time 35 | //create userID and store it in local storage 36 | //every time whe the user open the app check if the local storage var exist 37 | // if not recreate the userId 38 | function createUserId() { 39 | let random = Math.random().toString(36).substr(2, 9); 40 | random = random.toString(); 41 | let timestamp = Date.now(); 42 | timestamp = timestamp.toString(); 43 | let userId = random + timestamp; 44 | return userId; 45 | } 46 | 47 | function get_userId() { 48 | let userId = localStorage.getItem("userId"); 49 | 50 | //if id not set - do it 51 | if (userId == null) { 52 | localStorage.setItem("userId", createUserId()); 53 | create_user(localStorage.getItem("userId"), localStorage.getItem("userId")); 54 | return userId; 55 | 56 | //return the id 57 | } else { 58 | return userId; 59 | } 60 | } 61 | 62 | ////create user 63 | let max_tryout = 5; 64 | let i = 0; 65 | 66 | function create_user(username, logintoken) { 67 | // Creating a XHR object 68 | let xhr = new XMLHttpRequest({ mozSystem: true }); 69 | let url = "https://bhackers.uber.space/srs/v1/createuser"; 70 | 71 | // open a connection 72 | xhr.open("POST", url, true); 73 | 74 | xhr.setRequestHeader("Content-Type", "application/json"); 75 | xhr.onload = function () { 76 | if (xhr.readyState === 4 && xhr.status === 200) { 77 | console.log(this.responseText); 78 | } else { 79 | console.log(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found 80 | } 81 | }; 82 | 83 | xhr.onerror = function () { 84 | // only triggers if the request couldn't be made at all 85 | i++; 86 | if (i < max_tryout) { 87 | localStorage.removeItem("userId"); 88 | get_userId(); 89 | } 90 | if (i == max_tryout) { 91 | localStorage.removeItem("userId"); 92 | toaster(`Network Error`, 3000); 93 | } 94 | }; 95 | 96 | const json = { 97 | username: username, 98 | logintoken: logintoken, 99 | }; 100 | 101 | // Sending data with the request 102 | xhr.send(JSON.stringify(json)); 103 | } 104 | 105 | function send_rating( 106 | username, 107 | logintoken, 108 | appid_slug, 109 | appid, 110 | points, 111 | description, 112 | callback 113 | ) { 114 | let xhr = new XMLHttpRequest({ mozSystem: true }); 115 | let url = "https://bhackers.uber.space/srs/v1/ratings/" + appid_slug + "/add"; 116 | 117 | xhr.open("POST", url, true); 118 | 119 | xhr.setRequestHeader("Content-Type", "application/json"); 120 | xhr.onload = function () { 121 | if ((xhr.readyState === 4 && xhr.status === 200) || xhr.status === 201) { 122 | // Print received data from server 123 | //console.log(this.responseText); 124 | } else { 125 | //console.log(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found 126 | } 127 | callback(xhr.status); 128 | }; 129 | 130 | xhr.onerror = function () { 131 | // only triggers if the request couldn't be made at all 132 | //alert(`Network Error`); 133 | callback("Network Error"); 134 | }; 135 | 136 | // Converting JSON data to string 137 | let json = { 138 | username: username, 139 | logintoken: logintoken, 140 | appid: appid, 141 | points: points, 142 | description: description, 143 | }; 144 | 145 | // Sending data with the request 146 | xhr.send(JSON.stringify(json)); 147 | } 148 | 149 | function get_ratings(app_slug, callback) { 150 | let xhr = new XMLHttpRequest({ mozSystem: true }); 151 | let url = "https://bhackers.uber.space/srs/v1/ratings/" + app_slug; 152 | 153 | xhr.open("GET", url, true); 154 | 155 | xhr.responseType = "json"; 156 | 157 | xhr.onreadystatechange = function () { 158 | if (xhr.readyState === 4) { 159 | callback(xhr.response); 160 | } else { 161 | //callback(xhr.status); 162 | //console.log(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found 163 | } 164 | }; 165 | 166 | xhr.onerror = function () { 167 | // only triggers if the request couldn't be made at all 168 | //alert(`Network Error`); 169 | callback("Network Error"); 170 | }; 171 | 172 | // Sending data with the request 173 | xhr.send(""); 174 | } 175 | 176 | /////////////////////////// 177 | ////RATING//////////////// 178 | ////////////////////////// 179 | 180 | let rating_stars = 0; 181 | document 182 | .querySelector("div#rating-wrapper input.star") 183 | .addEventListener("keyup", function () { 184 | var val = Number(this.value); 185 | var i = 0; 186 | for (; i <= val; i++) { 187 | if (i > 0) { 188 | document.querySelector(`div#stars span:nth-child(${i})`).style.color = 189 | "yellow"; 190 | } 191 | } 192 | for (; i <= 5; i++) { 193 | document.querySelector(`div#stars span:nth-child(${i})`).style.color = 194 | "white"; 195 | } 196 | rating_stars = document.querySelector("div#rating-wrapper input.star") 197 | .value; 198 | document.querySelector("div#rating-wrapper input.star").value = ""; 199 | }); 200 | 201 | function rating_write_callback(data) { 202 | if (data == 201) { 203 | toaster("Thank you for your rating!", 3000); 204 | close_rating(); 205 | } 206 | if (data == 400) { 207 | toaster("I can't send anything without a rating", 3000); 208 | } 209 | if (data == 409) { 210 | toaster("You already posted a review for this app", 3000); 211 | } 212 | if (data == "Network Error") { 213 | toaster("Network Error", 3000); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /application/assets/js/scan.js: -------------------------------------------------------------------------------- 1 | const qr = ((_) => { 2 | let video; 3 | let start_scan = function (callback) { 4 | window_status = "scan"; 5 | bottom_bar("", "", ""); 6 | 7 | document.getElementById("qr-screen").style.display = "block"; 8 | 9 | navigator.getUserMedia = 10 | navigator.getUserMedia || 11 | navigator.webkitGetUserMedia || 12 | navigator.mozGetUserMedia; 13 | 14 | if (navigator.getUserMedia) { 15 | navigator.getUserMedia( 16 | { audio: false, video: { width: 400, height: 400 } }, 17 | function (stream) { 18 | video = document.querySelector("video"); 19 | video.srcObject = stream; 20 | 21 | video.onloadedmetadata = function (e) { 22 | video.play(); 23 | 24 | var barcodeCanvas = document.createElement("canvas"); 25 | let intv = setInterval(() => { 26 | barcodeCanvas.width = video.videoWidth; 27 | barcodeCanvas.height = video.videoHeight; 28 | var barcodeContext = barcodeCanvas.getContext("2d"); 29 | var imageWidth = video.videoWidth, 30 | imageHeight = video.videoHeight; 31 | barcodeContext.drawImage(video, 0, 0, imageWidth, imageHeight); 32 | 33 | var imageData = barcodeContext.getImageData( 34 | 0, 35 | 0, 36 | imageWidth, 37 | imageHeight 38 | ); 39 | var idd = imageData.data; 40 | 41 | let code = jsQR(idd, imageWidth, imageHeight); 42 | 43 | if (code) { 44 | callback(code.data); 45 | stop_scan("single-article"); 46 | } 47 | }, 1000); 48 | }; 49 | }, 50 | function (err) { 51 | console.log("The following error occurred: " + err.name); 52 | } 53 | ); 54 | } else { 55 | console.log("getUserMedia not supported"); 56 | } 57 | }; 58 | 59 | function stop_scan(route) { 60 | window_status = route; 61 | 62 | const stream = video.srcObject; 63 | const tracks = stream.getTracks(); 64 | 65 | tracks.forEach(function (track) { 66 | track.stop(); 67 | document.getElementById("qr-screen").style.display = "none"; 68 | }); 69 | 70 | video.srcObject = null; 71 | } 72 | 73 | return { start_scan, stop_scan }; 74 | })(); 75 | -------------------------------------------------------------------------------- /application/assets/js/search.js: -------------------------------------------------------------------------------- 1 | const search = ((_) => { 2 | let search_match = function () { 3 | let elements = document.getElementsByTagName("article"); 4 | 5 | if (!this.value) { 6 | for (var i = 0; i < elements.length; i++) { 7 | elements[i].style.display = "block"; 8 | } 9 | return false; 10 | } 11 | 12 | for (var i = 0; i < elements.length; i++) { 13 | elements[0].style.display = "block"; 14 | 15 | if ( 16 | String(elements[i].dataset.slug).indexOf(this.value.toLowerCase()) != 17 | -1 || 18 | String(elements[i].dataset.tags).indexOf(this.value.toLowerCase()) != -1 19 | ) { 20 | console.log(elements[i]); 21 | elements[i].style.display = "block"; 22 | 23 | $("div#app-panels article").removeAttr("tabindex"); 24 | $("div#app-panels article") 25 | .filter(":visible") 26 | .each(function (index) { 27 | $(this).prop("tabindex", index); 28 | }); 29 | } else { 30 | elements[i].style.display = "none"; 31 | } 32 | } 33 | }; 34 | 35 | document 36 | .querySelector("article#search input") 37 | .addEventListener("input", search_match); 38 | 39 | return { search_match }; 40 | })(); 41 | 42 | /* 43 | //https://github.com/devbridge/jquery-Autocomplete 44 | function buildAutocomplete(element, source, container) { 45 | $(element).autocomplete({ 46 | lookup: source, 47 | minChars: 1, 48 | triggerSelectOnValidInput: true, 49 | showNoSuggestionNotice: false, 50 | lookupLimit: 8, 51 | autoSelectFirst: false, 52 | appendTo: $("body"), 53 | 54 | onSearchStart: function () { 55 | $("article:not(article#search)").css("display", "none"); 56 | }, 57 | 58 | onSearchError: function (query, jqXHR, textStatus, errorThrown) { 59 | alert(query); 60 | }, 61 | onSelect: function (suggestion) { 62 | $("article:not(article#search)").css("display", "none"); 63 | $("*[data-tags=" + suggestion.value + "]").css("display", "block"); 64 | $("*[data-slug=" + suggestion.value + "]").css("display", "block"); 65 | 66 | $("div#app-panels article").removeAttr("tabindex"); 67 | $("div#app-panels article") 68 | .filter(":visible") 69 | .each(function (index) { 70 | $(this).prop("tabindex", index); 71 | }); 72 | article_array = $("div#app-panels article").filter(":visible"); 73 | $("body").find("article[tabindex = 1]").focus(); 74 | $("input").val(""); 75 | }, 76 | onHide: function () {}, 77 | 78 | onSearchComplete: function (query, suggestions) { 79 | if (suggestions == "") { 80 | $("body").append("
no result
"); 81 | } else { 82 | $("div.no-result").remove(); 83 | } 84 | }, 85 | }); 86 | } 87 | 88 | var search_list = []; 89 | var filter_search_list = []; 90 | 91 | function searchGetData() { 92 | setTimeout(() => { 93 | $("article").each(function(index) { 94 | if ($(this).attr("data-tags")) { 95 | if ($.inArray($(this).attr("data-tags"), filter_search_list) == -1) { 96 | search_list.push({ 97 | value: $(this).attr("data-tags"), 98 | data: $(this).attr("data-tags"), 99 | }); 100 | search_list.push({ 101 | value: $(this).attr("data-slug"), 102 | data: $(this).attr("data-slug"), 103 | }); 104 | } 105 | } 106 | }); 107 | 108 | //buildAutocomplete("article#search input", search_list, "div#app-panels"); 109 | }, 2000); 110 | } 111 | 112 | */ 113 | -------------------------------------------------------------------------------- /application/icons/donation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 55 | 60 | 65 | Please make a donation 81 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /application/icons/icon-112-112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/icons/icon-112-112.png -------------------------------------------------------------------------------- /application/icons/icon-56-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/icons/icon-56-56.png -------------------------------------------------------------------------------- /application/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/application/icons/icon.png -------------------------------------------------------------------------------- /application/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bHacker-store 5 | 9 | 10 | 11 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 |
39 | 42 |
43 |
44 | 52 |
53 | 117 |
118 |
119 |
120 |
121 | 122 |
123 | 132 | 133 |
134 | 135 |
136 |
Press 1-5 to rate the app and write a comment.
137 |
138 | 139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | 147 |
148 |
149 |
150 |

bHackers

151 |

152 | An alternative app store by free developers for free devices. The 153 | database of apps is hosted https://banana-hackers.gitlab.io/store-db , 154 | further can be added by a pull request. 155 |

156 |
157 |

Contributors

158 |
159 |
160 |
161 |

Respect

162 |
163 | Respect the licenses of the apps, it would be nice if you use app 164 | more often to support the developer with a donation.
Thanks! 165 |
166 | 167 |

Privacy Policy and Terms of use

168 |

169 | Your privacy is important to us, that's why we try to collect 170 | only the data that we need to provide the service to you. 171 |

172 |

Data that might get collected while using the app

173 |

174 | We rely on gitlab pages for hosting the data, this service might 175 | store cookies and save your ip address 176 | https://about.gitlab.com/privacy/ 179 |

180 |

181 | We only link to the app packages, we don't host them ourselves 182 | so when downloading apps you will download them from thirdparty 183 | servers who can then see your ip address. 184 |

185 |

Data that might be shared with third paries

186 |
    187 |
  • IP address when downloading apps
  • 188 |
189 |

Download Counter and ratings

190 |

191 | When using the app we fetch the download count for all apps from our 192 | ratings server. This request might be logged, but we don't 193 | explicitly store your ip address. 194 |

195 |

196 | When downloading apps a request to the download counter will be made 197 | to increase it, this request contains: 198 |

199 |
    200 |
  • The name of the downloaded app
  • 201 |
  • the time of the download
  • 202 |
  • 203 | The IP address of the user, but its not explicitly saved at the 204 | moment. 205 |
  • 206 |
207 |

208 | When fetching the ratings for an app the server gets the requested 209 | appname and the ip address but it is not explicitly saved. 210 |

211 |

212 | When rating apps we store your nickname and your ratings on our 213 | server to make the service possible. 214 |

215 |

Analytics

216 |

217 | Besides from counting downloads we don't do any analytics. But 218 | the apps you download might do it, have ads or even track you. We 219 | try to label apps that have tracking and/or advertisement. 220 |

221 |

Liability & Warranty

222 |

223 | This service is community maintained and provided on a best-effort 224 | basis. We try our best make sure the data is correct, but we do not 225 | give any warranties that the information is correct. Use this 226 | service on your own risk. 227 |

228 |

Abuse

229 |

230 | If you see an app that violates you rights or does bad stuff with 231 | your device, please report it on 232 | gitlab issues 235 |

236 |
237 |
238 |
239 |
240 |
241 |
242 | 243 |
244 | 245 | 246 |
247 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | -------------------------------------------------------------------------------- /application/manifest.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "name": "bHacker", 4 | "description": "An app to download and install apps", 5 | "origin": "app://bhstore.bananahackers.net", 6 | "launch_path": "/index.html", 7 | "type": "certified", 8 | "fullscreen": "true", 9 | 10 | "icons": { 11 | "56": "/icons/icon-56-56.png", 12 | "112": "/icons/icon-112-112.png" 13 | }, 14 | 15 | "developer": { 16 | "name": "strukturart", 17 | "url": "https://strukturart.com" 18 | }, 19 | 20 | "permissions": { 21 | "desktop-notification": { 22 | "description": "To show notifications" 23 | }, 24 | "video-capture": { 25 | "description": "Reading Codes using the Camera" 26 | }, 27 | 28 | "device-storage:sdcard": { 29 | "description": "Required for accessing app manifest files", 30 | "access": "readwrite" 31 | }, 32 | "device-storage:apps": { "access": "readwrite" }, 33 | "webapps-manage": {}, 34 | 35 | "browser": {}, 36 | 37 | "spatialnavigation-app-manage": { 38 | "navigator.spatialNavigationEnabled": false 39 | }, 40 | 41 | "systemXHR": { 42 | "description": "Required to load remote content" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /formatting.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | npm run formatting:test || npm run formatting:fix 3 | echo "OK" 4 | sleep 2 5 | npm run package 6 | echo "Done" 7 | sleep 2 8 | exit; -------------------------------------------------------------------------------- /images/bitcoin_rcv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/images/bitcoin_rcv.png -------------------------------------------------------------------------------- /images/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/images/image-1.png -------------------------------------------------------------------------------- /images/image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/images/image-2.png -------------------------------------------------------------------------------- /images/image-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/images/image-3.png -------------------------------------------------------------------------------- /images/image-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/images/image-4.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/images/logo.png -------------------------------------------------------------------------------- /images/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strukturart/bHacker-store-client/97a416036e154a558f73a3cabb1d70bf0c066150/images/paypal.png -------------------------------------------------------------------------------- /npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/usr/bin/node', '/usr/bin/npm', 'run', 'package' ] 3 | 2 info using npm@3.5.2 4 | 3 info using node@v8.10.0 5 | 4 verbose run-script [ 'prepackage', 'package', 'postpackage' ] 6 | 5 info lifecycle bhackers-store@1.0.0~prepackage: bhackers-store@1.0.0 7 | 6 silly lifecycle bhackers-store@1.0.0~prepackage: no script for prepackage, continuing 8 | 7 info lifecycle bhackers-store@1.0.0~package: bhackers-store@1.0.0 9 | 8 verbose lifecycle bhackers-store@1.0.0~package: unsafe-perm in lifecycle true 10 | 9 verbose lifecycle bhackers-store@1.0.0~package: PATH: /usr/share/npm/bin/node-gyp-bin:/home/strukturart/Desktop/Pages/kaiOs-alt-app-store/node_modules/.bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin 11 | 10 verbose lifecycle bhackers-store@1.0.0~package: CWD: /home/strukturart/Desktop/Pages/kaiOs-alt-app-store 12 | 11 silly lifecycle bhackers-store@1.0.0~package: Args: [ '-c', './tools/build.sh && ./tools/package.sh' ] 13 | 12 info lifecycle bhackers-store@1.0.0~package: Failed to exec package script 14 | 13 verbose stack Error: bhackers-store@1.0.0 package: `./tools/build.sh && ./tools/package.sh` 15 | 13 verbose stack spawn ENOENT 16 | 13 verbose stack at ChildProcess. (/usr/share/npm/lib/utils/spawn.js:17:16) 17 | 13 verbose stack at emitTwo (events.js:126:13) 18 | 13 verbose stack at ChildProcess.emit (events.js:214:7) 19 | 13 verbose stack at maybeClose (internal/child_process.js:925:16) 20 | 13 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:209:5) 21 | 14 verbose pkgid bhackers-store@1.0.0 22 | 15 verbose cwd /home/strukturart/Desktop/Pages/kaiOs-alt-app-store 23 | 16 error Linux 5.4.0-47-generic 24 | 17 error argv "/usr/bin/node" "/usr/bin/npm" "run" "package" 25 | 18 error node v8.10.0 26 | 19 error npm v3.5.2 27 | 20 error file sh 28 | 21 error code ELIFECYCLE 29 | 22 error errno ENOENT 30 | 23 error syscall spawn 31 | 24 error bhackers-store@1.0.0 package: `./tools/build.sh && ./tools/package.sh` 32 | 24 error spawn ENOENT 33 | 25 error Failed at the bhackers-store@1.0.0 package script './tools/build.sh && ./tools/package.sh'. 34 | 25 error Make sure you have the latest version of node.js and npm installed. 35 | 25 error If you do, this is most likely a problem with the bhackers-store package, 36 | 25 error not with npm itself. 37 | 25 error Tell the author that this fails on your system: 38 | 25 error ./tools/build.sh && ./tools/package.sh 39 | 25 error You can get information on how to open an issue for this project with: 40 | 25 error npm bugs bhackers-store 41 | 25 error Or if that isn't available, you can get their info via: 42 | 25 error npm owner ls bhackers-store 43 | 25 error There is likely additional logging output above. 44 | 26 verbose exit [ 1, true ] 45 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bhackers-store", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "buffer-from": { 8 | "version": "1.1.1", 9 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 10 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 11 | "dev": true 12 | }, 13 | "can-use-dom": { 14 | "version": "0.1.0", 15 | "resolved": "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz", 16 | "integrity": "sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo=" 17 | }, 18 | "commander": { 19 | "version": "2.20.3", 20 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 21 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 22 | "dev": true 23 | }, 24 | "core-js": { 25 | "version": "3.6.5", 26 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", 27 | "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" 28 | }, 29 | "lodash.debounce": { 30 | "version": "4.0.8", 31 | "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", 32 | "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" 33 | }, 34 | "lodash.memoize": { 35 | "version": "4.1.2", 36 | "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", 37 | "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" 38 | }, 39 | "lodash.throttle": { 40 | "version": "4.1.1", 41 | "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", 42 | "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" 43 | }, 44 | "mustache": { 45 | "version": "4.0.1", 46 | "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz", 47 | "integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA==" 48 | }, 49 | "prettier": { 50 | "version": "2.1.1", 51 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", 52 | "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==", 53 | "dev": true 54 | }, 55 | "resize-observer-polyfill": { 56 | "version": "1.5.1", 57 | "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", 58 | "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" 59 | }, 60 | "simplebar": { 61 | "version": "5.3.0", 62 | "resolved": "https://registry.npmjs.org/simplebar/-/simplebar-5.3.0.tgz", 63 | "integrity": "sha512-LgrGdIWpwHLLlI9HqfnGql62H/iZlF0KDZ7w3ZNbd2ZLwh9NKsODLHPzQgUlqQ8aZe7Y6/1xJMXK1PU5e810+w==", 64 | "requires": { 65 | "can-use-dom": "^0.1.0", 66 | "core-js": "^3.0.1", 67 | "lodash.debounce": "^4.0.8", 68 | "lodash.memoize": "^4.1.2", 69 | "lodash.throttle": "^4.1.1", 70 | "resize-observer-polyfill": "^1.5.1" 71 | } 72 | }, 73 | "source-map": { 74 | "version": "0.6.1", 75 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 76 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 77 | "dev": true 78 | }, 79 | "source-map-support": { 80 | "version": "0.5.19", 81 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", 82 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", 83 | "dev": true, 84 | "requires": { 85 | "buffer-from": "^1.0.0", 86 | "source-map": "^0.6.0" 87 | } 88 | }, 89 | "terser": { 90 | "version": "5.3.0", 91 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.0.tgz", 92 | "integrity": "sha512-XTT3D3AwxC54KywJijmY2mxZ8nJiEjBHVYzq8l9OaYuRFWeQNBwvipuzzYEP4e+/AVcd1hqG/CqgsdIRyT45Fg==", 93 | "dev": true, 94 | "requires": { 95 | "commander": "^2.20.0", 96 | "source-map": "~0.6.1", 97 | "source-map-support": "~0.5.12" 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bhackers-store", 3 | "version": "1.0.0", 4 | "description": "An client for https://gitlab.com/banana-hackers/store-db", 5 | "main": "index.js", 6 | "scripts": { 7 | "formatting:test": "npx prettier --check .", 8 | "formatting:fix": "npx prettier --write .", 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "package": "./tools/build.sh && ./tools/package.sh" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/strukturart/bHacker-store-client.git" 15 | }, 16 | "author": "strukturart ", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/strukturart/bHacker-store-client/issues" 20 | }, 21 | "devDependencies": { 22 | "prettier": "2.1.1", 23 | "terser": "^5.3.0" 24 | }, 25 | "homepage": "https://github.com/strukturart/bHacker-store-client#readme", 26 | "dependencies": { 27 | "mustache": "^4.0.1", 28 | "simplebar": "^5.3.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tools/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e #fail on error 3 | mkdir -p ./build/app 4 | rm -rf ./build/app/* || true 5 | 6 | # copy folders 7 | mkdir -p ./build/app/assets 8 | cp -r ./application/assets/css ./build/app/assets/css 9 | cp -r ./application/icons ./build/app/icons 10 | cp -r ./application/assets/exclude-js ./build/app/assets/exclude-js 11 | 12 | # copy metadata file 13 | cp ./application/manifest.webapp ./build/app/manifest.webapp 14 | 15 | # minify js 16 | # documentation under https://terser.org/docs/cli-usage 17 | npx terser ./application/assets/js/*.js ./application/app.js \ 18 | -o ./build/app/bundle.min.js \ 19 | --source-map "url='bundle.min.js.map',root='application/',includeSources=true"\ 20 | --ecma 5 21 | 22 | # copy index file 23 | #cp ./application/index.html ./build/app/index.html 24 | 25 | # copy all html file 26 | cp ./application/*.html ./build/app/ -------------------------------------------------------------------------------- /tools/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e #fail on error 3 | 4 | mkdir -p ./build/tmp 5 | 6 | rm ./build/tmp/application.zip || true 7 | rm ./build/bhacker-store.zip || true 8 | 9 | echo "{\"version\": 1,\"manifestURL\":\"app://bHacker-Store.org/manifest.webapp\"}" > ./build/tmp/metadata.json 10 | 11 | 12 | cd build/app 13 | zip -qr ../tmp/application.zip . 14 | 15 | cd ../tmp 16 | zip -qr ../bhacker-store.zip . 17 | 18 | echo "Created bhacker-store.zip" --------------------------------------------------------------------------------