[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "**/node_modules/\n*.html\nes/\n**/lib/\n**/dist/\n**/lib-types/\n_site/\n**/dist/\nCHANGELOG.md\n.rollup.cache\ntsconfig.tsbuildinfo\n!.storybook\n!.vuepress\ntypes/components.d.ts\ntypes/auto-imports.d.ts\n*.generated.ts\n**/.vuepress/examples/\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "//@ts-check\n\nmodule.exports = /** @type { import('eslint').Linter.Config } */ ({\n  root: true,\n  extends: [\n    'eslint:recommended',\n    'plugin:eslint-comments/recommended',\n    'plugin:import/recommended',\n    'plugin:unicorn/recommended',\n    'plugin:react/recommended',\n    'plugin:react/jsx-runtime',\n    'plugin:react-hooks/recommended',\n    'plugin:jsx-a11y/recommended',\n    'plugin:prettier/recommended',\n    'prettier'\n  ],\n  env: {\n    node: true,\n    browser: true,\n    es2022: true\n  },\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: {\n      jsx: true\n    }\n  },\n  settings: {\n    react: {\n      version: 'detect'\n    },\n    'import/extensions': ['.js', '.jsx']\n  },\n  ignorePatterns: ['node_modules/*'],\n  rules: {\n    /**\n     * Turn-off recommended rules\n     */\n    'array-callback-return': 'off',\n    'jsx-a11y/click-events-have-key-events': 'off',\n    'jsx-a11y/no-autofocus': 'off',\n    'react/display-name': 'off',\n    'react/prop-types': 'off',\n    'eslint-comments/disable-enable-pair': 'off',\n    'unicorn/no-array-reduce': 'off',\n    'unicorn/filename-case': 'off',\n    'unicorn/no-null': 'off',\n    'unicorn/prevent-abbreviations': 'off',\n    'unicorn/prefer-module': 'off',\n    'unicorn/prefer-top-level-await': 'off',\n    'unicorn/consistent-function-scoping': 'off',\n    'unicorn/no-array-for-each': 'off',\n    'unicorn/prefer-spread': 'off',\n\n    /**\n     * Adjust recommended rules\n     */\n    'no-empty': ['error', {allowEmptyCatch: true}],\n    'no-unused-vars': ['error', {args: 'none', ignoreRestSiblings: true}],\n    'react-hooks/exhaustive-deps': ['error', {additionalHooks: '(useRecoilCallback|useRecoilTransaction)'}],\n\n    /**\n     * Use additional rules\n     */\n    'default-case': 'error',\n    eqeqeq: ['error', 'smart'],\n    'no-array-constructor': 'error',\n    'no-caller': 'error',\n    'no-eval': 'error',\n    'no-extend-native': 'error',\n    'no-extra-bind': 'error',\n    'no-extra-label': 'error',\n    'no-implied-eval': 'error',\n    'no-label-var': 'error',\n    'no-labels': 'error',\n    'no-lone-blocks': 'error',\n    'no-loop-func': 'error',\n    'no-multi-str': 'error',\n    'no-new-func': 'error',\n    'no-new-object': 'error',\n    'no-new-wrappers': 'error',\n    'no-restricted-globals': ['error', ...require('confusing-browser-globals')],\n    'no-script-url': 'error',\n    'no-self-compare': 'error',\n    'no-sequences': 'error',\n    'no-template-curly-in-string': 'error',\n    'no-throw-literal': 'error',\n    'no-unused-expressions': [\n      'error',\n      {\n        allowShortCircuit: true,\n        allowTernary: true,\n        allowTaggedTemplates: true\n      }\n    ],\n    'no-useless-computed-key': 'error',\n    'no-useless-concat': 'error',\n    'no-useless-constructor': 'error',\n    'no-useless-rename': 'error',\n    strict: ['error', 'never'],\n    'react/jsx-pascal-case': ['error', {allowAllCaps: true}],\n    'react/no-array-index-key': 'error',\n    'react/no-typos': 'error',\n    'react/style-prop-object': 'error'\n  },\n  overrides: [\n    {\n      files: ['**/__tests__/**/*', '**/*.{spec,test}.*'],\n      extends: ['plugin:testing-library/react', 'plugin:jest-dom/recommended']\n    },\n    {\n      files: ['**/*.stories.*'],\n      rules: {\n        'import/no-anonymous-default-export': 'off'\n      }\n    },\n    {\n      files: ['**/*.ts?(x)', '**/*.vue'],\n      parser: 'vue-eslint-parser',\n      parserOptions: {\n        parser: '@typescript-eslint/parser',\n        ecmaversion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: {\n          jsx: true\n        },\n        extraFileExtensions: ['.vue'],\n        project: [\n          './tsconfig.json',\n          './packages/*/tsconfig.json',\n          './examples/**/tsconfig.json',\n          './playgrounds/**/tsconfig.json'\n        ]\n      },\n      settings: {\n        react: {version: 'detect'},\n        'import/resolver': {\n          typescript: {\n            alwaysTryTypes: true\n          }\n        }\n      },\n      env: {\n        browser: true,\n        node: true,\n        es6: true\n      },\n      extends: [\n        'plugin:@typescript-eslint/recommended',\n        'plugin:@typescript-eslint/recommended-requiring-type-checking',\n        'plugin:@typescript-eslint/strict',\n        'plugin:import/typescript',\n        'plugin:vue/recommended',\n        'plugin:prettier/recommended',\n        'prettier'\n      ],\n      rules: {\n        /**\n         * Turn-off recommended rules\n         */\n        '@typescript-eslint/no-floating-promises': 'off',\n        '@typescript-eslint/non-nullable-type-assertion-style': 'off',\n        '@typescript-eslint/no-unsafe-assignment': 'off',\n        '@typescript-eslint/no-non-null-assertion': 'off',\n        '@typescript-eslint/no-unsafe-call': 'off',\n        '@typescript-eslint/no-unnecessary-type-assertion': 'off',\n        '@typescript-eslint/prefer-ts-expect-error': 'off',\n\n        // 'tsc' already handles this (https://typescript-eslint.io/docs/linting/troubleshooting/#eslint-plugin-import)\n        'import/default': 'off',\n        'import/namespace': 'off',\n        'import/no-named-as-default-member': 'off',\n        'import/no-unresolved': 'off',\n\n        /**\n         * Adjust recommended rules\n         */\n        '@typescript-eslint/consistent-type-definitions': ['error', 'type'],\n        '@typescript-eslint/no-misused-promises': ['error', {checksVoidReturn: {arguments: false, attributes: false}}],\n        '@typescript-eslint/no-unused-vars': ['error', {args: 'none', ignoreRestSiblings: true}],\n\n        /**\n         * Use additional rules\n         */\n        '@typescript-eslint/consistent-type-imports': 'error',\n        'import/first': 'error',\n        'import/no-anonymous-default-export': 'error',\n\n        /**\n         * Replace additional rules\n         */\n        'default-case': 'off', // 'tsc' noFallthroughCasesInSwitch option is more robust\n        'no-array-constructor': 'off',\n        '@typescript-eslint/no-array-constructor': 'error',\n        'no-implied-eval': 'off',\n        '@typescript-eslint/no-implied-eval': 'error',\n        'no-loop-func': 'off',\n        '@typescript-eslint/no-loop-func': 'error',\n        'no-unused-expressions': 'off',\n        '@typescript-eslint/no-unused-expressions': [\n          'error',\n          {\n            allowShortCircuit: true,\n            allowTernary: true,\n            allowTaggedTemplates: true\n          }\n        ],\n        'no-throw-literal': 'off',\n        '@typescript-eslint/no-throw-literal': 'error',\n        'no-useless-constructor': 'off',\n        '@typescript-eslint/no-useless-constructor': 'error',\n\n        // vue\n        'vue/no-v-html': 'off',\n        'vue/singleline-html-element-content-newline': 'off',\n        'vue/html-self-closing': 'off',\n        'vue/max-attributes-per-line': [\n          'error',\n          {\n            singleline: 10,\n            multiline: 1\n          }\n        ],\n        'vue/require-default-prop': 'off',\n        'vue/html-closing-bracket-spacing': 'error',\n        'vue/no-unused-vars': 'warn',\n        'vue/multi-word-component-names': 'off',\n        'vue/one-component-per-file': 'off',\n        'vue/no-v-model-argument': 'off',\n        'vue/comment-directive': [\n          'warn',\n          {\n            reportUnusedDisableDirectives: false\n          }\n        ]\n      }\n    }\n  ]\n})\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [2214962083]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: \"\\U0001F41E Bug report\"\ndescription: Report an issue with Vr360\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report!\n  - type: textarea\n    id: bug-description\n    attributes:\n      label: Describe the bug\n      description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks!\n      placeholder: Bug description\n    validations:\n      required: true\n  - type: input\n    id: reproduction\n    attributes:\n      label: Reproduction\n      description: Please provide a link via [vr360](https://stackblitz.com/edit/vr360/) or a link to a repo that can reproduce the problem you ran into. A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is required.\n      placeholder: Reproduction\n    validations:\n      required: true\n  - type: textarea\n    id: system-info\n    attributes:\n      label: System Info\n      description: Output of `npx envinfo --system --npmPackages '{@nicepkg/vr360-core,three}' --binaries --browsers`\n      render: Shell\n      placeholder: System, Binaries, Browsers\n    validations:\n      required: true\n  - type: dropdown\n    id: package-manager\n    attributes:\n      label: Used Package Manager\n      description: Select the used package manager\n      options:\n        - npm\n        - yarn\n        - pnpm\n    validations:\n      required: true\n  - type: checkboxes\n    id: checkboxes\n    attributes:\n      label: Validations\n      description: Before submitting the issue, please make sure you do the following\n      options:\n        - label: Follow our [Code of Conduct](https://github.com/nicepkg/vr360/blob/master/CODE_OF_CONDUCT.md)\n          required: true\n        - label: Read the [Contributing Guidelines](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).\n          required: true\n        - label: Check that there isn't [already an issue](https://github.com/nicepkg/vr360/issues) that reports the same bug to avoid creating a duplicate.\n          required: true\n        - label: Make sure this is a Vr360 issue and not a framework-specific issue. For example, if it's a threejs related bug, it should likely be reported to https://github.com/mrdoob/three.js instead.\n          required: true\n        - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/nicepkg/vr360/discussions).\n          required: true\n        - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Question\n    url: https://github.com/nicepkg/vr360/discussions/new?category=Q-A\n    about: Ask a question or discuss about vr360\n  - name: Ideas\n    url: https://github.com/nicepkg/vr360/discussions/new?category=Ideas\n    about: Start a discussion to improve vr360\n  - name: GitHub Sponsors\n    url: https://github.com/sponsors/2214962083\n    about: Like this project? Please consider supporting the author.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: \"\\U0001F680 New feature proposal\"\ndescription: Propose a new feature to be added to Vr360\nlabels: ['feature request']\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for your interest in the project and taking the time to fill out this feature report!\n  - type: textarea\n    id: feature-description\n    attributes:\n      label: Clear and concise description of the problem\n      description: 'As a developer using xxxx package I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description. Thanks!'\n    validations:\n      required: true\n  - type: textarea\n    id: suggested-solution\n    attributes:\n      label: Suggested solution\n      description: 'In module [xy] we could provide following implementation...'\n    validations:\n      required: true\n  - type: textarea\n    id: alternative\n    attributes:\n      label: Alternative\n      description: Clear and concise description of any alternative solutions or features you've considered.\n  - type: textarea\n    id: additional-context\n    attributes:\n      label: Additional context\n      description: Any other context or screenshots about the feature request here.\n  - type: checkboxes\n    id: checkboxes\n    attributes:\n      label: Validations\n      description: Before submitting the issue, please make sure you do the following\n      options:\n        - label: Follow our [Code of Conduct](https://github.com/nicepkg/vr360/blob/master/CODE_OF_CONDUCT.md)\n          required: true\n        - label: Read the [Contributing Guidelines](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).\n          required: true\n        - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate.\n          required: true\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Thank you for contributing! -->\n\n### Description\n\n<!-- Please insert your description here and provide especially info about the \"what\" this PR is solving -->\n\n### Additional context\n\n<!-- e.g. is there anything you'd like reviewers to focus on? -->\n\n---\n\n### What is the purpose of this pull request? <!-- (put an \"X\" next to an item) -->\n\n- [ ] Bug fix\n- [ ] New Feature\n- [ ] Documentation update\n- [ ] Other\n\n### Before submitting the PR, please make sure you do the following\n\n- [ ] Read the [Contributing Guidelines](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).\n- [ ] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.\n- [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`).\n- [ ] Ideally, include relevant tests that fail without this PR but pass with it.\n"
  },
  {
    "path": ".github/commit-convention.md",
    "content": "## Git Commit Message Convention\n\n> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).\n\n#### TL;DR:\n\nMessages must be matched by the following regex:\n\n```text\n/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\\(.+\\))?: .{1,50}/\n```\n\n#### Examples\n\nAppears under \"Features\" header, `link` subheader:\n\n```\nfeat(link): add `force` option\n```\n\nAppears under \"Bug Fixes\" header, `view` subheader, with a link to issue #28:\n\n```\nfix(view): handle keep-alive with aborted navigations\n\nclose #28\n```\n\nAppears under \"Performance Improvements\" header, and under \"Breaking Changes\" with the breaking change explanation:\n\n```\nperf: improve guard extraction\n\nBREAKING CHANGE: The 'beforeRouteEnter' option has been removed.\n```\n\nThe following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the \"Reverts\" header.\n\n```\nrevert: feat(compiler): add 'comments' option\n\nThis reverts commit 667ecc1654a317a13331b17617d973392f415f02.\n```\n\n### Full Message Format\n\nA commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\n### Revert\n\nIf the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.\n\n### Type\n\nIf the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.\n\nOther prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.\n\n### Scope\n\nThe scope could be anything specifying the place of the commit change. For example `core`, `compiler`, `ssr`, `v-model`, `transition` etc...\n\n### Subject\n\nThe subject contains a succinct description of the change:\n\n- use the imperative, present tense: \"change\" not \"changed\" nor \"changes\"\n- don't capitalize the first letter\n- no dot (.) at the end\n\n### Body\n\nJust as in the **subject**, use the imperative, present tense: \"change\" not \"changed\" nor \"changes\".\nThe body should include the motivation for the change and contrast this with previous behavior.\n\n### Footer\n\nThe footer should contain any information about **Breaking Changes** and is also the place to\nreference GitHub issues that this commit **Closes**.\n\n**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.\n"
  },
  {
    "path": ".github/settings.yml",
    "content": "labels:\n  - name: bug\n    color: ee0701\n  - name: contribution welcome\n    color: 0e8a16\n  - name: discussion\n    color: 4935ad\n  - name: docs\n    color: 8be281\n  - name: enhancement\n    color: a2eeef\n  - name: good first issue\n    color: 7057ff\n  - name: help wanted\n    color: 008672\n  - name: question\n    color: d876e3\n  - name: wontfix\n    color: ffffff\n  - name: WIP\n    color: ffffff\n  - name: need repro\n    color: c9581c\n  - name: feature request\n    color: fbca04\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Docs\n\non:\n  push:\n    branches:\n      - main\n      - master\n\n  pull_request:\n    branches:\n      - main\n      - master\n  # workflow_run:\n  #   workflows: ['Test']\n  #   types:\n  #     - completed\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    env:\n      TURBO_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n      TURBO_TEAM: ${{ secrets.VERCEL_TEAM }}\n      TURBO_CACHE_KEY: ubuntu-latest-16 # reuse cache key from ci workflow\n      NODE_OPTIONS: '--max_old_space_size=4096'\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v2.2.1\n\n      - name: Use Node.js\n        uses: actions/setup-node@v2\n        with:\n          node-version: 16.x\n          registry-url: https://registry.npmjs.org/\n          cache: pnpm\n\n      - name: Install Dependencies and build all packages\n        run: pnpm bootstrap\n\n      # - name: deploy docs to vercel\n      #   uses: BetaHuhn/deploy-to-vercel-action@v1\n      #   # see: https://github.com/BetaHuhn/deploy-to-vercel-action\n      #   with:\n      #     GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      #     VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n      #     VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}\n      #     VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_DOC_SITE }}\n      #     # for team settings\n      #     VERCEL_SCOPE: ${{ secrets.VERCEL_ORG_ID }}\n      #     # the docs build dist folder\n      #     WORKING_DIRECTORY: ./packages/doc/.vuepress/dist\n      #     # bind domains\n      #     # ALIAS_DOMAINS: |\n      #     #   docs.vr360.com\n      #     #   docs.vr360.cn\n\n      - name: deploy docs to vercel\n        uses: amondnet/vercel-action@v20\n        # see: https://github.com/amondnet/vercel-action\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          vercel-token: ${{ secrets.VERCEL_TOKEN }}\n          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}\n          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID_DOC_SITE }}\n          # for team settings\n          scope: ${{ secrets.VERCEL_ORG_ID }}\n          # just like npx vercel --prod\n          vercel-args: '--prod'\n          # the docs build dist folder\n          working-directory: ./packages/doc/.vuepress/dist\n          # bind domains\n          # alias-domains: |\n          #   docs.vr360.com\n          #   docs.vr360.cn\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n    branches:\n      - main\n      - master\n\n  pull_request:\n    branches:\n      - main\n      - master\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    env:\n      TURBO_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n      TURBO_TEAM: ${{ secrets.VERCEL_TEAM }}\n      TURBO_CACHE_KEY: ubuntu-latest-16 # reuse cache key from ci workflow\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v2.2.1\n\n      - name: Use Node.js\n        uses: actions/setup-node@v2\n        with:\n          node-version: 16\n          registry-url: https://registry.npmjs.org/\n          cache: pnpm\n\n      - name: Install Dependencies\n        run: pnpm bootstrap\n\n      - name: Lint\n        run: pnpm run lint\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    env:\n      TURBO_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n      TURBO_TEAM: ${{ secrets.VERCEL_TEAM }}\n      TURBO_CACHE_KEY: ubuntu-latest-16 # reuse cache key from ci workflow\n      NODE_OPTIONS: '--max_old_space_size=4096'\n\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v2.2.1\n\n      - name: Use Node.js v16\n        uses: actions/setup-node@v2\n        with:\n          node-version: 16\n          registry-url: https://registry.npmjs.org/\n          cache: pnpm\n\n      - run: npx conventional-github-releaser -p angular\n        continue-on-error: true\n        env:\n          CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{secrets.GITHUB_TOKEN}}\n\n      - name: Install Dependencies\n        run: pnpm i\n\n      - name: PNPM build\n        run: pnpm run build\n\n      - name: Publish to NPM\n        run: pnpm -r publish --access public --no-git-checks\n        env:\n          NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}\n\n      # - name: Publish to VSCE & OVSX\n      #   run: npm run publish\n      #   working-directory: ./packages/vscode\n      #   env:\n      #     VSCE_TOKEN: ${{secrets.VSCE_TOKEN}}\n      #     OVSX_TOKEN: ${{secrets.OVSX_TOKEN}}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  push:\n    branches:\n      - main\n      - master\n\n  pull_request:\n    branches:\n      - main\n      - master\n  # workflow_run:\n  #   workflows: ['Lint']\n  #   types:\n  #     - completed\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    env:\n      TURBO_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n      TURBO_TEAM: ${{ secrets.VERCEL_TEAM }}\n      TURBO_CACHE_KEY: ${{ matrix.os }}-${{ matrix.node-version }}\n      NODE_OPTIONS: '--max_old_space_size=4096'\n    strategy:\n      matrix:\n        node-version: [14, 16]\n        os: [ubuntu-latest, macOS-latest]\n      fail-fast: false\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v2.2.1\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v2\n        with:\n          node-version: ${{ matrix.node-version }}\n          registry-url: https://registry.npmjs.org/\n          cache: pnpm\n\n      - name: Install Dependencies and build all packages\n        run: pnpm bootstrap\n\n      # - name: Build\n      #   run: pnpm run build\n\n      - name: Test\n        run: pnpm run test\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.vite-ssg-dist\n.vite-ssg-temp\n*.local\ndist\ndist-ssr\nnode_modules\n.idea/\n*.log\nstats.html\n.vite-inspect\n.history\n**/.rollup.cache\n**/test/coverage\ntsconfig.tsbuildinfo\n**/.temp\n**/.cache\n\n# testing\n/coverage\ncypress/videos\ncypress/screenshots\n\n# misc\n.DS_Store\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# Editor directories and files\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n.vercel\n.turbo\n"
  },
  {
    "path": ".husky/commit-msg",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\n# lint commit message\nnpx --no-install commitlint --edit \"$1\"\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\n# lint git stash files\nnpx --no-install lint-staged\n"
  },
  {
    "path": ".husky/prepare-commit-msg",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\necho \"do not 'git commit' please run 'pnpm commit' instead\"\n# echo \"commit lint please see https://juejin.cn/post/6934292467160514567#heading-3\"\necho \"See .github/commit-convention.md for more details.\"\n"
  },
  {
    "path": ".npmrc",
    "content": "# registry = \"https://registry.npmmirror.com\"\n\nsass_binary_site = \"https://cdn.npmmirror.com/binaries/node-sass\"\nphantomjs_cdnurl = \"https://npmmirror.com/package/downloads\"\nelectron_mirror = \"https://registry.npmmirror.com/binary.html?path=electron/\"\nsqlite3_binary_host_mirror = \"https://foxgis.oss-cn-shanghai.aliyuncs.com/\"\nprofiler_binary_host_mirror = \"https://registry.npmmirror.com/binary.html?path=node-inspector/\"\nchromedriver_cdnurl = \"https://cdn.npmmirror.com/binaries/chromedriver\"\n\n\n# pnpm 子线程交叉打印\nstream = true\n\n# pnpm 工作区中的本地包是否优先于注册表中的包\nprefer-workspace-packages = true\n\n# pnpm 将 monorepo 工作区中的本地可用包链接到 node_modules，而不是从注册表重新下载它们。\nlink-workspace-packages = true\n\n# 依赖提升\npublic-hoist-pattern[] = *types*\npublic-hoist-pattern[] = *eslint*\npublic-hoist-pattern[] = *stylelint*\npublic-hoist-pattern[] = @prettier/plugin-*\npublic-hoist-pattern[] = *prettier-plugin-*\npublic-hoist-pattern[] = prettier\npublic-hoist-pattern[] = *jest*\npublic-hoist-pattern[] = *rollup*\npublic-hoist-pattern[] = *babel*\npublic-hoist-pattern[] = core-js\npublic-hoist-pattern[] = regenerator-runtime\npublic-hoist-pattern[] = esbuild\npublic-hoist-pattern[] = *conventional*\npublic-hoist-pattern[] = jsdom\n\nhoist-pattern[] = typescript\nhoist-pattern[] = @vue/*\nhoist-pattern[] = vue-template-compiler\nhoist-pattern[] = vue-template-es2015-compiler\nhoist-pattern[] = *vuepress*\nhoist-pattern[] = *plop*\nhoist-pattern[] = vue-demi\n\n# for doc-site\nhoist-pattern[] = nprogress\n"
  },
  {
    "path": ".stylelintignore",
    "content": "node_modules/\n**/*.spec.*\nes/\nlib/\n_site/\ndist/\n**/node_modules/*\n**/segi-ant-theme.less\n**/.rollup.cache/*\n**/index.html\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"antfu.iconify\",\n    \"antfu.unocss\",\n    \"antfu.goto-alias\",\n    \"csstools.postcss\",\n    \"dbaeumer.vscode-eslint\",\n    \"esbenp.prettier-vscode\",\n    \"mrmlnc.vscode-less\",\n    \"mikestead.dotenv\",\n    \"stylelint.vscode-stylelint\",\n    \"vue.volar\",\n    \"streetsidesoftware.code-spell-checker\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  // Use PNPM\n  \"npm.packageManager\": \"pnpm\",\n  // \"eslint.packageManager\": \"pnpm\",\n  \"typescript.tsdk\": \"./node_modules/typescript/lib\",\n  \"editor.tabSize\": 2,\n\n  \"stylelint.enable\": true,\n  \"stylelint.validate\": [\"css\", \"less\", \"postcss\", \"scss\", \"vue\", \"sass\"],\n  \"prettier.enable\": false,\n  \"editor.formatOnSave\": false,\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true,\n    \"source.fixAll.stylelint\": true\n  },\n\n  \"files.associations\": {\n    \"*.css\": \"postcss\"\n  },\n\n  \"typescript.inlayHints.parameterNames.enabled\": \"all\",\n  // \"typescript.inlayHints.variableTypes.enabled\": true,\n  // \"typescript.inlayHints.propertyDeclarationTypes.enabled\": true,\n  \"typescript.inlayHints.parameterTypes.enabled\": true,\n  // \"typescript.inlayHints.functionLikeReturnTypes.enabled\": true,\n  \"scss.lint.unknownAtRules\": \"ignore\",\n  \"less.lint.unknownAtRules\": \"ignore\",\n  \"css.lint.unknownAtRules\": \"ignore\",\n\n  \"unocss.root\": \"playgrounds/vue3\",\n\n  \"workbench.colorCustomizations\": {\n    \"activityBar.background\": \"#252F38\",\n    \"titleBar.activeBackground\": \"#34414F\",\n    \"titleBar.activeForeground\": \"#F9FAFB\"\n  },\n  \"cSpell.words\": [\n    \"alais\",\n    \"amondnet\",\n    \"antfu\",\n    \"apartuser\",\n    \"APPKEY\",\n    \"appr\",\n    \"Attributify\",\n    \"authc\",\n    \"axios\",\n    \"bonuse\",\n    \"bumpp\",\n    \"busi\",\n    \"Cascader\",\n    \"Certi\",\n    \"clsx\",\n    \"commitlint\",\n    \"Compat\",\n    \"cparagraph\",\n    \"csentence\",\n    \"cssnano\",\n    \"cust\",\n    \"cword\",\n    \"datacachesvr\",\n    \"Datetime\",\n    \"demi\",\n    \"docsearch\",\n    \"DOWNFILE\",\n    \"Dtos\",\n    \"ecmaversion\",\n    \"editble\",\n    \"envinfo\",\n    \"esno\",\n    \"Filterbar\",\n    \"globby\",\n    \"iconify\",\n    \"iife\",\n    \"INDIV\",\n    \"innercheckinmember\",\n    \"Inspction\",\n    \"intlify\",\n    \"jsdelivr\",\n    \"Jssdk\",\n    \"jweixin\",\n    \"Lazyload\",\n    \"micromessenger\",\n    \"mockjs\",\n    \"nicepkg\",\n    \"noscript\",\n    \"nouce\",\n    \"nprogress\",\n    \"oper\",\n    \"OVSX\",\n    \"Pagelist\",\n    \"pannellum\",\n    \"pano\",\n    \"pinia\",\n    \"pnpm\",\n    \"poppable\",\n    \"preinstall\",\n    \"prismjs\",\n    \"qrcode\",\n    \"raycaster\",\n    \"Realsee\",\n    \"redrun\",\n    \"restapi\",\n    \"rgba\",\n    \"safelist\",\n    \"Segi\",\n    \"semibold\",\n    \"shiki\",\n    \"Sname\",\n    \"solidjs\",\n    \"stackblitz\",\n    \"stackblitzrc\",\n    \"stylelint\",\n    \"svgr\",\n    \"tabbar\",\n    \"testop\",\n    \"threejs\",\n    \"touchmove\",\n    \"tweenjs\",\n    \"typecheck\",\n    \"typeof\",\n    \"uhome\",\n    \"uhomecp\",\n    \"unocss\",\n    \"unplugin\",\n    \"unproject\",\n    \"unref\",\n    \"userinfo\",\n    \"Vant\",\n    \"vconsole\",\n    \"VERCEL\",\n    \"Vite\",\n    \"vitejs\",\n    \"Vitesse\",\n    \"vitest\",\n    \"vpay\",\n    \"vuepress\",\n    \"vueuse\",\n    \"wcpay\",\n    \"webgl\",\n    \"Wechat\",\n    \"Weixin\",\n    \"wxappid\",\n    \"XFQYGM\",\n    \"XFQYXZ\",\n    \"yangjinming\",\n    \"zhong\",\n    \"ZLZYLX\",\n    \"ZZMM\"\n  ],\n  \"commentTranslate.targetLanguage\": \"zh-CN\"\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at 2214962083@qq.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThanks for being interested in contributing to this project!\n\n## Development\n\nTo improve our development process, we've provide a playground, you can use any package in playground. Vr360 uses a monorepo structure and packages/package can be consumed in isolation.\n\n## Setup\n\nThe following steps will get you up and running to contribute to Vr360:\n\n1. Fork the repo (click the <kbd>Fork</kbd> button at the top right of\n   [this page](https://github.com/nicepkg/vr360))\n\n2. Clone your fork locally\n\n```sh\ngit clone https://github.com/<your_github_username>/vr360.git\ncd vr360\n```\n\n3. Install Dependencies. This project depends on node v14+ and pnpm 6.x\n\nIf you don't have pnpm installed, you should execute:\n\n```bash\nnpm i -g pnpm@6.32.17\n```\n\nInstall the dependencies:\n\n```bash\npnpm bootstrap\n```\n\nWe use VuePress for rapid development and documenting. You can start it locally by\n\n```bash\ncd packages/doc-site\npnpm dev\n```\n\n### Commit Convention\n\nBefore you create a Pull Request, please check whether your commits comply with\nthe commit conventions used in this repository.\n\nWhen you create a commit we kindly ask you to follow the convention\n`category(scope or module): message` in your commit message while using one of\nthe following categories:\n\n- `feat / feature`: all changes that introduce completely new code or new\n  features\n- `fix`: changes that fix a bug (ideally you will additionally reference an\n  issue if present)\n- `refactor`: any code related change that is not a fix nor a feature\n- `docs`: changing existing or creating new documentation (i.e. README, docs for\n  usage of a lib or cli usage)\n- `build`: all changes regarding the build of the software, changes to\n  dependencies or the addition of new dependencies\n- `test`: all changes regarding tests (adding new tests or changing existing\n  ones)\n- `ci`: all changes regarding the configuration of continuous integration (i.e.\n  github actions, ci system)\n- `chore`: all changes to the repository that do not fit into any of the above\n  categories\n\nIf you are interested in the detailed specification you can visit\nhttps://www.conventionalcommits.org/ or check out the\n[Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines).\n\n### Steps to PR\n\n1. Fork of the vvr360 repository and clone your fork\n\n2. Create a new branch out of the `master` branch. We follow the convention\n   `[type/scope]`. For example `fix/vr360-core` or `docs/vr360-ui`. `type`\n   can be either `docs`, `fix`, `feat`, `build`, or any other conventional\n   commit type. `scope` is just a short id that describes the scope of work.\n\n3. Make and commit your changes following the\n   [commit convention](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md#commit-convention).\n   As you develop, you can run `pnpm --filter <module> build` and\n   `pnpm --filter <module> test` to make sure everything works as expected. Please\n   note that you might have to run `pnpm bootstrap` first in order to build all\n   dependencies.\n\n## Code Style\n\nDon't worry about the code style as long as you install the dev dependencies. Git hooks will format and fix them for you on committing.\n\n## Thanks\n\nThank you again for being interested in this project! You are awesome!\n\n## License\n\nBy contributing your code to the vr360 GitHub repository, you agree to\nlicense your contribution under the MIT license.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 YangJinMing <https://github.com/2214962083>\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": "<div align=\"center\">\n  <a href=\"https://vr360.nicepkg.cn/\">\n    <img src=\"https://vr360.nicepkg.cn/images/logo-bg.png\" width=\"50%\">\n  </a>\n  <br>\n  <br>\n  <p>快速实现你的全景开发需求，全景看房、全景街景、全景景点</p>\n  <p>\n    <img src=\"https://img.shields.io/github/package-json/v/nicepkg/vr360\" alt=\"version\">\n    <img src=\"https://img.shields.io/github/license/nicepkg/vr360\" alt=\"license\">\n    <img src=\"https://img.shields.io/github/stars/nicepkg/vr360?style=social\" alt=\"stars\">\n  </p>\n</div>\n\n## 介绍\n\nVr360 是一个基于 threejs 能让你快速实现业务全景需求的库，比如全景看房、全景街景、全景景点。\n\n它的核心被设计为框架无关性，可以用 json 配置的方式快速实现常见全景需求。\n\n后续还会提供高度封装的 viewer 和 editor 等组件，很适合懒人，会提供 vue2/3 、 react 、web component 版本。\n\njson 驱动视图的特性是好维护，你甚至可以不用接触 threejs。写出来的代码拉条哈士奇过来也能维护。\n\n## 文档\n\n[查看文档](https://vr360.nicepkg.cn/)\n\n## 库列表\n\n| 库名                                                           | 文档                                                  | 版本                                                                                             | 描述                                                                                        |\n| -------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |\n| [@nicepkg/vr360-core](./packages/vr360-core/README.md)         | [链接](https://vr360.nicepkg.cn/libs/vr360-core/)     | <img src=\"https://img.shields.io/npm/v/@nicepkg/vr360-core?style=flat-square\" alt=\"version\">     | json 驱动的全景浏库，设计框架无关性，可用于任何框架，如 vue/react/angular/                  |\n| [@nicepkg/vr360-ui](./packages/vr360-ui/README.md)             | [链接](https://vr360.nicepkg.cn/libs/vr360-ui/)       | <img src=\"https://img.shields.io/npm/v/@nicepkg/vr360-ui?style=flat-square\" alt=\"version\">       | (开发中...) 提供一个现成的 vr360 viewer 和 editor 组件，基于 stencil 构建的 web component。 |\n| [@nicepkg/vr360-ui-vue2](./packages/vr360-ui-vue2/README.md)   | [链接](https://vr360.nicepkg.cn/libs/vr360-ui-vue2/)  | <img src=\"https://img.shields.io/npm/v/@nicepkg/vr360-ui-vue2?style=flat-square\" alt=\"version\">  | (开发中...) vr360-ui 的 vue2 二次封装版                                                     |\n| [@nicepkg/vr360-ui-vue3](./packages/vr360-ui-vue3/README.md)   | [链接](https://vr360.nicepkg.cn/libs/vr360-ui-vue3/)  | <img src=\"https://img.shields.io/npm/v/@nicepkg/vr360-ui-vue3?style=flat-square\" alt=\"version\">  | (开发中...) vr360-ui 的 vue3 二次封装版本，开箱即用。                                       |\n| [@nicepkg/vr360-ui-react](./packages/vr360-ui-react/README.md) | [链接](https://vr360.nicepkg.cn/libs/vr360-ui-react/) | <img src=\"https://img.shields.io/npm/v/@nicepkg/vr360-ui-react?style=flat-square\" alt=\"version\"> | (开发中...) vr360-ui 的 react 二次封装版本，开箱即用。                                      |\n\n## Contributing\n\nLearn about contribution [here](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).\n\nThis project exists thanks to all the people who contribute:\n\n<a href=\"https://github.com/nicepkg/vr360/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=nicepkg/vr360\" />\n</a>\n\n## License\n\n[MIT](https://github.com/nicepkg/vr360/blob/master/LICENSE) License © 2022-PRESENT [nicepkg](https://github.com/nicepkg)\n"
  },
  {
    "path": "commitlint.config.js",
    "content": "module.exports = {\n  extends: ['@commitlint/config-conventional']\n}\n"
  },
  {
    "path": "lint-staged.config.js",
    "content": "module.exports = {\n  '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],\n  '*.json': ['prettier --write'],\n  '*.vue': ['eslint --fix', 'stylelint --aei --fix', 'prettier --write'],\n  '*.{scss,sass,less,css}': ['stylelint --aei --fix', 'prettier --write'],\n  '*.md': ['prettier --write']\n}\n"
  },
  {
    "path": "netlify.toml",
    "content": "[build.environment]\n  NODE_VERSION = \"16\"\n  NPM_FLAGS = \"--version\" # prevent Netlify npm install\n[build]\n  publish = \"packages/doc/.vuepress/dist\"\n  command = \"npm i -g pnpm && pnpm build:ci\"\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@nicepkg/vr360\",\n  \"version\": \"0.3.1\",\n  \"private\": true,\n  \"packageManager\": \"pnpm@7.0.0\",\n  \"author\": \"yangjinming\",\n  \"description\": \"快速实现你的全景开发需求，全景看房、全景街景、全景景点\",\n  \"engines\": {\n    \"node\": \">=14\",\n    \"pnpm\": \">=7\"\n  },\n  \"scripts\": {\n    \"bootstrap\": \"pnpm i &&pnpm build:all\",\n    \"build\": \"esno ./scripts/build.ts\",\n    \"build:all\": \"turbo run build\",\n    \"build:ci\": \"pnpm i --store=node_modules/.pnpm-store --frozen-lockfile && turbo run build --no-cache\",\n    \"check-update\": \"esno ./scripts/check-update.ts\",\n    \"clean\": \"rimraf **/node_modules/**\",\n    \"commit\": \"git add . &&git-cz\",\n    \"lint\": \"pnpm lint:es &&pnpm lint:css\",\n    \"lint:change\": \"lint-staged\",\n    \"lint:css\": \"stylelint --aei --fix ./**/*.{vue,css,sass,scss,less,html} --cache --cache-location node_modules/.cache/stylelint/\",\n    \"lint:es\": \"eslint --fix . --ext .jsx,.js,.vue,.ts,.tsx\",\n    \"preinstall\": \"npx only-allow pnpm\",\n    \"prepare\": \"husky install\",\n    \"release\": \"esno ./scripts/release.ts\",\n    \"test\": \"pnpm run -r test\"\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^17.1.2\",\n    \"@commitlint/config-conventional\": \"^17.1.0\",\n    \"@types/eslint\": \"^8.4.6\",\n    \"@types/lodash-es\": \"^4.17.6\",\n    \"@types/node\": \"*\",\n    \"@types/prettier\": \"^2.7.1\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.38.1\",\n    \"@typescript-eslint/parser\": \"^5.38.1\",\n    \"@vue/eslint-config-typescript\": \"^11.0.2\",\n    \"bumpp\": \"^8.2.1\",\n    \"chalk\": \"4.1.2\",\n    \"commitizen\": \"^4.2.5\",\n    \"confusing-browser-globals\": \"^1.0.11\",\n    \"conventional-changelog-cli\": \"^2.2.2\",\n    \"cross-env\": \"^7.0.3\",\n    \"cz-conventional-changelog\": \"^3.3.0\",\n    \"eslint\": \"^8.24.0\",\n    \"eslint-config-prettier\": \"^8.5.0\",\n    \"eslint-import-resolver-typescript\": \"^3.5.1\",\n    \"eslint-plugin-eslint-comments\": \"3.2.0\",\n    \"eslint-plugin-html\": \"^7.1.0\",\n    \"eslint-plugin-import\": \"^2.26.0\",\n    \"eslint-plugin-jest-dom\": \"^4.0.2\",\n    \"eslint-plugin-jsx-a11y\": \"^6.6.1\",\n    \"eslint-plugin-prettier\": \"^4.2.1\",\n    \"eslint-plugin-react\": \"7.31.7\",\n    \"eslint-plugin-react-hooks\": \"^4.6.0\",\n    \"eslint-plugin-testing-library\": \"^5.7.0\",\n    \"eslint-plugin-vue\": \"^9.5.1\",\n    \"eslint-plugin-unicorn\": \"43.0.2\",\n    \"esno\": \"*\",\n    \"globby\": \"11.1.0\",\n    \"husky\": \"^8.0.1\",\n    \"ini\": \"^3.0.1\",\n    \"less\": \"^4.1.3\",\n    \"lint-staged\": \"^13.0.3\",\n    \"npm-check-updates\": \"^16.3.3\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"ora\": \"6.1.2\",\n    \"plop\": \"^3.1.1\",\n    \"postcss\": \"^8.4.16\",\n    \"postcss-html\": \"^1.5.0\",\n    \"postcss-less\": \"^6.0.0\",\n    \"prettier\": \"^2.7.1\",\n    \"react\": \"*\",\n    \"rimraf\": \"^3.0.2\",\n    \"stylelint\": \"^14.13.0\",\n    \"stylelint-config-prettier\": \"^9.0.3\",\n    \"stylelint-config-recess-order\": \"^3.0.0\",\n    \"stylelint-config-recommended\": \"^9.0.0\",\n    \"stylelint-config-recommended-vue\": \"^1.4.0\",\n    \"stylelint-config-standard\": \"^28.0.0\",\n    \"stylelint-declaration-block-no-ignored-properties\": \"^2.5.0\",\n    \"stylelint-less\": \"^1.0.6\",\n    \"stylelint-order\": \"^5.0.0\",\n    \"stylelint-prettier\": \"^2.0.0\",\n    \"turbo\": \"^1.5.4\",\n    \"typescript\": \"*\"\n  },\n  \"pnpm\": {\n    \"overrides\": {\n      \"@testing-library/dom\": \"8.18.1\",\n      \"@testing-library/jest-dom\": \"5.16.5\",\n      \"@testing-library/react\": \"13.4.0\",\n      \"@types/node\": \"18.7.23\",\n      \"@types/react\": \"18.0.21\",\n      \"@types/react-dom\": \"18.0.6\",\n      \"@types/react-router-dom\": \"5.3.3\",\n      \"@vitejs/plugin-react\": \"2.1.0\",\n      \"concurrently\": \"7.4.0\",\n      \"conventional-changelog-cli\": \"2.2.2\",\n      \"esno\": \"0.16.3\",\n      \"react\": \"17.0.2\",\n      \"react-dom\": \"17.0.2\",\n      \"react-router-dom\": \"6.3.0\",\n      \"typescript\": \"4.7.4\",\n      \"vite\": \"3.0.9\",\n      \"vitest\": \"0.23.4\",\n      \"vue-demi\": \"0.13.11\"\n    },\n    \"peerDependencyRules\": {\n      \"allowedVersions\": {\n        \"react\": \"17\",\n        \"react-dom\": \"17\",\n        \"typescript\": \"4.8\"\n      }\n    },\n    \"allowedDeprecatedVersions\": {\n      \"stable\": \"*\",\n      \"core-js\": \"*\",\n      \"mkdirp\": \"*\",\n      \"uuid\": \"*\",\n      \"querystring\": \"*\",\n      \"sane\": \"*\",\n      \"chokidar\": \"*\",\n      \"fsevents\": \"*\",\n      \"source-map-resolve\": \"*\",\n      \"source-map-url\": \"*\",\n      \"resolve-url\": \"*\",\n      \"urix\": \"*\"\n    },\n    \"packageExtensions\": {\n      \"stylelint-config-recommended-vue\": {\n        \"dependencies\": {\n          \"postcss-html\": \"^1.4.1\"\n        }\n      },\n      \"vue-template-compiler\": {\n        \"devDependencies\": {\n          \"vue\": \"^2.6.14\"\n        },\n        \"peerDependencies\": {\n          \"vue\": \"^2.6.14\"\n        }\n      }\n    }\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"./node_modules/cz-conventional-changelog\"\n    }\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/client.ts",
    "content": "/* eslint-disable unicorn/no-await-expression-member */\nimport type {ProjectFiles} from '@stackblitz/sdk'\nimport {defineClientConfig} from '@vuepress/client'\nimport pkg from '../package.json'\n\nexport default defineClientConfig({\n  enhance({app}) {\n    app.config.globalProperties.version = pkg.version\n\n    if (!__VUEPRESS_SSR__) {\n      window.loadCodeDemoModeDefaultFiles = async _mode => {\n        const mode = _mode as 'node' | 'vue2' | 'vue3' | 'react' | 'html'\n\n        const defaultFiles: ProjectFiles = {\n          'src/demo.css': (await import('./public/code-demo-templates/demo.css.txt?raw')).default,\n          '.stackblitzrc': `{\n            \"startCommand\": \"npm run dev\"\n          }`\n        }\n\n        switch (mode) {\n          case 'node':\n          case 'vue3':\n            return {\n              ...defaultFiles,\n              'src/App.vue': (await import('./public/code-demo-templates/vue3/src/App.vue.txt?raw')).default,\n              'src/main.ts': (await import('./public/code-demo-templates/vue3/src/main.ts.txt?raw')).default,\n              'types/module.d.ts': (await import('./public/code-demo-templates/vue3/types/module.d.ts.txt?raw'))\n                .default,\n              'index.html': (await import('./public/code-demo-templates/vue3/index.html.txt?raw')).default,\n              'vite.config.ts': (await import('./public/code-demo-templates/vue3/vite.config.ts.txt?raw')).default,\n              'package.json': (await import('./public/code-demo-templates/vue3/package.json.txt?raw')).default,\n              'tsconfig.json': (await import('./public/code-demo-templates/vue3/tsconfig.json.txt?raw')).default\n            } as ProjectFiles\n          case 'vue2':\n            return {\n              ...defaultFiles,\n              'src/App.vue': (await import('./public/code-demo-templates/vue2/src/App.vue.txt?raw')).default,\n              'src/main.ts': (await import('./public/code-demo-templates/vue2/src/main.ts.txt?raw')).default,\n              'types/module.d.ts': (await import('./public/code-demo-templates/vue2/types/module.d.ts.txt?raw'))\n                .default,\n              'index.html': (await import('./public/code-demo-templates/vue2/index.html.txt?raw')).default,\n              'vite.config.ts': (await import('./public/code-demo-templates/vue2/vite.config.ts.txt?raw')).default,\n              'package.json': (await import('./public/code-demo-templates/vue2/package.json.txt?raw')).default,\n              'tsconfig.json': (await import('./public/code-demo-templates/vue2/tsconfig.json.txt?raw')).default\n            } as ProjectFiles\n          case 'react':\n            return {\n              ...defaultFiles,\n              'src/main.tsx': (await import('./public/code-demo-templates/react/src/main.tsx.txt?raw')).default,\n              'index.html': (await import('./public/code-demo-templates/react/index.html.txt?raw')).default,\n              'package.json': (await import('./public/code-demo-templates/react/package.json.txt?raw')).default,\n              'tsconfig.json': (await import('./public/code-demo-templates/react/tsconfig.json.txt?raw')).default,\n              'vite.config.ts': (await import('./public/code-demo-templates/react/vite.config.ts.txt?raw')).default\n            } as ProjectFiles\n          case 'html':\n            return {\n              'package.json': (await import('./public/code-demo-templates/html/package.json.txt?raw')).default,\n              'demo.css': (await import('./public/code-demo-templates/demo.css.txt?raw')).default,\n              '.stackblitzrc': `{\n                \"startCommand\": \"npm run dev\"\n              }`\n            } as ProjectFiles\n          default:\n            return {}\n        }\n      }\n    }\n  }\n})\n"
  },
  {
    "path": "packages/doc/.vuepress/components/DemoA.vue",
    "content": "<template>\n  <div class=\"demoA\">\n    <div\n      ref=\"tipRef\"\n      class=\"demoA-tip\"\n      :style=\"{\n        transform: `translate(${tipLeft}px, ${tipTop + 50}px)`,\n        zIndex: showTip ? 99 : -1,\n        visibility: showTip ? 'visible' : 'hidden'\n      }\"\n    >\n      <div class=\"demoA-tip-title\">{{ tipTitle }}</div>\n      <div class=\"demoA-tip-content\">{{ tipContent }}</div>\n    </div>\n    <div ref=\"containerRef\" class=\"demoA-container\"></div>\n    <div class=\"demoA-bottom-bar\">\n      <div\n        v-for=\"space in spacesConfig\"\n        :key=\"space.id\"\n        class=\"demoA-bottom-card\"\n        :style=\"{\n          backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`\n        }\"\n        @click=\"handleSwitchSpace(space)\"\n      ></div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\n/* eslint-disable import/no-named-as-default-member */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\nimport {onMounted, ref} from 'vue'\nimport type {SpaceConfig} from '@nicepkg/vr360-core'\nimport {Vr360} from '@nicepkg/vr360-core'\nimport textures from '../../../../textures.json'\n\nconst containerRef = ref<HTMLElement>()\n\nconst tipRef = ref<HTMLElement>()\nconst tipLeft = ref(0)\nconst tipTop = ref(0)\nconst showTip = ref(false)\nconst tipTitle = ref('')\nconst tipContent = ref('')\n\nlet vr360: InstanceType<typeof Vr360>\n\nconst spacesConfig = ref<SpaceConfig[]>([\n  {\n    id: 'spaceA',\n    tips: [\n      {\n        id: '1',\n        position: {x: 0, y: -10, z: 40},\n        content: {\n          title: '豪华跑车',\n          text: '比奥迪还贵的豪华跑车'\n        }\n      },\n      {\n        id: '2',\n        textureUrl: textures.hotpot,\n        targetSpaceId: 'spaceB',\n        position: {x: -10, y: -4, z: 40},\n        content: {\n          title: '去客厅',\n          text: '一起去尊贵的客厅吧'\n        }\n      }\n    ],\n    cubeSpaceTextureUrls: textures.firstHouseDoor\n  },\n  {\n    id: 'spaceB',\n    tips: [\n      {\n        id: '3',\n        position: {x: -2, y: -25, z: 40},\n        content: {\n          title: '香奈儿垃圾桶',\n          text: '里面装着主人不用的奢侈品'\n        }\n      },\n      {\n        id: '4',\n        position: {x: -20, y: 0, z: 40},\n        content: {\n          title: '宇宙牌冰箱',\n          text: '装着超级多零食'\n        }\n      },\n      {\n        id: '5',\n        textureUrl: textures.hotpot,\n        targetSpaceId: 'spaceA',\n        position: {\n          x: -8,\n          y: 0,\n          z: -40\n        },\n        content: {\n          title: '去门口',\n          text: '一起去门口吧'\n        }\n      }\n    ],\n    cubeSpaceTextureUrls: textures.firstHouseLivingRoom\n  }\n])\n\nfunction handleSwitchSpace(space: SpaceConfig) {\n  vr360.switchSpace(space.id)\n}\n\nonMounted(() => {\n  vr360 = new Vr360({\n    container: containerRef.value!,\n    tipContainer: tipRef.value!,\n    spacesConfig: spacesConfig.value\n  })\n\n  vr360.controls.autoRotate = true\n\n  vr360.render()\n\n  vr360.listenResize()\n\n  vr360.on('showTip', e => {\n    vr360!.controls.autoRotate = false\n    const {top, left, tip} = e\n    showTip.value = true\n    tipLeft.value = left\n    tipTop.value = top\n    tipTitle.value = tip.content.title\n    tipContent.value = tip.content.text\n  })\n\n  vr360.on('hideTip', () => {\n    vr360!.controls.autoRotate = true\n    showTip.value = false\n  })\n})\n</script>\n<style>\n.demoA {\n  position: relative;\n  z-index: 0;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  max-width: calc(100vw - 3rem);\n  height: 500px;\n  margin-bottom: 2rem;\n  overflow: hidden;\n  background-color: var(--c-bg);\n  border: 1px solid var(--c-border);\n  border-radius: 8px;\n}\n\n.demoA-tip {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: -1;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  width: 240px;\n  height: 60px;\n  padding: 4px;\n  color: #fff;\n  cursor: pointer;\n  background-color: rgba(0, 0, 0, 0.5);\n  border-radius: 4px;\n}\n\n.demoA-tip-title {\n  font-weight: bold;\n}\n\n.demoA-container {\n  width: 100%;\n  height: 100%;\n}\n\n.demoA-bottom-bar {\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: 100px;\n}\n\n.demoA-bottom-card {\n  width: 140px;\n  height: 70px;\n  margin-left: 1rem;\n  cursor: pointer;\n  background-repeat: no-repeat;\n  background-size: cover;\n  border-radius: 4px;\n}\n</style>\n"
  },
  {
    "path": "packages/doc/.vuepress/components/NpmBadge.vue",
    "content": "<script setup lang=\"ts\">\nimport {computed} from 'vue'\n\nconst props = defineProps({\n  package: {\n    type: String,\n    required: true\n  },\n  distTag: {\n    type: String,\n    required: false,\n    default: 'next'\n  }\n})\n\nconst badgeLink = computed(() => `https://www.npmjs.com/package/${props.package}`)\nconst badgeLabel = computed(() => {\n  if (props.distTag) {\n    return `${props.package}@${props.distTag}`\n  }\n\n  return props.package\n})\nconst badgeImg = computed(\n  () => `https://badgen.net/npm/v/${props.package}/${props.distTag}?label=${encodeURIComponent(badgeLabel.value)}`\n)\n</script>\n\n<template>\n  <a class=\"npm-badge\" :href=\"badgeLink\" :title=\"package\" target=\"_blank\" rel=\"noopener noreferrer\">\n    <img :src=\"badgeImg\" :alt=\"package\" />\n  </a>\n</template>\n\n<style scoped>\n.npm-badge {\n  margin-right: 0.5rem;\n}\n</style>\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/index.ts",
    "content": "export * as navbar from './navbar'\nexport * as sidebar from './sidebar'\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/navbar/en.ts",
    "content": "import type {NavbarConfig} from '@vuepress/theme-default'\nimport {version} from '../../utils/common'\n\nexport const en: NavbarConfig = [\n  {\n    text: 'Guide',\n    link: '/en/guide/'\n  },\n  {\n    text: `v${version}`,\n    children: [\n      {\n        text: 'Releases',\n        link: 'https://github.com/nicepkg/vr360/releases'\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/navbar/index.ts",
    "content": "export * from './en'\nexport * from './zh'\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/navbar/zh.ts",
    "content": "import type {NavbarConfig} from '@vuepress/theme-default'\nimport {version} from '../../utils/common'\n\nexport const zh: NavbarConfig = [\n  {\n    text: '指南',\n    link: '/guide/'\n  },\n  {\n    text: '库列表',\n    children: [\n      {\n        text: 'vr360-core',\n        link: '/libs/vr360-core/README.md'\n      }\n    ]\n  },\n  {\n    text: `v${version}`,\n    children: [\n      {\n        text: '更新日志',\n        link: 'https://github.com/nicepkg/vr360/releases'\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/sidebar/en.ts",
    "content": "import type {SidebarConfig} from '@vuepress/theme-default'\n\nexport const en: SidebarConfig = {\n  '/en/guide/': [\n    {\n      text: 'Guide',\n      children: ['/en/guide/README.md', '/en/guide/questions.md']\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/sidebar/index.ts",
    "content": "export * from './en'\nexport * from './zh'\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/sidebar/zh.ts",
    "content": "import type {SidebarConfig} from '@vuepress/theme-default'\n\nexport const zh: SidebarConfig = {\n  '/guide/': [\n    {\n      text: '指南',\n      children: ['/guide/README.md', '/guide/questions.md']\n    }\n  ],\n  '/libs/vr360-core/': [\n    {\n      text: 'vr360-core',\n      children: [\n        '/libs/vr360-core/README.md',\n        '/libs/vr360-core/properties.md',\n        '/libs/vr360-core/methods.md',\n        '/libs/vr360-core/events.md',\n        '/libs/vr360-core/example.md'\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/demo.css",
    "content": "* {\n  box-sizing: border-box;\n  padding: 0;\n  margin: 0;\n}\n\n.demo {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100vw;\n  height: 100vh;\n  overflow: hidden;\n  background-color: #fff;\n}\n\n.demo-tip {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: -1;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  width: 240px;\n  height: 60px;\n  padding: 4px;\n  color: #fff;\n  cursor: pointer;\n  visibility: hidden;\n  background-color: rgba(0, 0, 0, 0.5);\n  border-radius: 4px;\n}\n\n.demo-tip-title {\n  font-weight: bold;\n}\n\n.demo-container {\n  width: 100%;\n  height: 100%;\n}\n\n.demo-bottom-bar {\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: 100px;\n}\n\n.demo-bottom-card {\n  width: 140px;\n  height: 70px;\n  margin-left: 1rem;\n  cursor: pointer;\n  background-repeat: no-repeat;\n  background-size: cover;\n  border-radius: 4px;\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/html.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Html App</title>\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\"\n    />\n\n    <!-- 引入 threejs -->\n    <script src=\"https://unpkg.com/three@0.145.0/build/three.min.js\"></script>\n\n    <!-- 引入 vr360-core -->\n    <script src=\"https://unpkg.com/@nicepkg/vr360-core@^0.3.1/dist/index.min.umd.js\"></script>\n\n    <!-- 引入自己的 css -->\n    <link href=\"./demo.css\" rel=\"stylesheet\" />\n  </head>\n  <body>\n    <div class=\"demo\">\n      <!-- 提示 -->\n      <div class=\"demo-tip\">\n\n        <!-- 提示标题 -->\n        <div class=\"demo-tip-title\"></div>\n\n        <!-- 提示内容 -->\n        <div class=\"demo-tip-content\"></div>\n      </div>\n\n      <!-- 360全景容器 -->\n      <div class=\"demo-container\"></div>\n\n      <!-- 底部切换场景 -->\n      <div class=\"demo-bottom-bar\"></div>\n    </div>\n    <script>\n      const {Vr360} = Vr360Core // 原生使用时，所有的导出都在 window.Vr360Core 里\n      const container = document.querySelector('.demo-container') // 360全景容器\n      const tip = document.querySelector('.demo-tip') // 提示容器\n      const tipTitle = document.querySelector('.demo-tip-title') // 提示标题 el\n      const tipContent = document.querySelector('.demo-tip-content') // 提示内容 el\n      const bottomBar = document.querySelector('.demo-bottom-bar') // 底部切换场景容器\n\n      // 全景空间配置\n      const spacesConfig = [\n        {\n          id: 'spaceA', // 空间 id，用于切换空间，必须唯一\n          tips: [\n            // 提示，可选\n            {\n              id: '1', // 提示 id，用于缓存，在当前 tips 数组里要唯一，必须\n              position: {x: 0, y: -10, z: 40}, // 提示位置，必须\n              content: {\n                // 提示内容，在 showTip 事件会暴露，要包含什么你自己决定。\n                title: '豪华跑车',\n                text: '比奥迪还贵的豪华跑车'\n              }\n            },\n            {\n              id: '2',\n              // 自定义提示图标贴图，可选\n              textureUrl:\n                'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',\n              targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id，可选\n              position: {x: -10, y: -4, z: 40},\n              content: {\n                title: '去客厅',\n                text: '一起去尊贵的客厅吧'\n              }\n            }\n          ],\n          cubeSpaceTextureUrls: {\n            // 立方体贴图，分别为：背侧、下侧、前侧、左侧、右侧、上侧，必须\n            back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',\n            down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',\n            front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',\n            left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',\n            right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',\n            up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'\n          }\n        },\n        {\n          id: 'spaceB',\n          tips: [\n            {\n              id: '3',\n              position: {x: -2, y: -25, z: 40},\n              content: {\n                title: '香奈儿垃圾桶',\n                text: '里面装着主人不用的奢侈品'\n              }\n            },\n            {\n              id: '4',\n              position: {x: -20, y: 0, z: 40},\n              content: {\n                title: '宇宙牌冰箱',\n                text: '装着超级多零食'\n              }\n            },\n            {\n              id: '5',\n              textureUrl:\n                'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',\n              targetSpaceId: 'spaceA',\n              position: {\n                x: -8,\n                y: 0,\n                z: -40\n              },\n              content: {\n                title: '去门口',\n                text: '一起去门口吧'\n              }\n            }\n          ],\n          cubeSpaceTextureUrls: {\n            back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',\n            down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',\n            front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',\n            left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',\n            right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',\n            up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'\n          }\n        }\n      ]\n\n      // 初始化全景实例\n      const vr360 = new Vr360({\n        container, // 全景挂载容器\n        tipContainer: tip, // 提示挂载容器\n        spacesConfig // 全景配置\n      })\n\n      // 设置全景自动旋转\n      vr360.controls.autoRotate = true\n\n      // 开始渲染全景\n      vr360.render()\n\n      // 实时监听页面尺寸变化，更新全景尺寸\n      vr360.listenResize()\n\n      // 当需要显示提示时\n      vr360.on('showTip', e => {\n        // 停止旋转场景\n        vr360.controls.autoRotate = false\n\n        // 设置提示内容和提示容器位置\n        const {top, left} = e\n        Object.assign(tip.style, {\n          transform: `translate(${left}px, ${top + 50}px)`,\n          zIndex: 99,\n          visibility: 'visible'\n        })\n        tip.style.t\n        tipTitle.innerText = e.tip.content.title\n        tipContent.innerText = e.tip.content.text\n      })\n\n      // 当需要隐藏提示时\n      vr360.on('hideTip', () => {\n        // 重新开启旋转场景\n        vr360.controls.autoRotate = true\n\n        // 隐藏提示容器\n        tip.style.zIndex = -1\n        tip.style.visibility = 'hidden'\n      })\n\n      // 页面卸载时注销全景实例\n      window.addEventListener('unload', () => {\n        vr360.destroy()\n      })\n\n      // 切换全景空间\n      function handleSwitchSpace(space) {\n        vr360.switchSpace(space.id)\n      }\n\n      bottomBar.append(\n        ...spacesConfig.map(space => {\n          const card = document.createElement('div')\n          card.className = 'demo-bottom-card'\n          Object.assign(card.style, {\n            backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`\n          })\n          card.addEventListener('click', () => {\n            handleSwitchSpace(space)\n          })\n          return card\n        })\n      )\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/react.tsx",
    "content": "import React, {useEffect, useState, useRef} from 'react'\nimport {Vr360} from '@nicepkg/vr360-core'\nimport type {SpaceConfig} from '@nicepkg/vr360-core'\nimport './demo.css' // 引入自己的样式\n\nfunction Example() {\n  const containerRef = useRef<HTMLDivElement>(null) // 全景容器\n  const tipRef = useRef<HTMLDivElement>(null) // 提示容器\n  const [tipLeft, setTipLeft] = useState(0) // 提示容器的 left 或 translateX 值\n  const [tipTop, setTipTop] = useState(0) // 提示容器的 top 或 translateY 值\n  const [showTip, setShowTip] = useState(false) // 是否显示提示容器\n  const [tipTitle, setTipTitle] = useState('') // 提示容器的标题\n  const [tipContent, setTipContent] = useState('') // 提示容器的内容\n  const [vr360, setVr360] = useState<InstanceType<typeof Vr360>>() // 全景实例\n\n  // 全景空间配置\n  const spacesConfig: SpaceConfig[] = [\n    {\n      id: 'spaceA', // 空间 id，用于切换空间，必须唯一\n      tips: [\n        // 提示，可选\n        {\n          id: '1', // 提示 id，用于缓存，在当前 tips 数组里要唯一，必须\n          position: {x: 0, y: -10, z: 40}, // 提示位置，必须\n          content: {\n            // 提示内容，在 showTip 事件会暴露，要包含什么你自己决定。\n            title: '豪华跑车',\n            text: '比奥迪还贵的豪华跑车'\n          }\n        },\n        {\n          id: '2',\n          // 自定义提示图标贴图，可选\n          textureUrl:\n            'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',\n          targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id，可选\n          position: {x: -10, y: -4, z: 40},\n          content: {\n            title: '去客厅',\n            text: '一起去尊贵的客厅吧'\n          }\n        }\n      ],\n      cubeSpaceTextureUrls: {\n        // 立方体贴图，分别为：背侧、下侧、前侧、左侧、右侧、上侧，必须\n        back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',\n        down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',\n        front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',\n        left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',\n        right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',\n        up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'\n      }\n    },\n    {\n      id: 'spaceB',\n      tips: [\n        {\n          id: '3',\n          position: {x: -2, y: -25, z: 40},\n          content: {\n            title: '香奈儿垃圾桶',\n            text: '里面装着主人不用的奢侈品'\n          }\n        },\n        {\n          id: '4',\n          position: {x: -20, y: 0, z: 40},\n          content: {\n            title: '宇宙牌冰箱',\n            text: '装着超级多零食'\n          }\n        },\n        {\n          id: '5',\n          textureUrl:\n            'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',\n          targetSpaceId: 'spaceA',\n          position: {\n            x: -8,\n            y: 0,\n            z: -40\n          },\n          content: {\n            title: '去门口',\n            text: '一起去门口吧'\n          }\n        }\n      ],\n      cubeSpaceTextureUrls: {\n        back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',\n        down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',\n        front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',\n        left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',\n        right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',\n        up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'\n      }\n    }\n  ]\n\n  useEffect(() => {\n    // 初始化全景实例\n    setVr360(\n      new Vr360({\n        container: containerRef.current!,\n        tipContainer: tipRef.current!,\n        spacesConfig\n      })\n    )\n\n    return () => {\n      // 页面卸载时注销全景实例\n      vr360?.destroy?.()\n    }\n  }, [])\n\n  useEffect(() => {\n    if (vr360) {\n      // 设置全景自动旋转\n      vr360.controls.autoRotate = true\n\n      // 开始渲染全景\n      vr360.render()\n\n      // 实时监听页面尺寸变化，更新全景尺寸\n      vr360.listenResize()\n\n      // 当需要显示提示时\n      vr360.on('showTip', e => {\n        // 停止旋转场景\n        vr360!.controls.autoRotate = false\n\n        // 设置提示内容和提示容器位置\n        const {top, left, tip} = e\n        setShowTip(true)\n        setTipLeft(left)\n        setTipTop(top)\n        setTipTitle(tip.content.title)\n        setTipContent(tip.content.text)\n      })\n\n      // 当需要隐藏提示时\n      vr360.on('hideTip', () => {\n        // 重新开启旋转场景\n        vr360!.controls.autoRotate = true\n\n        // 隐藏提示容器\n        setShowTip(false)\n      })\n    }\n  }, [vr360])\n\n  // 切换全景空间\n  function handleSwitchSpace(space: SpaceConfig) {\n    vr360?.switchSpace?.(space.id)\n  }\n\n  return (\n    <div className=\"demo\">\n      {/* 提示 */}\n      <div\n        ref={tipRef}\n        className=\"demo-tip\"\n        style={{\n          transform: `translate(${tipLeft}px, ${tipTop + 50}px)`,\n          zIndex: showTip ? 99 : -1,\n          visibility: showTip ? 'visible' : 'hidden'\n        }}\n      >\n        {/* 提示标题 */}\n        <div className=\"demo-tip-title\">{tipTitle}</div>\n\n        {/* 提示内容 */}\n        <div className=\"demo-tip-content\">{tipContent}</div>\n      </div>\n\n      {/* 360全景容器 */}\n      <div ref={containerRef} className=\"demo-container\"></div>\n\n      {/* 底部切换场景 */}\n      <div className=\"demo-bottom-bar\">\n        {spacesConfig.map(space => (\n          <div\n            key={space.id}\n            className=\"demo-bottom-card\"\n            onClick={() => handleSwitchSpace(space)}\n            style={{\n              backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`\n            }}\n          ></div>\n        ))}\n      </div>\n    </div>\n  )\n}\n\nexport default Example\n"
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/vue2.vue",
    "content": "<template>\n  <div class=\"demo\">\n    <!-- 提示 -->\n    <div\n      ref=\"tipRef\"\n      class=\"demo-tip\"\n      :style=\"{\n        transform: `translate(${tip.left}px, ${tip.top + 50}px)`,\n        zIndex: tip.show ? 99 : -1,\n        visibility: tip.show ? 'visible' : 'hidden'\n      }\"\n    >\n      <!-- 提示标题 -->\n      <div class=\"demo-tip-title\">{{ tip.title }}</div>\n\n      <!-- 提示内容 -->\n      <div class=\"demo-tip-content\">{{ tip.content }}</div>\n    </div>\n\n    <!-- 360全景容器 -->\n    <div ref=\"containerRef\" class=\"demo-container\"></div>\n\n    <!-- 底部切换场景 -->\n    <div class=\"demo-bottom-bar\">\n      <div\n        v-for=\"space in spacesConfig\"\n        :key=\"space.id\"\n        class=\"demo-bottom-card\"\n        @click=\"handleSwitchSpace(space)\"\n        :style=\"{\n          backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`\n        }\"\n      ></div>\n    </div>\n  </div>\n</template>\n<script lang=\"ts\">\nimport {Vr360} from '@nicepkg/vr360-core'\nimport type {SpaceConfig} from '@nicepkg/vr360-core'\n\nexport default {\n  data() {\n    // 全景空间配置\n    const spacesConfig: SpaceConfig[] = [\n      {\n        id: 'spaceA', // 空间 id，用于切换空间，必须唯一\n        tips: [\n          // 提示，可选\n          {\n            id: '1', // 提示 id，用于缓存，在当前 tips 数组里要唯一，必须\n            position: {x: 0, y: -10, z: 40}, // 提示位置，必须\n            content: {\n              // 提示内容，在 showTip 事件会暴露，要包含什么你自己决定。\n              title: '豪华跑车',\n              text: '比奥迪还贵的豪华跑车'\n            }\n          },\n          {\n            id: '2',\n            // 自定义提示图标贴图，可选\n            textureUrl:\n              'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',\n            targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id，可选\n            position: {x: -10, y: -4, z: 40},\n            content: {\n              title: '去客厅',\n              text: '一起去尊贵的客厅吧'\n            }\n          }\n        ],\n        cubeSpaceTextureUrls: {\n          // 立方体贴图，分别为：背侧、下侧、前侧、左侧、右侧、上侧，必须\n          back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',\n          down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',\n          front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',\n          left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',\n          right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',\n          up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'\n        }\n      },\n      {\n        id: 'spaceB',\n        tips: [\n          {\n            id: '3',\n            position: {x: -2, y: -25, z: 40},\n            content: {\n              title: '香奈儿垃圾桶',\n              text: '里面装着主人不用的奢侈品'\n            }\n          },\n          {\n            id: '4',\n            position: {x: -20, y: 0, z: 40},\n            content: {\n              title: '宇宙牌冰箱',\n              text: '装着超级多零食'\n            }\n          },\n          {\n            id: '5',\n            textureUrl:\n              'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',\n            targetSpaceId: 'spaceA',\n            position: {\n              x: -8,\n              y: 0,\n              z: -40\n            },\n            content: {\n              title: '去门口',\n              text: '一起去门口吧'\n            }\n          }\n        ],\n        cubeSpaceTextureUrls: {\n          back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',\n          down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',\n          front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',\n          left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',\n          right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',\n          up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'\n        }\n      }\n    ]\n\n    return {\n      vr360: null as InstanceType<typeof Vr360> | null, // 全景实例\n      spacesConfig, // 全景空间配置\n      tip: {\n        // 提示属性\n        top: 0, // 提示容器的 top 或 translateY 值\n        left: 0, // 提示容器的 left 或 translateX 值\n        title: '', // 提示标题\n        content: '', // 提示内容\n        show: false // 是否显示提示\n      }\n    }\n  },\n  mounted() {\n    // 初始化全景实例\n    this.vr360 = new Vr360({\n      container: this.$refs.containerRef!,\n      tipContainer: this.$refs.tipRef!,\n      spacesConfig: this.spacesConfig\n    })\n\n    // 设置全景自动旋转\n    this.vr360.controls.autoRotate = true\n\n    // 开始渲染全景\n    this.vr360.render()\n\n    // 实时监听页面尺寸变化，更新全景尺寸\n    this.vr360.listenResize()\n\n    // 当需要显示提示时\n    this.vr360.on('showTip', e => {\n      // 停止旋转场景\n      this.vr360!.controls.autoRotate = false\n\n      // 设置提示内容和提示容器位置\n      const {top, left, tip} = e\n      this.tip = {\n        top,\n        left,\n        title: tip.content.title,\n        content: tip.content.text,\n        show: true\n      }\n    })\n\n    // 当需要隐藏提示时\n    this.vr360.on('hideTip', () => {\n      // 重新开启旋转场景\n      this.vr360!.controls.autoRotate = true\n\n      // 隐藏提示容器\n      this.tip.show = false\n    })\n  },\n  destroy() {\n    // 页面卸载时注销全景实例\n    this.vr360?.destroy?.()\n  },\n  methods: {\n    // 切换全景空间\n    handleSwitchSpace(space: SpaceConfig) {\n      this.vr360?.switchSpace?.(space.id)\n    }\n  }\n}\n</script>\n<style>\n/* 引入自己的样式 */\n@import './demo.css';\n</style>\n"
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/vue3.vue",
    "content": "<template>\n  <div class=\"demo\">\n    <!-- 提示 -->\n    <div\n      ref=\"tipRef\"\n      class=\"demo-tip\"\n      :style=\"{\n        transform: `translate(${tipLeft}px, ${tipTop + 50}px)`,\n        zIndex: showTip ? 99 : -1,\n        visibility: showTip ? 'visible' : 'hidden'\n      }\"\n    >\n      <!-- 提示标题 -->\n      <div class=\"demo-tip-title\">{{ tipTitle }}</div>\n\n      <!-- 提示内容 -->\n      <div class=\"demo-tip-content\">{{ tipContent }}</div>\n    </div>\n\n    <!-- 360全景容器 -->\n    <div ref=\"containerRef\" class=\"demo-container\"></div>\n\n    <!-- 底部切换场景 -->\n    <div class=\"demo-bottom-bar\">\n      <div\n        v-for=\"space in spacesConfig\"\n        :key=\"space.id\"\n        class=\"demo-bottom-card\"\n        @click=\"handleSwitchSpace(space)\"\n        :style=\"{\n          backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`\n        }\"\n      ></div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {onMounted, onUnmounted, ref} from 'vue'\nimport {Vr360} from '@nicepkg/vr360-core'\nimport type {SpaceConfig} from '@nicepkg/vr360-core'\n\nconst containerRef = ref<HTMLElement>() // 全景容器\n\nconst tipRef = ref<HTMLElement>() // 提示容器\nconst tipLeft = ref(0) // 提示容器的 left 或 translateX 值\nconst tipTop = ref(0) // 提示容器的 top 或 translateY 值\nconst showTip = ref(false) // 是否显示提示容器\nconst tipTitle = ref('') // 提示容器的标题\nconst tipContent = ref('') // 提示容器的内容\n\nlet vr360: InstanceType<typeof Vr360> // 全景实例\n\n// 全景空间配置\nconst spacesConfig: SpaceConfig[] = [\n  {\n    id: 'spaceA', // 空间 id，用于切换空间，必须唯一\n    tips: [\n      // 提示，可选\n      {\n        id: '1', // 提示 id，用于缓存，在当前 tips 数组里要唯一，必须\n        position: {x: 0, y: -10, z: 40}, // 提示位置，必须\n        content: {\n          // 提示内容，在 showTip 事件会暴露，要包含什么你自己决定。\n          title: '豪华跑车',\n          text: '比奥迪还贵的豪华跑车'\n        }\n      },\n      {\n        id: '2',\n        // 自定义提示图标贴图，可选\n        textureUrl: 'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',\n        targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id，可选\n        position: {x: -10, y: -4, z: 40},\n        content: {\n          title: '去客厅',\n          text: '一起去尊贵的客厅吧'\n        }\n      }\n    ],\n    cubeSpaceTextureUrls: {\n      // 立方体贴图，分别为：背侧、下侧、前侧、左侧、右侧、上侧，必须\n      back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',\n      down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',\n      front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',\n      left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',\n      right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',\n      up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'\n    }\n  },\n  {\n    id: 'spaceB',\n    tips: [\n      {\n        id: '3',\n        position: {x: -2, y: -25, z: 40},\n        content: {\n          title: '香奈儿垃圾桶',\n          text: '里面装着主人不用的奢侈品'\n        }\n      },\n      {\n        id: '4',\n        position: {x: -20, y: 0, z: 40},\n        content: {\n          title: '宇宙牌冰箱',\n          text: '装着超级多零食'\n        }\n      },\n      {\n        id: '5',\n        textureUrl: 'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',\n        targetSpaceId: 'spaceA',\n        position: {\n          x: -8,\n          y: 0,\n          z: -40\n        },\n        content: {\n          title: '去门口',\n          text: '一起去门口吧'\n        }\n      }\n    ],\n    cubeSpaceTextureUrls: {\n      back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',\n      down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',\n      front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',\n      left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',\n      right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',\n      up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'\n    }\n  }\n]\n\nonMounted(() => {\n  // 初始化全景实例\n  vr360 = new Vr360({\n    container: containerRef.value!,\n    tipContainer: tipRef.value!,\n    spacesConfig\n  })\n\n  // 设置全景自动旋转\n  vr360.controls.autoRotate = true\n\n  // 开始渲染全景\n  vr360.render()\n\n  // 实时监听页面尺寸变化，更新全景尺寸\n  vr360.listenResize()\n\n  // 当需要显示提示时\n  vr360.on('showTip', e => {\n    // 停止旋转场景\n    vr360!.controls.autoRotate = false\n\n    // 设置提示内容和提示容器位置\n    const {top, left, tip} = e\n    showTip.value = true\n    tipLeft.value = left\n    tipTop.value = top\n    tipTitle.value = tip.content.title\n    tipContent.value = tip.content.text\n  })\n\n  // 当需要隐藏提示时\n  vr360.on('hideTip', () => {\n    // 重新开启旋转场景\n    vr360!.controls.autoRotate = true\n\n    // 隐藏提示容器\n    showTip.value = false\n  })\n})\n\n// 切换全景空间\nfunction handleSwitchSpace(space: SpaceConfig) {\n  vr360.switchSpace(space.id)\n}\n\nonUnmounted(() => {\n  // 页面卸载时注销全景实例\n  vr360?.destroy?.()\n})\n</script>\n<style>\n/* 引入自己的样式 */\n@import './demo.css';\n</style>\n"
  },
  {
    "path": "packages/doc/.vuepress/index.build.html",
    "content": "<!DOCTYPE html>\n<html lang=\"{{ lang }}\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <meta name=\"generator\" content=\"VuePress {{ version }}\">\n    <!--vuepress-ssr-head-->\n    <!--vuepress-ssr-resources-->\n    <!--vuepress-ssr-styles-->\n  </head>\n  <body>\n    <div id=\"vr-teleport\"></div>\n    <div id=\"app\"><!--vuepress-ssr-app--></div>\n    <!--vuepress-ssr-scripts-->\n  </body>\n</html>\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/CodeDemo.props.ts",
    "content": "import type {Project} from '@stackblitz/sdk'\nimport type {ExtractPropTypes, PropType} from 'vue'\n\nexport const getProps = () => ({\n  mode: {\n    type: String,\n    default: 'node'\n  },\n  project: {\n    type: Object as PropType<Project>\n  },\n  previewOnly: {\n    type: Boolean,\n    default: false\n  },\n  clickToLoad: {\n    type: Boolean,\n    default: false\n  },\n  openFile: {\n    type: String,\n    default: 'src/main.ts'\n  }\n})\n\nexport type CodeDemoProps = Partial<ExtractPropTypes<ReturnType<typeof getProps>>>\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/CodeDemo.vue",
    "content": "<template>\n  <div\n    ref=\"codeDemoRef\"\n    class=\"code-demo\"\n    :style=\"{\n      border: isFullScreen ? 'none' : '1px solid var(--c-border)',\n      borderRadius: isFullScreen ? '0px' : '8px'\n    }\"\n  >\n    <div class=\"code-demo-top\">\n      <div class=\"code-demo-title\">{{ project?.title ?? DEFAULT_EDITOR_TITLE }}</div>\n      <div class=\"code-demo-fullscreen\" @click=\"toggleFullScreen\">\n        <ExitFullscreenIcon v-if=\"isFullScreen\" />\n        <FullscreenIcon v-else />\n      </div>\n    </div>\n    <div ref=\"codeDemoEditorRef\" class=\"code-demo-editor\"></div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {onMounted, ref} from 'vue'\nimport sdk from '@stackblitz/sdk'\nimport type {ProjectFiles, Project} from '@stackblitz/sdk'\nimport {getProps} from './CodeDemo.props'\nimport {DEFAULT_EDITOR_TITLE} from './constant'\nimport {ExitFullscreenIcon, FullscreenIcon} from './Icons'\n\nconst props = defineProps(getProps())\n\nconst codeDemoRef = ref<HTMLElement>()\nconst codeDemoEditorRef = ref<HTMLIFrameElement>()\nconst isFullScreen = ref(false)\n\nconst toggleFullScreen = () => {\n  if (!codeDemoRef.value) return\n  if (isFullScreen.value) {\n    document.exitFullscreen()\n    isFullScreen.value = false\n  } else {\n    codeDemoRef.value.requestFullscreen()\n    isFullScreen.value = true\n  }\n}\n\nonMounted(async () => {\n  if (!codeDemoEditorRef.value || !props.project) return\n\n  codeDemoEditorRef.value.setAttribute('frameborder', '0')\n\n  try {\n    // eslint-disable-next-line no-restricted-globals\n    const defaultFiles: ProjectFiles = (await self.loadCodeDemoModeDefaultFiles?.(props.mode)) ?? {}\n    const finalProject = {\n      ...props.project,\n      files: {\n        ...defaultFiles,\n        ...props.project.files\n      }\n    } as Project\n\n    await sdk.embedProject(codeDemoEditorRef.value, finalProject, {\n      forceEmbedLayout: true,\n      openFile: props.openFile,\n      hideNavigation: true,\n      height: '100%',\n      view: props.previewOnly ? 'preview' : undefined,\n      clickToLoad: props.clickToLoad\n    })\n  } catch (error) {\n    console.error('CodeDemoError:', error)\n  }\n})\n</script>\n<style>\n.code-demo {\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  background-color: #2e3138;\n}\n.code-demo-top {\n  box-sizing: border-box;\n  display: flex;\n  flex-shrink: 0;\n  align-items: center;\n  width: 100%;\n  height: 2rem;\n  padding: 0 0.5em;\n  overflow: hidden;\n  font-size: 14px;\n  color: #ccc;\n}\n.code-demo-title {\n  flex: 1;\n}\n.code-demo-fullscreen {\n  flex-shrink: 0;\n  font-size: 20px;\n  cursor: pointer;\n}\n.code-demo-editor {\n  box-sizing: border-box;\n  display: block;\n  height: 100%;\n  min-height: 500px;\n  border: none;\n}\n</style>\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/Icons.tsx",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {VNode} from 'vue'\nimport {defineComponent, h} from 'vue'\n\nconst createComponent = (name: string, render: (props: Record<string, any>) => VNode) =>\n  defineComponent({\n    name,\n    render() {\n      const props = {...this.$props, ...this.$attrs}\n      return render(props)\n    }\n  })\n\nexport const FullscreenIcon = createComponent('FullscreenIcon', props => {\n  //\n  //   <svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" {...props}>\n  //     <path d=\"M5 5h5v2H7v3H5V5m9 0h5v5h-2V7h-3V5m3 9h2v5h-5v-2h3v-3m-7 3v2H5v-5h2v3h3z\" fill=\"currentColor\"></path>\n  //   </svg>\n  //\n\n  return h(\n    'svg',\n    {\n      width: '1em',\n      height: '1em',\n      viewBox: '0 0 24 24',\n      ...props\n    },\n    [\n      h('path', {\n        d: 'M5 5h5v2H7v3H5V5m9 0h5v5h-2V7h-3V5m3 9h2v5h-5v-2h3v-3m-7 3v2H5v-5h2v3h3z',\n        fill: 'currentColor'\n      })\n    ]\n  )\n})\n\nexport const ExitFullscreenIcon = createComponent('ExitFullscreenIcon', props => {\n  // <svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" {...props}>\n  //   <path d=\"M14 14h5v2h-3v3h-2v-5m-9 0h5v5H8v-3H5v-2m3-9h2v5H5V8h3V5m11 3v2h-5V5h2v3h3z\" fill=\"currentColor\"></path>\n  // </svg>\n\n  return h(\n    'svg',\n    {\n      width: '1em',\n      height: '1em',\n      viewBox: '0 0 24 24',\n      ...props\n    },\n    [\n      h('path', {\n        d: 'M14 14h5v2h-3v3h-2v-5m-9 0h5v5H8v-3H5v-2m3-9h2v5H5V8h3V5m11 3v2h-5V5h2v3h3z',\n        fill: 'currentColor'\n      })\n    ]\n  )\n})\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/clientConfigFile.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {CodeDemoProps} from './CodeDemo.props'\nimport {defineClientConfig} from '@vuepress/client'\nimport type {ConcreteComponent, DefineComponent} from 'vue'\nimport {h, resolveComponent} from 'vue'\nimport * as base64 from 'js-base64'\n\nexport default defineClientConfig({\n  async enhance({app}) {\n    // eslint-disable-next-line @typescript-eslint/ban-types\n    let CodeDemo: DefineComponent<{}, {}, any> | undefined\n\n    if (!__VUEPRESS_SSR__) {\n      // eslint-disable-next-line unicorn/no-await-expression-member\n      CodeDemo = (await import('./CodeDemo.vue')).default\n\n      // set global css\n      const styleEl = document.createElement('style')\n      styleEl.innerHTML = `\n      .code-demo-wrapper {\n        margin-top: 1rem;\n        margin-bottom: 1rem;\n      }\n      .code-demo-wrapper + .code-demo-wrapper {\n        margin-top: 3rem;\n      }\n    `\n      document.head.append(styleEl)\n    }\n    if (CodeDemo) {\n      app.component('CodeDemo', CodeDemo)\n    }\n\n    // wrap the component with default options\n    app.component('CodeDemoClient', (defaultProps: CodeDemoProps) => {\n      if (!CodeDemo) return null\n      const ClientOnly = resolveComponent('ClientOnly')\n      // eslint-disable-next-line no-restricted-globals\n      const codeDemoOptions = self.loadCodeDemoOptions?.(defaultProps) ?? defaultProps\n\n      return h(ClientOnly, {}, () =>\n        h(CodeDemo as ConcreteComponent, {\n          ...codeDemoOptions\n        })\n      )\n    })\n\n    app.config.globalProperties.base64 = base64 // decode the options in sandbox component\n  }\n})\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/constant.ts",
    "content": "// 用于 Stackblitz 示例的默认标题（未覆盖时）\nexport const DEFAULT_EDITOR_TITLE = 'Vr360 Example'\n\n// 用于 Stackblitz 示例的默认描述（未覆盖时）\nexport const DEFAULT_EDITOR_DESCRIPTION = ''\n\n// 用于所有 @nicepkg/vr360-*包的默认包版本。\nexport const DEFAULT_VR360_VERSION = '^6.0.0'\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/global.d.ts",
    "content": "import type {ProjectFiles} from '@stackblitz/sdk'\nimport type {CodeDemoProps} from './CodeDemo.props'\n\ndeclare global {\n  // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n  interface Window {\n    loadCodeDemoOptions?: (preOptions: CodeDemoProps) => CodeDemoProps\n    loadCodeDemoModeDefaultFiles?: (mode: string) => ProjectFiles | Promise<ProjectFiles>\n  }\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/index.ts",
    "content": "export * from './plugin'\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/plugin.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport type {Plugin} from '@vuepress/core'\nimport markdownItContainer from 'markdown-it-container'\nimport type {MarkdownEnv} from '@vuepress/markdown'\nimport type * as Token from 'markdown-it/lib/token'\nimport type * as Renderer from 'markdown-it/lib/renderer'\nimport type {CodeDemoProps} from './CodeDemo.props'\nimport type {ProjectFiles} from '@stackblitz/sdk'\nimport * as base64 from 'js-base64'\nimport {DEFAULT_EDITOR_DESCRIPTION, DEFAULT_EDITOR_TITLE} from './constant'\n\nconst pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\n\nexport type MarkdownItRenderFn = (\n  tokens: Token[],\n  index: number,\n  options: any,\n  env: MarkdownEnv,\n  self: Renderer\n) => string\n\nexport type CodeDemoOptions = {\n  codeDemoMark?: string\n  clickToLoad?: boolean\n  resolvePath?: (filePath: string) => string\n}\n\nexport type RenderPlaceFunction = (description: string, codeBlockTokens?: Token[]) => string\n\nexport const codeDemoPlugin = (options: CodeDemoOptions = {}): Plugin => {\n  const {codeDemoMark = 'demo', clickToLoad = false, resolvePath = p => p} = options\n\n  // const START_TYPE = `container_${codeDemoMark}_open`\n  const END_TYPE = `container_${codeDemoMark}_close`\n  const CODE_BLOCK_TYPE = 'fence'\n  const START_NESTING = 1\n  // const END_NESTING = -1\n\n  const renderBefore: RenderPlaceFunction = (des, codeBlockTokens) => {\n    const files: ProjectFiles = {}\n    let openFile: string | undefined\n    const [mode, title, description] = des.split('--').map(s => s.trim())\n\n    if (codeBlockTokens && codeBlockTokens.length > 0) {\n      codeBlockTokens.map(token => {\n        const [lang, filename] = token.info.split(/\\s+/)\n\n        // 找出代码内容需要插入的部分 /*# 路径 #*/\n        const importPathReg = /\\/\\*#\\s*(.+)\\s*#\\*\\//g\n\n        // 代码内容\n        const codeContent = token.content.replace(importPathReg, (match, p: string) => {\n          const currentImportPath = pathResolve(resolvePath(p.trim()))\n          if (!fs.existsSync(currentImportPath)) return match\n          const importCode = fs.readFileSync(currentImportPath, 'utf8')\n          return importCode || match\n        })\n\n        // 最终文件名\n        const _filename = filename.endsWith(`.${lang}`) ? filename : `${filename}.${lang}`\n\n        if (!openFile) openFile = _filename\n\n        files[_filename] = codeContent\n      })\n    }\n\n    const options: CodeDemoProps = {\n      mode,\n      openFile,\n      clickToLoad,\n      project: {\n        template: 'node',\n        title: title || DEFAULT_EDITOR_TITLE,\n        description: description || DEFAULT_EDITOR_DESCRIPTION,\n        files\n      }\n    }\n    const optionsBase64 = base64.encode(JSON.stringify(options))\n\n    return `<div class=\"code-demo-wrapper ${codeDemoMark}\"><code-demo-client v-bind=\"JSON.parse(base64.decode('${optionsBase64}'))\">\\n`\n  }\n\n  const renderAfter: RenderPlaceFunction = () => '</code-demo-client></div>\\n'\n\n  const descriptionsStack: string[] = []\n  const render: MarkdownItRenderFn = (tokens, index, opts, env) => {\n    const token = tokens[index]\n    if (token.nesting === START_NESTING) {\n      // `before` tag\n\n      // resolve description (title)\n      const description = token.info.trim().slice(codeDemoMark.length).trim() || DEFAULT_EDITOR_TITLE\n      descriptionsStack.push(description)\n\n      let i = index + 1\n      const codeBlockTokens: Token[] = []\n      while (tokens[i].type !== END_TYPE) {\n        const nextToken = tokens[i]\n        if (nextToken.type === CODE_BLOCK_TYPE) {\n          codeBlockTokens.push(nextToken)\n        }\n        i++\n      }\n\n      return renderBefore(description, codeBlockTokens)\n    } else {\n      // `after` tag\n\n      // pop the description from stack\n      // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n      const description = descriptionsStack.pop() || ''\n\n      // render\n      return renderAfter(description)\n    }\n  }\n\n  const plugin: Plugin = {\n    name: 'vuepress-plugin-code-demo',\n    clientConfigFile: pathResolve('./clientConfigFile.ts').replace(/\\\\/g, '/'),\n    extendsMarkdown: md => {\n      md.use(markdownItContainer, codeDemoMark, {render})\n    }\n  }\n\n  return plugin\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/index.ts",
    "content": "import type {PluginConfig} from 'vuepress'\nimport {registerComponentsPlugin} from '@vuepress/plugin-register-components'\nimport {googleAnalyticsPlugin} from '@vuepress/plugin-google-analytics'\nimport {shikiPlugin} from '@vuepress/plugin-shiki'\nimport {path} from '@vuepress/utils'\nimport {isProd} from '../utils/common'\nimport {codeDemoPlugin} from './code-demo'\n\nconst pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\n\nconst vuepressPlugins: PluginConfig = [\n  // for google search\n  googleAnalyticsPlugin({\n    id: ''\n  }),\n  // auto register globally components\n  registerComponentsPlugin({\n    componentsDir: pathResolve('../components')\n  }),\n  codeDemoPlugin({\n    clickToLoad: true,\n    resolvePath: str => str.replace(/^@/, pathResolve('../'))\n  })\n]\n\nif (isProd) {\n  vuepressPlugins.push(\n    // code highlighting\n    shikiPlugin({\n      theme: 'dark-plus'\n    })\n  )\n}\n\nexport const plugins = vuepressPlugins\n"
  },
  {
    "path": "packages/doc/.vuepress/public/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"/images/icons/mstile-150x150.png\"/>\n            <TileColor>#0c4dc4</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/demo.css.txt",
    "content": "* {\n  box-sizing: border-box;\n  padding: 0;\n  margin: 0;\n}\n\n.demo {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100vw;\n  height: 100vh;\n  overflow: hidden;\n}\n\n.demo-tip {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: -1;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  width: 240px;\n  height: 60px;\n  padding: 4px;\n  color: #fff;\n  cursor: pointer;\n  background-color: rgba(0, 0, 0, 0.5);\n  border-radius: 4px;\n}\n\n.demo-tip-title {\n  font-weight: bold;\n}\n\n.demo-container {\n  width: 100%;\n  height: 100%;\n}\n\n.demo-bottom-bar {\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: 100px;\n}\n\n.demo-bottom-card {\n  width: 140px;\n  height: 70px;\n  margin-left: 1rem;\n  cursor: pointer;\n  background-repeat: no-repeat;\n  background-size: cover;\n  border-radius: 4px;\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/html/package.json.txt",
    "content": "{\n  \"name\": \"playground-html\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"http-server ./ -p 8000 -o\"\n  },\n  \"devDependencies\": {\n    \"http-server\": \"^14.1.1\"\n  }\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/index.html.txt",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>React App</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n  <script>\n    (function () {\n      // ios safari 禁止缩放\n      document.addEventListener('touchmove', function(event) {\n        event = event.originalEvent || event;\n        if(event.scale !== undefined && event.scale !== 1) {\n          event.preventDefault();\n        }\n      }, false);\n    })()\n  </script>\n</head>\n<body class=\"font-sans\">\n  <noscript>\n    <strong>We're sorry but web-cli doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n  </noscript>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"/src/main.tsx\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/package.json.txt",
    "content": "{\n  \"name\": \"playground-react\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@nicepkg/vr360-core\": \"latest\",\n    \"clsx\": \"^1.2.1\",\n    \"react\": \"17.0.2\",\n    \"react-dom\": \"17.0.2\",\n    \"three\": \"^0.145.0\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^18.7.23\",\n    \"@types/react\": \"18.0.21\",\n    \"@types/react-dom\": \"18.0.6\",\n    \"@types/three\": \"^0.144.0\",\n    \"@vitejs/plugin-react\": \"^2.1.0\",\n    \"typescript\": \"4.7.4\",\n    \"vite\": \"3.0.9\"\n  }\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/src/main.tsx.txt",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport Example from './Example'\n\nReactDOM.render(\n  <React.StrictMode>\n    <Example />\n  </React.StrictMode>,\n  document.querySelector('#app')\n)\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/tsconfig.json.txt",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"target\": \"esnext\",\n    \"composite\": true,\n    \"useDefineForClassFields\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"jsx\": \"react-jsx\",\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"lib\": [\"esnext\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"types\": [\"vite/client\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src/**/*\", \"types/**/*\", \"*.ts\", \"*.js\"]\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/vite.config.ts.txt",
    "content": "import {defineConfig} from 'vite'\nimport path from 'node:path'\nimport viteReact from '@vitejs/plugin-react'\n\nconst pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      '@/': `${pathResolve('./src')}/`\n    }\n  },\n\n  plugins: [\n    viteReact()\n  ]\n})\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/index.html.txt",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>Vue2 App</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n  <script>\n    (function () {\n      // ios safari 禁止缩放\n      document.addEventListener('touchmove', function(event) {\n        event = event.originalEvent || event;\n        if(event.scale !== undefined && event.scale !== 1) {\n          event.preventDefault();\n        }\n      }, false);\n    })()\n  </script>\n</head>\n<body class=\"font-sans\">\n  <noscript>\n    <strong>We're sorry but web-cli doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n  </noscript>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"/src/main.ts\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/package.json.txt",
    "content": "{\n  \"name\": \"playground-vue2\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@nicepkg/vr360-core\": \"latest\",\n    \"three\": \"^0.145.0\",\n    \"vue\": \"2.6.14\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"18.7.23\",\n    \"@types/three\": \"^0.144.0\",\n    \"@vue/runtime-dom\": \"latest\",\n    \"typescript\": \"4.7.4\",\n    \"vite\": \"^2.9.9\",\n    \"vite-plugin-vue2\": \"^2.0.2\",\n    \"vue-template-compiler\": \"2.6.14\"\n  }\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/src/App.vue.txt",
    "content": "<template>\n  <Example></Example>\n</template>\n\n<script lang=\"ts\">\nimport Example from './Example.vue'\n\nexport default {\n  name: 'App',\n  components: {\n    Example\n  }\n}\n</script>\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/src/main.ts.txt",
    "content": "import Vue from 'vue'\nimport App from './App.vue'\n\nVue.config.productionTip = false\n\nnew Vue({\n  render: h => h(App)\n}).$mount('#app')\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/tsconfig.json.txt",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"target\": \"esnext\",\n    \"composite\": true,\n    \"useDefineForClassFields\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"lib\": [\"esnext\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"types\": [\"vite/client\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src/**/*\", \"types/**/*\", \"*.ts\", \"*.js\"]\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/types/module.d.ts.txt",
    "content": "declare module '*.vue' {\n  import type {VueConstructor} from 'vue'\n  const component: VueConstructor\n  export default component\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/vite.config.ts.txt",
    "content": "import path from 'node:path'\nimport {defineConfig} from 'vite'\nimport {createVuePlugin} from 'vite-plugin-vue2'\n\nconst pathResolve = (...args: string[]) => path.resolve(__dirname, ...args)\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      '@/': `${pathResolve('./src')}/`\n    }\n  },\n\n  plugins: [\n    createVuePlugin({\n      jsx: true,\n      jsxOptions: {\n        compositionAPI: true\n      }\n    })\n  ]\n})\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/index.html.txt",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>Vue3 App</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n  <script>\n    (function () {\n      // ios safari 禁止缩放\n      document.addEventListener('touchmove', function(event) {\n        event = event.originalEvent || event;\n        if(event.scale !== undefined && event.scale !== 1) {\n          event.preventDefault();\n        }\n      }, false);\n    })()\n  </script>\n</head>\n<body class=\"font-sans\">\n  <noscript>\n    <strong>We're sorry but web-cli doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n  </noscript>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"/src/main.ts\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/package.json.txt",
    "content": "{\n  \"name\": \"playground-vue3\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@nicepkg/vr360-core\": \"latest\",\n    \"three\": \"^0.145.0\",\n    \"vue\": \"^3.2.40\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"18.7.23\",\n    \"@types/three\": \"^0.144.0\",\n    \"@vitejs/plugin-vue\": \"^3.1.0\",\n    \"@vitejs/plugin-vue-jsx\": \"^2.0.1\",\n    \"typescript\": \"4.7.4\",\n    \"vite\": \"3.0.9\"\n  }\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/src/App.vue.txt",
    "content": "<template>\n  <Example></Example>\n</template>\n\n<script setup lang=\"ts\">\nimport Example from './Example.vue'\n</script>\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/src/main.ts.txt",
    "content": "import {createApp} from 'vue'\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n\napp.config.globalProperties.productionTip = false\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/tsconfig.json.txt",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"target\": \"esnext\",\n    \"composite\": true,\n    \"useDefineForClassFields\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"lib\": [\"esnext\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"types\": [\"vite/client\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src/**/*\", \"types/**/*\", \"*.ts\", \"*.js\"]\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/types/module.d.ts.txt",
    "content": "declare module '*.vue' {\n  import type {DefineComponent} from 'vue-demi'\n  const Component: DefineComponent<{}, {}, any>\n  export default Component\n}\n\n"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/vite.config.ts.txt",
    "content": "import path from 'node:path'\nimport {defineConfig} from 'vite'\nimport Vue from '@vitejs/plugin-vue'\nimport vueJsx from '@vitejs/plugin-vue-jsx'\n\nconst pathResolve = (...args: string[]) => path.resolve(__dirname, ...args)\n\nexport default defineConfig({\n  resolve: {\n    alias: {\n      '@/': `${pathResolve('./src')}/`\n    }\n  },\n\n  plugins: [\n    Vue({\n      include: [/\\.vue$/]\n    }),\n    vueJsx()\n  ]\n})\n"
  },
  {
    "path": "packages/doc/.vuepress/public/manifest.webmanifest",
    "content": "{\n  \"name\": \"vr360\",\n  \"short_name\": \"vr360\",\n  \"description\": \"快速实现你的全景开发需求\",\n  \"theme_color\": \"#ffffff\",\n  \"background_color\": \"#ffffff\",\n  \"display\": \"standalone\",\n  \"scope\": \"./\",\n  \"start_url\": \"./\",\n  \"icons\": [\n    {\n      \"src\": \"/images/icons/icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/images/icons/icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/images/icons/icon-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/images/icons/icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/images/icons/icon-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/images/icons/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/images/icons/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/images/icons/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/styles/index.scss",
    "content": ":root {\n  // brand colors\n  --c-brand: #0c4dc4;\n  --c-brand-light: #0b3788;\n\n  scroll-behavior: smooth;\n}\n\nhtml.dark {\n  // brand colors\n  --c-brand: #1fa8f8;\n  --c-brand-light: #3bb3f8;\n}\n\n.sidebar-item.collapsible {\n  cursor: pointer;\n}\n\n.site-name.can-hide {\n  display: none;\n}\n\nbody {\n  position: relative;\n  width: 100vw;\n  min-height: 100vh;\n  overflow-x: hidden;\n  overflow-y: auto;\n  background-color: var(--c-bg);\n}\n\n#app {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 99;\n  min-width: 100vw;\n  min-height: 100vh;\n}\n\n.code-group {\n  width: 100%;\n  max-width: calc(100vw - 3rem);\n}\n\n.theme-default-content div[class*='language-'] {\n  width: calc(100% + 3rem);\n  max-width: 100vw;\n}\n\n.theme-default-content .code-group div[class*='language-'] {\n  width: auto;\n  max-width: auto;\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/theme/components/Home.vue",
    "content": "<script setup lang=\"ts\">\nimport HomeContent from '@vuepress/theme-default/components/HomeContent.vue'\nimport HomeFeatures from './HomeFeatures.vue'\nimport HomeFooter from '@vuepress/theme-default/components/HomeFooter.vue'\nimport HomeHero from '@vuepress/theme-default/components/HomeHero.vue'\nimport HomeVrBg from './HomeVrBg.vue'\n</script>\n\n<template>\n  <main class=\"home\">\n    <HomeHero />\n    <HomeFeatures />\n    <HomeContent />\n    <HomeFooter />\n    <ClientOnly>\n      <teleport to=\"#vr-teleport\">\n        <HomeVrBg :show-mask=\"true\"></HomeVrBg>\n      </teleport>\n    </ClientOnly>\n  </main>\n</template>\n"
  },
  {
    "path": "packages/doc/.vuepress/theme/components/HomeFeatures.vue",
    "content": "<script setup lang=\"ts\">\nimport {usePageFrontmatter} from '@vuepress/client'\nimport {isArray} from '@vuepress/shared'\nimport type {DefaultThemeHomePageFrontmatter} from '@vuepress/theme-default'\nimport {computed} from 'vue'\nimport AutoLink from '@vuepress/theme-default/components/AutoLink.vue'\n\ntype HomePageFormatter = {\n  features?: {\n    title: string\n    details: string\n    link: string\n    disabled: boolean\n  }[]\n} & Omit<DefaultThemeHomePageFrontmatter, 'features'>\n\n// eslint-disable-next-line react-hooks/rules-of-hooks\nconst frontmatter = usePageFrontmatter<HomePageFormatter>()\nconst features = computed(() => {\n  if (isArray(frontmatter.value.features)) {\n    return frontmatter.value.features\n  }\n  return []\n})\n</script>\n\n<template>\n  <div v-if=\"features.length > 0\" class=\"features\">\n    <div\n      v-for=\"feature in features\"\n      :key=\"feature.title\"\n      class=\"feature\"\n      :style=\"{\n        cursor: feature.disabled ? 'not-allowed' : 'pointer',\n        opacity: feature.disabled ? 0.8 : 1\n      }\"\n    >\n      <h4>\n        <AutoLink\n          v-if=\"!feature.disabled\"\n          :item=\"{\n            text: feature.title,\n            link: feature.link || './'\n          }\"\n        >\n          {{ feature.title }}\n        </AutoLink>\n        <div v-else>\n          {{ feature.title }}\n        </div>\n      </h4>\n\n      <p>{{ feature.details }}</p>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/doc/.vuepress/theme/components/HomeVrBg.vue",
    "content": "<template>\n  <div class=\"vr-container-wrapper\" :style=\"{height: height + 'px'}\">\n    <div\n      ref=\"tipRef\"\n      class=\"vr-tip\"\n      :style=\"{\n        left: tipLeft + 'px',\n        top: tipTop + 50 + 'px',\n        zIndex: showTip ? 99 : -1\n      }\"\n    >\n      <div class=\"vr-tip-title\">{{ tipTitle }}</div>\n      <div class=\"vr-tip-content\">{{ tipContent }}</div>\n    </div>\n    <div ref=\"containerRef\" class=\"vr-container\"></div>\n\n    <div v-if=\"showMask\" class=\"vr-mask\"></div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\n/* eslint-disable import/no-named-as-default-member */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\nimport {onMounted, ref, watch, nextTick, onUnmounted} from 'vue'\nimport type {SpaceConfig} from '@nicepkg/vr360-core'\nimport {Vr360} from '@nicepkg/vr360-core'\nimport {useElementSize} from '@vueuse/core'\nimport textures from '../../../../../textures.json'\n\ndefineProps({\n  showMask: {\n    type: Boolean,\n    default: false\n  }\n})\n\nconst appRef = ref<HTMLElement>()\n\nconst containerRef = ref<HTMLElement>()\n\nconst tipRef = ref<HTMLElement>()\nconst tipLeft = ref(0)\nconst tipTop = ref(0)\nconst showTip = ref(false)\nconst tipTitle = ref('')\nconst tipContent = ref('')\n\nlet vr360: InstanceType<typeof Vr360>\n\nconst spacesConfig = ref<SpaceConfig[]>([\n  {\n    id: 'spaceA',\n    camera: {\n      position: {\n        x: 0,\n        y: 0,\n        z: 0\n      }\n    },\n    tips: [],\n    cubeSpaceTextureUrls: textures.beijing\n  }\n])\n\nonMounted(() => {\n  vr360 = new Vr360({\n    container: containerRef.value!,\n    tipContainer: tipRef.value!,\n    spacesConfig: spacesConfig.value\n  })\n\n  vr360.controls.autoRotate = true\n  vr360.controls.autoRotateSpeed = 0.2\n\n  vr360.render()\n\n  vr360.listenResize()\n\n  vr360.on('showTip', e => {\n    vr360!.controls.autoRotate = false\n    const {top, left, tip} = e\n    showTip.value = true\n    tipLeft.value = left\n    tipTop.value = top\n    tipTitle.value = tip.content.title\n    tipContent.value = tip.content.text\n  })\n\n  vr360.on('hideTip', () => {\n    vr360!.controls.autoRotate = true\n    showTip.value = false\n  })\n\n  appRef.value = document.querySelector<HTMLElement>('#app')!\n})\n\nonUnmounted(() => {\n  vr360.destroy()\n})\n\n// eslint-disable-next-line react-hooks/rules-of-hooks\nconst {height} = useElementSize(appRef)\n\nwatch(height, async () => {\n  await nextTick()\n  vr360.updateContainerSize()\n})\n</script>\n<style scoped>\n.vr-container-wrapper {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 1;\n  display: flex;\n  flex-direction: column;\n  width: 100vw;\n  min-height: 100vh;\n  overflow: hidden;\n}\n\n.vr-tip {\n  position: absolute;\n  z-index: 99;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  width: 240px;\n  height: 60px;\n  padding: 1rem;\n  color: #fff;\n  cursor: pointer;\n  background-color: rgba(0, 0, 0, 0.5);\n}\n\n.vr-tip-title {\n  font-weight: bold;\n}\n\n.vr-container {\n  width: 100%;\n  height: 100%;\n\n  /* 修复 edge canvas底部空白，我也不知道为什么，但是有效 */\n  border: 0.5px solid;\n}\n\n.vr-container :deep(canvas) {\n  background-color: #fff;\n}\n\n.vr-mask {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 1;\n  width: 100%;\n  height: 100%;\n  background: linear-gradient(\n    to bottom,\n    rgba(255, 255, 255, 1) 60%,\n    rgba(255, 255, 255, 0.8) 90%,\n    rgba(255, 255, 255, 0.2)\n  );\n}\n\nhtml.dark .vr-mask {\n  background: linear-gradient(to bottom, rgba(34, 39, 46, 1) 60%, rgba(34, 39, 46, 0.8) 90%, rgba(34, 39, 46, 0.2));\n}\n</style>\n"
  },
  {
    "path": "packages/doc/.vuepress/theme/index.ts",
    "content": "import type {Theme} from '@vuepress/core'\nimport {defaultTheme} from '@vuepress/theme-default'\nimport {path} from '@vuepress/utils'\nimport {navbar, sidebar} from '../configs'\nimport {isProd} from '../utils/common'\n\nconst pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\n\nexport const localTheme = (): Theme => {\n  return {\n    name: 'vuepress-theme-local',\n    extends: defaultTheme({\n      logo: '/images/logo.png',\n      logoDark: '/images/logo-dark.png',\n\n      repo: 'nicepkg/vr360',\n\n      docsBranch: 'master',\n\n      docsDir: 'packages/doc',\n\n      // theme-level locales config\n      locales: {\n        /**\n         * Chinese locale config\n         */\n        '/': {\n          // navbar\n          navbar: navbar.zh,\n          selectLanguageName: '简体中文',\n          selectLanguageText: '选择语言',\n          selectLanguageAriaLabel: '选择语言',\n\n          // sidebar\n          sidebar: sidebar.zh,\n\n          // page meta\n          editLinkText: '在 GitHub 上编辑此页',\n          lastUpdatedText: '上次更新',\n          contributorsText: '贡献者',\n\n          // custom containers\n          tip: '提示',\n          warning: '注意',\n          danger: '警告',\n\n          // 404 page\n          notFound: ['这里什么都没有', '我们怎么到这来了？', '这是一个 404 页面', '看起来我们进入了错误的链接'],\n          backToHome: '返回首页',\n\n          // a11y\n          openInNewWindow: '在新窗口打开',\n          toggleColorMode: '切换主题',\n          toggleSidebar: '切换侧边栏'\n        },\n\n        /**\n         * English locale config\n         *\n         * As the default locale of @vuepress/theme-default is English,\n         * we don't need to set all of the locale fields\n         */\n        '/en/': {\n          // navbar\n          navbar: navbar.en,\n\n          // sidebar\n          sidebar: sidebar.en,\n\n          // page meta\n          editLinkText: 'Edit this page on GitHub'\n        }\n      },\n\n      themePlugins: {\n        // only enable git plugin in production mode\n        git: isProd,\n        // use shiki plugin in production mode instead\n        prismjs: !isProd,\n        // disable the @vuepress/plugin-nprogress plugin to fix the bug of `Cannot set properties of undefined (setting 'NProgress')`\n        nprogress: false\n      }\n    }),\n    alias: {\n      '@theme/Home.vue': pathResolve('components/Home.vue'),\n      '@theme/HomeFeatures.vue': pathResolve('components/HomeFeatures.vue')\n    }\n  }\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/types/module.d.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\ndeclare module '*.vue' {\n  import type {DefineComponent} from 'vue'\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  const Component: DefineComponent<{}, {}, any>\n  export default Component\n}\n"
  },
  {
    "path": "packages/doc/.vuepress/utils/common.ts",
    "content": "import rootPkg from '../../../../package.json'\n\nexport const version = rootPkg.version as string\nexport const isProd = process.env.NODE_ENV === 'production'\n"
  },
  {
    "path": "packages/doc/CHANGELOG.md",
    "content": "# 0.3.0 (2022-10-08)\n\n### Bug Fixes\n\n- **@nicepkg/vr360-core:** 修复移动端的提示移动 bug ([7090a98](https://github.com/nicepkg/vr360/commit/7090a9805bc472240de6ad4467bbadc14fd6151d))\n\n# 0.3.0 (2022-10-02)\n\n## 0.2.1 (2022-10-02)\n\n# 0.2.0 (2022-10-02)\n\n### Bug Fixes\n\n- 修复 vr360-core tips 在滑动时的 bug ([e179ebc](https://github.com/nicepkg/vr360/commit/e179ebc2697314bc455320eecf8beb6182a53ded))\n\n### Features\n\n- 暴露更多的事件，设置默认自动旋转 ([4e21c53](https://github.com/nicepkg/vr360/commit/4e21c53ac945532020a7fbbfa46644294c33b49d))\n- 初始化项目 ([06f39d1](https://github.com/nicepkg/vr360/commit/06f39d141004a1d0b1a125ad598298baf15ffee8))\n- 添加标签提示和空间切换功能 ([6aa67d3](https://github.com/nicepkg/vr360/commit/6aa67d39113b06d05036ecc1d66ab1d70a2f4cf5))\n- 添加如视 vr 导入支持 ([5a9eb5d](https://github.com/nicepkg/vr360/commit/5a9eb5d7a33d092be8cea5565490f268376d2f79))\n- 文档添加示例插件、core 的纹理 top、bottom 改为 up 和 down ([70ccb2c](https://github.com/nicepkg/vr360/commit/70ccb2c40a06079ffb3cf50b27f986ab19b1f7db))\n- 修改 package.json 说明 ([e5e4ad0](https://github.com/nicepkg/vr360/commit/e5e4ad04b1c7a9cddff3af6f73d438fd1de85b25))\n- 优化代码 ([4519c4a](https://github.com/nicepkg/vr360/commit/4519c4a0fb230bb62bed49f97e0824c8977be3ca))\n- 优化 sdk，添加纹理缓存预加载 ([9dd57e7](https://github.com/nicepkg/vr360/commit/9dd57e71b8f5a38ecc9901395a9b189481172edb))\n- 重写核心，添加最小化更新配置支持 ([289091b](https://github.com/nicepkg/vr360/commit/289091b13dfe0495b44ae9e5353b78d2712e9762))\n"
  },
  {
    "path": "packages/doc/README.md",
    "content": "---\nhome: true\ntitle: 首页\nheroImage: /images/logo.png\nheroImageDark: /images/logo-dark.png\nheroText: null\ntagline: 快速实现你的全景开发需求\nactions:\n  - text: 快速上手\n    link: /guide/\n    type: primary\n  - text: Github\n    link: https://github.com/nicepkg/vr360\n    type: secondary\nfeatures:\n  - title: vr360-core\n    details: json 驱动的全景浏览库，设计框架无关性，可用于任何框架，如vue/react/angular/svelte/solidjs...\n    link: '/libs/vr360-core/README.md'\n\n  - title: vr360-ui\n    details: （开发中...）提供一个现成的 vr360 viewer 和 editor 组件，基于 stencil 构建的 web component。\n    disabled: true\n    link: ''\n\n  - title: vr360-ui-vue2\n    details: （开发中...）vr360-ui 的 vue2 二次封装版本，开箱即用。\n    disabled: true\n    link: ''\n\n  - title: vr360-ui-vue3\n    details: （开发中...）vr360-ui 的 vue3 二次封装版本，开箱即用。\n    disabled: true\n    link: ''\n\n  - title: vr360-ui-react\n    details: （开发中...）vr360-ui 的 react 二次封装版本，开箱即用。\n    disabled: true\n    link: ''\n\n  - title:\n    details:\nfooter: MIT License | Copyright © 2022-present YangJinMing\n---\n"
  },
  {
    "path": "packages/doc/bundler.config.ts",
    "content": "import {path} from '@vuepress/utils'\nimport type {ViteBundlerOptions} from 'vuepress'\n\nconst pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\nexport const bundlerConfig = {\n  viteOptions: {\n    resolve: {\n      alias: [\n        {\n          find: /@(?=\\/)/,\n          replacement: pathResolve('./.vuepress')\n        }\n      ]\n    },\n    build: {\n      chunkSizeWarningLimit: Number.POSITIVE_INFINITY\n    }\n  }\n} as ViteBundlerOptions\n"
  },
  {
    "path": "packages/doc/guide/README.md",
    "content": "# 介绍\n\nVr360 是一个基于 threejs 能让你快速实现业务全景需求的库，比如全景看房、全景街景、全景景点。\n\n它的核心被设计为框架无关性，可以用 json 配置的方式快速实现常见全景需求。\n\n后续还会提供高度封装的 viewer 和 editor 等组件，很适合懒人，会提供 vue2/3 、 react 、web component 版本。\n\njson 驱动视图的特性是好维护，你甚至可以不用接触 threejs。写出来的代码拉条哈士奇过来也能维护。\n\n## 库列表\n\n#### 核心 [vr360-core](/libs/vr360-core/)\n\nvr360 核心库，提供了基础的全景浏览功能，你可以用它来快速实现你的全景需求。\n\n#### web 组件 [vr360-ui](/libs/vr360-ui/)\n\n提供一个现成的 vr360 viewer 和 editor 组件，基于 stencil 构建的 web component。（开发中...）\n\n#### vue2 组件 [vr360-ui-vue2](/libs/vr360-ui-vue2/)\n\nvr360-ui 的 vue2 二次封装版本，开箱即用。（开发中...）\n\n#### vue3 组件 [vr360-ui-vue3](/libs/vr360-ui-vue2/)\n\nvr360-ui 的 vue2 二次封装版本，开箱即用。（开发中...）\n\n#### react 组件 [vr360-ui-react](/libs/vr360-ui-react/)\n\nvr360-ui 的 react 二次封装版本，开箱即用。（开发中...）\n"
  },
  {
    "path": "packages/doc/guide/questions.md",
    "content": "# 常见问题\n\n## vr360-ui 为什么没有 angular 版\n\nvr360-ui 是基于 stencil.js 开发，适配 angular 是很容易的，但是由于本人没怎么用过 angular，适配完也不知道有没有 bug，所以欢迎提 pr 。\n"
  },
  {
    "path": "packages/doc/libs/vr360-core/README.md",
    "content": "# 介绍\n\n`@nicepkg/vr360-core` 是一个基于 [threejs](https://github.com/mrdoob/three.js/) 的全景库，非常适合用来做全景看房、全景街景、全景景点等业务需求。\n\n它支持 json 配置驱动视图，你可以用 json 配置的方式快速实现常见全景需求。\n\n## 特性\n\n- json 驱动视图，快速实现全景业务需求\n- 高性能，支持自动找出 json 变更的部分，最小化更新到 3d 全景里，不会整个场景重建。\n- 支持增删改提示点，hover 提示点的弹窗支持自定义定制，支持自定义提示点的贴图。\n- 内置场景切换穿梭动画\n- 暴露 scene、camera、renderer 等 [threejs](https://github.com/mrdoob/three.js/) 对象，方便你更高的定制化\n- 基于事件驱动的数据交互，设计框架无关性\n- 完整的 ts 支持\n\n## 安装\n\n:::: code-group\n::: code-group-item npm\n\n```bash\nnpm i @nicepkg/vr360-core threejs\n```\n\n:::\n::: code-group-item yarn\n\n```bash\nyarn add @nicepkg/vr360-core threejs\n```\n\n:::\n::: code-group-item pnpm\n\n```bash\npnpm add @nicepkg/vr360-core threejs\n```\n\n:::\n::::\n\n## 浏览器(CDN)\n\n:::: code-group\n::: code-group-item Unpkg\n\n```html:no-v-pre\n<!-- 引入 threejs -->\n<script src=\"https://unpkg.com/three@0.145.0/build/three.min.js\"></script>\n\n<!-- 引入 vr360-core -->\n<script src=\"https://unpkg.com/@nicepkg/vr360-core@{{version}}\"></script>\n\n<script>\n// 使用\nconst {Vr360} = Vr360Core // 原生使用时，所有的导出都在 window.Vr360Core 里\n\n// 初始化全景实例\nconst vr360 = new Vr360({...})\n\n// 开始渲染全景\nvr360.render()\n</script>\n```\n\n:::\n::: code-group-item JsDelivr\n\n```html:no-v-pre\n<!-- 引入 threejs -->\n<script src=\"https://cdn.jsdelivr.net/npm/three@0.145.0/build/three.min.js\"></script>\n\n<!-- 引入 vr360-core -->\n<script src=\"https://cdn.jsdelivr.net/npm/@nicepkg/vr360-core@{{version}}\"></script>\n\n<script>\n// 使用\nconst {Vr360} = Vr360Core // 原生使用时，所有的导出都在 window.Vr360Core 里\n\n// 初始化全景实例\nconst vr360 = new Vr360({...})\n\n// 开始渲染全景\nvr360.render()\n</script>\n```\n\n:::\n::::\n\n## 为什么\n\n如果产品叫你实现一个类似全景看房的功能，而且时间紧，你会怎么做？\n\n学 threejs 知识，然后徒手撸出全景，然后自己抽象出一个 json 配置驱动和后端数据联调对接？\n\n可能还要自己写一个全景编辑器？\n\n可以但没必要，你完全可以把时间省下来做些有意义的事，你只要用 `@nicepkg/vr360-core` 就可以了。\n\n简单点的业务需求仅需 json 配置就能实现，重要是这种代码拉条哈士奇也能维护。\n\n编辑器还在开发中，复杂度不会消失，只会转移，当你岁月静好，一定是作者在为你负重前行。\n\n## 使用\n\n请阅读我们的 [属性文档](./properties.md)、 [函数文档](./methods.md)、[事件文档](./events.md)，了解如何使用 `@nicepkg/vr360-core`。\n\n除了下面的简易示例，你也可以浏览我们的 [完整项目示例](./example.md)\n\n#### 简易示例效果\n\n<br>\n<DemoA></DemoA>\n\n#### 简易示例代码（cv 专用）\n\n<br>\n<CodeGroup>\n  <CodeGroupItem title=\"vue2\" active>\n\n@[code vue](@/examples/firstHouse/vue2.vue)\n\n  </CodeGroupItem>\n  <CodeGroupItem title=\"vue3\">\n\n@[code vue](@/examples/firstHouse/vue3.vue)\n\n  </CodeGroupItem>\n  <CodeGroupItem title=\"react\">\n\n@[code tsx](@/examples/firstHouse/react.tsx)\n\n  </CodeGroupItem>\n  <CodeGroupItem title=\"原生\">\n\n@[code html](@/examples/firstHouse/html.html)\n\n  </CodeGroupItem>\n  <CodeGroupItem title=\"demo.css\">\n\n@[code css](@/examples/firstHouse/demo.css)\n\n  </CodeGroupItem>\n</CodeGroup>\n"
  },
  {
    "path": "packages/doc/libs/vr360-core/events.md",
    "content": "# 事件\n\n## 显示提示\n\n#### 介绍\n\n```ts\ninterface Vr360Events {\n  /**\n   * 触发提示时的回调\n   */\n  showTip: (e: {\n    /**\n     * 提示配置信息\n     */\n    tip: Tip\n\n    /**\n     * 相对于 container 的 left\n     */\n    left: number\n\n    /**\n     * 相对于 container 的 top\n     */\n    top: number\n  }) => void\n}\n```\n\n点击查看 [tip](./methods.md#tip-空间配置里的提示配置) 类型\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\nvr360.on('showTip', (e) => {\n  console.log('触发提示时的回调', e)\n})\n```\n\n## 隐藏提示\n\n#### 介绍\n\n```ts\ninterface Vr360Events {\n  /**\n   * 隐藏提示时的回调\n   */\n  hideTip: (e: {\n    /**\n     * 提示配置信息\n     */\n    tip: Tip\n  }) => void\n}\n```\n\n点击查看 [tip](./methods.md#tip-空间配置里的提示配置) 类型\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\nvr360.on('hideTip', (e) => {\n  console.log('隐藏提示时的回调', e)\n})\n```\n\n## 点击提示图标\n\n#### 介绍\n\n```ts\ninterface Vr360Events {\n  /**\n   * 点击提示时的回调\n   */\n  clickTip: (e: {\n    /**\n     * 提示配置信息\n     */\n    tip: Tip\n  }) => void\n}\n```\n\n点击查看 [tip](./methods.md#tip-空间配置里的提示配置) 类型\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\nvr360.on('clickTip', (e) => {\n  console.log('点击提示时的回调', e)\n})\n```\n\n## 每一帧更新\n\n#### 介绍\n\n```ts\ninterface Vr360Events {\n  /**\n   * 每帧更新回调\n   */\n  update: () => void\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\nvr360.on('update', () => {\n  console.log('每帧更新回调')\n})\n```\n\n## 切换全景空间完成\n\n#### 介绍\n\n```ts\ninterface Vr360Events {\n  /**\n   * 完成跳转空间时的回调\n   */\n  afterSwitchSpace: (e: {\n    /**\n     * 跳转的目标空间配置\n     */\n    spaceConfig: SpaceConfig\n  }) => void\n}\n```\n\n点击查看 [SpaceConfig](./methods.md#spaceconfig-构造参数里的空间配置) 类型\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\nvr360.on('afterSwitchSpace', (e) => {\n  console.log('完成跳转空间时的回调', e)\n})\n```\n"
  },
  {
    "path": "packages/doc/libs/vr360-core/example.md",
    "content": "# 示例\n\n## 效果\n\n<br/>\n<DemoA></DemoA>\n\n### vue2 实现\n\n::: demo vue2 -- vue2 示例代码 -- 这里是示例代码的描述\n\n```vue src/Example.vue\n/*# @/examples/firstHouse/vue2.vue #*/\n```\n\n:::\n\n### vue3 实现\n\n::: demo vue3 -- vue3 示例代码 -- 这里是示例代码的描述\n\n```vue src/Example.vue\n/*# @/examples/firstHouse/vue3.vue #*/\n```\n\n:::\n\n### react 实现\n\n::: demo react -- react 示例代码 -- 这里是示例代码的描述\n\n```tsx src/Example.tsx\n/*# @/examples/firstHouse/react.tsx #*/\n```\n\n:::\n\n### 原生实现\n\n::: demo html -- html 示例代码 -- 这里是示例代码的描述\n\n```html index.html\n/*# @/examples/firstHouse/html.html #*/\n```\n\n:::\n"
  },
  {
    "path": "packages/doc/libs/vr360-core/methods.md",
    "content": "# 方法\n\n## 构造器\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * @param options 构造参数配置\n   * @returns Vr360 实例\n   */\n  constructor(options: Vr360Options) {\n    ...\n  }\n}\n```\n\n点击查看 [Vr360Options](#vr360options-构造参数) 类型\n\n#### 使用\n\n```ts\nimport {Vr360, Vr360Options} from '@nicepkg/vr360-core'\n\nconst vr360Options: Vr360Options = {\n  container: document.querySelector('#container'),\n  tipContainer: document.querySelector('#tip-container'),\n  spacesConfig: []\n}\nconst vr360 = new Vr360(vr360Options)\n```\n\n#### 相关类型\n\n##### Vr360Options 构造参数\n\n```ts\n/**\n *  vr360 的构造参数\n */\ninterface Vr360Options {\n  /**\n   * 容器\n   */\n  container: HTMLElement\n\n  /**\n   * 提示的 element 节点\n   */\n  tipContainer?: HTMLElement\n\n  /**\n   * 初始显示的空间 id\n   */\n  initSpaceId?: string\n\n  /**\n   * 空间配置\n   */\n  spacesConfig: SpaceConfig[]\n}\n```\n\n##### SpaceConfig 构造参数里的空间配置\n\n```ts\n/**\n * 空间配置\n */\ninterface SpaceConfig {\n  /**\n   * 空间 id\n   */\n  id: string\n\n  /**\n   * 相机配置\n   */\n  camera?: {\n    /**\n     * 位置\n     */\n    position: {\n      x: number\n      y: number\n      z: number\n    }\n\n    /**\n     * 缩放\n     */\n    scale?: {\n      x: number\n      y: number\n      z: number\n    }\n\n    /**\n     * 旋转\n     */\n    rotate?: {\n      x: number\n      y: number\n      z: number\n    }\n  }\n\n  /**\n   * 提示配置列表\n   */\n  tips?: Tip[]\n\n  /**\n   * 空间贴图列表\n   */\n  cubeSpaceTextureUrls: {\n    /**\n     * 左侧贴图 url\n     */\n    left: string\n\n    /**\n     * 右侧贴图 url\n     */\n    right: string\n\n    /**\n     * 上侧贴图 url\n     */\n    up: string\n\n    /**\n     * 下侧贴图 url\n     */\n    down: string\n\n    /**\n     * 前侧贴图 url\n     */\n    front: string\n\n    /**\n     * 后侧贴图 url\n     */\n    back: string\n  }\n}\n```\n\n##### Tip 空间配置里的提示配置\n\n```ts\ninterface Tip {\n  /**\n   * 提示 id\n   */\n  id: string\n\n  /**\n   * 跳转的空间 id\n   */\n  targetSpaceId?: string\n\n  /**\n   * 提示图案纹理贴图\n   */\n  textureUrl?: string\n\n  /**\n   * 位置\n   */\n  position: {\n    x: number\n    y: number\n    z: number\n  }\n\n  /**\n   * 缩放\n   */\n  scale?: {\n    x: number\n    y: number\n    z: number\n  }\n\n  /**\n   * 旋转\n   */\n  rotate?: {\n    x: number\n    y: number\n    z: number\n  }\n\n  /**\n   * 其它携带信息，比如你可以附加 title 、 content，在提示相关事件回调时你可以拿到\n   */\n  [key: string]: any\n}\n```\n\n## 监听页面尺寸变化\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 监听页面尺寸变化，更新画布尺寸\n   * @returns 返回取消监听函数\n   */\n  public listenResize(): () => void {\n    ...\n  }\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 让渲染器监听页面尺寸变化，实时更新画布尺寸\nconst removeResizeListener = vr360.listenResize()\n\n// 你可以随时移除监听\nremoveResizeListener()\n```\n\n## 刷新容器渲染宽高\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 刷新容器渲染宽高\n   */\n  public updateContainerSize(): void {\n    ...\n  }\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 手动更新画布尺寸\nvr360.updateContainerSize()\n```\n\n## 更新全景空间配置\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 更新 spacesConfig\n   * @param newSpacesConfig 新的空间配置\n   */\n  public updateSpacesConfig(newSpacesConfig: SpaceConfig[]): void {\n    ...\n  }\n}\n```\n\n点击查看 [SpaceConfig](#spaceconfig-构造参数里的空间配置) 类型\n\n#### 使用\n\n```ts\nimport {Vr360, SpaceConfig} from '@nicepkg/vr360-core'\n\nconst spacesConfig: SpaceConfig[] = []\nconst vr360 = new Vr360({\n  container: document.querySelector('#container'),\n  tipContainer: document.querySelector('#tip-container'),\n  spacesConfig\n})\n\nvr360.render()\n\nspacesConfig.push([\n  {\n    ....\n  }\n])\n\n// 手动更新空间配置\nvr360.updateSpacesConfig(spacesConfig)\n```\n\n## 切换全景空间\n\n#### 介绍\n\n```ts\nclass Vr360 {\n   /**\n   * 切换空间\n   * @param id 空间 id\n   */\n  public switchSpace(id: string): void {\n    ...\n  }\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360, SpaceConfig} from '@nicepkg/vr360-core'\n\nconst spacesConfig: SpaceConfig[] = [\n  {\n    id: 'spaceA',\n    ...\n  },\n  {\n    id: 'spaceB',\n    ...\n  }\n]\n\nconst vr360 = new Vr360({\n  container: document.querySelector('#container'),\n  tipContainer: document.querySelector('#tip-container'),\n  spacesConfig\n})\n\nvr360.render()\n\n// 切换到 spaceB 空间\nvr360.switchSpace('spaceB')\n```\n\n## 开始渲染全景\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 渲染\n   */\n  public render(): void {\n    ...\n  }\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n```\n\n## 把鼠标位置转换成 threejs 位置\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 根据鼠标位置获取当前空间的位置\n   * @param x 鼠标 x 坐标\n   * @param y 鼠标 y 坐标\n   * @returns 返回当前空间的位置\n   */\n  public getPositionFromMouseXY(x: number, y: number): {\n    x: number\n    y: number\n    z: number\n  } {\n    ...\n  }\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({\n  container: document.querySelector('#container'),\n  tipContainer: document.querySelector('#tip-container'),\n  spacesConfig: [....]\n})\n\nvr360.render()\n\n// 获取当前空间的位置\ndocument.querySelector('#container').addEventListener('click', (e) => {\n  const {x, y, z} = vr360.getPositionFromMouseXY(e.clientX, e.clientY)\n  console.log('点击的画布里的空间位置为：', x, y, z)\n})\n```\n\n## 注销全景实例\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 销毁实例\n   */\n  public destroy(): void {\n    ...\n  }\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 销毁实例\nvr360.destroy()\n```\n"
  },
  {
    "path": "packages/doc/libs/vr360-core/properties.md",
    "content": "# 属性\n\n## 全景容器\n\n##### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 容器\n   */\n  public container: HTMLElement\n}\n```\n\n##### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 获取容器\nconsole.log('3d 渲染容器是：', vr360.container)\n```\n\n## 相机\n\n##### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 相机\n   */\n  public camera: THREE.PerspectiveCamera\n}\n```\n\n##### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 获取相机\nconsole.log('3d 相机是：', vr360.camera)\n```\n\n## 渲染器\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 渲染器\n   */\n  public renderer: THREE.WebGLRenderer\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 获取渲染器\nconsole.log('3d 渲染器是：', vr360.renderer)\n```\n\n## 场景\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 场景\n   */\n  public scene: THREE.Scene\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 获取场景\nconsole.log('3d 场景是：', vr360.scene)\n```\n\n## 控制器\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 控制器\n   */\n  public controls: THREE.OrbitControls\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 获取控制器\nconsole.log('3d 控制器是：', vr360.controls)\n```\n\n## 全景空间配置\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 空间配置\n   */\n  public spacesConfig: SpaceConfig[]\n}\n```\n\n点击查看 [SpaceConfig](./methods.md#spaceconfig-构造参数里的空间配置) 类型\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 获取空间配置\nconsole.log('3d 空间配置是：', vr360.spacesConfig)\n```\n\n## 提示容器\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 提示容器\n   */\n  public tipContainer?: HTMLElement\n}\n```\n\n#### 使用\n\n```ts\nimport {Vr360} from '@nicepkg/vr360-core'\n\nconst vr360 = new Vr360({....})\n\nvr360.render()\n\n// 获取提示容器\nconsole.log('3d 提示容器是：', vr360.tipContainer)\n```\n"
  },
  {
    "path": "packages/doc/libs/vr360-ui/README.md",
    "content": "# 介绍\n\n（开发中...）提供一个现成的 vr360 viewer 和 editor 组件，基于 stencil 构建的 web component。\n"
  },
  {
    "path": "packages/doc/libs/vr360-ui-react/README.md",
    "content": "# 介绍\n\n（开发中...）提供一个现成的 vr360 viewer 和 editor 组件，适配 react 框架版。\n"
  },
  {
    "path": "packages/doc/libs/vr360-ui-vue2/README.md",
    "content": "# 介绍\n\n（开发中...）提供一个现成的 vr360 viewer 和 editor 组件，适配 vue2 框架版。\n"
  },
  {
    "path": "packages/doc/libs/vr360-ui-vue3/README.md",
    "content": "# 介绍\n\n（开发中...）提供一个现成的 vr360 viewer 和 editor 组件，适配 vue3 框架版。\n"
  },
  {
    "path": "packages/doc/package.json",
    "content": "{\n  \"name\": \"doc\",\n  \"version\": \"0.3.1\",\n  \"private\": true,\n  \"description\": \"the libs docs website\",\n  \"scripts\": {\n    \"build\": \"vuepress build --clean-cache\",\n    \"clean\": \"rimraf .vuepress/.temp .vuepress/.cache .vuepress/dist\",\n    \"dev\": \"vuepress dev --clean-cache\",\n    \"serve\": \"http-server --port 8080 .vuepress/dist\"\n  },\n  \"dependencies\": {\n    \"@nicepkg/vr360-core\": \"workspace:*\",\n    \"@stackblitz/sdk\": \"^1.8.0\",\n    \"@vueuse/core\": \"^9.3.0\",\n    \"vue-router\": \"^4.1.5\"\n  },\n  \"devDependencies\": {\n    \"@types/markdown-it-container\": \"^2.0.5\",\n    \"@vuepress/bundler-vite\": \"2.0.0-beta.51\",\n    \"@vuepress/cli\": \"2.0.0-beta.51\",\n    \"@vuepress/client\": \"2.0.0-beta.51\",\n    \"@vuepress/core\": \"2.0.0-beta.51\",\n    \"@vuepress/markdown\": \"2.0.0-beta.51\",\n    \"@vuepress/plugin-google-analytics\": \"2.0.0-beta.51\",\n    \"@vuepress/plugin-register-components\": \"2.0.0-beta.51\",\n    \"@vuepress/plugin-shiki\": \"2.0.0-beta.51\",\n    \"@vuepress/shared\": \"2.0.0-beta.51\",\n    \"@vuepress/theme-default\": \"2.0.0-beta.51\",\n    \"@vuepress/utils\": \"2.0.0-beta.51\",\n    \"http-server\": \"^14.1.1\",\n    \"js-base64\": \"^3.7.2\",\n    \"markdown-it\": \"^13.0.1\",\n    \"markdown-it-container\": \"^3.0.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"sass\": \"^1.55.0\",\n    \"vite\": \"*\",\n    \"vue\": \"^3.2.40\",\n    \"vuepress\": \"2.0.0-beta.51\"\n  }\n}\n"
  },
  {
    "path": "packages/doc/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"types\": [\"vite/client\", \"@vuepress/client/types\"],\n    \"paths\": {\n      \"@/*\": [\"./.vuepress/*\"]\n    }\n  },\n  \"include\": [\"./.vuepress/**/*\", \"./*.js\", \"./*.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\", \".vuepress/examples/**/*\"]\n}\n"
  },
  {
    "path": "packages/doc/vuepress.config.ts",
    "content": "import {defineUserConfig} from 'vuepress'\nimport {path} from '@vuepress/utils'\nimport {viteBundler} from '@vuepress/bundler-vite'\nimport {plugins} from './.vuepress/plugins'\nimport {bundlerConfig} from './bundler.config'\nimport {localTheme} from './.vuepress/theme'\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\n\nexport default defineUserConfig({\n  base: '/',\n\n  head: [\n    [\n      'link',\n      {\n        rel: 'icon',\n        type: 'image/png',\n        sizes: '16x16',\n        href: `/images/icons/favicon-16x16.png`\n      }\n    ],\n    [\n      'link',\n      {\n        rel: 'icon',\n        type: 'image/png',\n        sizes: '32x32',\n        href: `/images/icons/favicon-32x32.png`\n      }\n    ],\n    ['link', {rel: 'manifest', href: '/manifest.webmanifest'}],\n    ['meta', {name: 'application-name', content: 'Vr360'}],\n    ['meta', {name: 'apple-mobile-web-app-title', content: 'Vr360'}],\n    ['meta', {name: 'apple-mobile-web-app-status-bar-style', content: 'black'}],\n    ['link', {rel: 'apple-touch-icon', href: `/images/icons/apple-touch-icon.png`}],\n    [\n      'link',\n      {\n        rel: 'mask-icon',\n        href: '/images/icons/safari-pinned-tab.svg',\n        color: '#0c4dc4'\n      }\n    ],\n    ['meta', {name: 'msapplication-TileColor', content: '#0c4dc4'}],\n    ['meta', {name: 'theme-color', content: '#0c4dc4'}]\n  ],\n\n  // site-level locales config\n  locales: {\n    '/': {\n      lang: 'zh-CN',\n      title: 'Vr360',\n      description: '快速实现你的全景浏览开发需求'\n    }\n    // '/en/': {\n    //   lang: 'en-US',\n    //   title: 'Vr360',\n    //   description: 'Quickly realize your panoramic browsing development needs'\n    // }\n  },\n\n  theme: localTheme(),\n  bundler: viteBundler(bundlerConfig),\n  templateDev: pathResolve('.vuepress/index.build.html'),\n  templateBuild: pathResolve('.vuepress/index.build.html'),\n  markdown: {\n    importCode: {\n      handleImportPath: str => str.replace(/^@/, pathResolve('.vuepress'))\n    }\n  },\n  plugins\n})\n"
  },
  {
    "path": "packages/vr360-core/CHANGELOG.md",
    "content": "# 0.3.0 (2022-10-08)\n\n### Bug Fixes\n\n- **@nicepkg/vr360-core:** 修复移动端的提示移动 bug ([7090a98](https://github.com/nicepkg/vr360/commit/7090a9805bc472240de6ad4467bbadc14fd6151d))\n\n# 0.3.0 (2022-10-02)\n\n## 0.2.1 (2022-10-02)\n\n# 0.2.0 (2022-10-02)\n\n### Bug Fixes\n\n- 修复 vr360-core tips 在滑动时的 bug ([e179ebc](https://github.com/nicepkg/vr360/commit/e179ebc2697314bc455320eecf8beb6182a53ded))\n\n### Features\n\n- 暴露更多的事件，设置默认自动旋转 ([4e21c53](https://github.com/nicepkg/vr360/commit/4e21c53ac945532020a7fbbfa46644294c33b49d))\n- 初始化项目 ([06f39d1](https://github.com/nicepkg/vr360/commit/06f39d141004a1d0b1a125ad598298baf15ffee8))\n- 添加标签提示和空间切换功能 ([6aa67d3](https://github.com/nicepkg/vr360/commit/6aa67d39113b06d05036ecc1d66ab1d70a2f4cf5))\n- 添加如视 vr 导入支持 ([5a9eb5d](https://github.com/nicepkg/vr360/commit/5a9eb5d7a33d092be8cea5565490f268376d2f79))\n- 文档添加示例插件、core 的纹理 top、bottom 改为 up 和 down ([70ccb2c](https://github.com/nicepkg/vr360/commit/70ccb2c40a06079ffb3cf50b27f986ab19b1f7db))\n- 修改 package.json 说明 ([e5e4ad0](https://github.com/nicepkg/vr360/commit/e5e4ad04b1c7a9cddff3af6f73d438fd1de85b25))\n- 优化代码 ([4519c4a](https://github.com/nicepkg/vr360/commit/4519c4a0fb230bb62bed49f97e0824c8977be3ca))\n- 优化 sdk，添加纹理缓存预加载 ([9dd57e7](https://github.com/nicepkg/vr360/commit/9dd57e71b8f5a38ecc9901395a9b189481172edb))\n- 重写核心，添加最小化更新配置支持 ([289091b](https://github.com/nicepkg/vr360/commit/289091b13dfe0495b44ae9e5353b78d2712e9762))\n"
  },
  {
    "path": "packages/vr360-core/README.md",
    "content": "<div align=\"center\">\n  <a href=\"https://vr360.nicepkg.cn/libs/vr360-core/\">\n    <img src=\"https://vr360.nicepkg.cn/images/logo-bg.png\" width=\"50%\">\n  </a>\n  <h1>@nicepkg/vr360-core</h1>\n  <p>json 驱动的全景浏览库核心，设计框架无关性，可用于任何框架，如vue/react/angular/svelte/solidjs...</p>\n  <p>\n    <img src=\"https://img.shields.io/npm/v/@nicepkg/vr360-core?style=flat-square\" alt=\"version\">\n    <img src=\"https://img.shields.io/npm/dependency-version/@nicepkg/vr360-core/peer/three\" alt=\"three\">\n    <img src=\"https://img.shields.io/npm/l/@nicepkg/vr360-core.svg\" alt=\"license\">\n    <img src=\"https://img.shields.io/codecov/c/github/nicepkg/vr360\" alt=\"coverage\">\n    <img src=\"https://img.badgesize.io/https://unpkg.com/@nicepkg/vr360-core?compression=gzip&label=gzip\" alt=\"gzip\" />\n    <img src=\"https://img.shields.io/github/stars/nicepkg/vr360?style=social\" alt=\"stars\">\n  </p>\n</div>\n\n## 介绍\n\n`@nicepkg/vr360-core` 是一个基于 [threejs](https://github.com/mrdoob/three.js/) 的全景库，非常适合用来做全景看房、全景街景、全景景点等业务需求。\n\n它支持 json 配置驱动视图，你可以用 json 配置的方式快速实现常见全景需求。\n\n## 特性\n\n- json 驱动视图，快速实现全景业务需求\n- 高性能，支持自动找出 json 变更的部分，最小化更新到 3d 全景里，不会整个场景重建。\n- 支持增删改提示点，hover 提示点的弹窗支持自定义定制，支持自定义提示点的贴图。\n- 内置场景切换穿梭动画\n- 暴露 scene、camera、renderer 等 [threejs](https://github.com/mrdoob/three.js/) 对象，方便你更高的定制化\n- 基于事件驱动的数据交互，设计框架无关性\n- 完整的 ts 支持\n\n## 安装\n\nfor npm\n\n```bash\nnpm i @nicepkg/vr360-core threejs\n```\n\nfor yarn\n\n```bash\nyarn add @nicepkg/vr360-core threejs\n```\n\nfor pnpm\n\n```bash\npnpm add @nicepkg/vr360-core threejs\n```\n\n## 使用\n\n点击查看[使用文档](https://vr360.nicepkg.cn/libs/vr360-core/)。\n\n## Contributing\n\nLearn about contribution [here](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).\n\nThis project exists thanks to all the people who contribute:\n\n<a href=\"https://github.com/nicepkg/vr360/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=nicepkg/vr360\" />\n</a>\n\n## License\n\n[MIT](https://github.com/nicepkg/vr360/blob/master/LICENSE) License © 2022-PRESENT [nicepkg](https://github.com/nicepkg)\n"
  },
  {
    "path": "packages/vr360-core/package.json",
    "content": "{\n  \"name\": \"@nicepkg/vr360-core\",\n  \"version\": \"0.3.1\",\n  \"description\": \"快速实现你的全景开发需求，全景看房、全景街景、全景景点\",\n  \"keywords\": [\n    \"vr360\",\n    \"vr360-core\",\n    \"panorama\",\n    \"pannellum\",\n    \"photo-sphere-viewer\",\n    \"photograph\",\n    \"nicepkg\",\n    \"webgl\",\n    \"threejs\"\n  ],\n  \"author\": \"yangjinming <https://github.com/2214962083>\",\n  \"funding\": \"https://github.com/sponsors/2214962083\",\n  \"license\": \"MIT\",\n  \"private\": false,\n  \"scripts\": {\n    \"build\": \"pnpm type-check &&esno ./scripts/build.ts\",\n    \"build:watch\": \"cross-env WATCH=true pnpm build\",\n    \"clean\": \"rimraf ./dist/**/*\",\n    \"dev\": \"esno ./src/playground.ts\",\n    \"test\": \"vitest run --silent --passWithNoTests\",\n    \"test:watch\": \"pnpm test -- --watch\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"main\": \"./dist/index.cjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"unpkg\": \"./dist/index.min.umd.js\",\n  \"jsdelivr\": \"./dist/index.min.umd.js\",\n  \"exports\": {\n    \".\": {\n      \"require\": \"./dist/index.cjs\",\n      \"import\": \"./dist/index.mjs\",\n      \"types\": \"./dist/index.d.ts\"\n    },\n    \"./*\": \"./*\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": false,\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/nicepkg/vr360.git\",\n    \"directory\": \"packages/vr360-core\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/nicepkg/vr360/issues\"\n  },\n  \"homepage\": \"https://github.com/nicepkg/vr360#readme\",\n  \"peerDependencies\": {\n    \"three\": \"*\"\n  },\n  \"dependencies\": {\n    \"@tweenjs/tween.js\": \"^18.6.4\",\n    \"eventemitter3\": \"^4.0.7\"\n  },\n  \"devDependencies\": {\n    \"@nicepkg/vr360-shared\": \"workspace:*\",\n    \"@types/three\": \"^0.144.0\",\n    \"@types/node\": \"*\",\n    \"esno\": \"*\",\n    \"jsdom\": \"^20.0.1\",\n    \"three\": \"^0.145.0\",\n    \"typescript\": \"*\",\n    \"vite\": \"*\",\n    \"vitest\": \"*\"\n  }\n}\n"
  },
  {
    "path": "packages/vr360-core/scripts/build.ts",
    "content": "import {buildUtils} from '@nicepkg/vr360-shared'\nimport {minifyConfig, unMinifyConfig, packagePath} from '../vite.config'\n\nbuildUtils.build({\n  minifyConfig,\n  unMinifyConfig,\n  packagePath,\n  dtsOptions: {\n    // merge all .d.ts files\n    rollupTypes: true\n  }\n})\n"
  },
  {
    "path": "packages/vr360-core/src/helper.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-unnecessary-condition */\nimport * as THREE from 'three'\nimport type {DeepRequired, DeepPartial, Position, ThreeObjectBase} from './types'\n\nlet raycaster: THREE.Raycaster\nlet mouse: THREE.Vector2\nfunction getSingleValue() {\n  if (!raycaster) raycaster = new THREE.Raycaster()\n  if (!mouse) mouse = new THREE.Vector2()\n  return {\n    raycaster,\n    mouse\n  }\n}\n\nexport type ConvertMousePositionToThreePositionOptions = {\n  /**\n   * 鼠标 x 坐标\n   */\n  mouseX: number\n\n  /**\n   * 鼠标 y 坐标\n   */\n  mouseY: number\n\n  /**\n   * 获取依赖函数\n   */\n  getDeps: GetAddListenerToThreeObjectDeps\n}\n\n/**\n * 转换鼠标位置到 three 坐标\n * @param options 配置\n * @returns x,y,z 坐标\n */\nexport function convertMousePositionToThreePosition(options: ConvertMousePositionToThreePositionOptions): Position {\n  const {mouseX, mouseY, getDeps} = options\n  const {camera, scene, renderer} = getDeps()\n  if (!camera || !scene || !renderer) return {x: 0, y: 0, z: 0}\n  const {raycaster, mouse} = getSingleValue()\n  const renderDomBound = renderer.domElement.getBoundingClientRect()\n\n  mouse.x = ((mouseX - renderDomBound.left) / renderer.domElement.clientWidth) * 2 - 1\n  mouse.y = -((mouseY - renderDomBound.top) / renderer.domElement.clientHeight) * 2 + 1\n\n  raycaster.setFromCamera(mouse, camera)\n\n  const intersects = raycaster.intersectObjects(scene.children)\n  const firstIntersect = intersects[0] ?? undefined\n  return firstIntersect.point ?? {x: 0, y: 0, z: 0}\n}\n\n/**\n * 获取依赖函数\n */\nexport type GetAddListenerToThreeObjectDeps = () => {\n  /**\n   * 摄像机实例\n   */\n  camera?: THREE.PerspectiveCamera\n\n  /**\n   * 场景实例\n   */\n  scene?: THREE.Scene\n\n  /**\n   * 渲染器实例\n   */\n  renderer?: THREE.WebGLRenderer\n}\n\nexport type ThreeObjectDispatchEvent<T extends keyof HTMLElementEventMap> = {\n  type: T\n  intersect: THREE.Intersection\n  sourceEvent: HTMLElementEventMap[T]\n  isMouseDown: boolean\n}\n\n/**\n * 为 threejs 对象添加事件监听器\n * @param getDeps 获取依赖函数\n * @param events 监听的事件名称列表\n */\nexport function addListenerToThreeObject(\n  getDeps: GetAddListenerToThreeObjectDeps,\n  events: (keyof HTMLElementEventMap)[] = ['click', 'mousemove', 'touchmove', 'mousedown', 'mouseup']\n) {\n  const renderElement = getDeps().renderer?.domElement\n  if (!renderElement) return\n  const {raycaster, mouse} = getSingleValue()\n\n  // 上一个点击的对象\n  let mouseoverPreIntersect: THREE.Intersection | undefined\n\n  // 鼠标是否处于按压状态，用于传给 tip 判断，在按下鼠标滑动时不显示 tip\n  let isMouseDown = false\n\n  ;['mousedown'].map(eventName =>\n    renderElement.addEventListener(eventName, () => {\n      isMouseDown = true\n    })\n  )\n  ;['mouseup'].map(eventName =>\n    renderElement.addEventListener(eventName, () => {\n      isMouseDown = false\n    })\n  )\n\n  function handleEvent(eventName: string, event: MouseEvent | TouchEvent) {\n    event.preventDefault()\n    const {camera, scene, renderer} = getDeps()\n    if (!camera || !scene || !renderer) return\n    const renderDomBound = renderer.domElement.getBoundingClientRect()\n\n    const clientX = (event as TouchEvent)?.changedTouches?.[0]?.clientX ?? (event as MouseEvent).clientX\n    const clientY = (event as TouchEvent)?.changedTouches?.[0]?.clientY ?? (event as MouseEvent).clientY\n\n    mouse.x = ((clientX - renderDomBound.left) / renderer.domElement.clientWidth) * 2 - 1\n    mouse.y = -((clientY - renderDomBound.top) / renderer.domElement.clientHeight) * 2 + 1\n\n    raycaster.setFromCamera(mouse, camera)\n\n    const intersects = raycaster.intersectObjects(scene.children)\n    const firstIntersect = intersects[0] ?? undefined\n\n    if (!firstIntersect) return\n\n    if (\n      ['mousemove', 'touchmove'].includes(eventName) &&\n      mouseoverPreIntersect?.object.uuid !== firstIntersect.object.uuid\n    ) {\n      // 在 mousemove 事件时, 并且本次鼠标指中的 three 对象和上一个对象不相同时\n      // 触发上个 hover 对象的 mouseout 事件\n      mouseoverPreIntersect?.object.dispatchEvent({\n        type: 'mouseout',\n        intersect: mouseoverPreIntersect,\n        sourceEvent: event,\n        isMouseDown\n      } as ThreeObjectDispatchEvent<'mouseout'>)\n      mouseoverPreIntersect = firstIntersect\n\n      // 设置鼠标样式\n      const cursor = firstIntersect.object.userData.cursor || 'default'\n      if (renderElement && renderElement.style.cursor !== cursor) renderElement.style.cursor = cursor\n\n      // 触发本次 hover 对象的 mouseover 事件\n      firstIntersect.object.dispatchEvent({\n        type: 'mouseover',\n        intersect: firstIntersect,\n        sourceEvent: event,\n        isMouseDown\n      } as ThreeObjectDispatchEvent<'mouseover'>)\n    }\n\n    // 射线范围内没有东西，终止流程\n    if (intersects.length <= 0) return\n\n    // if (eventName === 'click') {\n    //   console.log(`当前点击位置: x=${firstIntersect.point.x}, y=${firstIntersect.point.y}, z=${firstIntersect.point.z}`)\n    // }\n\n    firstIntersect.object.dispatchEvent({\n      type: eventName === 'touchmove' ? 'mousemove' : eventName,\n      intersect: firstIntersect,\n      sourceEvent: event,\n      isMouseDown\n    } as ThreeObjectDispatchEvent<keyof HTMLElementEventMap>)\n  }\n\n  for (const eventName of events) {\n    renderElement.addEventListener(eventName, event => handleEvent(eventName, event as MouseEvent))\n  }\n}\n\n/**\n * 纹理缓存加载器\n */\nexport class TextureCacheLoader {\n  static instance: TextureCacheLoader\n  static getInstance() {\n    if (!TextureCacheLoader.instance) TextureCacheLoader.instance = new TextureCacheLoader()\n    return TextureCacheLoader.instance\n  }\n\n  /**\n   * 缓存\n   */\n  private cache = new Map<string, THREE.Texture>()\n\n  /**\n   * 加载器\n   */\n  private loader = new THREE.TextureLoader()\n\n  /**\n   * 根据单个链接加载纹理\n   * @param url 图片链接\n   * @returns 返回纹理\n   */\n  loadUrl(url: string) {\n    if (this.cache.has(url)) return this.cache.get(url)\n    const texture = this.loader.load(url)\n    this.cache.set(url, texture)\n    return texture\n  }\n\n  /**\n   * 根据多个链接加载纹理\n   * @param urls 图片链接数组\n   * @returns 返回纹理数组\n   */\n  loadUrls(urls: string[]) {\n    return urls.map(url => this.loadUrl(url))\n  }\n}\n\n/**\n * 判断该对象是否含有该键名\n * @param obj 对象\n * @param key 键名\n * @returns 返回该对象是否含有该键名\n */\nexport function hasOwnProperty(obj: any, key: string): boolean {\n  return Object.prototype.hasOwnProperty.call(obj, key)\n}\n\n/**\n * 判断两个值是否相等\n * @param a 值 a\n * @param b 值 b\n * @returns 值 a 和 值 b 是否相等\n */\nexport function isEqual(a: any, b: any): boolean {\n  if (a === b) return true\n  if (typeof a !== typeof b) return false\n  if (typeof a !== 'object') return false\n  if (a === null || b === null) return false\n  if (Array.isArray(a) !== Array.isArray(b)) return false\n  if (Array.isArray(a)) {\n    if (a.length !== b.length) return false\n    for (const [i, element] of a.entries()) {\n      if (!isEqual(element, b[i])) return false\n    }\n    return true\n  }\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n  const aKeys = Object.keys(a)\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n  const bKeys = Object.keys(b)\n  if (aKeys.length !== bKeys.length) return false\n  for (const key of aKeys) {\n    if (!isEqual(a[key], b[key])) return false\n  }\n  return true\n}\n\n/**\n * 防抖\n * @param func 函数\n * @param wait 延迟时间，单位毫秒\n * @param immediate 是否立即执行\n * @returns 返回一个新的函数\n */\nexport function debounce<T extends (...args: any[]) => any>(func: T, wait: number, immediate = true) {\n  let timeout: number | undefined\n  let hasRunImmediate = false\n  return function (this: any, ...args: Parameters<T>) {\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n    const run = () => func.apply(this, args)\n\n    if (timeout) clearTimeout(timeout)\n    if (immediate && !hasRunImmediate) {\n      hasRunImmediate = true\n      run()\n    } else {\n      timeout = window.setTimeout(run, wait)\n    }\n  } as T\n}\n\n/**\n * 更新 threejs 3d 对象的位置、缩放、旋转等基础信息\n * @param object threejs 3d 对象\n * @param baseInfo 位置、缩放、旋转信息\n */\nexport function update3dObjectBaseInfo(object: THREE.Object3D, baseInfo: Partial<ThreeObjectBase>): void {\n  const {position, rotate, scale} = baseInfo\n  if (position && !new THREE.Vector3(position.x, position.y, position.z).equals(object.position)) {\n    object.position.set(position.x, position.y, position.z)\n  }\n\n  if (scale && !new THREE.Vector3(scale.x, scale.y, scale.z).equals(object.scale)) {\n    object.scale.set(scale.x, scale.y, scale.z)\n  }\n\n  if (rotate && !new THREE.Euler(rotate.x, rotate.y, rotate.z).equals(object.rotation)) {\n    object.rotation.set(rotate.x, rotate.y, rotate.z)\n  }\n}\n\n/**\n * 获取 threejs 3d 对象的位置、缩放、旋转等基础信息\n * @param object threejs 3d 对象\n * @returns 返回 threejs 3d 对象的位置、缩放、旋转等基础信息\n */\nexport function get3dObjectBaseInfo(object?: THREE.Object3D): ThreeObjectBase {\n  return {\n    position: {\n      x: object?.position?.x ?? 0,\n      y: object?.position?.y ?? 0,\n      z: object?.position?.z ?? 0\n    },\n    rotate: {\n      x: object?.rotation?.x ?? 0,\n      y: object?.rotation?.y ?? 0,\n      z: object?.rotation?.z ?? 0\n    },\n    scale: {\n      x: object?.scale?.x ?? 1,\n      y: object?.scale?.y ?? 1,\n      z: object?.scale?.z ?? 1\n    }\n  }\n}\n\n/**\n * 处理位置、缩放、旋转等基础信息默认值\n * @param baseInfo 位置、缩放、旋转信息\n * @returns 返回位置、缩放、旋转等基础信息\n */\nexport function formatBaseInfo(baseInfo: DeepPartial<ThreeObjectBase> | undefined): DeepRequired<ThreeObjectBase> {\n  return {\n    position: {\n      x: baseInfo?.position?.x ?? 0,\n      y: baseInfo?.position?.y ?? 0,\n      z: baseInfo?.position?.z ?? 0\n    },\n    rotate: {\n      x: baseInfo?.rotate?.x ?? 0,\n      y: baseInfo?.rotate?.y ?? 0,\n      z: baseInfo?.rotate?.z ?? 0\n    },\n    scale: {\n      x: baseInfo?.scale?.x ?? 1,\n      y: baseInfo?.scale?.y ?? 1,\n      z: baseInfo?.scale?.z ?? 1\n    }\n  }\n}\n"
  },
  {
    "path": "packages/vr360-core/src/index.ts",
    "content": "export * from './vr360'\nexport * from './types'\nexport * from './manager'\n"
  },
  {
    "path": "packages/vr360-core/src/manager/index.ts",
    "content": "export * from './space'\nexport * from './tip'\n\nexport type ConfigModel = {\n  id: string\n}\n\nexport type ConfigModelManager<CM extends ConfigModel, TM> = {\n  create(configModel: CM): TM\n  add(configModel: CM): TM\n  update(configModel: Partial<CM> & Pick<CM, 'id'>): TM | undefined\n  find(configModel: string | (Partial<CM> & Pick<CM, 'id'>)): TM | undefined\n  findOrCreate(configModel: CM): TM\n  remove(configModel: string | (Partial<CM> & Pick<CM, 'id'>)): void\n  removeAll(): void\n  destroy(): void\n}\n"
  },
  {
    "path": "packages/vr360-core/src/manager/space.ts",
    "content": "/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport * as THREE from 'three'\nimport type {TextureCacheLoader} from '../helper'\nimport {isEqual} from '../helper'\nimport {EventEmitter} from 'eventemitter3'\nimport type {ConfigModelManager} from './index'\nimport type {CubeSpaceTextureUrls, SpaceConfig} from '../types'\nimport type {TipManagerEvents, TipManagerOptions} from './tip'\nimport {tipEventNames, TipManager} from './tip'\n\n/**\n * 空间管理器事件列表\n */\nexport type SpaceManagerEvents = TipManagerEvents\n\nexport type SpaceEventName = keyof SpaceManagerEvents\nexport const spaceEventNames: SpaceEventName[] = [...tipEventNames]\n\nexport type SpaceManagerOptions = Omit<TipManagerOptions, 'tipContainer'> & {\n  tipContainer?: HTMLElement\n}\n\n/**\n * curd SpaceConfig to threejs\n */\nexport class SpaceManager\n  extends EventEmitter<SpaceManagerEvents>\n  implements ConfigModelManager<SpaceConfig, THREE.Group>\n{\n  private container: HTMLElement\n  private camera: THREE.PerspectiveCamera\n  private scene: THREE.Scene\n  private renderer: THREE.WebGLRenderer\n  private parent: THREE.Object3D\n  private tipContainer?: HTMLElement\n  private textureCacheLoader: TextureCacheLoader\n\n  private spaceIdGroupMap = new Map<string, THREE.Group>()\n  private spaceIdTipManager = new Map<string, TipManager>()\n\n  constructor(options: SpaceManagerOptions) {\n    super()\n    this.container = options.container\n    this.camera = options.camera\n    this.scene = options.scene\n    this.renderer = options.renderer\n    this.parent = options.parent\n    this.tipContainer = options.tipContainer\n    this.textureCacheLoader = options.textureCacheLoader\n  }\n\n  /**\n   * 创建提示管理器\n   * @param parent threejs 组\n   * @returns 提示管理器\n   */\n  private createTipManager(parent: THREE.Object3D): TipManager | undefined {\n    if (!this.tipContainer) return\n\n    const tipManager = new TipManager({\n      container: this.container,\n      camera: this.camera,\n      scene: this.scene,\n      renderer: this.renderer,\n      tipContainer: this.tipContainer,\n      textureCacheLoader: this.textureCacheLoader,\n      parent\n    })\n\n    // 提示的事件继承\n    tipEventNames.forEach(eventName => {\n      tipManager.on(eventName, e => {\n        // @ts-ignore\n        this.emit(eventName, e)\n      })\n    })\n\n    return tipManager\n  }\n\n  /**\n   * 创建正方体空间 mesh\n   * @param cubeSpaceTextureUrls 空间贴图\n   */\n  private createCubeSpaceMesh(cubeSpaceTextureUrls: CubeSpaceTextureUrls) {\n    // 创建空间\n    const boxGeometry = new THREE.BoxGeometry(100, 100, 100)\n\n    // 随机挑选一个面翻转扩大，使得贴图能够正常渲染\n    boxGeometry.scale(-1, 1, 1)\n\n    // 贴材质\n    const boxMaterials = this.createCubeSpaceMaterials(cubeSpaceTextureUrls)\n    const spaceMesh = new THREE.Mesh(boxGeometry, boxMaterials)\n    return spaceMesh\n  }\n\n  /**\n   * 创建正方体空间材料\n   * @param cubeSpaceTextureUrls 空间贴图\n   */\n  private createCubeSpaceMaterials(cubeSpaceTextureUrls: CubeSpaceTextureUrls) {\n    const directions = ['right', 'left', 'up', 'down', 'front', 'back'] as const\n    const boxMaterials: THREE.MeshBasicMaterial[] = directions.map(direction => {\n      const texture = this.textureCacheLoader.loadUrl(\n        cubeSpaceTextureUrls[direction as keyof typeof cubeSpaceTextureUrls]\n      )\n      return new THREE.MeshBasicMaterial({map: texture})\n    })\n    return boxMaterials\n  }\n\n  public create(spaceConfig: SpaceConfig): THREE.Group {\n    const {cubeSpaceTextureUrls, tips = [], id} = spaceConfig\n    const group = new THREE.Group()\n\n    // 创建提示精灵\n    if (this.tipContainer) {\n      const tipManager = this.createTipManager(group)!\n      tips.forEach(tip => {\n        const sprite = tipManager.findOrCreate(tip)\n        group.add(sprite)\n      })\n      this.spaceIdTipManager.set(id, tipManager)\n    }\n\n    // 创建空间\n    const spaceMesh = this.createCubeSpaceMesh(cubeSpaceTextureUrls)\n    spaceMesh.userData.type = 'spaceMesh'\n\n    // 挂载当前 spaceConfig 到 group 上，在事件里面使用（更新时会覆盖这个挂载）\n    group.userData.type = 'spaceGroup'\n    group.userData.spaceConfig = spaceConfig\n\n    group.add(spaceMesh)\n    this.spaceIdGroupMap.set(id, group)\n\n    return group\n  }\n\n  public add(spaceConfig: SpaceConfig): THREE.Group {\n    const group = this.findOrCreate(spaceConfig)\n    this.parent.add(group)\n    return group\n  }\n\n  public update(spaceConfig: Partial<SpaceConfig> & Pick<SpaceConfig, 'id'>): THREE.Group | undefined {\n    const {id, tips, cubeSpaceTextureUrls} = spaceConfig\n    const group = this.spaceIdGroupMap.get(id)\n\n    if (!group) return\n\n    if (tips && this.tipContainer) {\n      const tipManager = this.spaceIdTipManager.get(id) ?? this.createTipManager(group)!\n\n      const sprites = new Set(\n        tips.map(tip => {\n          const sprite = tipManager.update(tip) ?? tipManager.create(tip)\n          return sprite\n        })\n      )\n\n      const children = [...group.children]\n\n      // 找出需要删除的精灵并删除\n      children.forEach(child => {\n        if (child.type === 'Sprite' && child.userData.type === 'tipSprite' && !sprites.has(child as THREE.Sprite)) {\n          group.remove(child)\n        }\n      })\n\n      // 找出需要添加的精灵并添加\n      sprites.forEach(sprite => {\n        if (!group.children.includes(sprite)) {\n          group.add(sprite)\n        }\n      })\n    }\n\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n    if (cubeSpaceTextureUrls && !isEqual(group.userData.spaceConfig?.cubeSpaceTextureUrls, cubeSpaceTextureUrls)) {\n      let spaceMesh = group.children.find(child => child.type === 'Mesh' && child.userData.type === 'spaceMesh') as\n        | THREE.Mesh\n        | undefined\n\n      if (!spaceMesh) {\n        spaceMesh = this.createCubeSpaceMesh(cubeSpaceTextureUrls)\n      } else {\n        const boxMaterials = this.createCubeSpaceMaterials(cubeSpaceTextureUrls)\n        spaceMesh.material = boxMaterials\n      }\n    }\n\n    group.userData.spaceConfig = spaceConfig\n    // this.spaceIdGroupMap.set(id, group)\n    return group\n  }\n\n  public find(spaceConfig: string | (Partial<SpaceConfig> & Pick<SpaceConfig, 'id'>)): THREE.Group | undefined {\n    const id = typeof spaceConfig === 'string' ? spaceConfig : spaceConfig.id\n    return this.spaceIdGroupMap.get(id)\n  }\n\n  public findOrCreate(spaceConfig: SpaceConfig): THREE.Group {\n    const group = this.find(spaceConfig)\n    if (group) return group\n    return this.create(spaceConfig)\n  }\n\n  public remove(spaceConfig: string | (Partial<SpaceConfig> & Pick<SpaceConfig, 'id'>)): void {\n    const id = typeof spaceConfig === 'string' ? spaceConfig : spaceConfig.id\n    const group = this.spaceIdGroupMap.get(id)\n\n    if (!group) return\n    this.parent.remove(group)\n    this.spaceIdGroupMap.delete(id)\n    this.spaceIdTipManager.delete(id)\n  }\n\n  public removeAll(): void {\n    this.spaceIdGroupMap.forEach(group => {\n      this.parent.remove(group)\n    })\n\n    this.spaceIdTipManager.forEach(tipManager => {\n      tipManager.destroy()\n    })\n\n    this.spaceIdGroupMap.clear()\n    this.spaceIdTipManager.clear()\n  }\n\n  public destroy(): void {\n    this.removeAll()\n    this.removeAllListeners()\n  }\n}\n"
  },
  {
    "path": "packages/vr360-core/src/manager/tip.ts",
    "content": "import * as THREE from 'three'\nimport type {TextureCacheLoader, ThreeObjectDispatchEvent} from '../helper'\nimport {update3dObjectBaseInfo} from '../helper'\nimport type {Tip} from '../types'\nimport defaultTipUrl from '../assets/tips.png'\nimport {EventEmitter} from 'eventemitter3'\nimport type {ConfigModelManager} from '../manager'\n\n/**\n * 触发提示时的回调 event\n */\nexport type ShowTipEvent = {\n  /**\n   * 提示配置信息\n   */\n  tip: Tip\n\n  /**\n   * 相对于 container 的 left\n   */\n  left: number\n\n  /**\n   * 相对于 container 的 top\n   */\n  top: number\n}\n\n/**\n * 隐藏提示时的回调 event\n */\nexport type HideTipEvent = {\n  tip: Tip\n}\n\n/**\n * 点击提示时的回调 event\n */\nexport type ClickTipEvent = {\n  tip: Tip\n}\n\n/**\n * 切换空间事件\n */\nexport type SwitchSpaceEvent = {\n  /**\n   * 跳转目标空间的 id\n   */\n  targetSpaceId: string\n\n  /**\n   * 点击切换的位置\n   */\n  clickPosition?: THREE.Vector3\n}\n\n/**\n * 提示管理器事件列表\n */\nexport type TipManagerEvents = {\n  /**\n   * 触发提示时的回调\n   */\n  showTip: (e: ShowTipEvent) => void\n\n  /**\n   * 隐藏提示时的回调\n   */\n  hideTip: (e: HideTipEvent) => void\n\n  /**\n   * 点击提示时的回调\n   */\n  clickTip: (e: ClickTipEvent) => void\n\n  /**\n   * 切换空间时的回调\n   */\n  switchSpace: (e: SwitchSpaceEvent) => void\n}\n\nexport type TipEventName = keyof TipManagerEvents\nexport const tipEventNames: TipEventName[] = ['showTip', 'hideTip', 'clickTip', 'switchSpace']\n\nexport type TipManagerOptions = {\n  /**\n   * 容器\n   */\n  container: HTMLElement\n\n  /**\n   * 相机\n   */\n  camera: THREE.PerspectiveCamera\n\n  /**\n   * 场景\n   */\n  scene: THREE.Scene\n\n  /**\n   * 渲染器\n   */\n  renderer: THREE.WebGLRenderer\n\n  /**\n   * 3d 组, 用于添加提示 sprite\n   */\n  parent: THREE.Object3D\n\n  /**\n   * texture 缓存器\n   */\n  textureCacheLoader: TextureCacheLoader\n\n  /**\n   * 提示容器\n   */\n  tipContainer: HTMLElement\n}\n\n/**\n * 提示配置管理器\n */\nexport class TipManager extends EventEmitter<TipManagerEvents> implements ConfigModelManager<Tip, THREE.Sprite> {\n  private container: HTMLElement\n  private camera: THREE.PerspectiveCamera\n  private scene: THREE.Scene\n  private renderer: THREE.WebGLRenderer\n  private parent: THREE.Object3D\n  private textureCacheLoader: TextureCacheLoader\n  private tipContainer: HTMLElement\n\n  private tipIdSpriteMap = new Map<string, THREE.Sprite>()\n\n  constructor(options: TipManagerOptions) {\n    super()\n    this.container = options.container\n    this.camera = options.camera\n    this.scene = options.scene\n    this.renderer = options.renderer\n    this.parent = options.parent\n    this.textureCacheLoader = options.textureCacheLoader\n    this.tipContainer = options.tipContainer\n  }\n\n  public create(tip: Tip): THREE.Sprite {\n    const {position, textureUrl = defaultTipUrl, scale, rotate, id} = tip\n    const texture = this.textureCacheLoader.loadUrl(textureUrl)\n    const material = new THREE.SpriteMaterial({map: texture})\n    const sprite = new THREE.Sprite(material)\n\n    // 调整位置大小旋转角度\n    sprite.scale.set(scale?.x ?? 3, scale?.y ?? 3, scale?.z ?? 3)\n    sprite.position.set(position.x, position.y, position.z)\n    if (rotate) {\n      sprite.rotation.set(rotate.x, rotate.y, rotate.z)\n    }\n\n    // 设置鼠标样式\n    sprite.userData.cursor = 'pointer'\n\n    // 挂载当前 tip 到 sprite 上，在事件里面使用（更新时会覆盖这个挂载）\n    sprite.userData.type = 'tipSprite'\n    sprite.userData.tip = tip\n\n    // 触发展示 tips 事件\n    const emitShowTip = (e: ThreeObjectDispatchEvent<'mouseover'> | ThreeObjectDispatchEvent<'mouseup'>) => {\n      // hover 的对象\n      const intersect = e.intersect\n      const tipFromUserData = intersect.object.userData.tip as Tip\n\n      const containerHalfWidth = this.container.clientWidth / 2\n      const containerHalfHeight = this.container.clientHeight / 2\n\n      // 注意：如果外面是使用 display none 控制 tip 显隐，这里的 tipContainer 宽高均为 0，导致 tip 偏移\n      const tipContainerWidth = this.tipContainer.clientWidth\n      const tipContainerHeight = this.tipContainer.clientHeight\n      const rendererOffsetLeft = this.renderer.domElement.offsetLeft\n      const rendererOffsetTop = this.renderer.domElement.offsetTop\n      const percentPosition = intersect.object.position.clone().project(this.camera)\n      const left = (percentPosition.x + 1) * containerHalfWidth - tipContainerWidth / 2 + rendererOffsetLeft\n      const top = (1 - percentPosition.y) * containerHalfHeight - tipContainerHeight / 2 + rendererOffsetTop\n\n      const showTipEvent: ShowTipEvent = {tip: tipFromUserData, left, top}\n      // console.log('展示提示', showTipEvent)\n      this.emit('showTip', showTipEvent)\n    }\n\n    // 鼠标 hover 时展示提示\n    sprite.addEventListener('mouseover', _e => {\n      const e = _e as unknown as ThreeObjectDispatchEvent<'mouseover'>\n\n      // 如果鼠标处于按压下状态，不展示提示，此举是为了防止和 controls 滑动冲突导致位置偏差\n      if (e.isMouseDown) return\n\n      emitShowTip(e)\n    })\n\n    // 如果在 tip 上松开鼠标，则判断为需要展示 tips，因为用户可能 controls 滑倒 tips 再松开\n    sprite.addEventListener('mouseup', _e => {\n      const e = _e as unknown as ThreeObjectDispatchEvent<'mouseup'>\n\n      emitShowTip(e)\n    })\n\n    // 鼠标移出时移除提示\n    sprite.addEventListener('mouseout', _e => {\n      const e = _e as unknown as ThreeObjectDispatchEvent<'mouseout'>\n\n      const intersect = e.intersect\n      const tipFromUserData = intersect.object.userData.tip as Tip\n\n      const hideTipEvent: HideTipEvent = {tip: tipFromUserData}\n      // console.log('隐藏提示', hideTipEvent)\n      this.emit('hideTip', hideTipEvent)\n    })\n\n    sprite.addEventListener('click', _e => {\n      const e = _e as unknown as ThreeObjectDispatchEvent<'click'>\n\n      const intersect = e.intersect\n      const tipFromUserData = intersect.object.userData.tip as Tip\n\n      this.emit('clickTip', {tip: tipFromUserData})\n\n      if (tipFromUserData.targetSpaceId) {\n        // 存在目标空间 id，点击跳转\n        // 跳转时隐藏提示\n        this.emit('hideTip', {tip: tipFromUserData})\n\n        const switchSpaceEvent: SwitchSpaceEvent = {\n          targetSpaceId: tipFromUserData.targetSpaceId,\n          clickPosition: intersect.point\n        }\n\n        // console.log('调转到空间', switchSpaceEvent)\n        this.emit('switchSpace', switchSpaceEvent)\n      }\n    })\n\n    this.tipIdSpriteMap.set(id, sprite)\n    return sprite\n  }\n\n  public add(tip: Tip): THREE.Sprite {\n    const sprite = this.findOrCreate(tip)\n    this.parent.add(sprite)\n    return sprite\n  }\n\n  public update(tip: Partial<Tip> & Pick<Tip, 'id'>): THREE.Sprite | undefined {\n    const {id, textureUrl, position, scale, rotate} = tip\n    const sprite = this.tipIdSpriteMap.get(id)\n\n    if (!sprite) return\n\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n    if (textureUrl && textureUrl !== sprite.material.map?.image?.src) {\n      const texture = this.textureCacheLoader.loadUrl(textureUrl)\n      const material = new THREE.SpriteMaterial({map: texture})\n      sprite.material = material\n    }\n\n    update3dObjectBaseInfo(sprite, {\n      position,\n      scale,\n      rotate\n    })\n\n    sprite.userData.tip = tip\n    // this.tipIdSpriteMap.set(id, sprite)\n    return sprite\n  }\n\n  public find(tip: string | (Partial<Tip> & Pick<Tip, 'id'>)): THREE.Sprite | undefined {\n    const id = typeof tip === 'string' ? tip : tip.id\n    return this.tipIdSpriteMap.get(id)\n  }\n\n  public findOrCreate(tip: Tip): THREE.Sprite {\n    const sprite = this.find(tip)\n    if (sprite) return sprite\n    return this.create(tip)\n  }\n\n  public remove(tip: string | (Partial<Tip> & Pick<Tip, 'id'>)): void {\n    const id = typeof tip === 'string' ? tip : tip.id\n    const sprite = this.tipIdSpriteMap.get(id)\n    if (!sprite) return\n\n    this.parent.remove(sprite)\n    this.tipIdSpriteMap.delete(id)\n  }\n\n  public removeAll(): void {\n    this.tipIdSpriteMap.forEach(sprite => {\n      this.parent.remove(sprite)\n    })\n    this.tipIdSpriteMap.clear()\n  }\n\n  public destroy(): void {\n    this.removeAll()\n    this.removeAllListeners()\n  }\n}\n"
  },
  {
    "path": "packages/vr360-core/src/types.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type {ConfigModel} from './manager'\nimport type {SpaceManagerEvents} from './manager/space'\n\n/**\n * 完成跳转空间时的回调 event\n */\nexport type AfterSwitchSpaceEvent = {\n  /**\n   * 跳转的目标空间配置\n   */\n  spaceConfig: SpaceConfig\n}\n\n/**\n * vr360 暴露的自定义事件\n */\nexport type Vr360Events = {\n  /**\n   * 每帧更新回调\n   */\n  update: () => void\n\n  /**\n   * 完成跳转空间时的回调\n   */\n  afterSwitchSpace: (e: AfterSwitchSpaceEvent) => void\n} & Omit<SpaceManagerEvents, 'switchSpace'>\n\n/**\n * 通用坐标\n */\nexport type Vector3Position = {\n  /**\n   * x 轴坐标\n   */\n  x: number\n\n  /**\n   * y 轴坐标\n   */\n  y: number\n\n  /**\n   * z 轴坐标\n   */\n  z: number\n}\n\n/**\n * 位置\n */\nexport type Position = Vector3Position\n\n/**\n * 缩放\n */\nexport type Scale = Vector3Position\n\n/**\n * 旋转\n */\nexport type Rotate = Vector3Position\n\n/**\n * 每个 threejs 对象的通用配置\n */\nexport type ThreeObjectBase = {\n  /**\n   * 位置\n   */\n  position: Position\n\n  /**\n   * 缩放\n   */\n  scale?: Scale\n\n  /**\n   * 旋转\n   */\n  rotate?: Rotate\n}\n\n/**\n * 立方体空间纹理贴图 url 列表\n */\nexport type CubeSpaceTextureUrls = {\n  /**\n   * 左侧贴图 url\n   */\n  left: string\n\n  /**\n   * 右侧贴图 url\n   */\n  right: string\n\n  /**\n   * 上侧贴图 url\n   */\n  up: string\n\n  /**\n   * 下侧贴图 url\n   */\n  down: string\n\n  /**\n   * 前侧贴图 url\n   */\n  front: string\n\n  /**\n   * 后侧贴图 url\n   */\n  back: string\n}\n\n/**\n * 提示配置\n */\nexport type Tip = {\n  /**\n   * 提示 id\n   */\n  id: string\n\n  /**\n   * 跳转的空间 id\n   */\n  targetSpaceId?: string\n\n  /**\n   * 提示图案纹理贴图\n   */\n  textureUrl?: string\n\n  /**\n   * 其它携带信息，比如你可以附加 title 、 content，在提示相关事件回调时你可以拿到\n   */\n  [key: string]: any\n} & ThreeObjectBase &\n  ConfigModel\n\n/**\n * 空间配置\n */\nexport type SpaceConfig = {\n  /**\n   * 空间 id\n   */\n  id: string\n\n  /**\n   * 相机配置\n   */\n  camera?: ThreeObjectBase\n\n  /**\n   * 提示配置列表\n   */\n  tips?: Tip[]\n\n  /**\n   * 空间贴图列表\n   */\n  cubeSpaceTextureUrls: CubeSpaceTextureUrls\n} & ConfigModel\n\n/**\n * vr360 构造参数\n */\nexport type Vr360Options = {\n  /**\n   * 容器\n   */\n  container: HTMLElement\n\n  /**\n   * 提示的 element 节点\n   */\n  tipContainer?: HTMLElement\n\n  /**\n   * 初始显示的空间 id\n   */\n  initSpaceId?: string\n\n  /**\n   * 空间配置\n   */\n  spacesConfig: SpaceConfig[]\n}\n\n/**\n * 深度处理 interface key，使所有 key 为必填项\n */\nexport type DeepRequired<T> = {\n  [P in keyof T]-?: T[P] extends (infer U)[] ? DeepRequired<U>[] : T[P] extends object ? DeepRequired<T[P]> : T[P]\n}\n\n/**\n * 深度处理 interface key，使所有 key 为可选项\n */\nexport type DeepPartial<T> = {\n  [P in keyof T]?: T[P] extends (infer U)[] ? DeepPartial<U>[] : T[P] extends object ? DeepPartial<T[P]> : T[P]\n}\n"
  },
  {
    "path": "packages/vr360-core/src/vr360.ts",
    "content": "/* eslint-disable unicorn/no-array-callback-reference */\n/* eslint-disable @typescript-eslint/ban-ts-comment */\n/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */\n/* eslint-disable @typescript-eslint/no-unnecessary-condition */\nimport * as THREE from 'three'\nimport {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'\nimport TWEEN from '@tweenjs/tween.js'\nimport {EventEmitter} from 'eventemitter3'\nimport {\n  addListenerToThreeObject,\n  convertMousePositionToThreePosition,\n  formatBaseInfo,\n  get3dObjectBaseInfo,\n  TextureCacheLoader,\n  update3dObjectBaseInfo\n} from './helper'\nimport type {Position, SpaceConfig, ThreeObjectBase, Vr360Events, Vr360Options} from './types'\nimport {spaceEventNames, SpaceManager} from './manager/space'\nimport type {SpaceEventName} from './manager/space'\n\nexport type Vr360EventName = keyof Vr360Events\n\n/**\n * 继承的空间管理器事件列表\n */\nconst extendsSpaceEventNames = spaceEventNames.filter(eventName => !['switchSpace'].includes(eventName)) as Exclude<\n  SpaceEventName,\n  'switchSpace'\n>[]\n\n/**\n * vr360 的事件列表\n */\nexport const vr360EventNames: Vr360EventName[] = [...extendsSpaceEventNames, 'update', 'afterSwitchSpace']\n\nexport class Vr360 extends EventEmitter<Vr360Events> {\n  /**\n   * 销毁队列\n   */\n  private destroyTasks: (() => void)[] = []\n\n  /**\n   * 当前空间 id\n   */\n  private currentSpaceId = ''\n\n  /**\n   * 初始化空间 id\n   */\n  public initSpaceId?: string\n\n  /**\n   * texture 缓存器\n   */\n  private textureCacheLoader: TextureCacheLoader\n\n  /**\n   * 容器\n   */\n  public container: HTMLElement\n\n  /**\n   * 相机\n   */\n  public camera: THREE.PerspectiveCamera\n\n  /**\n   * 场景\n   */\n  public scene: THREE.Scene\n\n  /**\n   * 渲染器\n   */\n  public renderer: THREE.WebGLRenderer\n\n  /**\n   * 控制器\n   */\n  public controls: OrbitControls\n\n  /**\n   * 提示容器\n   */\n  public tipContainer?: HTMLElement\n\n  /**\n   * 空间管理器\n   */\n  public spaceManager: SpaceManager\n\n  /**\n   * 空间配置\n   */\n  public spacesConfig: SpaceConfig[]\n\n  /**\n   * 容器宽\n   */\n  public get containerWidth() {\n    return this.container.clientWidth\n  }\n\n  /**\n   * 容器高\n   */\n  public get containerHeight() {\n    return this.container.clientHeight\n  }\n\n  constructor(options: Vr360Options) {\n    super()\n    const {container, tipContainer, initSpaceId, spacesConfig = []} = options\n\n    this.textureCacheLoader = TextureCacheLoader.getInstance()\n    this.container = container\n    this.camera = this.createCamera()\n    this.scene = this.createScene()\n    this.renderer = this.createRenderer()\n    this.updateContainerSize()\n    this.controls = this.createControls()\n    this.container.append(this.renderer.domElement)\n\n    this.tipContainer = tipContainer\n    this.spacesConfig = [...spacesConfig]\n    this.initSpaceId = String(initSpaceId || '')\n\n    // 为 mesh 添加事件支持\n    addListenerToThreeObject(() => {\n      return {\n        camera: this.camera,\n        scene: this.scene,\n        renderer: this.renderer\n      }\n    })\n\n    // 缓存所有纹理\n    this.cacheAllTextures()\n\n    // 创建空间管理器\n    this.spaceManager = this.createSpaceManager()\n\n    // 实例化空间\n    this.updateSpacesConfig(this.spacesConfig)\n\n    // 初始化切换到第一个空间\n    // this.switchSpace(this.currentSpaceId)\n  }\n\n  /**\n   * 一次性加载并配置里所有的纹理图片\n   */\n  public cacheAllTextures(): void {\n    const urls = this.spacesConfig.reduce<string[]>((urls, spaceConfig) => {\n      const {cubeSpaceTextureUrls} = spaceConfig\n      return [...urls, ...Object.values(cubeSpaceTextureUrls)]\n    }, [])\n    this.textureCacheLoader.loadUrls(urls)\n  }\n\n  /**\n   * 监听页面尺寸变化，更新画布尺寸\n   * @returns 返回取消监听函数\n   */\n  public listenResize(): () => void {\n    const resizeFn = () => {\n      this.updateContainerSize()\n    }\n    window.addEventListener('resize', resizeFn)\n\n    const remove = () => {\n      window.removeEventListener('resize', resizeFn)\n    }\n\n    this.destroyTasks.push(remove)\n    return remove\n  }\n\n  /**\n   * 更新容器宽高\n   */\n  public updateContainerSize(): void {\n    const width = this.containerWidth\n    const height = this.containerHeight\n\n    if (this.camera) {\n      this.camera.aspect = width / height\n      this.camera.updateProjectionMatrix()\n    }\n\n    if (this.renderer) {\n      this.renderer.setSize(width, height)\n    }\n  }\n\n  /**\n   * 更新 spacesConfig\n   * @param newSpacesConfig 新的空间配置\n   */\n  public updateSpacesConfig(newSpacesConfig: SpaceConfig[]): void {\n    const oldSpacesConfig = this.spacesConfig\n\n    const newSpacesIds = new Set<string>()\n    newSpacesConfig.map(spaceConfig => {\n      // 如果空间已经存在，则更新空间配置，否则创建空间\n      this.spaceManager.update(spaceConfig) ?? this.spaceManager.create(spaceConfig)\n      newSpacesIds.add(String(spaceConfig.id))\n    })\n\n    oldSpacesConfig.map(spaceConfig => {\n      // 删掉旧的空间\n      if (!newSpacesIds.has(String(spaceConfig.id))) {\n        this.spaceManager.remove(String(spaceConfig.id))\n      }\n    })\n\n    this.spacesConfig = [...newSpacesConfig]\n\n    // 如果当前空间不存在，则切换到另一个空间\n    if (!this.currentSpaceId || !newSpacesIds.has(this.currentSpaceId)) {\n      // 默认空间 id\n      const defaultSpaceId = String(newSpacesConfig[0].id || '')\n\n      // 要跳转的空间 id\n      const switchSpaceId =\n        !this.currentSpaceId && this.initSpaceId ? this.initSpaceId || defaultSpaceId : defaultSpaceId\n\n      this.switchSpace(switchSpaceId)\n    }\n  }\n\n  /**\n   * 切换空间\n   * @param id 空间 id\n   * @param clickPosition 点击切换的位置，如果提高则会自动计算过渡动画\n   */\n  public switchSpace(id: string, clickPosition?: THREE.Vector3): void {\n    const targetSpaceId = String(id)\n\n    // 相同的房间就不执行跳转了\n    if (this.currentSpaceId === targetSpaceId || !targetSpaceId) return\n\n    const spaceGroup = this.spaceManager.find(targetSpaceId)\n\n    // 找不到空间 id 相关的空间组，不执行跳转\n    if (!spaceGroup) return\n\n    // 找到空间组，但是挂载的信息丢失了，抛错\n    if (!spaceGroup.userData.spaceConfig) throw new Error('vr360: spaceConfig 不存在')\n\n    this.currentSpaceId = targetSpaceId\n\n    const spaceConfig = spaceGroup.userData.spaceConfig as SpaceConfig\n    const {camera} = spaceConfig\n\n    // 添加目标空间进场景\n    if (!this.scene.children.includes(spaceGroup)) {\n      this.scene.add(spaceGroup)\n    }\n\n    // 从场景里移除其它空间\n    this.scene.children.forEach(child => {\n      if (child instanceof THREE.Group && child.userData.type === 'spaceGroup' && child !== spaceGroup) {\n        this.scene.remove(child)\n      }\n    })\n\n    // 场景动画结束时，添加小标签和白点\n    const handleCompleteTween = () => {\n      // 触发切换空间完成事件\n      this.emit('afterSwitchSpace', {spaceConfig: spaceConfig})\n    }\n\n    // 下一个镜头的信息\n    const nextCameraInfo = formatBaseInfo(camera)\n\n    if (clickPosition) {\n      // 当前相机的信息\n      const currentCameraInfo: ThreeObjectBase = {\n        ...get3dObjectBaseInfo(this.camera),\n        position: {\n          x: nextCameraInfo.position.x - (clickPosition.x - this.camera.position.x),\n          y: nextCameraInfo.position.y - (clickPosition.y - this.camera.position.y),\n          z: nextCameraInfo.position.z - (clickPosition.z - this.camera.position.z)\n        }\n      }\n\n      // 用 tween.js 添加镜头切换动画\n      new TWEEN.Tween(currentCameraInfo)\n        .to(nextCameraInfo, 500)\n        .onUpdate(cameraInfo => {\n          update3dObjectBaseInfo(this.camera, cameraInfo)\n        })\n        .easing(TWEEN.Easing.Linear.None)\n        .start()\n        .onComplete(() => {\n          handleCompleteTween()\n        })\n    } else {\n      update3dObjectBaseInfo(this.camera, nextCameraInfo)\n      handleCompleteTween()\n    }\n  }\n\n  /**\n   * 渲染\n   */\n  public render(): void {\n    requestAnimationFrame(this.render.bind(this))\n    this.controls.update()\n    this.handleUpdate()\n    TWEEN.update()\n  }\n\n  /**\n   * 每次渲染更新时执行一些东西\n   */\n  private handleUpdate(): void {\n    this.emit('update')\n    this.renderer.render(this.scene, this.camera)\n  }\n\n  /**\n   * 创建场景\n   * @returns 返回场景\n   */\n  private createScene(): THREE.Scene {\n    const scene = new THREE.Scene()\n\n    this.destroyTasks.push(() => {\n      scene.remove(...scene.children)\n    })\n\n    return scene\n  }\n\n  /**\n   * 创建相机\n   * @returns 返回相机\n   */\n  private createCamera(): THREE.PerspectiveCamera {\n    const camera = new THREE.PerspectiveCamera(75, this.containerWidth / this.containerHeight, 0.01, 10_000)\n    camera.position.set(0, 0, 0)\n\n    return camera\n  }\n\n  /**\n   * 创建 renderer 渲染器\n   * @returns 返回渲染器\n   */\n  private createRenderer(): THREE.WebGLRenderer {\n    const renderer = new THREE.WebGLRenderer({\n      antialias: true,\n      alpha: true\n    })\n    renderer.setClearColor(0xff_ff_ff, 0)\n    renderer.setPixelRatio(window.devicePixelRatio)\n    renderer.setSize(this.containerWidth, this.containerHeight)\n\n    this.destroyTasks.push(() => {\n      renderer.dispose()\n      renderer.forceContextLoss()\n      renderer.domElement.remove()\n    })\n\n    return renderer\n  }\n\n  /**\n   * 创建控制器\n   * @returns 返回控制器\n   */\n  private createControls(): OrbitControls {\n    const controls = new OrbitControls(this.camera, this.renderer.domElement)\n    controls.listenToKeyEvents(window)\n    controls.autoRotate = false\n    controls.autoRotateSpeed = 0.5\n    controls.enableZoom = false\n    controls.enableDamping = true\n    controls.enablePan = true\n    controls.enableRotate = true\n    controls.rotateSpeed = 0.5\n    controls.minDistance = 1\n    controls.maxDistance = 100\n    // controls.maxPolarAngle = Math.PI / 2\n    // controls.screenSpacePanning = false\n\n    // 控制器初始化位置为相机看到的位置\n    controls.target = new THREE.Vector3(0, 0, 1)\n    controls.update()\n\n    this.destroyTasks.push(() => {\n      controls.dispose()\n    })\n\n    return controls\n  }\n\n  /**\n   * 创建空间管理器\n   */\n  private createSpaceManager(): SpaceManager {\n    const spaceManager = new SpaceManager({\n      container: this.container,\n      camera: this.camera,\n      scene: this.scene,\n      renderer: this.renderer,\n      tipContainer: this.tipContainer,\n      textureCacheLoader: this.textureCacheLoader,\n      parent: this.scene\n    })\n\n    // 空间事件继承\n    extendsSpaceEventNames.forEach(eventName => {\n      spaceManager.on(eventName, e => {\n        // @ts-ignore\n        this.emit(eventName, e)\n      })\n    })\n\n    // 特殊处理\n    spaceManager.on('switchSpace', e => {\n      this.switchSpace(e.targetSpaceId, e.clickPosition)\n    })\n\n    return spaceManager\n  }\n\n  /**\n   * 把鼠标 x、y坐标转换为画布内的三维坐标\n   * @param x 鼠标 x 坐标\n   * @param y 鼠标 y 坐标\n   * @returns 返回三维坐标\n   */\n  public getPositionFromMouseXY(x: number, y: number): Position {\n    return convertMousePositionToThreePosition({\n      mouseX: x,\n      mouseY: y,\n      getDeps: () => ({\n        camera: this.camera,\n        renderer: this.renderer,\n        scene: this.scene\n      })\n    })\n  }\n\n  /**\n   * 销毁实例\n   */\n  public destroy(): void {\n    this.destroyTasks.forEach(task => task())\n    this.removeAllListeners()\n  }\n}\n"
  },
  {
    "path": "packages/vr360-core/test/index.ts",
    "content": "import {polyfillFetch} from '@nicepkg/vr360-shared/test-utils'\nexport * from '@nicepkg/vr360-shared/test-utils'\nexport * from '@nicepkg/vr360-shared/test-vue-utils'\n\npolyfillFetch()\n"
  },
  {
    "path": "packages/vr360-core/test/setup.ts",
    "content": "import {polyfillFetch, polyfillPointerEvents} from '@nicepkg/vr360-shared/test-utils'\nimport {setupVueSwitch} from '@nicepkg/vr360-shared/test-vue-utils'\nimport {beforeAll, beforeEach} from 'vitest'\n\npolyfillFetch()\npolyfillPointerEvents()\n\nsetupVueSwitch()\n\nbeforeAll(() => {\n  setupVueSwitch()\n})\n\nbeforeEach(() => {\n  document.body.innerHTML = ''\n  document.head.innerHTML = ''\n})\n"
  },
  {
    "path": "packages/vr360-core/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"types\": [\"vite/client\", \"vitest\", \"vitest/globals\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@test/*\": [\"./test/*\"]\n    }\n  },\n  \"include\": [\"./src/**/*\", \"./scripts/**/*\", \"./types/**/*\", \"./test/**/*\", \"./*.js\", \"./*.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "packages/vr360-core/vite.config.ts",
    "content": "/// <reference types=\"vitest\" />\nimport type {UserConfig} from 'vite'\nimport {defineConfig} from 'vite'\nimport {buildUtils} from '@nicepkg/vr360-shared'\n\nexport const packagePath = __dirname\n\ntype CreateViteConfigOptions = {\n  minify?: boolean\n}\n\nconst createViteConfig = (options: CreateViteConfigOptions = {}): UserConfig => {\n  const {minify = false} = options\n  return buildUtils.createViteConfig({\n    packagePath,\n    minify,\n    externalMap: {\n      three: 'THREE'\n    },\n    dedupe: ['three'] // use the same version\n  })\n}\n\n// default config and build prod config\nexport const unMinifyConfig = createViteConfig({minify: false})\n\n// build prod and build prod config\nexport const minifyConfig = createViteConfig({minify: true})\n\n// for vitest\nexport default defineConfig(unMinifyConfig)\n"
  },
  {
    "path": "packages/vr360-shared/CHANGELOG.md",
    "content": "# 0.3.0 (2022-10-08)\n\n### Bug Fixes\n\n- **@nicepkg/vr360-core:** 修复移动端的提示移动 bug ([7090a98](https://github.com/nicepkg/vr360/commit/7090a9805bc472240de6ad4467bbadc14fd6151d))\n\n# 0.3.0 (2022-10-02)\n\n## 0.2.1 (2022-10-02)\n\n# 0.2.0 (2022-10-02)\n\n### Bug Fixes\n\n- 修复 vr360-core tips 在滑动时的 bug ([e179ebc](https://github.com/nicepkg/vr360/commit/e179ebc2697314bc455320eecf8beb6182a53ded))\n\n### Features\n\n- 暴露更多的事件，设置默认自动旋转 ([4e21c53](https://github.com/nicepkg/vr360/commit/4e21c53ac945532020a7fbbfa46644294c33b49d))\n- 初始化项目 ([06f39d1](https://github.com/nicepkg/vr360/commit/06f39d141004a1d0b1a125ad598298baf15ffee8))\n- 添加标签提示和空间切换功能 ([6aa67d3](https://github.com/nicepkg/vr360/commit/6aa67d39113b06d05036ecc1d66ab1d70a2f4cf5))\n- 添加如视 vr 导入支持 ([5a9eb5d](https://github.com/nicepkg/vr360/commit/5a9eb5d7a33d092be8cea5565490f268376d2f79))\n- 文档添加示例插件、core 的纹理 top、bottom 改为 up 和 down ([70ccb2c](https://github.com/nicepkg/vr360/commit/70ccb2c40a06079ffb3cf50b27f986ab19b1f7db))\n- 修改 package.json 说明 ([e5e4ad0](https://github.com/nicepkg/vr360/commit/e5e4ad04b1c7a9cddff3af6f73d438fd1de85b25))\n- 优化代码 ([4519c4a](https://github.com/nicepkg/vr360/commit/4519c4a0fb230bb62bed49f97e0824c8977be3ca))\n- 优化 sdk，添加纹理缓存预加载 ([9dd57e7](https://github.com/nicepkg/vr360/commit/9dd57e71b8f5a38ecc9901395a9b189481172edb))\n- 重写核心，添加最小化更新配置支持 ([289091b](https://github.com/nicepkg/vr360/commit/289091b13dfe0495b44ae9e5353b78d2712e9762))\n"
  },
  {
    "path": "packages/vr360-shared/README.md",
    "content": "# Vr360-shared\n\n内部实用功能的共享包，包括构建和测试功能。\n"
  },
  {
    "path": "packages/vr360-shared/build.config.ts",
    "content": "import {defineBuildConfig} from 'unbuild'\n\nexport default defineBuildConfig({\n  entries: ['src/index', 'src/test-utils', 'src/test-vue-utils', 'src/test-react-utils'],\n  clean: true,\n  declaration: true,\n  externals: ['vite', 'vitest', 'vue-demi', 'react'],\n  rollup: {\n    emitCJS: true,\n    cjsBridge: true\n  }\n})\n"
  },
  {
    "path": "packages/vr360-shared/package.json",
    "content": "{\n  \"name\": \"@nicepkg/vr360-shared\",\n  \"version\": \"0.3.1\",\n  \"description\": \"A shared package of internal utility functions, including build and test functions.\",\n  \"keywords\": [\n    \"vr360-shared\",\n    \"nicepkg\",\n    \"utils\"\n  ],\n  \"author\": \"yangjinming <https://github.com/2214962083>\",\n  \"funding\": \"https://github.com/sponsors/2214962083\",\n  \"license\": \"MIT\",\n  \"private\": false,\n  \"scripts\": {\n    \"build\": \"pnpm type-check &&unbuild\",\n    \"build:watch\": \"pnpm build -- --stub\",\n    \"clean\": \"rimraf ./dist/**/*\",\n    \"type-check\": \"tsc --noEmit\"\n  },\n  \"main\": \"./dist/index.cjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"require\": \"./dist/index.cjs\",\n      \"import\": \"./dist/index.mjs\",\n      \"types\": \"./dist/index.d.ts\"\n    },\n    \"./*\": \"./*\",\n    \"./test-utils\": {\n      \"require\": \"./dist/test-utils.cjs\",\n      \"import\": \"./dist/test-utils.mjs\",\n      \"types\": \"./dist/test-utils.d.ts\"\n    },\n    \"./test-vue-utils\": {\n      \"require\": \"./dist/test-vue-utils.cjs\",\n      \"import\": \"./dist/test-vue-utils.mjs\",\n      \"types\": \"./dist/test-vue-utils.d.ts\"\n    },\n    \"./test-react-utils\": {\n      \"require\": \"./dist/test-react-utils.cjs\",\n      \"import\": \"./dist/test-react-utils.mjs\",\n      \"types\": \"./dist/test-react-utils.d.ts\"\n    }\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": false,\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/nicepkg/vr360.git\",\n    \"directory\": \"packages/vr360-shared\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/nicepkg/vr360/issues\"\n  },\n  \"homepage\": \"https://github.com/nicepkg/vr360#readme\",\n  \"peerDependencies\": {\n    \"vite\": \"*\",\n    \"vitest\": \"*\",\n    \"vue-demi\": \"*\"\n  },\n  \"peerDependenciesMeta\": {\n    \"vite\": {\n      \"optional\": true\n    },\n    \"vitest\": {\n      \"optional\": true\n    },\n    \"vue-demi\": {\n      \"optional\": true\n    },\n    \"react\": {\n      \"optional\": true\n    },\n    \"react-dom\": {\n      \"optional\": true\n    },\n    \"react-router-dom\": {\n      \"optional\": true\n    },\n    \"@testing-library/jest-dom\": {\n      \"optional\": true\n    }\n  },\n  \"dependencies\": {\n    \"@testing-library/react\": \"*\",\n    \"@testing-library/user-event\": \"^14.4.3\",\n    \"@types/fs-extra\": \"^9.0.13\",\n    \"@types/mockjs\": \"^1.0.7\",\n    \"@types/node\": \"*\",\n    \"@types/node-fetch\": \"^2.6.2\",\n    \"@types/rimraf\": \"^3.0.2\",\n    \"@vitejs/plugin-react\": \"^2.1.0\",\n    \"chalk\": \"4.1.2\",\n    \"fs-extra\": \"^10.1.0\",\n    \"globby\": \"11.1.0\",\n    \"mockjs\": \"^1.1.0\",\n    \"msw\": \"^0.39.2\",\n    \"node-fetch\": \"2.6.7\",\n    \"ora\": \"5.4.1\",\n    \"rimraf\": \"^3.0.2\",\n    \"rollup-plugin-visualizer\": \"^5.8.2\",\n    \"unplugin-auto-import\": \"^0.11.2\",\n    \"vite-plugin-dts\": \"^1.6.1\",\n    \"vite-plugin-inspect\": \"0.6.1\",\n    \"vite-plugin-mock\": \"^2.9.6\",\n    \"vite-plugin-svgr\": \"^2.2.1\",\n    \"vite-tsconfig-paths\": \"^3.5.1\",\n    \"vitest-fetch-mock\": \"0.2.1\"\n  },\n  \"devDependencies\": {\n    \"@testing-library/dom\": \"*\",\n    \"@testing-library/jest-dom\": \"*\",\n    \"@types/react\": \"*\",\n    \"@types/react-dom\": \"*\",\n    \"@types/react-router-dom\": \"*\",\n    \"autoprefixer\": \"^10.4.12\",\n    \"conventional-changelog-cli\": \"*\",\n    \"cross-env\": \"^7.0.3\",\n    \"cssnano\": \"^5.1.13\",\n    \"esno\": \"*\",\n    \"postcss\": \"^8.4.16\",\n    \"react\": \"*\",\n    \"react-dom\": \"*\",\n    \"react-router-dom\": \"*\",\n    \"rollup\": \"^2.79.1\",\n    \"typescript\": \"*\",\n    \"unbuild\": \"^0.8.11\",\n    \"vite\": \"*\",\n    \"vitest\": \"*\",\n    \"vue\": \"^3.2.40\",\n    \"vue-demi\": \"*\",\n    \"vue2\": \"npm:vue@2.6.14\"\n  }\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/build-utils/build-script.util.ts",
    "content": "import type {InlineConfig, UserConfig, BuildOptions as ViteBuildOptions} from 'vite'\nimport {build as viteBuild} from 'vite'\nimport rimraf from 'rimraf'\nimport path from 'node:path'\nimport dts from 'vite-plugin-dts'\nimport type {PluginVisualizerOptions} from 'rollup-plugin-visualizer'\nimport bundleVisualizer from 'rollup-plugin-visualizer'\n\nexport const WATCH = Boolean(process.env.WATCH)\nexport const REPORT = Boolean(process.env.REPORT)\n\nexport type DtsOptions = Parameters<typeof dts>[0]\n\ntype GetItemType<T> = T extends (infer U)[] ? U : T\ntype RollupOutput = NonNullable<NonNullable<ViteBuildOptions['rollupOptions']>['output']>\nexport type OutputOptions = GetItemType<RollupOutput>\n\nexport type ChangeConfigOptions = {\n  genDts?: boolean\n  dtsOptions?: DtsOptions\n  report?: boolean\n  reportOptions?: (outputOptions: OutputOptions) => PluginVisualizerOptions\n  watch?: boolean\n  packagePath: string\n}\n\nexport function changeViteConfig(config: UserConfig, options: ChangeConfigOptions): InlineConfig {\n  const {genDts = false, watch = false, dtsOptions, packagePath, report = false, reportOptions} = options\n\n  const pathResolve = (..._path: string[]) => path.resolve(packagePath, ..._path)\n\n  if (!config.build) config.build = {}\n  if (!config.build.rollupOptions) config.build.rollupOptions = {}\n  if (!config.build.rollupOptions.plugins) config.build.rollupOptions.plugins = []\n  if (!config.plugins) config.plugins = []\n\n  // don not clear dist folder\n  config.build.emptyOutDir = false\n\n  if (genDts) {\n    config.plugins.push(\n      dts({\n        insertTypesEntry: true,\n        tsConfigFilePath: pathResolve('./tsconfig.json'),\n        ...dtsOptions\n      })\n    )\n  }\n\n  if (report) {\n    config.build.rollupOptions.plugins.push(\n      bundleVisualizer(outputOptions => {\n        return {\n          open: true,\n          filename: path.join(outputOptions.dir ?? '', 'stats.html'),\n          ...reportOptions?.(outputOptions as OutputOptions)\n        }\n      }) as Plugin\n    )\n  }\n\n  if (watch) {\n    config.build.watch = {\n      include: pathResolve('./src/**/*')\n    }\n  }\n\n  return {\n    ...config,\n    configFile: false // don't use vite.config.ts\n  }\n}\n\nexport type ChangeConfigFn = (config: UserConfig, options: ChangeConfigOptions) => Promise<InlineConfig>\nexport type BuildOptions = {\n  minifyConfig: UserConfig\n  unMinifyConfig: UserConfig\n  packagePath: string\n  dtsOptions?: DtsOptions\n  reportOptions?: (outputOptions: OutputOptions) => PluginVisualizerOptions\n  changeConfigFn?: ChangeConfigFn\n}\n\nexport async function build(config: BuildOptions) {\n  const {\n    minifyConfig,\n    unMinifyConfig,\n    packagePath,\n    changeConfigFn = changeViteConfig,\n    dtsOptions,\n    reportOptions\n  } = config\n\n  const pathResolve = (..._path: string[]) => path.resolve(packagePath, ..._path)\n\n  // clear dist folder\n  rimraf.sync(pathResolve('./dist/**/*'))\n\n  if (!WATCH) {\n    // build minify, don't build in watch mode\n    await viteBuild(await changeConfigFn(minifyConfig, {packagePath, dtsOptions, report: REPORT, reportOptions}))\n  }\n\n  // build un minify\n  await viteBuild(\n    await changeConfigFn(unMinifyConfig, {\n      genDts: true,\n      watch: WATCH,\n      packagePath,\n      dtsOptions\n    })\n  )\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/build-utils/index.ts",
    "content": "export * from './build-script.util'\nexport * from './vite-config-common.util'\n"
  },
  {
    "path": "packages/vr360-shared/src/build-utils/vite-config-common.util.ts",
    "content": "/* eslint-disable @typescript-eslint/no-var-requires */\n/// <reference types=\"vitest\" />\nimport type {InlineConfig, LibraryFormats, UserConfig, PluginOption, Alias} from 'vite'\nimport path from 'node:path'\n\nexport const pascalCase = (str: string) =>\n  str\n    .split('-')\n    .map(s => s.charAt(0).toUpperCase() + s.slice(1))\n    .join('')\n\n// const pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\n\nconst getOutputIndexName = (minify: boolean) => (minify ? 'index.min' : 'index')\n\nexport const getOutputMap = (minify: boolean): Record<LibraryFormats, string> => ({\n  es: `${getOutputIndexName(minify)}.mjs`,\n  cjs: `${getOutputIndexName(minify)}.cjs`,\n  umd: `${getOutputIndexName(minify)}.umd.js`,\n  iife: `${getOutputIndexName(minify)}.iife.js`\n})\n\nexport type CreateViteConfigOptions = {\n  minify?: boolean\n  packagePath: string\n  formats?: LibraryFormats[]\n  externalMap?: Record<string, string>\n  dedupe?: string[] // use the same version\n  plugins?: PluginOption[]\n  alias?: Alias[]\n  test?: InlineConfig\n}\n\nexport const createViteConfig = (options: CreateViteConfigOptions): UserConfig => {\n  const {\n    minify = false,\n    packagePath,\n    formats = ['es', 'cjs', 'umd', 'iife'],\n    externalMap = {},\n    dedupe: _dedupe = [],\n    plugins = [],\n    alias: _alias = [],\n    test\n  } = options\n\n  const outputMap = Object.fromEntries(\n    Object.entries(getOutputMap(minify)).filter(([format]) => formats.includes(format as LibraryFormats))\n  ) as Record<LibraryFormats, string>\n\n  const pathResolve = (..._path: string[]) => path.resolve(packagePath, ..._path)\n\n  const pkg = require(pathResolve('./package.json')) as Record<string, string | object>\n  const getPkgName = () => pkg.name as string\n\n  const dedupe = [\n    'vue',\n    'vue-demi',\n    '@vue/runtime-core',\n    '@vue/runtime-dom',\n    'vue2',\n    '@vue/composition-api',\n    ..._dedupe\n  ] // use the same version\n\n  const alias: Alias[] = [\n    ..._alias,\n    {\n      find: /@(?=\\/)/,\n      replacement: pathResolve('src')\n    },\n    {\n      find: /^@test/,\n      replacement: pathResolve('test')\n    }\n  ]\n\n  return {\n    root: pathResolve('./'),\n    plugins,\n    resolve: {\n      dedupe,\n      alias\n    },\n    optimizeDeps: {\n      exclude: ['vue-demi']\n    },\n    css: {\n      postcss: {\n        plugins: [require('autoprefixer'), require('cssnano')]\n      },\n      preprocessorOptions: {\n        less: {\n          javascriptEnabled: true,\n          rewriteUrls: 'all'\n        }\n      }\n    },\n    build: {\n      outDir: pathResolve('dist'),\n      lib: {\n        entry: pathResolve('src/index.ts'),\n        name: pascalCase(getPkgName().split('/').pop() as string),\n        formats: Object.keys(outputMap) as LibraryFormats[],\n        fileName: format => outputMap[format as LibraryFormats]\n      },\n      rollupOptions: {\n        output: {\n          globals: externalMap,\n          chunkFileNames: () => {\n            return '[format]/[name].[format].js'\n          }\n        },\n        external: Object.keys(externalMap)\n      },\n      sourcemap: true,\n      minify\n    },\n    // for vitest\n    test: {\n      globals: true,\n      environment: 'jsdom',\n      setupFiles: [pathResolve('./test/setup.ts')],\n      reporters: 'dot',\n      deps: {\n        inline: ['vue', 'vue2', 'vue-demi', '@vue/composition-api']\n      },\n      ...test\n    }\n  } as UserConfig\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/index.ts",
    "content": "/* eslint-disable unicorn/prefer-export-from */\n\nimport chalk from 'chalk'\nimport * as msw from 'msw'\nimport * as globby from 'globby'\nimport * as ora from 'ora'\nimport * as nodeFetch from 'node-fetch'\nimport * as fsExtra from 'fs-extra'\nimport * as rimraf from 'rimraf'\nimport * as mock from 'mockjs'\nimport * as buildUtils from './build-utils'\n\n// vite\nimport * as viteReact from '@vitejs/plugin-react'\nimport * as viteInspect from 'vite-plugin-inspect'\nimport * as viteMock from 'vite-plugin-mock'\nimport * as viteTsconfigPaths from 'vite-tsconfig-paths'\nimport * as viteSvgr from 'vite-plugin-svgr'\n\nexport {\n  chalk,\n  msw,\n  globby,\n  ora,\n  nodeFetch,\n  fsExtra,\n  rimraf,\n  mock,\n  buildUtils,\n\n  // vite\n  viteReact,\n  viteInspect,\n  viteMock,\n  viteTsconfigPaths,\n  viteSvgr\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-react-utils.ts",
    "content": "// this file will build as a bundle\nexport * from './test-utils/react'\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/helper.util.ts",
    "content": "// Like `until` but works off of any assertion, not application code.\nexport const retry = (assertion: () => void, {interval = 1, timeout = 100} = {}) => {\n  return new Promise((resolve, reject) => {\n    const startTime = Date.now()\n\n    const tryAgain = () => {\n      setTimeout(() => {\n        try {\n          resolve(assertion())\n        } catch (error) {\n          Date.now() - startTime > timeout ? reject(error) : tryAgain()\n        }\n      }, interval)\n      try {\n        // If useFakeTimers hasn't been called, this will throw\n        vitest.advanceTimersByTime(interval)\n      } catch {\n        /* Expected to throw */\n      }\n    }\n\n    tryAgain()\n  })\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/index.ts",
    "content": "// this file will build as a bundle\nexport * from './helper.util'\nexport * from './mock-server.util'\nexport * from './polyfill-fetch.util'\nexport * from './polyfill-pointer-events.util'\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/mock-global-api.ts",
    "content": "export function mockGlobalApi() {\n  // Mock matchMedia\n  vi.stubGlobal('matchMedia', (query: string) => ({\n    matches: false,\n    media: query,\n    onchange: null,\n    addListener: vi.fn(),\n    removeListener: vi.fn(),\n    addEventListener: vi.fn(),\n    removeEventListener: vi.fn(),\n    dispatchEvent: vi.fn()\n  }))\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/mock-server.util.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * Network mocking with MSW.\n * Import this helper into the specific tests that need to make network requests.\n */\n\nimport {setupServer} from 'msw/node'\nimport type {RestContext, RestRequest} from 'msw'\nimport {rest} from 'msw'\n\nconst defaultJsonMessage = {hello: 'world'}\nconst defaultTextMessage = 'Hello World'\nconst baseUrl = 'https://example.com'\n\nconst commonTransformers = (req: RestRequest, _: any, ctx: RestContext) => {\n  const t = []\n  const qs = req.url.searchParams\n\n  if (qs.get('delay')) t.push(ctx.delay(Number(qs.get('delay'))))\n  if (qs.get('status')) t.push(ctx.status(Number(qs.get('status'))))\n  if (qs.get('text') != null) {\n    t.push(ctx.text(qs.get('text') ?? defaultTextMessage))\n  } else if (qs.get('json') != null) {\n    const jsonVal = qs.get('json')\n    const jsonTransformer = ctx.json(jsonVal?.length ? JSON.parse(jsonVal) : defaultJsonMessage)\n    t.push(jsonTransformer)\n  }\n  return t\n}\n\n/**\n * Allow the client to define the response body.\n * @example https://example.com?status=400 will respond with { status: 400 }.\n * @example https://example.com?json will respond with the default json message ({ hello: 'world' }).\n * @example https://example.com?text will respond with the default text message ('Hello World').\n * @example https://example.com?delay=1000 will respond in 1000ms.\n * @example https://example.com?status=301&text=thanks&delay=1000\n *          will respond in 1000ms with statusCode 300 and the response body \"thanks\" as a string\n */\nexport const createServer = () =>\n  setupServer(\n    rest.post(baseUrl, (req, res, ctx) => {\n      // Support all the normal examples (delay, status, text, and json)\n      const t = commonTransformers(req, res, ctx)\n\n      // Echo back the request payload\n      if (typeof req.body === 'number' || typeof req.body === 'string') t.push(ctx.text(String(req.body)))\n      else t.push(ctx.json(req.body))\n\n      return res(...t)\n    }),\n\n    rest.get(baseUrl, (req, res, ctx) => res(...commonTransformers(req, res, ctx))),\n\n    // Another duplicate route for the sole purpose of re-triggering requests on url change.\n    rest.get(`${baseUrl}/test`, (req, res, ctx) => res(...commonTransformers(req, res, ctx)))\n  )\n\nexport const mockServer = () => {\n  const server = createServer()\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n  return server\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/polyfill-fetch.util.ts",
    "content": "import nodeFetch from 'node-fetch'\n\nexport const polyfillFetch = () => {\n  // @ts-expect-error override\n  window.fetch = nodeFetch\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/polyfill-pointer-events.util.ts",
    "content": "export const polyfillPointerEvents = () => {\n  /* eslint-disable @typescript-eslint/no-explicit-any */\n  // polyfill for jsdom (https://github.com/jsdom/jsdom/pull/2666)\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (!global.PointerEvent) {\n    class PointerEvent extends MouseEvent {\n      public pointerId?: number\n\n      constructor(type: string, params: PointerEventInit = {}) {\n        super(type, params)\n        this.pointerId = params.pointerId\n      }\n    }\n    global.PointerEvent = PointerEvent as any\n  }\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/react/index.ts",
    "content": "/* eslint-disable unicorn/prefer-export-from */\n// this file will build as a bundle\n// all of react utils\nimport * as testLibReact from '@testing-library/react'\nimport * as testLibUserEvent from '@testing-library/user-event'\n\nexport * from './react-helper.util'\nexport * from './react-mount.util'\n\nexport {testLibReact, testLibUserEvent}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/react/react-helper.util.ts",
    "content": "import {mockGlobalApi} from '../common/mock-global-api'\nimport createFetchMock from 'vitest-fetch-mock'\n\nexport function commonSetup(options?: {title?: string}) {\n  document.title = options?.title ?? 'React Starter'\n\n  // Mock fetch\n  createFetchMock(vi).enableMocks()\n\n  mockGlobalApi()\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/react/react-mount.util.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\nexport function mount() {\n  console.log('mount')\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/vue/index.ts",
    "content": "// this file will build as a bundle\n// all of vue-demi utils\nexport * from './vue-helper.util'\nexport * from './vue-mount.util'\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/vue/vue-helper.util.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {FunctionalComponent} from 'vue-demi'\nimport {isVue2, Vue2, install} from 'vue-demi'\n\nexport const nextTwoTick = () =>\n  new Promise<void>(resolve => {\n    setTimeout(() => {\n      setTimeout(resolve)\n    })\n  })\n\nexport function setProps(props: Record<string, any>) {\n  const {style, class: className, ...otherProps} = props\n  return isVue2\n    ? {\n        style,\n        class: className,\n        props: otherProps\n      }\n    : {\n        style,\n        class: className,\n        ...props\n      }\n}\n\nexport function createFunctionComponent(fn: (...args: any[]) => any) {\n  return isVue2\n    ? {\n        functional: true,\n        render: fn\n      }\n    : (fn as FunctionalComponent)\n}\n\n/**\n * get functional component slots\n * @param ctx functional component context\n * @returns slots\n */\nexport function getSlots(ctx: any, slotName: string) {\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n  const slots: Record<string, any> = isVue2 ? ctx.slots?.() : ctx.slots\n  const slot = slots[slotName]\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call\n  return typeof slot === 'function' ? slot() : slot\n}\n\nexport function setupVueSwitch() {\n  if (isVue2) {\n    Vue2.config.productionTip = false\n    Vue2.config.devtools = false\n    install(Vue2) // install vue composition-api\n  }\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/vue/vue-mount.util.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {Component, InjectionKey, Ref} from 'vue-demi'\nimport {createApp, defineComponent, h, provide, ref, resolveComponent, isVue2, isVue3} from 'vue-demi'\n\ntype InstanceType<V> = V extends new (...arg: any[]) => infer X ? X : never\nexport type VM<V> = InstanceType<V> & {unmount(): void}\n\nexport function find(selector: string, target?: HTMLElement | null | undefined) {\n  const el = target?.querySelector<HTMLElement>(selector)\n  return {\n    el,\n    find: (selector: string) => find(selector, el),\n    text() {\n      return el?.textContent ?? ''\n    },\n    html() {\n      return el?.innerHTML ?? ''\n    }\n  }\n}\n\nexport type SnapType = 'all' | 'vue2' | 'vue3'\n\ntype MountResult<V> = VM<V> & {\n  find: typeof find\n  toSnap: (type?: SnapType) => void\n}\n\nexport function mount<V extends Component>(Comp: V): MountResult<V> {\n  const el = document.createElement('div')\n  document.body.append(el)\n  const app = createApp(Comp)\n\n  const unmount = () => {\n    app.unmount()\n    el.remove()\n  }\n\n  const comp = app.mount(el) as any as VM<V>\n  comp.unmount = unmount\n\n  const _find = (selector: string) => {\n    const el = (comp as any).$el as HTMLElement\n    return find(selector, el.parentElement)\n  }\n\n  const toSnap = (type: SnapType = 'vue3') => {\n    const shouldSnap = type === 'all' || (type === 'vue2' && isVue2) || (type === 'vue3' && isVue3)\n    if (shouldSnap) {\n      const el = (comp as any).$el as HTMLElement\n      expect(el).toMatchSnapshot()\n    }\n  }\n\n  Object.assign(comp, {find: _find, toSnap})\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n  return comp as MountResult<V>\n}\n\n/**\n * get component for h first argument\n * @param tagName component tag name\n * @returns component for h first argument\n */\nexport function getComponent(tagName: string) {\n  return isVue2 ? tagName : resolveComponent(tagName)\n}\n\nexport function useSetup<V>(setup: () => V) {\n  const Comp = defineComponent({\n    setup,\n    render() {\n      return h('div', [])\n    }\n  })\n\n  return mount(Comp)\n}\n\nexport const Key: InjectionKey<Ref<number>> = Symbol('num')\n\nexport function useInjectedSetup<V>(setup: () => V) {\n  const Comp = defineComponent({\n    setup,\n    render() {\n      return h('div', [])\n    }\n  })\n\n  const Provider = defineComponent({\n    components: Comp,\n    setup() {\n      provide(Key, ref(1))\n    },\n    render() {\n      return h('div', [])\n    }\n  })\n\n  return mount(Provider)\n}\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils.ts",
    "content": "// this file will build as a bundle\nexport * from './test-utils/common'\n"
  },
  {
    "path": "packages/vr360-shared/src/test-vue-utils.ts",
    "content": "// this file will build as a bundle\nexport * from './test-utils/vue'\n"
  },
  {
    "path": "packages/vr360-shared/test-react-utils.d.ts",
    "content": "export * from './dist/test-react-utils'\n"
  },
  {
    "path": "packages/vr360-shared/test-utils.d.ts",
    "content": "export * from './dist/test-utils'\n"
  },
  {
    "path": "packages/vr360-shared/test-vue-utils.d.ts",
    "content": "export * from './dist/test-vue-utils'\n"
  },
  {
    "path": "packages/vr360-shared/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"types\": [\"vite/client\", \"vitest\", \"vitest/globals\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@test/*\": [\"./test/*\"]\n    }\n  },\n  \"include\": [\"./src/**/*\", \"./scripts/**/*\", \"./types/**/*\", \"./test/**/*\", \"./*.js\", \"./*.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "packages/vr360-shared/types/global.d.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\ndeclare global {\n  type Writable<T> = {\n    -readonly [P in keyof T]: T[P]\n  }\n\n  type DeepPartial<T> = {\n    [P in keyof T]?: DeepPartial<T[P]>\n  }\n\n  type TimeoutHandle = ReturnType<typeof setTimeout>\n  type IntervalHandle = ReturnType<typeof setInterval>\n\n  function parseInt(s: string | number, radix?: number): number\n\n  function parseFloat(string: string | number): number\n}\n\nexport {}\n"
  },
  {
    "path": "packages/vr360-ui/README.md",
    "content": "# 开发中\n"
  },
  {
    "path": "packages/vr360-ui-react/README.md",
    "content": "# 开发中\n"
  },
  {
    "path": "packages/vr360-ui-vue2/README.md",
    "content": "# 开发中\n"
  },
  {
    "path": "packages/vr360-ui-vue3/README.md",
    "content": "# 开发中\n"
  },
  {
    "path": "playgrounds/react/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>React App</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n  <script>\n    (function () {\n      // ios safari 禁止缩放\n      document.addEventListener('touchmove', function(event) {\n        event = event.originalEvent || event;\n        if(event.scale !== undefined && event.scale !== 1) {\n          event.preventDefault();\n        }\n      }, false);\n    })()\n  </script>\n</head>\n<body class=\"font-sans\">\n  <noscript>\n    <strong>We're sorry but web-cli doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n  </noscript>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"/src/main.tsx\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "playgrounds/react/package.json",
    "content": "{\n  \"name\": \"playground-react\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"concurrently -r -k -g 'npm:build:deps' 'npm:dev:vite'\",\n    \"dev:vite\": \"vite\",\n    \"build\": \"vite build\",\n    \"build:deps\": \"pnpm --filter @nicepkg/vr360-core build:watch\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@nicepkg/vr360-core\": \"workspace:*\",\n    \"clsx\": \"^1.2.1\",\n    \"react\": \"*\",\n    \"react-dom\": \"*\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^18.7.23\",\n    \"@types/react\": \"*\",\n    \"@types/react-dom\": \"*\",\n    \"@unocss/reset\": \"^0.45.26\",\n    \"@vitejs/plugin-react\": \"^2.1.0\",\n    \"concurrently\": \"*\",\n    \"typescript\": \"*\",\n    \"unocss\": \"^0.45.26\",\n    \"vite\": \"*\"\n  }\n}\n"
  },
  {
    "path": "playgrounds/react/src/Example.tsx",
    "content": "function Example() {\n  return <div className=\"bg-gray-100\">Example</div>\n}\n\nexport default Example\n"
  },
  {
    "path": "playgrounds/react/src/main.tsx",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport Example from './Example'\n\nimport '@unocss/reset/tailwind.css'\nimport 'uno.css'\n\nReactDOM.render(\n  <React.StrictMode>\n    <Example />\n  </React.StrictMode>,\n  document.querySelector('#app')\n)\n"
  },
  {
    "path": "playgrounds/react/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"jsx\": \"react-jsx\",\n    \"types\": [\"vite/client\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"./src/**/*\", \"./scripts/**/*\", \"./types/**/*\", \"./test/**/*\", \"./*.js\", \"./*.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "playgrounds/react/vite.config.ts",
    "content": "import {defineConfig} from 'vite'\nimport path from 'node:path'\nimport viteReact from '@vitejs/plugin-react'\nimport viteUnocss from 'unocss/vite'\n\nconst pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\n\nexport default defineConfig({\n  root: pathResolve('./'),\n  plugins: [\n    viteReact(),\n\n    // https://github.com/antfu/unocss\n    // 有关配置，请参见 unocss.config.ts\n    viteUnocss()\n  ],\n  server: {\n    host: true\n  }\n})\n"
  },
  {
    "path": "playgrounds/vue2/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>Vue2 App</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n  <script>\n    (function () {\n      // ios safari 禁止缩放\n      document.addEventListener('touchmove', function(event) {\n        event = event.originalEvent || event;\n        if(event.scale !== undefined && event.scale !== 1) {\n          event.preventDefault();\n        }\n      }, false);\n    })()\n  </script>\n</head>\n<body class=\"font-sans\">\n  <noscript>\n    <strong>We're sorry but web-cli doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n  </noscript>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"/src/main.ts\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "playgrounds/vue2/package.json",
    "content": "{\n  \"name\": \"playground-vue2\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"concurrently -r -k -g 'npm:build:deps' 'npm:dev:vite'\",\n    \"dev:vite\": \"vite\",\n    \"build\": \"vite build\",\n    \"build:deps\": \"pnpm --filter @nicepkg/vr360-core build:watch \",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@nicepkg/vr360-core\": \"workspace:*\",\n    \"@vue/composition-api\": \"^1.7.1\",\n    \"vue\": \"2.6.14\",\n    \"vue-demi\": \"*\",\n    \"vue-template-compiler\": \"2.6.14\"\n  },\n  \"devDependencies\": {\n    \"@unocss/reset\": \"^0.45.22\",\n    \"@vue/runtime-dom\": \"latest\",\n    \"concurrently\": \"*\",\n    \"unocss\": \"^0.45.22\",\n    \"unplugin-vue2-script-setup\": \"^0.11.3\",\n    \"vite\": \"^2.9.9\",\n    \"vite-plugin-vue2\": \"^2.0.2\"\n  }\n}\n"
  },
  {
    "path": "playgrounds/vue2/src/App.vue",
    "content": "<template>\n  <div class=\"relative w-100vw h-100vh overflow-hidden flex flex-col\">\n    <div\n      ref=\"tipRef\"\n      class=\"tip absolute rounded-4px left-0 top-0 cursor-pointer z-99 p-4 flex flex-col w-240px h-60px justify-center bg-black bg-op-50 text-white\"\n      :style=\"{\n        transform: `translate(${tipLeft}px, ${tipTop + 60}px)`,\n        zIndex: showTip ? 99 : -1,\n        visibility: showTip ? 'visible' : 'hidden'\n      }\"\n    >\n      <div class=\"tip-title font-bold\">{{ tipTitle }}</div>\n      <div class=\"tip-content\">{{ tipContent }}</div>\n    </div>\n    <div ref=\"containerRef\" class=\"w-full h-full\"></div>\n    <div class=\"w-full h-100px flex items-center\">\n      <div\n        v-for=\"space in spacesConfig\"\n        :key=\"space.id\"\n        class=\"w-140px h-70px rounded-4px cursor-pointer ml-4\"\n        @click=\"handleSwitchSpace(space)\"\n      >\n        <img class=\"w-full h-full object-cover rounded-4px\" :src=\"space.cubeSpaceTextureUrls.left\" />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\n/* eslint-disable import/no-named-as-default-member */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\nimport {onMounted, onUnmounted, ref} from '@vue/composition-api'\nimport type {SpaceConfig} from '@nicepkg/vr360-core'\nimport {Vr360} from '@nicepkg/vr360-core'\nimport textures from '../../../textures.json'\n\nconst containerRef = ref<HTMLElement>()\n\nconst tipRef = ref<HTMLElement>()\nconst tipLeft = ref(0)\nconst tipTop = ref(0)\nconst showTip = ref(false)\nconst tipTitle = ref('')\nconst tipContent = ref('')\n\nlet vr360: InstanceType<typeof Vr360>\n\nconst spacesConfig = ref<SpaceConfig[]>([\n  {\n    id: 'spaceA',\n    tips: [\n      {\n        id: '1',\n        position: {x: 0, y: -10, z: 40},\n        content: {\n          title: '豪华跑车',\n          text: '比奥迪还贵的豪华跑车'\n        }\n      },\n      {\n        id: '2',\n        textureUrl: 'picture/hotpot.png',\n        targetSpaceId: 'spaceB',\n        position: {x: -10, y: -4, z: 40},\n        content: {\n          title: '去客厅',\n          text: '一起去尊贵的客厅吧'\n        }\n      }\n    ],\n    cubeSpaceTextureUrls: textures.firstHouseDoor\n  },\n  {\n    id: 'spaceB',\n    tips: [\n      {\n        id: '3',\n        position: {x: -2, y: -25, z: 40},\n        content: {\n          title: '香奈儿垃圾桶',\n          text: '里面装着主人不用的奢侈品'\n        }\n      },\n      {\n        id: '4',\n        position: {x: -20, y: 0, z: 40},\n        content: {\n          title: '宇宙牌冰箱',\n          text: '装着超级多零食'\n        }\n      },\n      {\n        id: '5',\n        textureUrl: 'picture/hotpot.png',\n        targetSpaceId: 'spaceA',\n        position: {\n          x: -8,\n          y: 0,\n          z: -40\n        },\n        content: {\n          title: '去门口',\n          text: '一起去门口吧'\n        }\n      }\n    ],\n    cubeSpaceTextureUrls: textures.firstHouseLivingRoom\n  }\n])\n\nfunction handleSwitchSpace(space: SpaceConfig) {\n  vr360.switchSpace(space.id)\n}\n\nonMounted(() => {\n  vr360 = new Vr360({\n    container: containerRef.value!,\n    tipContainer: tipRef.value!,\n    spacesConfig: spacesConfig.value\n  })\n\n  vr360.controls.autoRotate = true\n\n  vr360.render()\n\n  vr360.listenResize()\n\n  vr360.on('showTip', e => {\n    vr360!.controls.autoRotate = false\n    const {top, left, tip} = e\n    showTip.value = true\n    tipLeft.value = left\n    tipTop.value = top\n    tipTitle.value = tip.content.title\n    tipContent.value = tip.content.text\n  })\n\n  vr360.on('hideTip', () => {\n    vr360!.controls.autoRotate = true\n    showTip.value = false\n  })\n})\n\nonUnmounted(() => {\n  vr360.destroy()\n})\n</script>\n"
  },
  {
    "path": "playgrounds/vue2/src/main.ts",
    "content": "import Vue from 'vue'\nimport App from './App.vue'\nimport VueCompositionAPI from '@vue/composition-api'\n\n// css\nimport 'uno.css'\nimport '@unocss/reset/tailwind.css'\n\nVue.use(VueCompositionAPI)\n\nVue.config.productionTip = false\n\nnew Vue({\n  render: h => h(App)\n}).$mount('#app')\n"
  },
  {
    "path": "playgrounds/vue2/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"types\": [\"vite/client\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"vueCompilerOptions\": {\n    \"experimentalCompatMode\": 2,\n    \"target\": 2,\n    \"experimentalTemplateCompilerOptions\": {\n      \"compatConfig\": {\"MODE\": 2} // optional\n    }\n  },\n  \"include\": [\"./src/**/*\", \"./scripts/**/*\", \"./types/**/*\", \"./*.js\", \"./*.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "playgrounds/vue2/types/module.d.ts",
    "content": "declare module '*.vue' {\n  import type {VueConstructor} from 'vue'\n  const component: VueConstructor\n  export default component\n}\n\ndeclare module 'vue-demi/scripts/utils.js' {\n  /**\n   * switch vue version\n   */\n  const switchVersion: (vueVersion: 2 | 3 | number) => void\n}\n"
  },
  {
    "path": "playgrounds/vue2/unocss.config.ts",
    "content": "import {\n  defineConfig,\n  presetAttributify,\n  presetIcons,\n  presetUno,\n  transformerDirectives,\n  transformerVariantGroup\n} from 'unocss'\n\nexport default defineConfig({\n  shortcuts: [\n    [\n      'btn',\n      'px-4 py-1 rounded inline-block bg-teal-700 text-white cursor-pointer hover:bg-teal-800 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'\n    ],\n    [\n      'icon-btn',\n      'inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-teal-600'\n    ]\n  ],\n  presets: [\n    presetUno(),\n    presetAttributify(),\n    presetIcons({\n      scale: 1.2,\n      warn: true\n    })\n  ],\n  transformers: [transformerDirectives(), transformerVariantGroup()],\n  safelist: 'prose prose-sm m-auto text-left'.split(' ')\n})\n"
  },
  {
    "path": "playgrounds/vue2/vite.config.ts",
    "content": "import path from 'node:path'\nimport {defineConfig} from 'vite'\nimport {createVuePlugin} from 'vite-plugin-vue2'\nimport ScriptSetup from 'unplugin-vue2-script-setup/vite'\nimport Unocss from 'unocss/vite'\nimport {switchVersion} from 'vue-demi/scripts/utils.js'\n\nconst pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\n\nswitchVersion(2)\n\nexport default defineConfig({\n  server: {\n    host: true\n  },\n  plugins: [\n    createVuePlugin({\n      jsx: true,\n      jsxOptions: {\n        compositionAPI: true\n      }\n    }),\n    ScriptSetup(), // supports vue3 setup sugar\n\n    // https://github.com/antfu/unocss\n    // 有关配置，请参见 unocss.config.ts\n    Unocss()\n  ],\n  resolve: {\n    dedupe: ['vue', 'vue-demi', '@vue/runtime-core', '@vue/runtime-dom'], // use the same version\n    alias: {\n      '@/': `${pathResolve('./src')}/`,\n      vue: pathResolve('./node_modules/vue/dist/vue.esm.js') // use the same version, also use runtime template compiler\n    }\n  }\n})\n"
  },
  {
    "path": "playgrounds/vue3/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>Vue3 App</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover\">\n  <script>\n    (function () {\n      // ios safari 禁止缩放\n      document.addEventListener('touchmove', function(event) {\n        event = event.originalEvent || event;\n        if(event.scale !== undefined && event.scale !== 1) {\n          event.preventDefault();\n        }\n      }, false);\n    })()\n  </script>\n</head>\n<body class=\"font-sans\">\n  <noscript>\n    <strong>We're sorry but web-cli doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n  </noscript>\n  <div id=\"app\"></div>\n  <script type=\"module\" src=\"/src/main.ts\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "playgrounds/vue3/package.json",
    "content": "{\n  \"name\": \"playground-vue3\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"concurrently -r -k -g 'npm:build:deps' 'npm:dev:vite'\",\n    \"dev:vite\": \"vite\",\n    \"build\": \"vite build\",\n    \"build:deps\": \"pnpm --filter @nicepkg/vr360-core build:watch\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@nicepkg/vr360-core\": \"workspace:*\",\n    \"qs\": \"^6.11.0\",\n    \"vue\": \"^3.2.40\",\n    \"vue-demi\": \"*\"\n  },\n  \"devDependencies\": {\n    \"@types/qs\": \"^6.9.7\",\n    \"@unocss/reset\": \"^0.45.22\",\n    \"@vitejs/plugin-vue\": \"^3.1.0\",\n    \"@vitejs/plugin-vue-jsx\": \"^2.0.1\",\n    \"concurrently\": \"*\",\n    \"typescript\": \"*\",\n    \"unocss\": \"^0.45.22\",\n    \"vite\": \"*\"\n  }\n}\n"
  },
  {
    "path": "playgrounds/vue3/src/App.vue",
    "content": "<template>\n  <div class=\"w-100vw h-100vh\">\n    <Editor>\n      <div class=\"relative w-full h-full overflow-hidden flex flex-col\">\n        <div\n          ref=\"tipRef\"\n          class=\"tip absolute rounded-4px left-0 top-0 cursor-pointer z-99 p-4 flex flex-col w-240px h-60px justify-center bg-black bg-op-50 text-white\"\n          :style=\"{\n            transform: `translate(${tipLeft}px, ${tipTop + 60}px)`,\n            zIndex: showTip ? 99 : -1,\n            visibility: showTip ? 'visible' : 'hidden'\n          }\"\n        >\n          <div class=\"tip-title font-bold\">{{ tipTitle }}</div>\n          <div class=\"tip-content\">{{ tipContent }}</div>\n        </div>\n        <div ref=\"containerRef\" class=\"w-full flex-1\"></div>\n        <!-- <div class=\"w-full h-100px flex items-center flex-shrink-0\">\n          <div\n            v-for=\"space in spacesConfig\"\n            :key=\"space.id\"\n            class=\"w-140px h-70px rounded-4px cursor-pointer ml-4\"\n            @click=\"handleSwitchSpace(space)\"\n          >\n            <img class=\"w-full h-full object-cover rounded-4px\" :src=\"space.cubeSpaceTextureUrls.left\" />\n          </div>\n        </div> -->\n      </div>\n    </Editor>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\n/* eslint-disable import/no-named-as-default-member */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-unused-vars */\nimport {ref} from 'vue'\nimport type {SpaceConfig} from '@nicepkg/vr360-core'\nimport Editor from './Editor.vue'\nimport {useVr360} from './useVr360'\nimport textures from '../../../textures.json'\n\nconst containerRef = ref<HTMLElement>()\nconst tipRef = ref<HTMLElement>()\n\n// eslint-disable-next-line react-hooks/rules-of-hooks\nconst {tipContent, tipTitle, tipLeft, tipTop, showTip, vr360, spacesConfig} = useVr360({\n  containerEl: containerRef,\n  tipEl: tipRef,\n  spacesConfig: [\n    {\n      id: 'spaceA',\n      tips: [\n        {\n          id: '1',\n          position: {x: 0, y: -10, z: 40},\n          content: {\n            title: '豪华跑车',\n            text: '比奥迪还贵的豪华跑车'\n          }\n        },\n        {\n          id: '2',\n          textureUrl: 'picture/hotpot.png',\n          targetSpaceId: 'spaceB',\n          position: {x: -10, y: -4, z: 40},\n          content: {\n            title: '去客厅',\n            text: '一起去尊贵的客厅吧'\n          }\n        }\n      ],\n      cubeSpaceTextureUrls: textures.firstHouseDoor\n    },\n    {\n      id: 'spaceB',\n      tips: [\n        {\n          id: '3',\n          position: {x: -2, y: -25, z: 40},\n          content: {\n            title: '香奈儿垃圾桶',\n            text: '里面装着主人不用的奢侈品'\n          }\n        },\n        {\n          id: '4',\n          position: {x: -20, y: 0, z: 40},\n          content: {\n            title: '宇宙牌冰箱',\n            text: '装着超级多零食'\n          }\n        },\n        {\n          id: '5',\n          textureUrl: 'picture/hotpot.png',\n          targetSpaceId: 'spaceA',\n          position: {\n            x: -8,\n            y: 0,\n            z: -40\n          },\n          content: {\n            title: '去门口',\n            text: '一起去门口吧'\n          }\n        }\n      ],\n      cubeSpaceTextureUrls: textures.firstHouseLivingRoom\n    }\n  ]\n})\n\nconsole.log(spacesConfig)\n\nfunction handleSwitchSpace(space: SpaceConfig) {\n  vr360.value?.switchSpace(space.id)\n}\n</script>\n"
  },
  {
    "path": "playgrounds/vue3/src/ContextMenu.vue",
    "content": "<template>\n  <div\n    v-show=\"showContextMenu\"\n    ref=\"contextMenuRef\"\n    class=\"vr360-context-menu absolute z-999 text-sm bg-white shadow rounded-4px overflow-hidden\"\n    :style=\"{\n      top: `${contextMenuTop}px`,\n      left: `${contextMenuLeft}px`\n    }\"\n  >\n    <div\n      v-for=\"(menu, index) in contextMenus\"\n      :key=\"index\"\n      class=\"vr360-context-menu-item w-full h-40px flex items-center p-4 hover:bg-light-5 cursor-pointer\"\n    >\n      <div class=\"vr360-context-menu-item-icon mr-2\">\n        <component :is=\"menu.icon\" class=\"w-4 h-4\"></component>\n      </div>\n      <div class=\"vr360-context-menu-item-text\">\n        {{ menu.title }}\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport type {Position} from '@nicepkg/vr360-core'\nimport {onMounted, onUnmounted, ref, shallowRef} from 'vue'\nimport {LocationIcon, PaperAirPlaneIcon} from './Icons'\nimport {useVr360} from './useVr360'\n\n// eslint-disable-next-line react-hooks/rules-of-hooks\nconst {vr360} = useVr360({})\n\nconst contextMenuRef = ref<HTMLElement>()\nconst contextMenuLeft = ref(0)\nconst contextMenuTop = ref(0)\n\nconst contextMenu3dPosition = ref<Position>()\n\nconst contextMenus = shallowRef([\n  {\n    title: '添加文本标签',\n    icon: LocationIcon\n  },\n  {\n    title: '添加传送白点',\n    icon: PaperAirPlaneIcon\n  }\n])\n\nconst showContextMenu = ref(false)\n\nfunction handleContextMenu(e: MouseEvent) {\n  e.preventDefault()\n  e.stopPropagation()\n  contextMenuLeft.value = e.clientX\n  contextMenuTop.value = e.clientY\n  contextMenu3dPosition.value = vr360.value?.getPositionFromMouseXY(e.clientX, e.clientY)\n  console.log('contextMenu3dPosition', contextMenu3dPosition.value)\n  showContextMenu.value = true\n}\n\nfunction handleDocumentClick() {\n  showContextMenu.value = false\n}\n\nonMounted(() => {\n  document.addEventListener('click', handleDocumentClick)\n  contextMenuRef.value?.parentElement?.addEventListener('contextmenu', handleContextMenu)\n})\n\nonUnmounted(() => {\n  document.removeEventListener('click', handleDocumentClick)\n  contextMenuRef.value?.parentElement?.removeEventListener('contextmenu', handleContextMenu)\n})\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "playgrounds/vue3/src/Editor.vue",
    "content": "<template>\n  <div class=\"vr360-editor relative w-full h-full overflow-hidden flex flex-col\">\n    <div class=\"vr360-editor-top-bar-wrapper flex-shrink-0 w-full h-60px border-b overflow-hidden bg-white\">\n      <EditorTopBar></EditorTopBar>\n    </div>\n    <div class=\"vr360-editor-container flex-1 flex\">\n      <div class=\"vr360-editor-container-left bg-white min-w-150px flex-shrink-0\">\n        <EditorLeftBar></EditorLeftBar>\n      </div>\n      <div class=\"vr360-editor-container-center flex-1 bg-light-3 overflow-hidden\">\n        <slot></slot>\n        <ContextMenu></ContextMenu>\n      </div>\n      <!-- <div class=\"vr360-editor-container-right bg-white w-300px flex-shrink-0\"></div> -->\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport EditorTopBar from './EditorTopBar.vue'\nimport EditorLeftBar from './EditorLeftBar.vue'\nimport ContextMenu from './ContextMenu.vue'\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "playgrounds/vue3/src/EditorHotPointManager.vue",
    "content": "<template>\n  <div class=\"editor-hot-point-manager w-full h-full flex flex-col\">传送白点</div>\n</template>\n\n<script setup lang=\"ts\"></script>\n\n<style scoped></style>\n"
  },
  {
    "path": "playgrounds/vue3/src/EditorLeftBar.vue",
    "content": "<template>\n  <div class=\"vr360-editor-left-bar w-full h-full flex\">\n    <div class=\"vr360-editor-left-bar-menu-list h-full flex flex-col p-4\">\n      <div\n        v-for=\"(menu, index) in menus\"\n        :key=\"index\"\n        class=\"vr360-editor-left-bar-menu cursor-pointer w-full h-40px flex items-center rounded-10px hover:bg-light-5 mb-4 px-4 color-dark-1\"\n        :class=\"{\n          'color-black': menuActiveIndex === index,\n          'bg-light-9': menuActiveIndex === index\n        }\"\n        @click=\"menuActiveIndex = index\"\n      >\n        {{ menu.title }}\n      </div>\n    </div>\n\n    <div\n      class=\"vr360-editor-left-bar-menu-detail border-x w-250px h-full flex flex-col items-center p-4 overflow-x-hidden overflow-y-auto\"\n    >\n      <SceneManager v-if=\"menuActiveIndex === 0\"></SceneManager>\n      <TipsManager v-if=\"menuActiveIndex === 1\"></TipsManager>\n      <HotPointManager v-if=\"menuActiveIndex === 2\"></HotPointManager>\n      <Settings v-if=\"menuActiveIndex === 3\"></Settings>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {ref} from 'vue'\nimport HotPointManager from './EditorHotPointManager.vue'\nimport SceneManager from './EditorSceneManager.vue'\nimport Settings from './EditorSettings.vue'\nimport TipsManager from './EditorTipsManager.vue'\n\nconst menuActiveIndex = ref(0)\n\nconst menus = ref([\n  {\n    title: '场景管理'\n  },\n  {\n    title: '文本标签'\n  },\n  {\n    title: '传送白点'\n  },\n  {\n    title: '设置'\n  }\n])\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "playgrounds/vue3/src/EditorSceneManager.vue",
    "content": "<template>\n  <div class=\"editor-scene-manager w-full h-full flex flex-col\">\n    <input\n      v-model=\"realseeVrUrl\"\n      type=\"text\"\n      class=\"mb-4 bg-gray-50 border border-gray-3 text-gray-9 text-sm rounded-lg block w-full p-2.5 dark:bg-gray-7 dark:border-gray-6 dark:placeholder-gray-4 dark:text-white\"\n      placeholder=\"输入如视 vr 分享地址\"\n    />\n    <button\n      :disabled=\"addSceneLoading\"\n      :class=\"{\n        'bg-dark-1': addSceneLoading,\n        'bg-dark-7 hover:bg-dark-8': !addSceneLoading\n      }\"\n      class=\"mb-6 text-white focus:ring-4 focus:outline-none focus:ring-light-9 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center\"\n      @click=\"handleAddScene\"\n    >\n      {{ addSceneLoading ? '加载中...' : '添加场景' }}\n    </button>\n\n    <div class=\"flex flex-col border-1 rounded-10px p-4\">\n      <div\n        v-for=\"(space, index) in spacesConfig\"\n        :key=\"space.id\"\n        class=\"w-full h-100px relative rounded-4px cursor-pointer\"\n        :class=\"{\n          'mt-4': index > 0\n        }\"\n        @click=\"handleSwitchSpace(space)\"\n      >\n        <img class=\"w-full h-full object-cover rounded-4px object-cover\" :src=\"space.cubeSpaceTextureUrls.left\" />\n        <DeleteIcon\n          v-if=\"spacesConfig && spacesConfig.length > 1\"\n          class=\"absolute top-2 right-2 z-1 color-white w-4 h-4\"\n          @click.stop=\"handleDeleteSpace(space)\"\n        ></DeleteIcon>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport type {SpaceConfig} from '@nicepkg/vr360-core'\nimport {useVr360} from './useVr360'\nimport {DeleteIcon} from './Icons'\nimport {getRealseeTextureUrls} from './helper'\nimport {ref} from 'vue'\n\n// eslint-disable-next-line react-hooks/rules-of-hooks\nconst {vr360, spacesConfig} = useVr360({})\n\nfunction handleSwitchSpace(space: SpaceConfig) {\n  vr360.value?.switchSpace(space.id)\n}\n\nfunction handleDeleteSpace(space: SpaceConfig) {\n  console.log('handleDeleteSpace', space, spacesConfig)\n  spacesConfig.value = spacesConfig.value!.filter(item => item.id !== space.id)\n}\n\nconst realseeVrUrl = ref('')\nconst addSceneLoading = ref(false)\n\nasync function handleAddScene() {\n  if (addSceneLoading.value) {\n    return\n  }\n  addSceneLoading.value = true\n\n  try {\n    const textureUrls = await getRealseeTextureUrls(realseeVrUrl.value)\n    spacesConfig.value = [\n      ...spacesConfig.value!,\n      {\n        id: `space${spacesConfig.value!.length}`,\n        cubeSpaceTextureUrls: textureUrls\n      }\n    ]\n  } finally {\n    addSceneLoading.value = false\n  }\n}\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "playgrounds/vue3/src/EditorSettings.vue",
    "content": "<template>\n  <div class=\"editor-settings w-full h-full flex flex-col\">设置</div>\n</template>\n\n<script setup lang=\"ts\"></script>\n\n<style scoped></style>\n"
  },
  {
    "path": "playgrounds/vue3/src/EditorTipsManager.vue",
    "content": "<template>\n  <div class=\"editor-tips-manager w-full h-full flex flex-col\">文本标签</div>\n</template>\n\n<script setup lang=\"ts\"></script>\n\n<style scoped></style>\n"
  },
  {
    "path": "playgrounds/vue3/src/EditorTopBar.vue",
    "content": "<template>\n  <div class=\"vr360-editor-top-bar w-full h-full flex items-center\">\n    <span class=\"text-2xl font-bold ml-8\">Vr360</span>\n  </div>\n</template>\n\n<script setup lang=\"ts\"></script>\n\n<style scoped></style>\n"
  },
  {
    "path": "playgrounds/vue3/src/Icons.tsx",
    "content": "/* eslint-disable react/no-unknown-property */\nexport const DeleteIcon = () => (\n  <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n    <path\n      stroke-linecap=\"round\"\n      stroke-linejoin=\"round\"\n      d=\"M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0\"\n    />\n  </svg>\n)\n\nexport const LocationIcon = () => (\n  <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15 10.5a3 3 0 11-6 0 3 3 0 016 0z\" />\n    <path\n      stroke-linecap=\"round\"\n      stroke-linejoin=\"round\"\n      d=\"M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z\"\n    />\n  </svg>\n)\n\nexport const PaperAirPlaneIcon = () => (\n  <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n    <path\n      stroke-linecap=\"round\"\n      stroke-linejoin=\"round\"\n      d=\"M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5\"\n    />\n  </svg>\n)\n"
  },
  {
    "path": "playgrounds/vue3/src/helper.ts",
    "content": "/* eslint-disable unicorn/prefer-add-event-listener */\nimport type {CubeSpaceTextureUrls} from '@nicepkg/vr360-core'\nimport qs from 'qs'\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nexport type HttpOptions = {\n  url: string\n  method?: 'get' | 'post'\n  type?: 'json' | 'text' | 'blob'\n  contentType?: string\n  params?: Record<string, any>\n  proxy?: boolean\n}\n\nexport const http = <T = any>(options: HttpOptions): Promise<T> => {\n  const {url, method = 'get', type = 'json', contentType = '', params = {}, proxy = false} = options\n  let finalUrl = proxy ? `https://bird.ioliu.cn/v1?url=${url}` : url\n\n  if (method === 'get') {\n    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n    finalUrl += `?${qs.stringify(params)}`\n  }\n\n  return new Promise<T>((resolve, reject) => {\n    fetch(finalUrl, {\n      method,\n      headers: {\n        'Content-Type': contentType || (type === 'json' ? 'application/json' : 'text/plain')\n      },\n      body: method === 'post' ? JSON.stringify(params) : undefined,\n      mode: 'cors'\n    })\n      .then(res => (type === 'json' ? res.json() : type === 'text' ? res.text() : res.blob()))\n      .then(res => {\n        resolve(res as T)\n      })\n      .catch(error => {\n        reject(error)\n      })\n  })\n}\n\nexport const bytesToBase64 = (bytes: number[]) => {\n  return `data:image/png;base64,${window.btoa(bytes.reduce((data, byte) => data + String.fromCodePoint(byte), ''))}`\n}\n\nexport const blobToBase64 = (blob: Blob): Promise<string> => {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader()\n    reader.onload = () => {\n      resolve(reader.result as string)\n    }\n    reader.onerror = error => {\n      reject(error)\n    }\n    reader.readAsDataURL(blob)\n  })\n}\n\nexport const getRealseeTextureUrls = async (realseeVrShareUrl: string): Promise<CubeSpaceTextureUrls> => {\n  const html = await http<string>({\n    url: realseeVrShareUrl,\n    method: 'get',\n    type: 'json', // md，如果是 text 会自动过滤 xss，导致匹配不了\n    proxy: true\n  })\n\n  const getScriptCommentContent = (html: string, id: string): string => {\n    return (\n      [\n        ...html.matchAll(\n          new RegExp(\n            `<script\\\\s+type=\"application/json\"\\\\s+id=\"${id}\">\\\\s*<\\\\!--\\\\s*([\\\\w\\\\W]+?)\\\\s*-->\\\\s*</script>`,\n            'ig'\n          )\n        )\n      ][0]?.[1] ?? ''\n    )\n  }\n\n  const getScriptJson = <T extends Record<string, any>>(html: string, id: string): T => {\n    const jsonStr = getScriptCommentContent(html, id).replace(/\\n/g, '\\\\n')\n    try {\n      return JSON.parse(jsonStr) as T\n    } catch (error) {\n      console.error('vr360: getRealseeTextureUrls json parse error', error)\n      return {} as T\n    }\n  }\n\n  const realseeVrInfo = getScriptJson<DeepPartial<RealseeVrInfo>>(html, 'vr-work-state')\n\n  const imageBaseUrl = realseeVrInfo.base_url ?? ''\n  const {front = '', back = '', left = '', right = '', up = '', down = ''} = realseeVrInfo.panorama?.list?.[0] ?? {}\n  const imageEndFixUrl = '?imageMogr2/quality/70/thumbnail/512x'\n\n  const textureUrls = {\n    front: `${imageBaseUrl}${front}${imageEndFixUrl}`,\n    back: `${imageBaseUrl}${back}${imageEndFixUrl}`,\n    left: `${imageBaseUrl}${left}${imageEndFixUrl}`,\n    right: `${imageBaseUrl}${right}${imageEndFixUrl}`,\n    up: `${imageBaseUrl}${up}${imageEndFixUrl}`,\n    down: `${imageBaseUrl}${down}${imageEndFixUrl}`\n  }\n\n  console.log('vr360: getRealseeTextureUrls', textureUrls)\n\n  // fetch all images to base64\n  const textureUrlsBase64 = await Promise.allSettled(\n    Object.entries(textureUrls).map(async ([key, value]) => {\n      const {data} = await http<{data: number[]}>({\n        url: value,\n        method: 'get',\n        type: 'json',\n        proxy: true\n      })\n      const base64 = bytesToBase64(data)\n      return [key, base64] as [string, string]\n    })\n  ).then(res => {\n    return res.reduce<Record<string, string>>((prev, curr) => {\n      if (curr.status === 'fulfilled') {\n        const [key, value] = curr.value\n        return {...prev, [key]: value}\n      }\n      return prev\n    }, {})\n  })\n\n  return {\n    front: textureUrlsBase64.front,\n    back: textureUrlsBase64.back,\n    left: textureUrlsBase64.left,\n    right: textureUrlsBase64.right,\n    up: textureUrlsBase64.up,\n    down: textureUrlsBase64.down\n  }\n}\n\nexport type DeepPartial<T> = {\n  [P in keyof T]?: T[P] extends (infer U)[]\n    ? DeepPartial<U>[]\n    : T[P] extends readonly (infer V)[]\n    ? readonly DeepPartial<V>[]\n    : DeepPartial<T[P]>\n}\n\nexport type RealseeVrInfo = {\n  _signature: string\n  allow_hosts: any[]\n  base_url: string\n  certificate: string\n  create_time: Date\n  expire_at: string\n  initial: RealseeVrInfoInitial\n  model: RealseeVrInfoModel\n  observers: RealseeVrInfoObserver[]\n  panorama: RealseeVrInfoPanorama\n  picture_url: string\n  title_picture_url: string\n  vr_code: string\n  vr_type: string\n  work_code: string\n}\n\nexport type RealseeVrInfoInitial = {\n  fov: number\n  heading: number\n  latitude: number\n  longitude: number\n  pano_index: number\n}\n\nexport type RealseeVrInfoModel = {\n  file_url: string\n  material_base_url: string\n  material_textures: string[]\n  modify_time: Date\n  type: number\n}\n\nexport type RealseeVrInfoObserver = {\n  accessible_nodes: any[]\n  floor_index: number\n  index: number\n  offset_point_count: number\n  position: number[]\n  quaternion: RealseeVrInfoObserverQuaternion\n  standing_position: number[]\n  visible_nodes: any[]\n}\n\nexport type RealseeVrInfoObserverQuaternion = {\n  w: number\n  x: number\n  y: number\n  z: number\n}\n\nexport type RealseeVrInfoPanorama = {\n  count: number\n  list: RealseeVrInfoPanoramaList[]\n}\n\nexport type RealseeVrInfoPanoramaList = {\n  back: string\n  derived_id: number\n  down: string\n  front: string\n  index: number\n  left: string\n  right: string\n  tiles: number[]\n  up: string\n}\n"
  },
  {
    "path": "playgrounds/vue3/src/main.ts",
    "content": "import {createApp} from 'vue'\nimport App from './App.vue'\n\n// css\nimport 'uno.css'\nimport '@unocss/reset/tailwind.css'\n\nconst app = createApp(App)\napp.mount('#app')\n\napp.config.globalProperties.productionTip = false\n"
  },
  {
    "path": "playgrounds/vue3/src/useVr360.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unnecessary-type-arguments */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\nimport type {Ref} from 'vue'\nimport {shallowRef, ref, watch} from 'vue'\nimport type {SpaceConfig} from '@nicepkg/vr360-core'\nimport {Vr360} from '@nicepkg/vr360-core'\n\nexport type MaybeRef<T> = T | Ref<T>\n\nexport type UseVr360Options = {\n  containerEl?: MaybeRef<HTMLElement | undefined>\n  tipEl?: MaybeRef<HTMLElement | undefined>\n  spacesConfig?: MaybeRef<SpaceConfig[]>\n}\n\nconst containerRef = ref<HTMLElement>()\nconst tipRef = ref<HTMLElement>()\nconst tipLeft = ref(0)\nconst tipTop = ref(0)\nconst showTip = ref(false)\nconst tipTitle = ref('')\nconst tipContent = ref('')\nconst vr360 = shallowRef<InstanceType<typeof Vr360>>()\n\nconst spacesConfig = ref<SpaceConfig[]>()\n\nwatch(\n  [containerRef, tipRef, spacesConfig],\n  () => {\n    if (containerRef.value && tipRef.value && spacesConfig.value) {\n      if (!vr360.value) {\n        console.log('创建 vr360实例')\n        vr360.value = new Vr360({\n          container: containerRef.value,\n          tipContainer: tipRef.value,\n          spacesConfig: spacesConfig.value\n        })\n\n        // vr360.value.controls.autoRotate = true\n\n        vr360.value.render()\n\n        vr360.value.listenResize()\n\n        vr360.value.on('showTip', e => {\n          // vr360.value!.controls.autoRotate = false\n          const {top, left, tip} = e\n          showTip.value = true\n          tipLeft.value = left\n          tipTop.value = top\n          tipTitle.value = tip.content.title\n          tipContent.value = tip.content.text\n        })\n\n        vr360.value.on('hideTip', () => {\n          // vr360.value!.controls.autoRotate = true\n          showTip.value = false\n        })\n      } else {\n        vr360.value.updateSpacesConfig(spacesConfig.value)\n      }\n    }\n  },\n  {\n    immediate: true,\n    deep: true\n  }\n)\n\nexport function useVr360(options: UseVr360Options) {\n  const createReturn = () => {\n    return {\n      vr360,\n      containerRef,\n      tipRef,\n      spacesConfig,\n      tipLeft,\n      tipTop,\n      showTip,\n      tipTitle,\n      tipContent\n    }\n  }\n\n  watch(\n    ref(options.containerEl),\n    val => {\n      if (val) containerRef.value = val\n    },\n    {\n      immediate: true\n    }\n  )\n\n  watch(\n    ref(options.tipEl),\n    val => {\n      if (val) tipRef.value = val\n    },\n    {\n      immediate: true\n    }\n  )\n\n  watch(\n    ref(options.spacesConfig),\n    val => {\n      if (val) spacesConfig.value = val\n    },\n    {\n      immediate: true,\n      deep: true\n    }\n  )\n\n  return createReturn()\n}\n"
  },
  {
    "path": "playgrounds/vue3/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"types\": [\"vite/client\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"./src/**/*\", \"./scripts/**/*\", \"./types/**/*\", \"./*.js\", \"./*.ts\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "playgrounds/vue3/types/module.d.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\ndeclare module '*.vue' {\n  import type {DefineComponent} from 'vue-demi'\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  const Component: DefineComponent<{}, {}, any>\n  export default Component\n}\n\ndeclare module 'vue-demi/scripts/utils.js' {\n  /**\n   * switch vue version\n   */\n  const switchVersion: (vueVersion: 2 | 3 | number) => void\n}\n"
  },
  {
    "path": "playgrounds/vue3/unocss.config.ts",
    "content": "import {\n  defineConfig,\n  presetAttributify,\n  presetIcons,\n  presetUno,\n  transformerDirectives,\n  transformerVariantGroup\n} from 'unocss'\n\nexport default defineConfig({\n  shortcuts: [\n    [\n      'btn',\n      'px-4 py-1 rounded inline-block bg-teal-700 text-white cursor-pointer hover:bg-teal-800 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'\n    ],\n    [\n      'icon-btn',\n      'inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-teal-600'\n    ]\n  ],\n  presets: [\n    presetUno(),\n    presetAttributify(),\n    presetIcons({\n      scale: 1.2,\n      warn: true\n    })\n  ],\n  transformers: [transformerDirectives(), transformerVariantGroup()],\n  safelist: 'prose prose-sm m-auto text-left'.split(' ')\n})\n"
  },
  {
    "path": "playgrounds/vue3/vite.config.ts",
    "content": "import path from 'node:path'\nimport type {UserConfig} from 'vite'\nimport {defineConfig, loadEnv} from 'vite'\nimport Vue from '@vitejs/plugin-vue'\nimport vueJsx from '@vitejs/plugin-vue-jsx'\nimport Unocss from 'unocss/vite'\nimport {switchVersion} from 'vue-demi/scripts/utils.js'\n\nconst pathResolve = (...args: string[]) => path.resolve(__dirname, ...args)\n\nswitchVersion(3)\n\nexport default defineConfig(({mode}) => {\n  // 根据当前工作目录中的“mode”加载env文件。\n  // 将第三个参数设置为 '' 以加载所有 env\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  const env = loadEnv(mode, process.cwd(), '')\n\n  return {\n    resolve: {\n      dedupe: ['vue', 'vue-demi'], // use the same version\n      alias: {\n        '@/': `${pathResolve('./src')}/`\n      }\n    },\n\n    plugins: [\n      // vue3 语法支持\n      Vue({\n        include: [/\\.vue$/],\n        reactivityTransform: true\n      }),\n      vueJsx(),\n\n      // https://github.com/antfu/unocss\n      // 有关配置，请参见 unocss.config.ts\n      Unocss()\n    ],\n\n    server: {\n      host: true\n    }\n  } as UserConfig\n})\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - 'packages/**'\n  - 'examples/**'\n  - 'playgrounds/**'\n  - '!**/test/**'\n"
  },
  {
    "path": "prettier.config.js",
    "content": "// @ts-check\n\nmodule.exports = /** @type { import ('prettier').RequiredOptions }  */ ({\n  printWidth: 120,\n  semi: false,\n  singleQuote: true,\n  trailingComma: 'none',\n  bracketSpacing: false,\n  arrowParens: 'avoid',\n  insertPragma: false,\n  tabWidth: 2,\n  useTabs: false,\n  endOfLine: 'auto'\n})\n"
  },
  {
    "path": "scripts/build.ts",
    "content": "import {build, copyFiles} from './utils'\n\ncopyFiles()\n\nbuild()\n"
  },
  {
    "path": "scripts/check-update.ts",
    "content": "import * as ncu from 'npm-check-updates'\nimport fs from 'node:fs'\nimport {packagesPaths, pathResolve} from './utils'\n\nasync function checkNpmPkgUpdate() {\n  const rookPath = pathResolve('../')\n  for (const packagePath of [rookPath, ...packagesPaths]) {\n    const pkgJson = pathResolve(packagePath, 'package.json')\n    if (!fs.existsSync(pkgJson)) continue\n    await ncu.run({\n      // Pass any cli option\n      packageFile: pkgJson,\n      upgrade: false,\n      jsonUpgraded: false,\n      silent: false\n    })\n  }\n}\n\ncheckNpmPkgUpdate()\n"
  },
  {
    "path": "scripts/release.ts",
    "content": "import {release} from './utils'\n\nrelease()\n"
  },
  {
    "path": "scripts/utils.ts",
    "content": "import {copyFileSync, readFileSync, existsSync} from 'node:fs'\nimport {execSync} from 'node:child_process'\nimport path from 'node:path'\nimport globby from 'globby'\n\nexport const pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)\nexport const pathResolveUnix = (..._path: string[]) => pathResolve(..._path).replace(/\\\\/g, '/')\n\nexport const packagesGlobPaths = pathResolveUnix('../packages/*/')\nexport const packagesPaths = globby.sync(packagesGlobPaths, {onlyFiles: false, onlyDirectories: true})\nexport const rootLicense = pathResolve('../', 'LICENSE')\n\nexport function copyFiles() {\n  packagesPaths.map(packagePath => {\n    const pkgJson = pathResolve(packagePath, 'package.json')\n    const license = pathResolve(packagePath, 'LICENSE')\n\n    if (!existsSync(pkgJson)) return\n\n    const pkg: Record<string, string> = JSON.parse(readFileSync(pkgJson, 'utf8')) || {}\n    if (pkg.private) return\n    if (!existsSync(license)) copyFileSync(rootLicense, license)\n  })\n}\n\nexport type PackageInfo = {path: string; name: string}\nexport function getPackagesInfo(type: 'public' | 'all'): PackageInfo[] {\n  return packagesPaths.reduce<PackageInfo[]>((pkgInfos: PackageInfo[], packagePath) => {\n    const pkgJson = pathResolve(packagePath, 'package.json')\n\n    if (!existsSync(pkgJson)) return pkgInfos\n\n    const pkg: Record<string, string> = JSON.parse(readFileSync(pkgJson, 'utf8')) || {}\n\n    if (type === 'public') {\n      if (pkg.private) return pkgInfos\n      return [\n        ...pkgInfos,\n        {\n          path: packagePath,\n          name: pkg.name\n        }\n      ]\n    } else {\n      return [\n        ...pkgInfos,\n        {\n          path: packagePath,\n          name: pkg.name\n        }\n      ]\n    }\n  }, [])\n}\n\nexport function build() {\n  const cmd = `pnpm run build:all`\n  console.log('start run command:', cmd)\n  execSync(cmd, {stdio: 'inherit'})\n}\n\nexport function generateChangelog() {\n  const pkgInfos = getPackagesInfo('all')\n\n  for (const pkgInfo of pkgInfos) {\n    const cmd = `pnpm exec conventional-changelog -p angular -i '${path.resolve(\n      pkgInfo.path,\n      './CHANGELOG.md'\n    )}' -s --commit-path . -l ${pkgInfo.name} -r 0`\n    console.log('start run command:', cmd)\n    execSync(cmd, {stdio: 'inherit'})\n  }\n}\n\nexport function release() {\n  generateChangelog()\n  execSync('git add .', {stdio: 'inherit'})\n  execSync(\n    'pnpm exec bumpp package.json packages/*/package.json --push --tag --all --commit \"build: the v%s release\"',\n    {\n      stdio: 'inherit'\n    }\n  )\n}\n"
  },
  {
    "path": "stylelint.config.js",
    "content": "//@ts-check\n\nmodule.exports = /** @type { Partial<import('stylelint').Config> } */ ({\n  customSyntax: 'postcss-less',\n  defaultSeverity: 'error',\n  extends: [\n    'stylelint-config-standard',\n    'stylelint-config-recess-order',\n    'stylelint-config-recommended-vue',\n    'stylelint-prettier/recommended'\n  ],\n  plugins: ['stylelint-declaration-block-no-ignored-properties'],\n  overrides: [\n    {\n      files: ['**/*.(less|css|html|vue)'],\n      customSyntax: 'postcss-less',\n      plugins: ['stylelint-less']\n    },\n    {\n      files: ['**/*.(html|vue)'],\n      customSyntax: 'postcss-html'\n    }\n  ],\n  rules: {\n    'media-feature-name-no-vendor-prefix': true,\n    'at-rule-no-vendor-prefix': true,\n    'selector-no-vendor-prefix': true,\n    'property-no-vendor-prefix': true,\n    'value-no-vendor-prefix': true,\n    'block-no-empty': null,\n    'comment-empty-line-before': null,\n    'declaration-empty-line-before': null,\n    'function-comma-newline-after': null,\n    'function-name-case': null,\n    'function-parentheses-newline-inside': null,\n    'function-max-empty-lines': null,\n    'function-whitespace-after': null,\n    indentation: null,\n    'number-leading-zero': null,\n    'number-no-trailing-zeros': null,\n    'rule-empty-line-before': null,\n    'selector-combinator-space-after': null,\n    'selector-list-comma-newline-after': null,\n    'selector-pseudo-element-colon-notation': null,\n    'unit-no-unknown': null,\n    'value-list-max-empty-lines': null,\n    'font-family-no-missing-generic-family-keyword': null,\n    'no-descending-specificity': null,\n    'selector-class-pattern': null,\n    'keyframes-name-pattern': null,\n    'color-function-notation': 'legacy',\n    'alpha-value-notation': 'number',\n    'no-empty-source': null,\n    'function-no-unknown': [\n      true,\n      {\n        ignoreFunctions: ['constant']\n      }\n    ],\n    'at-rule-no-unknown': [\n      true,\n      {\n        ignoreAtRules: ['extends', 'tailwind', 'apply', 'variants', 'responsive', 'screen']\n      }\n    ],\n    'declaration-block-trailing-semicolon': null,\n    'selector-pseudo-element-no-unknown': [\n      true,\n      {\n        ignorePseudoElements: ['v-deep']\n      }\n    ]\n  }\n})\n"
  },
  {
    "path": "textures.json",
    "content": "{\n  \"hotpot\": \"https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png\",\n  \"beijing\": {\n    \"right\": \"https://m.360buyimg.com/babel/jfs/t1/191407/17/28098/575348/63395813Ea04f5691/f49f0122480b4eb4.jpg\",\n    \"left\": \"https://m.360buyimg.com/babel/jfs/t1/74850/32/21805/482922/63395814E01705209/d52a60db8499e89b.jpg\",\n    \"up\": \"https://m.360buyimg.com/babel/jfs/t1/104617/17/33299/12291/63395812Ec9ff0c26/f27b377e7685fedc.jpg\",\n    \"down\": \"https://m.360buyimg.com/babel/jfs/t1/91477/10/30691/701629/63395813E112bdf50/19c029eab324c169.jpg\",\n    \"front\": \"https://m.360buyimg.com/babel/jfs/t1/84076/31/15387/515197/63395812Eb7340684/0a428e64e6e7a9b9.jpg\",\n    \"back\": \"https://m.360buyimg.com/babel/jfs/t1/194825/33/29620/583981/63395813E5bee9d7f/bfcf24541d089f01.jpg\"\n  },\n  \"firstHouseDoor\": {\n    \"back\": \"https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg\",\n    \"down\": \"https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg\",\n    \"front\": \"https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg\",\n    \"left\": \"https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg\",\n    \"right\": \"https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg\",\n    \"up\": \"https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg\"\n  },\n  \"firstHouseLivingRoom\": {\n    \"back\": \"https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg\",\n    \"down\": \"https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg\",\n    \"front\": \"https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg\",\n    \"left\": \"https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg\",\n    \"right\": \"https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg\",\n    \"up\": \"https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg\"\n  },\n  \"secondHouseStudyRoom\": {\n    \"back\": \"https://m.360buyimg.com/babel/jfs/t1/23811/37/19596/116370/633983fbE34d30250/f1099ee36e330e80.jpg\",\n    \"down\": \"https://m.360buyimg.com/babel/jfs/t1/192201/4/28516/139038/63398414E7f79ccf0/c6952458f2e6f3f6.jpg\",\n    \"front\": \"https://m.360buyimg.com/babel/jfs/t1/100591/34/32139/109806/63398425E009b97b7/2eb84bda9afffb11.jpg\",\n    \"left\": \"https://m.360buyimg.com/babel/jfs/t1/170041/17/30615/76889/63398439Ee255b70a/7eb5499dfa219696.jpg\",\n    \"right\": \"https://m.360buyimg.com/babel/jfs/t1/101772/8/31985/106412/6339844cE0e5f0b8a/0ab425fc69cddc10.jpg\",\n    \"up\": \"https://m.360buyimg.com/babel/jfs/t1/82864/5/16335/24812/6339845fEed6b2704/d155848fa774e400.jpg\"\n  },\n  \"secondHouseLivingRoom\": {\n    \"back\": \"https://m.360buyimg.com/babel/jfs/t1/205010/17/25032/112321/63398494E447a0165/762fedee9a8ae0da.jpg\",\n    \"down\": \"https://m.360buyimg.com/babel/jfs/t1/138584/25/29591/124219/6339849fE359438a0/7058a11663e8c446.jpg\",\n    \"front\": \"https://m.360buyimg.com/babel/jfs/t1/76445/23/20067/118945/633984adEaec5f805/8e4b45f2db8a4376.jpg\",\n    \"left\": \"https://m.360buyimg.com/babel/jfs/t1/95805/37/31991/93956/633984b6E40f803e6/caf128edf7542856.jpg\",\n    \"right\": \"https://m.360buyimg.com/babel/jfs/t1/149003/6/30747/90162/633984c3E17e4ae68/e7da3ca956708bb7.jpg\",\n    \"up\": \"https://m.360buyimg.com/babel/jfs/t1/190194/34/27331/16125/633984cfEa0c620d3/1c66d9cb66d5ccbd.jpg\"\n  }\n}\n"
  },
  {
    "path": "tsconfig-base.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"module\": \"esnext\",\n    \"typeRoots\": [\"./node_modules/@types\", \"types\"],\n    \"lib\": [\"dom\", \"esnext\", \"dom.iterable\", \"scripthost\"],\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"jsx\": \"preserve\",\n    \"skipLibCheck\": true,\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true,\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"noUnusedLocals\": false,\n    \"noImplicitAny\": true,\n    \"noUnusedParameters\": false,\n    \"noImplicitThis\": true,\n    \"noEmitOnError\": false,\n    \"strictNullChecks\": true,\n    \"allowJs\": true,\n    \"sourceMap\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"forceConsistentCasingInFileNames\": true\n  },\n  \"exclude\": [\"./node_modules/**/*\", \"./lib/**/*\", \"./es/**/*\", \"./dist/**/*\"],\n  \"ts-node\": {\n    \"compilerOptions\": {\n      \"module\": \"CommonJS\",\n      \"resolveJsonModule\": true,\n      \"noImplicitAny\": false,\n      \"noEmitOnError\": false,\n      \"noUnusedLocals\": false,\n      \"noUnusedParameters\": false\n    }\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\"\n  },\n  \"include\": [\"./scripts/**/*\", \"./types/**/*\"],\n  \"exclude\": [\"./node_modules/**/*\", \"./lib/**/*\", \"./es/**/*\", \"./dist/**/*\", \"./packages/**/*\"]\n}\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turborepo.org/schema.json\",\n  \"baseBranch\": \"origin/master\",\n  \"pipeline\": {\n    \"@nicepkg/vr360-core#build\": {\n      \"dependsOn\": [\"@nicepkg/vr360-shared#build\"],\n      \"inputs\": [\"src/**\", \"scripts/**\", \"types/**\"],\n      \"outputs\": [\"dist/**\"]\n    },\n    \"doc-site#build\": {\n      \"dependsOn\": [\"^build\"],\n      \"inputs\": [\"./**\"],\n      \"outputs\": [\"./.vuepress/dist/**\"]\n    },\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"inputs\": [\"src/**\", \"scripts/**\", \"types/**\"],\n      \"outputs\": [\"dist/**\"]\n    },\n    \"test\": {\n      \"dependsOn\": [\"build\"],\n      \"outputs\": []\n    },\n    \"dev\": {\n      \"cache\": false\n    }\n  },\n  \"globalDependencies\": [\"tsconfig-base.json\"]\n}\n"
  }
]