├── .eslintrc.json ├── .github └── workflows │ └── gh-pages.deploy.yml ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── public ├── avatar.svg ├── images │ ├── 3d-tetris.jpg │ ├── gallery.jpg │ ├── hextris.jpg │ ├── pixelImage.jpg │ ├── react-paint.jpg │ ├── rubiks-cube.jpg │ ├── solar-system.jpg │ ├── star.jpg │ └── torch.jpg └── sitemap.xml ├── sitemap.js ├── src ├── components │ ├── accent.tsx │ ├── backgroundPicker.tsx │ ├── backtop.tsx │ ├── colorModeSwitch.tsx │ ├── footer │ │ └── index.tsx │ ├── githubLink.tsx │ ├── glslBackgrounds │ │ ├── glsl.ts │ │ ├── glslCanvas.js │ │ └── index.tsx │ ├── header │ │ ├── avatar.tsx │ │ ├── index.tsx │ │ └── subHeader.tsx │ ├── link.tsx │ ├── primitives │ │ └── typography.tsx │ ├── svg.tsx │ └── types.ts ├── glsl │ ├── defaultBackground.fs │ ├── function │ │ └── plot.glsl │ ├── vertLines.fs │ └── wave.fs ├── hooks │ └── useLocalSetting.ts ├── next-seo.json ├── pages │ ├── [code] │ │ └── index.tsx │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx ├── server │ ├── codes.json │ └── codes.ts ├── typing.ts ├── typings │ └── decs.d.ts └── ui │ ├── prism.tsx │ └── theme │ ├── foundations │ ├── colors.ts │ └── textStyles.ts │ ├── index.ts │ └── styles.tsx ├── tsconfig.json └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [14.x] 14 | 15 | steps: 16 | - name: Get files 17 | uses: actions/checkout@v2 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - name: Install packages 23 | run: npm ci 24 | - name: Build project 25 | run: npm run build 26 | - name: Export static files 27 | run: npm run export 28 | - name: Add .nojekyll file 29 | run: touch ./out/.nojekyll 30 | - name: Deploy 31 | uses: JamesIves/github-pages-deploy-action@4.1.1 32 | with: 33 | branch: gh-pages 34 | folder: out 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "attach", 7 | "name": "connect", 8 | "restart": true, 9 | "protocol": "inspector", 10 | "port": 9300 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-present, Pengfei Wang 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MINICODE 2 | 3 | 一个小程序平台,[https://pengfeiw.github.io/minicode/](https://pengfeiw.github.io/minicode/)。 4 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withPlugins = require("next-compose-plugins") 2 | const {createSitemap} = require("./sitemap"); 3 | const {merge} = require("webpack-merge"); 4 | const webpackconfig = require("./webpack.config"); 5 | 6 | createSitemap(); 7 | 8 | const IsDevelopment = process.env.NODE_ENV === "development"; 9 | const nextConfig = { 10 | webpack5: !IsDevelopment, 11 | pageExtensions: ["js", "jsx", "ts", "tsx"], 12 | assetPrefix: IsDevelopment ? "" : "/minicode", 13 | }; 14 | 15 | module.exports = withPlugins([], { 16 | ...nextConfig, 17 | webpack: (config, options) => merge(config, webpackconfig) 18 | }); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pengfeixc.com", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "Wang Pengfei (http://pengfeixc.com/)", 6 | "homepage": "https://pengfeiw.github.io/minicode", 7 | "bugs": "https://github.com/pengfeiw/minicode/issues", 8 | "description": "A minicode platform", 9 | "keywords": ["3d", "web", "minicode", "grahpics"], 10 | "scripts": { 11 | "dev": "next dev", 12 | "dev:debug": "node --inspect-brk=9300 ./node_modules/next/dist/bin/next -p 3100", 13 | "build": "next build", 14 | "export": "next export", 15 | "start": "next start", 16 | "lint": "next lint" 17 | }, 18 | "dependencies": { 19 | "@chakra-ui/core": "^0.8.0", 20 | "@chakra-ui/icons": "^1.0.16", 21 | "@chakra-ui/react": "^1.6.9", 22 | "@emotion/react": "^11.5.0", 23 | "@emotion/styled": "^11.3.0", 24 | "framer-motion": "^4.1.17", 25 | "mitt": "^3.0.0", 26 | "moment": "^2.29.1", 27 | "next": "11.1.2", 28 | "next-compose-plugins": "^2.2.1", 29 | "next-seo": "^4.28.1", 30 | "next-transpile-modules": "^8.0.0", 31 | "polyfill-object.fromentries": "^1.0.1", 32 | "rc-pagination": "^3.1.15", 33 | "react": "17.0.2", 34 | "react-dom": "17.0.2", 35 | "react-icons": "^4.3.1", 36 | "sitemap": "^7.0.0" 37 | }, 38 | "devDependencies": { 39 | "@types/react": "17.0.27", 40 | "eslint": "8.0.0", 41 | "eslint-config-next": "11.1.2", 42 | "glslify-import-loader": "^0.1.2", 43 | "glslify-loader": "^2.0.0", 44 | "raw-loader": "^4.0.2", 45 | "typescript": "4.4.4", 46 | "webpack-merge": "^5.8.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/avatar.svg: -------------------------------------------------------------------------------- 1 | 头像 副本 -------------------------------------------------------------------------------- /public/images/3d-tetris.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengfeiw/minicode/86ee8f26db97725fe78b287a59d88d727481d2a1/public/images/3d-tetris.jpg -------------------------------------------------------------------------------- /public/images/gallery.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengfeiw/minicode/86ee8f26db97725fe78b287a59d88d727481d2a1/public/images/gallery.jpg -------------------------------------------------------------------------------- /public/images/hextris.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengfeiw/minicode/86ee8f26db97725fe78b287a59d88d727481d2a1/public/images/hextris.jpg -------------------------------------------------------------------------------- /public/images/pixelImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengfeiw/minicode/86ee8f26db97725fe78b287a59d88d727481d2a1/public/images/pixelImage.jpg -------------------------------------------------------------------------------- /public/images/react-paint.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengfeiw/minicode/86ee8f26db97725fe78b287a59d88d727481d2a1/public/images/react-paint.jpg -------------------------------------------------------------------------------- /public/images/rubiks-cube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengfeiw/minicode/86ee8f26db97725fe78b287a59d88d727481d2a1/public/images/rubiks-cube.jpg -------------------------------------------------------------------------------- /public/images/solar-system.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengfeiw/minicode/86ee8f26db97725fe78b287a59d88d727481d2a1/public/images/solar-system.jpg -------------------------------------------------------------------------------- /public/images/star.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengfeiw/minicode/86ee8f26db97725fe78b287a59d88d727481d2a1/public/images/star.jpg -------------------------------------------------------------------------------- /public/images/torch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengfeiw/minicode/86ee8f26db97725fe78b287a59d88d727481d2a1/public/images/torch.jpg -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | https://pengfeiw.github.io/monthly1.0https://pengfeiw.github.io/minicodemonthly0.9https://pengfeiw.github.io/minicode/3d-tetrisweekly0.8https://pengfeiw.github.io/minicode/react-paintweekly0.8https://pengfeiw.github.io/minicode/solar-systemweekly0.8https://pengfeiw.github.io/minicode/threejs-rubikweekly0.8https://pengfeiw.github.io/minicode/threejs-galleryweekly0.8https://pengfeiw.github.io/minicode/threejs-starweekly0.8https://pengfeiw.github.io/minicode/threejs-torchweekly0.8https://pengfeiw.github.io/minicode/pixel-imageweekly0.8https://pengfeiw.github.io/minicode/hextrisweekly0.8 -------------------------------------------------------------------------------- /sitemap.js: -------------------------------------------------------------------------------- 1 | const {SitemapStream, XMLToSitemapItemStream} = require("sitemap"); 2 | const nodeFs = require("fs"); 3 | const path = require("path"); 4 | const codes = require("./src/server/codes.json"); 5 | 6 | /** 7 | * generate sitemap.xml 8 | */ 9 | const createSitemap = () => { 10 | const sitemap = new SitemapStream({hostname: "https://pengfeiw.github.io"}); 11 | const writeStream = nodeFs.createWriteStream(path.join(__dirname, "./public/sitemap.xml")); 12 | sitemap.pipe(writeStream); 13 | 14 | // home 15 | sitemap.write({url: "/", changefreq: "monthly", priority: 1}); 16 | // hellolinearalgebra home 17 | sitemap.write({url: "/minicode", changefreq: "monthly", priority: 0.9}); 18 | 19 | for (let i = 0; i < codes.length; i++) { 20 | // codes 21 | sitemap.write({url: `/minicode/${codes[i].path}`, changefreq: "weekly", priority: 0.8}); 22 | } 23 | 24 | sitemap.end(); 25 | }; 26 | 27 | module.exports = {createSitemap}; 28 | -------------------------------------------------------------------------------- /src/components/accent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {IconButton, IconButtonProps} from '@chakra-ui/react' 3 | import {Svg, SvgProps} from "./svg"; 4 | import {theme, ColorKeys, useLinkColor, accentKeys} from 'src/ui/theme' 5 | import {useLocalSetting} from 'src/hooks/useLocalSetting' 6 | import {css, Global} from '@emotion/react' 7 | import {getTagBackgroundDark} from 'src/ui/theme/foundations/colors' 8 | 9 | export const AccentPickerIcon: React.FC = ({...props}) => { 10 | const color = useLinkColor() 11 | return ( 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export const AccentPicker: React.FC = ({...props}) => { 19 | const [key, setAccentKey] = useLocalSetting( 20 | 'accent', 21 | 'defaultAccent' 22 | ); 23 | 24 | const update = React.useCallback(() => { 25 | let index = accentKeys.indexOf(key) 26 | index = (index + 1) % accentKeys.length 27 | setAccentKey(accentKeys[index]) 28 | }, [key]) 29 | 30 | return ( 31 | } 33 | isRound 34 | onMouseDown={update} 35 | {...props} 36 | /> 37 | ) 38 | } 39 | 40 | export function useAccentStyles(accentKey: ColorKeys, selector: string = '&') { 41 | const accent = theme.colors[accentKey] 42 | return React.useMemo( 43 | () => ({ 44 | [selector]: { 45 | '--colors-accent-50': accent[50], 46 | '--colors-accent-100': accent[100], 47 | '--colors-accent-200': accent[200], 48 | '--colors-accent-300': accent[300], 49 | '--colors-accent-400': accent[400], 50 | '--colors-accent-500': accent[500], 51 | '--colors-accent-600': accent[600], 52 | '--colors-accent-700': accent[700], 53 | '--colors-accent-800': accent[800], 54 | '--colors-accent-900': accent[900], 55 | '--colors-accent-tag-bg-dark': getTagBackgroundDark(accentKey, theme) 56 | } 57 | }), 58 | [accentKey, selector] 59 | ) 60 | } 61 | 62 | export const AccentGlobal: React.FC = () => { 63 | const [accentKey] = useLocalSetting('accent', 'defaultAccent') 64 | const accent = theme.colors[accentKey]; 65 | const styles = React.useMemo( 66 | () => css` 67 | :root { 68 | --colors-accent-50: ${accent[50]}; 69 | --colors-accent-100: ${accent[100]}; 70 | --colors-accent-200: ${accent[200]}; 71 | --colors-accent-300: ${accent[300]}; 72 | --colors-accent-400: ${accent[400]}; 73 | --colors-accent-500: ${accent[500]}; 74 | --colors-accent-600: ${accent[600]}; 75 | --colors-accent-700: ${accent[700]}; 76 | --colors-accent-800: ${accent[800]}; 77 | --colors-accent-900: ${accent[900]}; 78 | --colors-accent-tag-bg-dark: ${getTagBackgroundDark(accentKey, theme)}; 79 | } 80 | `, 81 | [accentKey] 82 | ); 83 | return 84 | }; 85 | -------------------------------------------------------------------------------- /src/components/backgroundPicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {IconButton, IconButtonProps} from '@chakra-ui/react' 3 | import {useLocalSetting} from 'src/hooks/useLocalSetting' 4 | import glslBackgrounds, {GlslBackgroundKeys} from "src/components/glslBackgrounds/glsl"; 5 | import {BsLifePreserver} from "react-icons/bs" 6 | 7 | export const BackgroundPicker: React.FC = ({...props}) => { 8 | const [key, setBackgroundKey] = useLocalSetting("glslBackgroundKey", "defaultBackground"); 9 | 10 | const update = React.useCallback(() => { 11 | const backgroundKeys = Object.keys(glslBackgrounds) as GlslBackgroundKeys[]; 12 | let index = backgroundKeys.indexOf(key) 13 | index = (index + 1) % backgroundKeys.length; 14 | setBackgroundKey(backgroundKeys[index]) 15 | }, [key]) 16 | 17 | return ( 18 | } 20 | isRound 21 | onMouseDown={update} 22 | {...props} 23 | /> 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/components/backtop.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from "react"; 2 | import {Button} from "@chakra-ui/react"; 3 | import {ChevronUpIcon} from "@chakra-ui/icons"; 4 | import {useLinkColor} from "src/ui/theme"; 5 | 6 | const BackTop = () => { 7 | const [visible, setVisible] = useState(false); 8 | 9 | useEffect(() => { 10 | window.addEventListener("scroll", toggleVisible); 11 | }, []); 12 | 13 | const toggleVisible = () => { 14 | const scrolled = document.documentElement.scrollTop; 15 | 16 | if (scrolled > 300) { 17 | setVisible(true); 18 | } else { 19 | setVisible(false); 20 | } 21 | }; 22 | 23 | const scrollToTop = () => { 24 | window.scrollTo({ 25 | top: 0, 26 | behavior: "smooth" 27 | }); 28 | }; 29 | 30 | return ( 31 | 34 | ); 35 | }; 36 | 37 | export default BackTop; 38 | -------------------------------------------------------------------------------- /src/components/colorModeSwitch.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import {IconButton, IconButtonProps, useColorMode} from "@chakra-ui/react" 3 | import {FiSun, FiMoon} from "react-icons/fi" 4 | 5 | export interface ColorModeSwitchProps 6 | extends Omit { } 7 | 8 | export const ColorModeSwitch: React.FC = ({ 9 | ...props 10 | }) => { 11 | const {colorMode, toggleColorMode} = useColorMode() 12 | return ( 13 | : } 16 | isRound 17 | onMouseDown={toggleColorMode} 18 | {...props} 19 | /> 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Box, Text} from "@chakra-ui/react"; 3 | import {OutgoingLink} from "src/components/link"; 4 | import {H1} from "../primitives/typography"; 5 | 6 | const Footer = () => ( 7 | 8 | 9 |

window.open("./")} 14 | _hover={{ 15 | textDecoration: "underline", 16 | cursor: "pointer" 17 | }}> 🛕minicode

18 | | 19 | 📖sitemap 20 | | 21 | 🏔WangPF 22 |
23 | 24 | 本站采用 25 | NextJS 26 | 和 27 | ChakraUI 28 | 搭建, 29 | 感谢 30 | Github 31 | 提供的免费服务。 32 | 33 |
34 | ); 35 | 36 | export default Footer; 37 | -------------------------------------------------------------------------------- /src/components/githubLink.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import {IconButton, useColorMode} from "@chakra-ui/react" 3 | import {GoMarkGithub} from "react-icons/go"; 4 | import {OutgoingLink} from "./link"; 5 | 6 | interface Props { 7 | src?: string; 8 | } 9 | 10 | export const GithubLink: React.FC = (props) => { 11 | const {src} = props; 12 | 13 | const href = src ?? "https://github.com/pengfeiw/minicode"; 14 | 15 | const {colorMode} = useColorMode(); 16 | return ( 17 | 18 | } 21 | isRound 22 | {...props} 23 | /> 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/glslBackgrounds/glsl.ts: -------------------------------------------------------------------------------- 1 | import defaultBackground from "src/glsl/defaultBackground.fs"; 2 | import waveBackground from "src/glsl/wave.fs"; 3 | import vertLinesBackground from "src/glsl/vertLines.fs"; 4 | 5 | const glslBackgrounds = { 6 | defaultBackground, 7 | waveBackground, 8 | vertLinesBackground 9 | }; 10 | 11 | export type GlslBackgroundKeys = keyof typeof glslBackgrounds; 12 | 13 | export default glslBackgrounds; 14 | -------------------------------------------------------------------------------- /src/components/glslBackgrounds/glslCanvas.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 4 | 5 | 6 | 7 | 8 | 9 | function createCommonjsModule(fn, module) { 10 | return module = { exports: {} }, fn(module, module.exports), module.exports; 11 | } 12 | 13 | var win; 14 | 15 | if (typeof window !== "undefined") { 16 | win = window; 17 | } else if (typeof commonjsGlobal !== "undefined") { 18 | win = commonjsGlobal; 19 | } else if (typeof self !== "undefined"){ 20 | win = self; 21 | } else { 22 | win = {}; 23 | } 24 | 25 | var window_1 = win; 26 | 27 | var isFunction_1 = isFunction; 28 | 29 | var toString = Object.prototype.toString; 30 | 31 | function isFunction (fn) { 32 | var string = toString.call(fn); 33 | return string === '[object Function]' || 34 | (typeof fn === 'function' && string !== '[object RegExp]') || 35 | (typeof window !== 'undefined' && 36 | // IE8 and below 37 | (fn === window.setTimeout || 38 | fn === window.alert || 39 | fn === window.confirm || 40 | fn === window.prompt)) 41 | } 42 | 43 | var trim_1 = createCommonjsModule(function (module, exports) { 44 | exports = module.exports = trim; 45 | 46 | function trim(str){ 47 | return str.replace(/^\s*|\s*$/g, ''); 48 | } 49 | 50 | exports.left = function(str){ 51 | return str.replace(/^\s*/, ''); 52 | }; 53 | 54 | exports.right = function(str){ 55 | return str.replace(/\s*$/, ''); 56 | }; 57 | }); 58 | 59 | var forEach_1 = forEach; 60 | 61 | var toString$1 = Object.prototype.toString; 62 | var hasOwnProperty = Object.prototype.hasOwnProperty; 63 | 64 | function forEach(list, iterator, context) { 65 | if (!isFunction_1(iterator)) { 66 | throw new TypeError('iterator must be a function') 67 | } 68 | 69 | if (arguments.length < 3) { 70 | context = this; 71 | } 72 | 73 | if (toString$1.call(list) === '[object Array]') 74 | forEachArray$1(list, iterator, context); 75 | else if (typeof list === 'string') 76 | forEachString(list, iterator, context); 77 | else 78 | forEachObject(list, iterator, context); 79 | } 80 | 81 | function forEachArray$1(array, iterator, context) { 82 | for (var i = 0, len = array.length; i < len; i++) { 83 | if (hasOwnProperty.call(array, i)) { 84 | iterator.call(context, array[i], i, array); 85 | } 86 | } 87 | } 88 | 89 | function forEachString(string, iterator, context) { 90 | for (var i = 0, len = string.length; i < len; i++) { 91 | // no such thing as a sparse string. 92 | iterator.call(context, string.charAt(i), i, string); 93 | } 94 | } 95 | 96 | function forEachObject(object, iterator, context) { 97 | for (var k in object) { 98 | if (hasOwnProperty.call(object, k)) { 99 | iterator.call(context, object[k], k, object); 100 | } 101 | } 102 | } 103 | 104 | var isArray = function(arg) { 105 | return Object.prototype.toString.call(arg) === '[object Array]'; 106 | }; 107 | 108 | var parseHeaders = function (headers) { 109 | if (!headers) 110 | return {} 111 | 112 | var result = {}; 113 | 114 | forEach_1( 115 | trim_1(headers).split('\n') 116 | , function (row) { 117 | var index = row.indexOf(':') 118 | , key = trim_1(row.slice(0, index)).toLowerCase() 119 | , value = trim_1(row.slice(index + 1)); 120 | 121 | if (typeof(result[key]) === 'undefined') { 122 | result[key] = value; 123 | } else if (isArray(result[key])) { 124 | result[key].push(value); 125 | } else { 126 | result[key] = [ result[key], value ]; 127 | } 128 | } 129 | ); 130 | 131 | return result 132 | }; 133 | 134 | var immutable = extend; 135 | 136 | var hasOwnProperty$1 = Object.prototype.hasOwnProperty; 137 | 138 | function extend() { 139 | var target = {}; 140 | 141 | for (var i = 0; i < arguments.length; i++) { 142 | var source = arguments[i]; 143 | 144 | for (var key in source) { 145 | if (hasOwnProperty$1.call(source, key)) { 146 | target[key] = source[key]; 147 | } 148 | } 149 | } 150 | 151 | return target 152 | } 153 | 154 | "use strict"; 155 | 156 | 157 | 158 | 159 | 160 | var xhr = createXHR; 161 | // Allow use of default import syntax in TypeScript 162 | var default_1 = createXHR; 163 | createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop; 164 | createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window_1.XDomainRequest; 165 | 166 | forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) { 167 | createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) { 168 | options = initParams(uri, options, callback); 169 | options.method = method.toUpperCase(); 170 | return _createXHR(options) 171 | }; 172 | }); 173 | 174 | function forEachArray(array, iterator) { 175 | for (var i = 0; i < array.length; i++) { 176 | iterator(array[i]); 177 | } 178 | } 179 | 180 | function isEmpty(obj){ 181 | for(var i in obj){ 182 | if(obj.hasOwnProperty(i)) return false 183 | } 184 | return true 185 | } 186 | 187 | function initParams(uri, options, callback) { 188 | var params = uri; 189 | 190 | if (isFunction_1(options)) { 191 | callback = options; 192 | if (typeof uri === "string") { 193 | params = {uri:uri}; 194 | } 195 | } else { 196 | params = immutable(options, {uri: uri}); 197 | } 198 | 199 | params.callback = callback; 200 | return params 201 | } 202 | 203 | function createXHR(uri, options, callback) { 204 | options = initParams(uri, options, callback); 205 | return _createXHR(options) 206 | } 207 | 208 | function _createXHR(options) { 209 | if(typeof options.callback === "undefined"){ 210 | throw new Error("callback argument missing") 211 | } 212 | 213 | var called = false; 214 | var callback = function cbOnce(err, response, body){ 215 | if(!called){ 216 | called = true; 217 | options.callback(err, response, body); 218 | } 219 | }; 220 | 221 | function readystatechange() { 222 | if (xhr.readyState === 4) { 223 | setTimeout(loadFunc, 0); 224 | } 225 | } 226 | 227 | function getBody() { 228 | // Chrome with requestType=blob throws errors arround when even testing access to responseText 229 | var body = undefined; 230 | 231 | if (xhr.response) { 232 | body = xhr.response; 233 | } else { 234 | body = xhr.responseText || getXml(xhr); 235 | } 236 | 237 | if (isJson) { 238 | try { 239 | body = JSON.parse(body); 240 | } catch (e) {} 241 | } 242 | 243 | return body 244 | } 245 | 246 | function errorFunc(evt) { 247 | clearTimeout(timeoutTimer); 248 | if(!(evt instanceof Error)){ 249 | evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ); 250 | } 251 | evt.statusCode = 0; 252 | return callback(evt, failureResponse) 253 | } 254 | 255 | // will load the data & process the response in a special response object 256 | function loadFunc() { 257 | if (aborted) return 258 | var status; 259 | clearTimeout(timeoutTimer); 260 | if(options.useXDR && xhr.status===undefined) { 261 | //IE8 CORS GET successful response doesn't have a status field, but body is fine 262 | status = 200; 263 | } else { 264 | status = (xhr.status === 1223 ? 204 : xhr.status); 265 | } 266 | var response = failureResponse; 267 | var err = null; 268 | 269 | if (status !== 0){ 270 | response = { 271 | body: getBody(), 272 | statusCode: status, 273 | method: method, 274 | headers: {}, 275 | url: uri, 276 | rawRequest: xhr 277 | }; 278 | if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE 279 | response.headers = parseHeaders(xhr.getAllResponseHeaders()); 280 | } 281 | } else { 282 | err = new Error("Internal XMLHttpRequest Error"); 283 | } 284 | return callback(err, response, response.body) 285 | } 286 | 287 | var xhr = options.xhr || null; 288 | 289 | if (!xhr) { 290 | if (options.cors || options.useXDR) { 291 | xhr = new createXHR.XDomainRequest(); 292 | }else{ 293 | xhr = new createXHR.XMLHttpRequest(); 294 | } 295 | } 296 | 297 | var key; 298 | var aborted; 299 | var uri = xhr.url = options.uri || options.url; 300 | var method = xhr.method = options.method || "GET"; 301 | var body = options.body || options.data; 302 | var headers = xhr.headers = options.headers || {}; 303 | var sync = !!options.sync; 304 | var isJson = false; 305 | var timeoutTimer; 306 | var failureResponse = { 307 | body: undefined, 308 | headers: {}, 309 | statusCode: 0, 310 | method: method, 311 | url: uri, 312 | rawRequest: xhr 313 | }; 314 | 315 | if ("json" in options && options.json !== false) { 316 | isJson = true; 317 | headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user 318 | if (method !== "GET" && method !== "HEAD") { 319 | headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user 320 | body = JSON.stringify(options.json === true ? body : options.json); 321 | } 322 | } 323 | 324 | xhr.onreadystatechange = readystatechange; 325 | xhr.onload = loadFunc; 326 | xhr.onerror = errorFunc; 327 | // IE9 must have onprogress be set to a unique function. 328 | xhr.onprogress = function () { 329 | // IE must die 330 | }; 331 | xhr.onabort = function(){ 332 | aborted = true; 333 | }; 334 | xhr.ontimeout = errorFunc; 335 | xhr.open(method, uri, !sync, options.username, options.password); 336 | //has to be after open 337 | if(!sync) { 338 | xhr.withCredentials = !!options.withCredentials; 339 | } 340 | // Cannot set timeout with sync request 341 | // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly 342 | // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent 343 | if (!sync && options.timeout > 0 ) { 344 | timeoutTimer = setTimeout(function(){ 345 | if (aborted) return 346 | aborted = true;//IE9 may still call readystatechange 347 | xhr.abort("timeout"); 348 | var e = new Error("XMLHttpRequest timeout"); 349 | e.code = "ETIMEDOUT"; 350 | errorFunc(e); 351 | }, options.timeout ); 352 | } 353 | 354 | if (xhr.setRequestHeader) { 355 | for(key in headers){ 356 | if(headers.hasOwnProperty(key)){ 357 | xhr.setRequestHeader(key, headers[key]); 358 | } 359 | } 360 | } else if (options.headers && !isEmpty(options.headers)) { 361 | throw new Error("Headers cannot be set on an XDomainRequest object") 362 | } 363 | 364 | if ("responseType" in options) { 365 | xhr.responseType = options.responseType; 366 | } 367 | 368 | if ("beforeSend" in options && 369 | typeof options.beforeSend === "function" 370 | ) { 371 | options.beforeSend(xhr); 372 | } 373 | 374 | // Microsoft Edge browser sends "undefined" when send is called with undefined value. 375 | // XMLHttpRequest spec says to pass null as body to indicate no body 376 | // See https://github.com/naugtur/xhr/issues/100. 377 | xhr.send(body || null); 378 | 379 | return xhr 380 | 381 | 382 | } 383 | 384 | function getXml(xhr) { 385 | // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException" 386 | // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML. 387 | try { 388 | if (xhr.responseType === "document") { 389 | return xhr.responseXML 390 | } 391 | var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; 392 | if (xhr.responseType === "" && !firefoxBugTakenEffect) { 393 | return xhr.responseXML 394 | } 395 | } catch (e) {} 396 | 397 | return null 398 | } 399 | 400 | function noop() {} 401 | 402 | xhr.default = default_1; 403 | 404 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 405 | return typeof obj; 406 | } : function (obj) { 407 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 408 | }; 409 | 410 | 411 | 412 | 413 | 414 | var asyncGenerator = function () { 415 | function AwaitValue(value) { 416 | this.value = value; 417 | } 418 | 419 | function AsyncGenerator(gen) { 420 | var front, back; 421 | 422 | function send(key, arg) { 423 | return new Promise(function (resolve, reject) { 424 | var request = { 425 | key: key, 426 | arg: arg, 427 | resolve: resolve, 428 | reject: reject, 429 | next: null 430 | }; 431 | 432 | if (back) { 433 | back = back.next = request; 434 | } else { 435 | front = back = request; 436 | resume(key, arg); 437 | } 438 | }); 439 | } 440 | 441 | function resume(key, arg) { 442 | try { 443 | var result = gen[key](arg); 444 | var value = result.value; 445 | 446 | if (value instanceof AwaitValue) { 447 | Promise.resolve(value.value).then(function (arg) { 448 | resume("next", arg); 449 | }, function (arg) { 450 | resume("throw", arg); 451 | }); 452 | } else { 453 | settle(result.done ? "return" : "normal", result.value); 454 | } 455 | } catch (err) { 456 | settle("throw", err); 457 | } 458 | } 459 | 460 | function settle(type, value) { 461 | switch (type) { 462 | case "return": 463 | front.resolve({ 464 | value: value, 465 | done: true 466 | }); 467 | break; 468 | 469 | case "throw": 470 | front.reject(value); 471 | break; 472 | 473 | default: 474 | front.resolve({ 475 | value: value, 476 | done: false 477 | }); 478 | break; 479 | } 480 | 481 | front = front.next; 482 | 483 | if (front) { 484 | resume(front.key, front.arg); 485 | } else { 486 | back = null; 487 | } 488 | } 489 | 490 | this._invoke = send; 491 | 492 | if (typeof gen.return !== "function") { 493 | this.return = undefined; 494 | } 495 | } 496 | 497 | if (typeof Symbol === "function" && Symbol.asyncIterator) { 498 | AsyncGenerator.prototype[Symbol.asyncIterator] = function () { 499 | return this; 500 | }; 501 | } 502 | 503 | AsyncGenerator.prototype.next = function (arg) { 504 | return this._invoke("next", arg); 505 | }; 506 | 507 | AsyncGenerator.prototype.throw = function (arg) { 508 | return this._invoke("throw", arg); 509 | }; 510 | 511 | AsyncGenerator.prototype.return = function (arg) { 512 | return this._invoke("return", arg); 513 | }; 514 | 515 | return { 516 | wrap: function (fn) { 517 | return function () { 518 | return new AsyncGenerator(fn.apply(this, arguments)); 519 | }; 520 | }, 521 | await: function (value) { 522 | return new AwaitValue(value); 523 | } 524 | }; 525 | }(); 526 | 527 | 528 | 529 | 530 | 531 | var classCallCheck = function (instance, Constructor) { 532 | if (!(instance instanceof Constructor)) { 533 | throw new TypeError("Cannot call a class as a function"); 534 | } 535 | }; 536 | 537 | var createClass = function () { 538 | function defineProperties(target, props) { 539 | for (var i = 0; i < props.length; i++) { 540 | var descriptor = props[i]; 541 | descriptor.enumerable = descriptor.enumerable || false; 542 | descriptor.configurable = true; 543 | if ("value" in descriptor) descriptor.writable = true; 544 | Object.defineProperty(target, descriptor.key, descriptor); 545 | } 546 | } 547 | 548 | return function (Constructor, protoProps, staticProps) { 549 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 550 | if (staticProps) defineProperties(Constructor, staticProps); 551 | return Constructor; 552 | }; 553 | }(); 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | var toConsumableArray = function (arr) { 596 | if (Array.isArray(arr)) { 597 | for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; 598 | 599 | return arr2; 600 | } else { 601 | return Array.from(arr); 602 | } 603 | }; 604 | 605 | var lastError = ''; 606 | 607 | /** 608 | * Creates the HTLM for a failure message 609 | * @param {string} canvasContainerId id of container of th 610 | * canvas. 611 | * @return {string} The html. 612 | */ 613 | function makeFailHTML(msg) { 614 | return '\n\n
\n
\n
' + msg + '
\n
\n
\n'; 615 | } 616 | 617 | /** 618 | * Message for getting a webgl browser 619 | * @type {string} 620 | */ 621 | var GET_A_WEBGL_BROWSER = '\n\tThis page requires a browser that supports WebGL.
\n\tClick here to upgrade your browser.\n'; 622 | 623 | /** 624 | * Message for need better hardware 625 | * @type {string} 626 | */ 627 | var OTHER_PROBLEM = '\n\tIt does not appear your computer can support WebGL.
\n\tClick here for more information.\n'; 628 | 629 | /** 630 | * Code to return in `onError` callback when the browser doesn't support webgl 631 | * @type {number} 632 | */ 633 | var ERROR_BROWSER_SUPPORT = 1; 634 | 635 | /** 636 | * Code to return in `onError` callback there's any other problem related to webgl 637 | * @type {number} 638 | */ 639 | var ERROR_OTHER = 2; 640 | 641 | /** 642 | * Creates a webgl context. If creation fails it will 643 | * change the contents of the container of the 644 | * tag to an error message with the correct links for WebGL, 645 | * unless `onError` option is defined to a callback, 646 | * which allows for custom error handling.. 647 | * @param {Element} canvas. The canvas element to create a 648 | * context from. 649 | * @param {WebGLContextCreationAttributes} optAttribs Any 650 | * creation attributes you want to pass in. 651 | * @return {WebGLRenderingContext} The created context. 652 | */ 653 | function setupWebGL(canvas, optAttribs, onError) { 654 | function showLink(str) { 655 | var container = canvas.parentNode; 656 | if (container) { 657 | container.innerHTML = makeFailHTML(str); 658 | } 659 | } 660 | 661 | function handleError(errorCode, msg) { 662 | if (typeof onError === 'function') { 663 | onError(errorCode); 664 | } else { 665 | showLink(msg); 666 | } 667 | } 668 | 669 | if (!window.WebGLRenderingContext) { 670 | handleError(ERROR_BROWSER_SUPPORT, GET_A_WEBGL_BROWSER); 671 | return null; 672 | } 673 | 674 | var context = create3DContext(canvas, optAttribs); 675 | if (!context) { 676 | handleError(ERROR_OTHER, OTHER_PROBLEM); 677 | } else { 678 | context.getExtension('OES_standard_derivatives'); 679 | } 680 | return context; 681 | } 682 | 683 | /** 684 | * Creates a webgl context. 685 | * @param {!Canvas} canvas The canvas tag to get context 686 | * from. If one is not passed in one will be created. 687 | * @return {!WebGLContext} The created context. 688 | */ 689 | function create3DContext(canvas, optAttribs) { 690 | var names = ['webgl', 'experimental-webgl']; 691 | var context = null; 692 | for (var ii = 0; ii < names.length; ++ii) { 693 | try { 694 | context = canvas.getContext(names[ii], optAttribs); 695 | } catch (e) { 696 | if (context) { 697 | break; 698 | } 699 | } 700 | } 701 | return context; 702 | } 703 | 704 | /* 705 | * Create a Vertex of a specific type (gl.VERTEX_SHADER/) 706 | */ 707 | function createShader(main, source, type, offset) { 708 | var gl = main.gl; 709 | 710 | var shader = gl.createShader(type); 711 | gl.shaderSource(shader, source); 712 | gl.compileShader(shader); 713 | 714 | var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); 715 | 716 | if (!compiled) { 717 | // Something went wrong during compilation; get the error 718 | lastError = gl.getShaderInfoLog(shader); 719 | console.error('*** Error compiling shader ' + shader + ':' + lastError); 720 | main.trigger('error', { 721 | shader: shader, 722 | source: source, 723 | type: type, 724 | error: lastError, 725 | offset: offset || 0 726 | }); 727 | gl.deleteShader(shader); 728 | return null; 729 | } 730 | 731 | return shader; 732 | } 733 | 734 | /** 735 | * Loads a shader. 736 | * @param {!WebGLContext} gl The WebGLContext to use. 737 | * @param {string} shaderSource The shader source. 738 | * @param {number} shaderType The type of shader. 739 | * @param {function(string): void) opt_errorCallback callback for errors. 740 | * @return {!WebGLShader} The created shader. 741 | */ 742 | function createProgram(main, shaders, optAttribs, optLocations) { 743 | var gl = main.gl; 744 | 745 | var program = gl.createProgram(); 746 | for (var ii = 0; ii < shaders.length; ++ii) { 747 | gl.attachShader(program, shaders[ii]); 748 | } 749 | if (optAttribs) { 750 | for (var _ii = 0; _ii < optAttribs.length; ++_ii) { 751 | gl.bindAttribLocation(program, optLocations ? optLocations[_ii] : _ii, optAttribs[_ii]); 752 | } 753 | } 754 | gl.linkProgram(program); 755 | 756 | // Check the link status 757 | var linked = gl.getProgramParameter(program, gl.LINK_STATUS); 758 | if (!linked) { 759 | // something went wrong with the link 760 | lastError = gl.getProgramInfoLog(program); 761 | console.log('Error in program linking:' + lastError); 762 | gl.deleteProgram(program); 763 | return null; 764 | } 765 | return program; 766 | } 767 | 768 | // By Brett Camber on 769 | // https://github.com/tangrams/tangram/blob/master/src/gl/glsl.js 770 | function parseUniforms(uniforms) { 771 | var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 772 | 773 | var parsed = []; 774 | 775 | for (var name in uniforms) { 776 | var uniform = uniforms[name]; 777 | var u = void 0; 778 | 779 | if (prefix) { 780 | name = prefix + '.' + name; 781 | } 782 | 783 | // Single float 784 | if (typeof uniform === 'number') { 785 | parsed.push({ 786 | type: 'float', 787 | method: '1f', 788 | name: name, 789 | value: uniform 790 | }); 791 | } 792 | // Array: vector, array of floats, array of textures, or array of structs 793 | else if (Array.isArray(uniform)) { 794 | // Numeric values 795 | if (typeof uniform[0] === 'number') { 796 | // float vectors (vec2, vec3, vec4) 797 | if (uniform.length === 1) { 798 | parsed.push({ 799 | type: 'float', 800 | method: '1f', 801 | name: name, 802 | value: uniform 803 | }); 804 | } 805 | // float vectors (vec2, vec3, vec4) 806 | else if (uniform.length >= 2 && uniform.length <= 4) { 807 | parsed.push({ 808 | type: 'vec' + uniform.length, 809 | method: uniform.length + 'fv', 810 | name: name, 811 | value: uniform 812 | }); 813 | } 814 | // float array 815 | else if (uniform.length > 4) { 816 | parsed.push({ 817 | type: 'float[]', 818 | method: '1fv', 819 | name: name + '[0]', 820 | value: uniform 821 | }); 822 | } 823 | // TODO: assume matrix for (typeof == Float32Array && length == 16)? 824 | } 825 | // Array of textures 826 | else if (typeof uniform[0] === 'string') { 827 | parsed.push({ 828 | type: 'sampler2D', 829 | method: '1i', 830 | name: name, 831 | value: uniform 832 | }); 833 | } 834 | // Array of arrays - but only arrays of vectors are allowed in this case 835 | else if (Array.isArray(uniform[0]) && typeof uniform[0][0] === 'number') { 836 | // float vectors (vec2, vec3, vec4) 837 | if (uniform[0].length >= 2 && uniform[0].length <= 4) { 838 | // Set each vector in the array 839 | for (u = 0; u < uniform.length; u++) { 840 | parsed.push({ 841 | type: 'vec' + uniform[0].length, 842 | method: uniform[u].length + 'fv', 843 | name: name + '[' + u + ']', 844 | value: uniform[u] 845 | }); 846 | } 847 | } 848 | // else error? 849 | } 850 | // Array of structures 851 | else if (_typeof(uniform[0]) === 'object') { 852 | for (u = 0; u < uniform.length; u++) { 853 | // Set each struct in the array 854 | parsed.push.apply(parsed, toConsumableArray(parseUniforms(uniform[u], name + '[' + u + ']'))); 855 | } 856 | } 857 | } 858 | // Boolean 859 | else if (typeof uniform === 'boolean') { 860 | parsed.push({ 861 | type: 'bool', 862 | method: '1i', 863 | name: name, 864 | value: uniform 865 | }); 866 | } 867 | // Texture 868 | else if (typeof uniform === 'string') { 869 | parsed.push({ 870 | type: 'sampler2D', 871 | method: '1i', 872 | name: name, 873 | value: uniform 874 | }); 875 | } 876 | // Structure 877 | else if ((typeof uniform === 'undefined' ? 'undefined' : _typeof(uniform)) === 'object') { 878 | // Set each field in the struct 879 | parsed.push.apply(parsed, toConsumableArray(parseUniforms(uniform, name))); 880 | } 881 | // TODO: support other non-float types? (int, etc.) 882 | } 883 | return parsed; 884 | } 885 | 886 | function isCanvasVisible(canvas) { 887 | return canvas.getBoundingClientRect().top + canvas.height > 0 && canvas.getBoundingClientRect().top < (window.innerHeight || document.documentElement.clientHeight); 888 | } 889 | 890 | function isPowerOf2(value) { 891 | return (value & value - 1) === 0; 892 | } 893 | 894 | function isSafari() { 895 | return (/^((?!chrome|android).)*safari/i.test(navigator.userAgent) 896 | ); 897 | } 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | function isDiff(a, b) { 906 | if (a && b) { 907 | return a.toString() !== b.toString(); 908 | } 909 | return false; 910 | } 911 | 912 | function subscribeMixin$1(target) { 913 | var listeners = new Set(); 914 | 915 | return Object.assign(target, { 916 | on: function on(type, f) { 917 | var listener = {}; 918 | listener[type] = f; 919 | listeners.add(listener); 920 | }, 921 | off: function off(type, f) { 922 | if (f) { 923 | var listener = {}; 924 | listener[type] = f; 925 | listeners.delete(listener); 926 | } else { 927 | var _iteratorNormalCompletion = true; 928 | var _didIteratorError = false; 929 | var _iteratorError = undefined; 930 | 931 | try { 932 | for (var _iterator = listeners[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 933 | var item = _step.value; 934 | var _iteratorNormalCompletion2 = true; 935 | var _didIteratorError2 = false; 936 | var _iteratorError2 = undefined; 937 | 938 | try { 939 | for (var _iterator2 = Object.keys(item)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 940 | var key = _step2.value; 941 | 942 | if (key === type) { 943 | listeners.delete(item); 944 | return; 945 | } 946 | } 947 | } catch (err) { 948 | _didIteratorError2 = true; 949 | _iteratorError2 = err; 950 | } finally { 951 | try { 952 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 953 | _iterator2.return(); 954 | } 955 | } finally { 956 | if (_didIteratorError2) { 957 | throw _iteratorError2; 958 | } 959 | } 960 | } 961 | } 962 | } catch (err) { 963 | _didIteratorError = true; 964 | _iteratorError = err; 965 | } finally { 966 | try { 967 | if (!_iteratorNormalCompletion && _iterator.return) { 968 | _iterator.return(); 969 | } 970 | } finally { 971 | if (_didIteratorError) { 972 | throw _iteratorError; 973 | } 974 | } 975 | } 976 | } 977 | }, 978 | listSubscriptions: function listSubscriptions() { 979 | var _iteratorNormalCompletion3 = true; 980 | var _didIteratorError3 = false; 981 | var _iteratorError3 = undefined; 982 | 983 | try { 984 | for (var _iterator3 = listeners[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 985 | var item = _step3.value; 986 | 987 | console.log(item); 988 | } 989 | } catch (err) { 990 | _didIteratorError3 = true; 991 | _iteratorError3 = err; 992 | } finally { 993 | try { 994 | if (!_iteratorNormalCompletion3 && _iterator3.return) { 995 | _iterator3.return(); 996 | } 997 | } finally { 998 | if (_didIteratorError3) { 999 | throw _iteratorError3; 1000 | } 1001 | } 1002 | } 1003 | }, 1004 | subscribe: function subscribe(listener) { 1005 | listeners.add(listener); 1006 | }, 1007 | unsubscribe: function unsubscribe(listener) { 1008 | listeners.delete(listener); 1009 | }, 1010 | unsubscribeAll: function unsubscribeAll() { 1011 | listeners.clear(); 1012 | }, 1013 | trigger: function trigger(event) { 1014 | for (var _len = arguments.length, data = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 1015 | data[_key - 1] = arguments[_key]; 1016 | } 1017 | 1018 | var _iteratorNormalCompletion4 = true; 1019 | var _didIteratorError4 = false; 1020 | var _iteratorError4 = undefined; 1021 | 1022 | try { 1023 | for (var _iterator4 = listeners[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { 1024 | var listener = _step4.value; 1025 | 1026 | if (typeof listener[event] === 'function') { 1027 | listener[event].apply(listener, toConsumableArray(data)); 1028 | } 1029 | } 1030 | } catch (err) { 1031 | _didIteratorError4 = true; 1032 | _iteratorError4 = err; 1033 | } finally { 1034 | try { 1035 | if (!_iteratorNormalCompletion4 && _iterator4.return) { 1036 | _iterator4.return(); 1037 | } 1038 | } finally { 1039 | if (_didIteratorError4) { 1040 | throw _iteratorError4; 1041 | } 1042 | } 1043 | } 1044 | } 1045 | }); 1046 | } 1047 | 1048 | // Texture management 1049 | // GL texture wrapper object for keeping track of a global set of textures, keyed by a unique user-defined name 1050 | 1051 | var Texture = function () { 1052 | function Texture(gl, name) { 1053 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 1054 | classCallCheck(this, Texture); 1055 | 1056 | subscribeMixin$1(this); 1057 | 1058 | this.gl = gl; 1059 | this.texture = gl.createTexture(); 1060 | if (this.texture) { 1061 | this.valid = true; 1062 | } 1063 | this.bind(); 1064 | 1065 | this.name = name; 1066 | this.source = null; 1067 | this.sourceType = null; 1068 | this.loading = null; // a Promise object to track the loading state of this texture 1069 | 1070 | // Default to a 1-pixel black texture so we can safely render while we wait for an image to load 1071 | // See: http://stackoverflow.com/questions/19722247/webgl-wait-for-texture-to-load 1072 | this.setData(1, 1, new Uint8Array([0, 0, 0, 255]), { filtering: 'linear' }); 1073 | this.setFiltering(options.filtering); 1074 | 1075 | this.load(options); 1076 | } 1077 | 1078 | // Destroy a single texture instance 1079 | 1080 | 1081 | createClass(Texture, [{ 1082 | key: 'destroy', 1083 | value: function destroy() { 1084 | if (!this.valid) { 1085 | return; 1086 | } 1087 | this.gl.deleteTexture(this.texture); 1088 | this.texture = null; 1089 | delete this.data; 1090 | this.data = null; 1091 | this.valid = false; 1092 | } 1093 | }, { 1094 | key: 'bind', 1095 | value: function bind(unit) { 1096 | if (!this.valid) { 1097 | return; 1098 | } 1099 | if (typeof unit === 'number') { 1100 | if (Texture.activeUnit !== unit) { 1101 | this.gl.activeTexture(this.gl.TEXTURE0 + unit); 1102 | Texture.activeUnit = unit; 1103 | } 1104 | } 1105 | if (Texture.activeTexture !== this.texture) { 1106 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); 1107 | Texture.activeTexture = this.texture; 1108 | } 1109 | } 1110 | }, { 1111 | key: 'load', 1112 | value: function load() { 1113 | var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 1114 | 1115 | this.loading = null; 1116 | 1117 | if (typeof options.url === 'string') { 1118 | if (this.url === undefined || options.url !== this.url) { 1119 | this.setUrl(options.url, options); 1120 | } 1121 | } else if (options.element) { 1122 | this.setElement(options.element, options); 1123 | } else if (options.data && options.width && options.height) { 1124 | this.setData(options.width, options.height, options.data, options); 1125 | } 1126 | } 1127 | 1128 | // Sets texture from an url 1129 | 1130 | }, { 1131 | key: 'setUrl', 1132 | value: function setUrl(url) { 1133 | var _this = this; 1134 | 1135 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1136 | 1137 | if (!this.valid) { 1138 | return; 1139 | } 1140 | 1141 | this.url = url; // save URL reference (will be overwritten when element is loaded below) 1142 | this.source = this.url; 1143 | this.sourceType = 'url'; 1144 | 1145 | this.loading = new Promise(function (resolve, reject) { 1146 | var ext = url.split('.').pop().toLowerCase(); 1147 | var isVideo = ext === 'ogv' || ext === 'webm' || ext === 'mp4'; 1148 | 1149 | var element = undefined; 1150 | if (isVideo) { 1151 | element = document.createElement('video'); 1152 | element.autoplay = true; 1153 | options.filtering = 'nearest'; 1154 | // element.preload = 'auto'; 1155 | // element.style.display = 'none'; 1156 | // document.body.appendChild(element); 1157 | } else { 1158 | element = new Image(); 1159 | } 1160 | 1161 | element.onload = function () { 1162 | try { 1163 | _this.setElement(element, options); 1164 | } catch (e) { 1165 | console.log('Texture \'' + _this.name + '\': failed to load url: \'' + _this.source + '\'', e, options); 1166 | } 1167 | resolve(_this); 1168 | }; 1169 | element.onerror = function (e) { 1170 | // Warn and resolve on error 1171 | console.log('Texture \'' + _this.name + '\': failed to load url: \'' + _this.source + '\'', e, options); 1172 | resolve(_this); 1173 | }; 1174 | 1175 | // Safari has a bug loading data-URL elements with CORS enabled, so it must be disabled in that case 1176 | // https://bugs.webkit.org/show_bug.cgi?id=123978 1177 | if (!(isSafari() && _this.source.slice(0, 5) === 'data:')) { 1178 | element.crossOrigin = 'anonymous'; 1179 | } 1180 | 1181 | element.src = _this.source; 1182 | if (isVideo) { 1183 | _this.setElement(element, options); 1184 | } 1185 | }); 1186 | return this.loading; 1187 | } 1188 | 1189 | // Sets texture to a raw image buffer 1190 | 1191 | }, { 1192 | key: 'setData', 1193 | value: function setData(width, height, data) { 1194 | var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; 1195 | 1196 | this.width = width; 1197 | this.height = height; 1198 | 1199 | this.source = data; 1200 | this.sourceType = 'data'; 1201 | 1202 | this.update(options); 1203 | this.setFiltering(options); 1204 | 1205 | this.loading = Promise.resolve(this); 1206 | return this.loading; 1207 | } 1208 | 1209 | // Sets the texture to track a element (canvas/image) 1210 | 1211 | }, { 1212 | key: 'setElement', 1213 | value: function setElement(element, options) { 1214 | var _this2 = this; 1215 | 1216 | var el = element; 1217 | 1218 | // a string element is interpeted as a CSS selector 1219 | if (typeof element === 'string') { 1220 | element = document.querySelector(element); 1221 | } 1222 | 1223 | if (element instanceof HTMLCanvasElement || element instanceof HTMLImageElement || element instanceof HTMLVideoElement) { 1224 | this.source = element; 1225 | this.sourceType = 'element'; 1226 | 1227 | if (element instanceof HTMLVideoElement) { 1228 | element.addEventListener('canplaythrough', function () { 1229 | _this2.intervalID = setInterval(function () { 1230 | _this2.update(options); 1231 | }, 15); 1232 | }, true); 1233 | element.addEventListener('ended', function () { 1234 | element.currentTime = 0; 1235 | element.play(); 1236 | }, true); 1237 | } else { 1238 | this.update(options); 1239 | } 1240 | this.setFiltering(options); 1241 | } else { 1242 | var msg = 'the \'element\' parameter (`element: ' + JSON.stringify(el) + '`) must be a CSS '; 1243 | msg += 'selector string, or a , or