[
  {
    "path": ".eslintrc",
    "content": "{\n    // `root: true` prevent eslint to combine .eslintrc in parent folder with this one\n    // ext developer can remove this line if desire to let eslint to traverses eslint config \n    // see <http://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy>\n    \"root\": true,\n    // require to install @typescript-eslint/parser + typescript\n    \"parser\": \"@typescript-eslint/parser\",\n    // Tells ESLint to load the plugin. this allows you to use \n    // the rules within your codebase\n    \"plugins\": [\n        \"@typescript-eslint\",\n        \"react-hooks\"\n    ],\n    \"extends\": [\n        // ESLint's inbuilt \"recommended\" config\n        \"eslint:recommended\",\n        // require to install @typescript-eslint/eslint-plugin\n        // just like eslint:recommended, except it only turns on rules from TypeScript-specific plugin\n        \"plugin:@typescript-eslint/recommended\"\n    ],\n    \"env\": {\n      // this tells eslint it's safe to use `require`/`module.exports`/`__dirname`...\n      // that are only legal in node.js, we use these in `webpack.config.js`\n      \"node\": true,\n      \"jest\": true\n    },\n    \"rules\": {\n      \"indent\": [\"error\", 2],\n      \"linebreak-style\": [\"error\", \"unix\"],\n      \"quotes\": [\"error\", \"double\"],\n      \"react-hooks/rules-of-hooks\": \"error\", // Checks rules of Hooks\n     \"react-hooks/exhaustive-deps\": \"warn\" // Checks effect dependencies\n    }\n}\n"
  },
  {
    "path": ".github/workflows/pr_release.yaml",
    "content": "on:\n  pull_request:\n    types: [closed]\n\njobs:\n  versioning:\n    name: Auto versioning/tag => Create a release with .tgz\n    # github does not have PR event type 'merged' for github action\n    # so we use 'closed' instead, and add condition:\n    if: github.event.pull_request.merged == true\n    runs-on: ubuntu-latest\n\n    steps:\n\n    - uses: actions/checkout@v2\n      with:\n        persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token\n        fetch-depth: 0 # otherwise, you will failed to push refs to dest repo\n\n    # same node.js version as in package.json > engines\n    - uses: actions/setup-node@v2-beta\n      with:\n        node-version: '12'\n    \n    # npm version patch will automatically git commit/tag\n    - name: Run 'npm version patch'\n      run: |\n        git config --local user.email \"github-action@github.com\"\n        git config --local user.name \"Github Action Bot\"\n        npm version patch\n\n    - name: Push commits\n      uses: ad-m/github-push-action@master\n      with:\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n        branch: ${{ github.ref }}\n        tags: true\n\n    - name: Install dependencies and build extension\n      run: |\n        yarn\n        yarn build\n\n    - name: Create tarball from package\n      run: |\n        npm pack\n\n    - name: Get version\n      id: version\n      run: |\n        VERSION=$(node -p -e \"require('./package.json').version\")\n        echo \"::set-output name=value::$VERSION\"\n\n    - name: Create GitHub release\n      uses: actions/create-release@v1\n      id: create_release\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        tag_name: \"v${{ steps.version.outputs.value }}\"\n        release_name: \"v${{ steps.version.outputs.value }}\"\n        draft: false\n\n    - name: Upload GitHub assets\n      uses: actions/upload-release-asset@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        upload_url: ${{ steps.create_release.outputs.upload_url }}\n        asset_path: lensapp-lens-ext-invaders-${{ steps.version.outputs.value }}.tgz\n        asset_name: lensapp-lens-ext-invaders-${{ steps.version.outputs.value }}.tgz\n        asset_content_type: application/octet-stream\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\n.DS_Store\nlens.log\n*.tgz\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 chh\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Start Invading Clusters\n\n⚠️⚠️⚠️\nPlease don't play this game on your production cluster. 😅\nAlien 👾 = pod on cluster.\n⚠️⚠️⚠️\n\n![screencast](screencast_256.gif)\n\n## Install\n\n```bash\ncd ~ && git clone https://github.com/chenhunghan/lens-ext-invaders.git\ncd lens-ext-invaders && yarn && yarn build\nln -s ~/lens-ext-invaders ~/.k8slens/extensions/lens-ext-invaders\n```\n\nOR\n\nCopy the `.tgz` link in [Release](https://github.com/chenhunghan/lens-ext-invaders/releases)\n\nAdd install the extension by paste the link in Lens -> Extension\n\n![intstall](https://i.imgur.com/oFzyvGG.png)\n\n\n## Acknowledgements\n\nThis project is largely inspired by <https://codeheir.com/2019/03/17/how-to-code-space-invaders-1978-7/>\n"
  },
  {
    "path": "babel.config.js",
    "content": "// the babel config is for jest to parse jsx in test/spec files\nconst presets = [\n  \"@babel/preset-env\",\n  \"@babel/preset-react\",\n  \"@babel/preset-typescript\"\n];\n\nmodule.exports = { presets };\n"
  },
  {
    "path": "components/Alien.ts",
    "content": "import p5 from \"p5\";\nimport { K8sApi } from \"@k8slens/extensions\";\nimport { AlienImages } from \"./Invaders\";\n\nclass Alien {\n\n  x: number\n  y: number\n  alienImages: AlienImages\n  p5: p5\n  pod: K8sApi.Pod;\n  hits: number;\n  width: number;\n  height: number;\n\n  constructor(x: number, y: number, alienImages: AlienImages, p5: p5, pod: K8sApi.Pod) {\n    this.x = x;\n    this.y = y;\n    this.alienImages = alienImages;\n    this.p5 = p5;\n    this.pod = pod\n    this.hits = 0;\n    this.width = alienImages.greenAlien.width / 20;\n    this.height = alienImages.greenAlien.height / 20;\n  }\n\n  bulletHit(): number {\n    this.pod.delete();\n    return this.hits += 1;\n  }\n\n  draw(): void {\n    let image: p5.Image;\n    this.p5.textSize(8);\n\n    const status = this.pod.getStatus();\n    if (this.pod.metadata.deletionTimestamp || this.hits > 0) {\n      image = this.alienImages.redAlien;\n    } else if (status === \"Running\") {\n      image = this.alienImages.greenAlien;\n    } else if (status === \"Pending\") {\n      image = this.alienImages.yellowAlien\n    } else {\n      image = this.alienImages.orangeAlien\n    }\n    image && this.p5.image(image, this.x, this.y, this.width, this.height);\n\n    const name = this.pod.getName();\n    if (name && name.length > 7) {\n      this.p5.text(`${name.substring(0, 7)}..`, this.x, this.y + 40);\n    } else {\n      this.p5.text(`${name}`, this.x, this.y + 40);\n    }\n  }\n}\n\nexport default Alien\n"
  },
  {
    "path": "components/AlienBullet.ts",
    "content": "import Bullet from \"./Bullet\";\nimport p5 from \"p5\";\n\nclass AlienBullet extends Bullet {\n  x: number;\n  y: number;\n\n  constructor(x: number, y: number, p5: p5) {\n    super(x, y, p5);\n  }\n\n  update(): void {\n    this.y += 2;\n  }\n}\n\nexport default AlienBullet\n"
  },
  {
    "path": "components/Bullet.ts",
    "content": "import p5 from \"p5\";\nimport Player from \"./Player\";\n\nclass Bullet {\n  p5: p5;\n  x: number;\n  y: number;\n\n  constructor(x: number, y: number, p5: p5) {\n    this.x = x;\n    this.y = y;\n    this.p5 = p5;\n  }\n\n  isOffScreen(): boolean {\n    return this.y <= 0;\n  }\n    \n  draw(): void {\n    this.p5.fill(255);\n    this.p5.rect(this.x, this.y, 3, 15);\n  }\n\n  hasHit(player: Player): boolean {\n    return this.p5.dist(this.x, this.y, player.x + 10, player.y + 10) < 20;\n  }\n}\n\nexport default Bullet;\n"
  },
  {
    "path": "components/ClusterPage.tsx",
    "content": "import React, { useEffect, useState } from \"react\"\nimport Game from \"./Game\"\nimport { K8sApi } from \"@k8slens/extensions\";\n\nconst ClusterPage = (): JSX.Element => {\n\n  console.info(\"🔥 Cluster page rendered\");\n\n  const [podsStore] = useState(K8sApi.apiManager.getStore(K8sApi.podsApi))\n\n  useEffect(() => {\n    const ensure = async () => {\n      if (!podsStore.isLoaded) {\n        await podsStore.loadAll();\n        podsStore.subscribe();\n      }\n    }\n    ensure();\n  }, [podsStore])\n\n  return (\n    <div className=\"TabLayout\">\n      <main>\n        <div className=\"ItemListLayout flex column\">\n          <div className=\"header flex gaps align-center\">\n            <h5>Space Invaders</h5>\n          </div>\n          <Game pods={podsStore.items} />\n        </div>\n      </main>\n    </div>\n  )\n}\n\nexport default ClusterPage\n"
  },
  {
    "path": "components/Game.tsx",
    "content": "import React, { memo, useState, useLayoutEffect } from \"react\"\nimport { K8sApi } from \"@k8slens/extensions\";\nimport p5 from \"p5\";\nimport Invaders from \"./Invaders\";\nimport Player from \"./Player\";\nimport { IObservableArray } from \"mobx\";\nimport Particle from \"./Particle\";\nimport { configMapsStore } from \"@k8slens/extensions/dist/src/renderer/components/+config-maps/config-maps.store\";\n\ntype Props = { pods: IObservableArray<K8sApi.Pod> }\n\nlet keyboardEvenListener: (ev: KeyboardEvent) => void\nlet mouseEvenListener: (ev: Event) => void;\nlet windowResizeEvenListener: (ev: Event) => void;\n\n// an array to add multiple particles\nconst particles: Array<Particle> = [];\n\nconst sketch = (pods: IObservableArray<K8sApi.Pod>) => (p: p5) => {\n  let invaders: Invaders;\n  let player: Player;\n  let enableParticles = false;\n  let lastMouseTarget: EventTarget;\n  let started = false;\n  const gameImage = p.loadImage(\"https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/11a10a01-ac23-4fea-ad5a-b51f53084159/d5eu5dw-11a48688-3762-4f92-ba46-ebf94abe51b1.png/v1/fill/w_900,h_389,strp/space_invaders_logo__us__by_ringostarr39_d5eu5dw-fullview.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD0zODkiLCJwYXRoIjoiXC9mXC8xMWExMGEwMS1hYzIzLTRmZWEtYWQ1YS1iNTFmNTMwODQxNTlcL2Q1ZXU1ZHctMTFhNDg2ODgtMzc2Mi00ZjkyLWJhNDYtZWJmOTRhYmU1MWIxLnBuZyIsIndpZHRoIjoiPD05MDAifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.0wP1LBCQjWzqUQ5czHR2NjUv9iaiB2lUp_y-d9FuX-8\");\n\n  const setup = () => {\n    keyboardEvenListener && document.removeEventListener(\"keydown\", keyboardEvenListener);\n    mouseEvenListener && document.removeEventListener(\"mousedown\", mouseEvenListener);\n    windowResizeEvenListener && document.removeEventListener(\"resize\", windowResizeEvenListener);\n    const playerImage = p.loadImage(\"https://i.imgur.com/cCmEvHN.png\");\n    const greenAlien = p.loadImage(\"https://i.imgur.com/fqeDYa0.png\");\n    const redAlien = p.loadImage(\"https://i.imgur.com/iHKEnRq.png\");\n    const yellowAlien = p.loadImage(\"https://i.imgur.com/lVEg9GG.png\");\n    const orangeAlien = p.loadImage(\"https://i.imgur.com/LRYWNG0.png\");\n    const container = document.getElementById(\"p5_canvas_container\");\n    const canvas = p.createCanvas(container.offsetWidth, container.clientHeight + 100);\n    canvas.parent(container);\n    canvas.style(\"position\", \"relative\");\n    canvas.attribute(\"tabindex\", \"1\");\n    p.frameRate(24);\n    invaders = new Invaders({\n      greenAlien, yellowAlien, orangeAlien, redAlien\n    }, p, pods);\n    player = new Player(playerImage, p, invaders);\n\n    const starter = (event: Event) => {\n      lastMouseTarget = event.target;\n      if (event.target === canvas.elt) {\n        started = true;\n      }\n    }\n    document.addEventListener(\"mousedown\", starter, false);\n    mouseEvenListener = starter;\n\n    const bind = ({ code }: { code: string }) => {\n      if (code == \"Backquote\") {\n        // for easier screen record w/o mouse\n        started = !started;\n        return;\n      }\n      if (lastMouseTarget !== canvas.elt) {\n        return;\n      }\n      switch (code) {\n      case \"ArrowRight\":\n        player.moveRight()\n        break;\n      case \"ArrowLeft\":\n        player.moveLeft()\n        break;\n      case \"Space\":\n        console.info(\"🔫🔫🔫 You humans!\")\n        player.shoot()\n        break;\n      case \"KeyS\":\n        enableParticles = !enableParticles;\n        console.info(\"enableParticles\", enableParticles)\n        break;\n      default:\n        break;\n      }\n      \n    };\n\n    document.addEventListener(\"keydown\", bind);\n    keyboardEvenListener = bind;\n    \n    for (let i = 0; i < p.width / 20; i++) {\n      particles.push(new Particle(p));\n    }\n  }\n\n  p.setup = () => {\n    setup();\n  };\n\n  const draw = () => {\n    p.background(0);\n\n    if (started) {\n      invaders.update(player);\n      invaders.draw();\n      player.update();\n      player.draw();\n    } else {\n      const imageWidth = p.width / 3;\n      const imageHeight = p.width / 6;\n      const x = p.width / 2;\n      const y = p.width / 2;\n      const textSize = p.width / 50;\n      p.image(gameImage, x - imageWidth / 2, y - imageHeight - textSize * 5, imageWidth, imageHeight);\n      p.textSize(textSize);\n      p.fill(\"rgba(0,255,0,1)\");\n      p.textFont(\"Courier New\");\n      p.textAlign(p.CENTER)\n      p.text(\"CLICK TO START\", x, y - textSize * 3);\n    }\n\n    if (player.score > (invaders.aliens.length * player.level)) {\n      invaders.aliens = [];\n      invaders.speed += 0.4;\n      player.level += 1;\n    }\n\n    if (player.lives == 0) {\n      p.textFont(\"Courier New\");\n      p.textSize(p.width / 20);\n      p.fill(\"rgba(0,255,0,1)\");\n      const x = p.width / 2;\n      const y = p.width / 2.5;\n      p.text(\"GAME OVER \", x, y);\n      p.fill(\"rgba(255,255,255,1)\");\n      p.textSize(p.width / 80);\n      p.text(\"CMD/Ctrl + R to Restart\", x - p.width / 80, y + p.width / 40);\n      player.stop();\n    }\n\n    if (enableParticles) {\n      for (let i = 0; i < particles.length; i++) {\n        particles[i].createParticle();\n        particles[i].moveParticle();\n        particles[i].joinParticles(particles.slice(i));\n      }\n    }\n  }\n  p.draw = () => {\n    draw();\n  };\n\n  const resizer = () => {\n    const container = document.getElementById(\"p5_canvas_container\");\n    p.resizeCanvas(container.clientWidth, container.clientHeight);\n    draw();\n  }\n\n  window.addEventListener(\"resize\", resizer);\n  windowResizeEvenListener = resizer;\n};\n\nconst Game = memo(({ pods }: Props): JSX.Element => {\n\n  const [init, setInit] = useState(false);\n  useLayoutEffect(() => {\n    if (!init) {\n      new p5(sketch(pods));\n      console.info(\"👾 P5 Canvas Injected\");\n      setInit(true)\n    }\n  }, [init, pods]);\n\n\n  return (\n    <div id='p5_canvas_container' className=\"flex box column grow\"></div>\n  )\n})\n\nexport default Game\n"
  },
  {
    "path": "components/Invaders.ts",
    "content": "import Alien from \"./Alien\";\nimport p5 from \"p5\";\nimport AlienBullet from \"./AlienBullet\";\nimport Bullet from \"./Bullet\";\nimport Player from \"./Player\";\nimport { K8sApi } from \"@k8slens/extensions\";\nimport { IObservableArray } from \"mobx\";\n\ntype AlienImages = {\n  greenAlien: p5.Image, yellowAlien: p5.Image, orangeAlien: p5.Image, redAlien: p5.Image\n}\n\nclass Invaders {\n\n    alienImages: AlienImages;\n    direction: number;\n    y: number;\n    aliens: Array<Alien> = [];\n    bullets: Array<Bullet>;\n    speed: number;\n    timeSinceLastBullet: number;\n    p5: p5;\n    pods: Array<K8sApi.Pod>;\n\n    constructor(alienImages: AlienImages, p5: p5, pods: IObservableArray<K8sApi.Pod>) {\n      this.alienImages = alienImages;\n      this.direction = 0;\n      this.y = 40;\n      this.p5 = p5;\n      this.bullets = [];\n      this.speed = 0.2;\n      this.pods = pods;\n\n      // to make sure the aliens dont spam\n      this.timeSinceLastBullet = 0;\n    }\n\n    update(player: Player): void {\n      this.updateAliens();\n\n      for (const alien of this.aliens) {\n        if (this.direction == 0) {\n          alien.x += this.speed;\n        } else if (this.direction == 1) {\n          alien.x -= this.speed;\n        }\n      }\n      this.updateBullets(player);\n\n      if (this.hasChangedDirection()) {\n        this.moveAlienDown();\n      }\n\n      if (this.timeSinceLastBullet >= 40) {\n        const bottomAliens = this.getBottomAliens();\n        if (bottomAliens.length) {\n          this.makeABottomAlienShoot(bottomAliens);\n        }\n      }\n\n      this.timeSinceLastBullet++;\n    }\n\n    hasChangedDirection(): boolean {\n      for (const alien of this.aliens) {\n        if (alien.x >= this.p5.width - 40) {\n          this.direction = 1;\n          return true;\n        } else if (alien.x <= 20) {\n          this.direction = 0;\n          return true;\n        }\n      }\n      return false;\n    }\n\n    moveAlienDown(): void {\n      for (const alien of this.aliens) {\n        alien.y += 10;\n      }\n    }\n\n    // to make sure only the bottom row will shoot\n    getBottomAliens(): Array<Alien> {\n      const allXPositions = this.getAllXPositions();\n\n      const aliensAtTheBottom = [];\n      for (const alienAtX of allXPositions) {\n        let bestYPosition = 0;\n        let lowestAlien;\n        for (const alien of this.aliens) {\n          if (alien.x == alienAtX) {\n            if (alien.y > bestYPosition) {\n              bestYPosition = alien.y;\n              lowestAlien = alien;\n            }\n          }\n        }\n        aliensAtTheBottom.push(lowestAlien);\n      }\n      return aliensAtTheBottom;\n    }\n\n    nextLevel(): void {\n      this.speed += 0.5;\n      this.updateAliens();\n    }\n\n    // get all the x positions for a single frame\n    getAllXPositions(): Set<unknown> {\n      const allXPositions = new Set();\n      for (const alien of this.aliens) {\n        allXPositions.add(alien.x);\n      }\n      return allXPositions\n    }\n\n    updateAliens(): void {\n      const maxX = this.p5.width - 100;\n      const minX = 100;\n      const gapX = 50;\n\n      const newPods: K8sApi.Pod[] = []\n      this.pods.forEach((pod: K8sApi.Pod) => {\n        const alien = this.aliens.find((a) => a.pod.getId() === pod.getId())\n        if(!alien) {\n          newPods.push(pod);\n        } else {\n          alien.pod = pod;\n        }\n      })\n\n      const podIds = this.pods.map((p) => p.getId());\n      this.aliens.forEach((alien, index) => {\n        if (!podIds.includes(alien.pod.getId())) {\n          this.aliens.splice(index, 1);\n        }\n      })\n\n      const empty = this.aliens.length === 0;\n      newPods.sort(() => Math.random() - 0.5);\n      if (empty) {\n        newPods.forEach((pod) => {\n          let y = 80;\n          let x = minX;\n          const lastAlien = this.aliens[this.aliens.length - 1];\n          if (lastAlien) {\n            x = lastAlien.x + gapX;\n            y = lastAlien.y;\n            if (x >= maxX) {\n              x = minX;\n              y = lastAlien.y + gapX;\n            }\n          }\n          if (this.aliens.length < 100) {\n            this.aliens.push(new Alien(x, y, this.alienImages, this.p5, pod))\n          }\n        })\n      } else {\n        const pod = newPods[0]\n        if (!pod) {\n          return\n        }\n        const alien = this.aliens[Math.floor(Math.random() * this.aliens.length)];\n        const aliensOnSameRow = this.aliens.filter((a) => a.y === alien.y)\n        if (!aliensOnSameRow.find((a) => a.x >= (alien.x - (gapX + 3)) && a.x < alien.x && (alien.x - gapX) > minX)) {\n          if (alien.x - gapX > minX) {\n            this.aliens.push(new Alien(alien.x - gapX, alien.y, this.alienImages, this.p5, pod))\n          }\n        }\n      }\n    }\n\n    checkCollision(x: number, y: number): boolean {\n      for (let i = this.aliens.length - 1; i >= 0; i--) {\n        const currentAlien = this.aliens[i];\n        // the numbers are hard-coded for the width of the image\n        if (this.p5.dist(x, y, currentAlien.x + (currentAlien.width / 2), currentAlien.y + (currentAlien.height / 2)) < currentAlien.width / 2) {\n          currentAlien.bulletHit();\n          return true;\n        }\n      }\n      return false;\n    }\n\n    makeABottomAlienShoot(bottomAliens: Array<Alien>): void {\n      const shootingAlien = this.p5.random(bottomAliens.filter((a) => a.hits === 0));\n      const bullet = new AlienBullet(shootingAlien.x + 10, shootingAlien.y + 10, this.p5);\n      this.bullets.push(bullet);\n      this.timeSinceLastBullet = 0;\n    }\n\n    updateBullets(player: Player): void {\n      for (let i = this.bullets.length - 1; i >= 0; i--) {\n        this.bullets[i].y += 4;\n        if (this.bullets[i].hasHit(player)) {\n          player.bulletHit();\n          this.bullets.splice(i, 1);\n        }\n      }\n    }\n\n    draw(): void {\n      for (const alien of this.aliens) {\n        alien.draw();\n      }\n      for (const bullet of this.bullets) {\n        this.p5.rect(bullet.x, bullet.y, 3, 10);\n      }\n    }\n}\n\nexport { AlienImages }\nexport default Invaders\n"
  },
  {
    "path": "components/Particle.ts",
    "content": "import p5 from \"p5\";\n\n// this class describes the properties of a single particle.\nclass Particle {\n  // setting the co-ordinates, radius and the\n  // speed of a particle in both the co-ordinates axes.\n  x: number;\n  y: number;\n  r: number;\n  xSpeed: number;\n  ySpeed: number;\n  p5: p5;\n\n  constructor(p5: p5) {\n    this.p5 = p5;\n    this.x = this.p5.random(0, this.p5.windowWidth);\n    this.y = this.p5.random(0, this.p5.windowHeight);\n    this.r = this.p5.random(1, 8);\n    this.xSpeed = this.p5.random(-2, 2);\n    this.ySpeed = this.p5.random(-1, 1.5);\n  }\n\n  // creation of a particle.\n  createParticle(): void {\n    this.p5.noStroke();\n    this.p5.fill(\"rgba(200,169,169,0.5)\");\n    this.p5.circle(this.x, this.y, this.r);\n  }\n\n  // setting the particle in motion.\n  moveParticle(): void {\n    if (this.x < 0 || this.x > this.p5.windowWidth)\n      this.xSpeed *= -1;\n    if (this.y < 0 || this.y > this.p5.windowHeight)\n      this.ySpeed *= -1;\n    this.x += this.xSpeed;\n    this.y += this.ySpeed;\n  }\n\n  // this function creates the connections(lines)\n  // between particles which are less than a certain distance apart\n  joinParticles(particles: Array<Particle>): void {\n    particles.forEach((element: Particle) => {\n      const dis = this.p5.dist(this.x, this.y, element.x, element.y);\n      if (dis < 85) {\n        this.p5.stroke(\"rgba(255,255,255,0.08)\");\n        this.p5.line(this.x, this.y, element.x, element.y);\n      }\n    });\n  }\n}\n\nexport default Particle;\n"
  },
  {
    "path": "components/Player.ts",
    "content": "import p5 from \"p5\";\nimport PlayerBullet from \"./PlayerBullet\"\nimport Invaders from \"./Invaders\";\n\nclass Player {\n  image: p5.Image;\n  x: number;\n  y: number;\n  isMovingLeft: boolean;\n  isMovingRight: boolean;\n  bullets: Array<PlayerBullet>;\n  p5: p5;\n  invaders: Invaders;\n  lives: number;\n  level: number;\n  score: number;\n\n  constructor(shooterImage: p5.Image, p5: p5, invaders: Invaders) {\n    this.image = shooterImage;\n    this.isMovingLeft = false;\n    this.isMovingRight = false;\n    this.bullets = [];\n    this.p5 = p5;\n    this.x = this.p5.width / 2;\n    // there seems to be a bug of p5.height which won't update when window is being\n    // resize, so we use windowHeight to cal y instead. \n    this.y = this.p5.windowHeight - Math.max(210, this.p5.windowHeight / 3.8);\n    this.invaders = invaders;\n    this.lives = 3;\n    this.score = 0;\n    this.level = 1;\n  }\n\n  update(): void {\n    if (this.isMovingRight) {\n      this.x += 5;\n    } else if (this.isMovingLeft) {\n      this.x -= 5;\n    } else {\n      this.x = this.p5.width / 2;\n    }\n    this.y = this.p5.windowHeight - Math.max(210, this.p5.windowHeight / 3.8);\n    if(this.invaders.aliens.find((a) => a.y >= this.p5.height)) {\n      this.lives = 0;\n    }\n    this.constrain();\n    this.updateBullets();\n  }\n\n  addScore(): void {\n    this.score = this.score + 1;\n  }\n\n  updateBullets(): void {\n\n    for (let i = this.bullets.length - 1; i >= 0; i--) {\n      this.bullets[i].update();\n      if (this.hasHitAlien(this.bullets[i])) {\n        this.bullets.splice(i, 1);\n        this.addScore();\n        break;\n      } else if (this.bullets[i].isOffScreen()) {\n        this.bullets.splice(i, 1);\n        break;\n      }\n    }\n  }\n\n  hasHitAlien(bullet: PlayerBullet): boolean {\n    return this.invaders.checkCollision(bullet.x, bullet.y);\n  }\n\n  bulletHit(): void {\n    if (this.lives > 0) {\n      this.lives--;\n    }\n  }\n\n  constrain(): void {\n    if (this.x <= 0) {\n      this.x = 0;\n    } else if (this.x > this.p5.width - 23) {\n      this.x = this.p5.width - 23;\n    }\n  }\n\n  draw(): void {\n    this.p5.image(this.image, this.x, this.y, this.image.width / 10, this.image.height / 10);\n    this.drawBullets();\n    this.drawLives();\n    this.drawLevel();\n    this.drawScore();\n  }\n\n  drawBullets(): void {\n    for (const bullet of this.bullets) {\n      bullet.draw();\n    }\n  }\n\n  drawLives(): void {\n    this.p5.fill(255);\n    this.p5.textSize(15);\n    this.p5.text(\"LIVES\", 50, 25);\n    for (let i = 0; i < this.lives; i++) {\n      this.p5.image(this.image, 100 + i * 30, 10, this.image.width / 20, this.image.height / 20);\n    }\n  }\n\n  drawLevel(): void {\n    this.p5.text(\"LEVEL\", this.p5.width - 200, 25);\n    this.p5.push();\n    this.p5.fill(100, 255, 100);\n    this.p5.text(this.level, this.p5.width - 150, 25);\n    this.p5.pop();\n  }\n\n  drawScore(): void {\n    this.p5.text(\"SCORE\", this.p5.width - 120, 25);\n    this.p5.push();\n    this.p5.fill(100, 255, 100);\n    this.p5.text(this.score, this.p5.width - 50, 25);\n    this.p5.pop();\n  }\n\n  moveLeft(): void {\n    this.isMovingRight = false;\n    this.isMovingLeft = true;\n  }\n  moveRight(): void {\n    this.isMovingLeft = false;\n    this.isMovingRight = true;\n  }\n\n  stop(): void {\n    this.isMovingLeft = false;\n    this.isMovingRight = false;\n  }\n\n  shoot(): void {\n    this.bullets.push(new PlayerBullet(this.x + 12, this.y, this.p5));\n  }\n}\n\nexport default Player;\n"
  },
  {
    "path": "components/PlayerBullet.ts",
    "content": "import Bullet from \"./Bullet\"\nimport p5 from \"p5\";\n\nclass PlayerBullet extends Bullet {\n  constructor(x: number, y: number, p5: p5) {\n    super(x, y, p5);\n  }\n\n  update(): void {\n    this.y -= 12;\n  }\n}\n\nexport default PlayerBullet;\n"
  },
  {
    "path": "main.ts",
    "content": "import { LensMainExtension } from \"@k8slens/extensions\";\n\nexport default class MainExtension extends LensMainExtension {\n    \n  onActivate(): void {\n    console.log(\"activated\");\n  }\n\n  onDeactivate(): void {\n    console.log(\"deactivated\");\n  }\n\n  appMenus = [\n    {\n      parentId: \"help\",\n      label: \"Invade\",\n      click(): void {\n        this.navigate();\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"@lensapp/lens-ext-invaders\",\n\t\"version\": \"0.0.7\",\n\t\"description\": \"Space Invaders for Lens\",\n\t\"publisher\": \"IRATA Inc\",\n\t\"main\": \"dist/main.js\",\n\t\"renderer\": \"dist/renderer.js\",\n\t\"engines\": {\n\t\t\"node\": \">=12.0 <13.0\"\n\t},\n\t\"contributes\": {\n\t\t\"crds\": [],\n\t\t\"cloudProviders\": [],\n\t\t\"kubernetesDistros\": []\n\t},\n\t\"keywords\": [\n\t\t\"lens\",\n\t\t\"extension\",\n\t\t\"k8slens\",\n\t\t\"kubernetes\"\n\t],\n\t\"scripts\": {\n\t\t\"start\": \"webpack --watch\",\n\t\t\"build\": \"npm run clean && webpack\",\n\t\t\"clean\": \"rm -rf ./dist\",\n\t\t\"test\": \"jest\"\n\t},\n\t\"dependencies\": {\n\t\t\"@types/p5\": \"^0.9.1\",\n\t\t\"p5\": \"^1.1.9\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@babel/preset-env\": \"^7.12.7\",\n\t\t\"@babel/preset-react\": \"^7.12.7\",\n\t\t\"@babel/preset-typescript\": \"^7.12.7\",\n\t\t\"@jest-runner/electron\": \"^3.0.0\",\n\t\t\"@k8slens/extensions\": \"4.2.1\",\n\t\t\"@testing-library/jest-dom\": \"^5.11.6\",\n\t\t\"@testing-library/react\": \"^11.2.2\",\n\t\t\"@types/jest\": \"^26.0.15\",\n\t\t\"@types/node\": \"^12.12.9\",\n\t\t\"@types/react\": \"^17.0.0\",\n\t\t\"@typescript-eslint/eslint-plugin\": \"^4.8.1\",\n\t\t\"@typescript-eslint/parser\": \"^4.8.1\",\n\t\t\"electron\": \"^11.0.3\",\n\t\t\"eslint\": \"^7.13.0\",\n\t\t\"eslint-plugin-react-hooks\": \"^4.2.0\",\n\t\t\"jest\": \"^26.6.3\",\n\t\t\"react\": \"^17.0.1\",\n\t\t\"react-dom\": \"^17.0.1\",\n\t\t\"mobx\": \"^5.0.0\",\n\t\t\"mobx-react\": \"^6.2.2\",\n\t\t\"ts-loader\": \"^8.0.11\",\n\t\t\"typescript\": \"^4.1.2\",\n\t\t\"webpack\": \"^4.44.2\",\n\t\t\"webpack-cli\": \"^3.3.11\"\n\t},\n\t\"jest\": {\n\t\t\"runner\": \"@jest-runner/electron\",\n\t\t\"testEnvironment\": \"@jest-runner/electron/environment\"\n\t},\n\t\"lingui\": {\n\t\t\"locales\": [\n\t\t\t\"en\"\n\t\t]\n\t}\n}\n"
  },
  {
    "path": "renderer.tsx",
    "content": "import { LensRendererExtension, Component } from \"@k8slens/extensions\";\nimport React from \"react\"\n\nimport ClusterPage from \"./components/ClusterPage\";\n\nconst { Icon } = Component;\n\nexport default class RendererExtension extends LensRendererExtension {\n\n  #clusterPageId = \"space_invader_clusters_page\";\n  clusterPages = [\n    {\n      id: this.#clusterPageId,\n      title: \"Space Invaders\",\n      components: {\n        Page: ClusterPage\n      }\n    },\n  ]\n\n  clusterPageMenus = [\n    // a cluster menu item which links to a cluster page\n    {\n      title: \"Space Invaders\",\n      target: {\n        pageId: this.#clusterPageId,\n        params: {}\n      },\n      components: {\n        Icon: (): JSX.Element => <Icon material=\"my_location\" />,\n      }\n    },\n  ]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"outDir\": \"dist\",\n\t\t\"module\": \"CommonJS\",\n\t\t\"target\": \"ES2017\",\n\t\t\"lib\": [\n\t\t\t\"ESNext\",\n\t\t\t\"DOM\",\n\t\t\t\"DOM.Iterable\"\n\t\t],\n\t\t\"moduleResolution\": \"Node\",\n\t\t\"sourceMap\": false,\n\t\t\"declaration\": false,\n\t\t\"strict\": false,\n\t\t\"noImplicitAny\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"allowSyntheticDefaultImports\": true,\n\t\t\"experimentalDecorators\": true,\n\t\t\"jsx\": \"react\"\n\t},\n\t\"include\": [\n\t\t\"./*.ts\",\n\t\t\"./*.tsx\"\n\t],\n\t\"exclude\": [\n\t\t\"node_modules\",\n\t\t\"*.js\"\n\t]\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "/* eslint @typescript-eslint/no-var-requires: \"off\" */\n\nconst path = require(\"path\");\n\nmodule.exports = [\n  {\n    entry: \"./main.ts\",\n    context: __dirname,\n    target: \"electron-main\",\n    mode: \"development\",\n    devtool: \"eval-source-map\",\n    module: {\n      rules: [\n        {\n          test: /\\.tsx?$/,\n          use: [\n            {\n              loader: \"ts-loader\",\n              options: {\n                transpileOnly: true,\n              }\n            }\n          ],\n          exclude: /node_modules/,\n\n        },\n      ],\n    },\n    externals: [\n      {\n        \"@k8slens/extensions\": \"var global.LensExtensions\",\n        \"mobx\": \"var global.Mobx\",\n        \"react\": \"var global.React\",\n        \"mobx-react\": \"var global.MobxReact\"\n      }\n    ],\n    optimization: {\n      minimize: false,\n    },\n    resolve: {\n      extensions: [\".tsx\", \".ts\", \".js\"],\n    },\n    output: {\n      libraryTarget: \"commonjs2\",\n      filename: \"main.js\",\n      path: path.resolve(__dirname, \"dist\"),\n    },\n  },\n  {\n    entry: \"./renderer.tsx\",\n    context: __dirname,\n    target: \"electron-renderer\",\n    mode: \"development\",\n    devtool: \"eval-source-map\",\n    module: {\n      rules: [\n        {\n          test: /\\.tsx?$/,\n          use: [\n            {\n              loader: \"ts-loader\",\n              options: {\n                transpileOnly: true,\n              }\n            }\n          ],\n          exclude: /node_modules/,\n        },\n      ],\n    },\n    optimization: {\n      minimize: false,\n    },\n    externals: [\n      {\n        \"@k8slens/extensions\": \"var global.LensExtensions\",\n        \"react\": \"var global.React\",\n        \"mobx\": \"var global.Mobx\"\n      }\n    ],\n    resolve: {\n      extensions: [\".tsx\", \".ts\", \".js\"],\n    },\n    output: {\n      libraryTarget: \"commonjs2\",\n      globalObject: \"this\",\n      filename: \"renderer.js\",\n      path: path.resolve(__dirname, \"dist\"),\n    },\n    node: {\n      __dirname: false,\n      __filename: false\n    }\n  },\n];\n"
  }
]