Full Code of nicepkg/vr360 for AI

master 7d6598b1134d cached
193 files
241.0 KB
79.8k tokens
150 symbols
1 requests
Download .txt
Showing preview only (299K chars total). Download the full file or copy to clipboard to get everything.
Repository: nicepkg/vr360
Branch: master
Commit: 7d6598b1134d
Files: 193
Total size: 241.0 KB

Directory structure:
gitextract_4oa5dsxh/

├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── commit-convention.md
│   ├── settings.yml
│   └── workflows/
│       ├── docs.yml
│       ├── lint.yml
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   ├── pre-commit
│   └── prepare-commit-msg
├── .npmrc
├── .stylelintignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── commitlint.config.js
├── lint-staged.config.js
├── netlify.toml
├── package.json
├── packages/
│   ├── doc/
│   │   ├── .vuepress/
│   │   │   ├── client.ts
│   │   │   ├── components/
│   │   │   │   ├── DemoA.vue
│   │   │   │   └── NpmBadge.vue
│   │   │   ├── configs/
│   │   │   │   ├── index.ts
│   │   │   │   ├── navbar/
│   │   │   │   │   ├── en.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── zh.ts
│   │   │   │   └── sidebar/
│   │   │   │       ├── en.ts
│   │   │   │       ├── index.ts
│   │   │   │       └── zh.ts
│   │   │   ├── examples/
│   │   │   │   └── firstHouse/
│   │   │   │       ├── demo.css
│   │   │   │       ├── html.html
│   │   │   │       ├── react.tsx
│   │   │   │       ├── vue2.vue
│   │   │   │       └── vue3.vue
│   │   │   ├── index.build.html
│   │   │   ├── plugins/
│   │   │   │   ├── code-demo/
│   │   │   │   │   ├── CodeDemo.props.ts
│   │   │   │   │   ├── CodeDemo.vue
│   │   │   │   │   ├── Icons.tsx
│   │   │   │   │   ├── clientConfigFile.ts
│   │   │   │   │   ├── constant.ts
│   │   │   │   │   ├── global.d.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── plugin.ts
│   │   │   │   └── index.ts
│   │   │   ├── public/
│   │   │   │   ├── browserconfig.xml
│   │   │   │   ├── code-demo-templates/
│   │   │   │   │   ├── demo.css.txt
│   │   │   │   │   ├── html/
│   │   │   │   │   │   └── package.json.txt
│   │   │   │   │   ├── react/
│   │   │   │   │   │   ├── index.html.txt
│   │   │   │   │   │   ├── package.json.txt
│   │   │   │   │   │   ├── src/
│   │   │   │   │   │   │   └── main.tsx.txt
│   │   │   │   │   │   ├── tsconfig.json.txt
│   │   │   │   │   │   └── vite.config.ts.txt
│   │   │   │   │   ├── vue2/
│   │   │   │   │   │   ├── index.html.txt
│   │   │   │   │   │   ├── package.json.txt
│   │   │   │   │   │   ├── src/
│   │   │   │   │   │   │   ├── App.vue.txt
│   │   │   │   │   │   │   └── main.ts.txt
│   │   │   │   │   │   ├── tsconfig.json.txt
│   │   │   │   │   │   ├── types/
│   │   │   │   │   │   │   └── module.d.ts.txt
│   │   │   │   │   │   └── vite.config.ts.txt
│   │   │   │   │   └── vue3/
│   │   │   │   │       ├── index.html.txt
│   │   │   │   │       ├── package.json.txt
│   │   │   │   │       ├── src/
│   │   │   │   │       │   ├── App.vue.txt
│   │   │   │   │       │   └── main.ts.txt
│   │   │   │   │       ├── tsconfig.json.txt
│   │   │   │   │       ├── types/
│   │   │   │   │       │   └── module.d.ts.txt
│   │   │   │   │       └── vite.config.ts.txt
│   │   │   │   └── manifest.webmanifest
│   │   │   ├── styles/
│   │   │   │   └── index.scss
│   │   │   ├── theme/
│   │   │   │   ├── components/
│   │   │   │   │   ├── Home.vue
│   │   │   │   │   ├── HomeFeatures.vue
│   │   │   │   │   └── HomeVrBg.vue
│   │   │   │   └── index.ts
│   │   │   ├── types/
│   │   │   │   └── module.d.ts
│   │   │   └── utils/
│   │   │       └── common.ts
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── bundler.config.ts
│   │   ├── guide/
│   │   │   ├── README.md
│   │   │   └── questions.md
│   │   ├── libs/
│   │   │   ├── vr360-core/
│   │   │   │   ├── README.md
│   │   │   │   ├── events.md
│   │   │   │   ├── example.md
│   │   │   │   ├── methods.md
│   │   │   │   └── properties.md
│   │   │   ├── vr360-ui/
│   │   │   │   └── README.md
│   │   │   ├── vr360-ui-react/
│   │   │   │   └── README.md
│   │   │   ├── vr360-ui-vue2/
│   │   │   │   └── README.md
│   │   │   └── vr360-ui-vue3/
│   │   │       └── README.md
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── vuepress.config.ts
│   ├── vr360-core/
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── scripts/
│   │   │   └── build.ts
│   │   ├── src/
│   │   │   ├── helper.ts
│   │   │   ├── index.ts
│   │   │   ├── manager/
│   │   │   │   ├── index.ts
│   │   │   │   ├── space.ts
│   │   │   │   └── tip.ts
│   │   │   ├── types.ts
│   │   │   └── vr360.ts
│   │   ├── test/
│   │   │   ├── index.ts
│   │   │   └── setup.ts
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   ├── vr360-shared/
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── build.config.ts
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── build-utils/
│   │   │   │   ├── build-script.util.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── vite-config-common.util.ts
│   │   │   ├── index.ts
│   │   │   ├── test-react-utils.ts
│   │   │   ├── test-utils/
│   │   │   │   ├── common/
│   │   │   │   │   ├── helper.util.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── mock-global-api.ts
│   │   │   │   │   ├── mock-server.util.ts
│   │   │   │   │   ├── polyfill-fetch.util.ts
│   │   │   │   │   └── polyfill-pointer-events.util.ts
│   │   │   │   ├── react/
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── react-helper.util.ts
│   │   │   │   │   └── react-mount.util.ts
│   │   │   │   └── vue/
│   │   │   │       ├── index.ts
│   │   │   │       ├── vue-helper.util.ts
│   │   │   │       └── vue-mount.util.ts
│   │   │   ├── test-utils.ts
│   │   │   └── test-vue-utils.ts
│   │   ├── test-react-utils.d.ts
│   │   ├── test-utils.d.ts
│   │   ├── test-vue-utils.d.ts
│   │   ├── tsconfig.json
│   │   └── types/
│   │       └── global.d.ts
│   ├── vr360-ui/
│   │   └── README.md
│   ├── vr360-ui-react/
│   │   └── README.md
│   ├── vr360-ui-vue2/
│   │   └── README.md
│   └── vr360-ui-vue3/
│       └── README.md
├── playgrounds/
│   ├── react/
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── Example.tsx
│   │   │   └── main.tsx
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   ├── vue2/
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── App.vue
│   │   │   └── main.ts
│   │   ├── tsconfig.json
│   │   ├── types/
│   │   │   └── module.d.ts
│   │   ├── unocss.config.ts
│   │   └── vite.config.ts
│   └── vue3/
│       ├── index.html
│       ├── package.json
│       ├── src/
│       │   ├── App.vue
│       │   ├── ContextMenu.vue
│       │   ├── Editor.vue
│       │   ├── EditorHotPointManager.vue
│       │   ├── EditorLeftBar.vue
│       │   ├── EditorSceneManager.vue
│       │   ├── EditorSettings.vue
│       │   ├── EditorTipsManager.vue
│       │   ├── EditorTopBar.vue
│       │   ├── Icons.tsx
│       │   ├── helper.ts
│       │   ├── main.ts
│       │   └── useVr360.ts
│       ├── tsconfig.json
│       ├── types/
│       │   └── module.d.ts
│       ├── unocss.config.ts
│       └── vite.config.ts
├── pnpm-workspace.yaml
├── prettier.config.js
├── scripts/
│   ├── build.ts
│   ├── check-update.ts
│   ├── release.ts
│   └── utils.ts
├── stylelint.config.js
├── textures.json
├── tsconfig-base.json
├── tsconfig.json
└── turbo.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true


================================================
FILE: .eslintignore
================================================
**/node_modules/
*.html
es/
**/lib/
**/dist/
**/lib-types/
_site/
**/dist/
CHANGELOG.md
.rollup.cache
tsconfig.tsbuildinfo
!.storybook
!.vuepress
types/components.d.ts
types/auto-imports.d.ts
*.generated.ts
**/.vuepress/examples/


================================================
FILE: .eslintrc.js
================================================
//@ts-check

module.exports = /** @type { import('eslint').Linter.Config } */ ({
  root: true,
  extends: [
    'eslint:recommended',
    'plugin:eslint-comments/recommended',
    'plugin:import/recommended',
    'plugin:unicorn/recommended',
    'plugin:react/recommended',
    'plugin:react/jsx-runtime',
    'plugin:react-hooks/recommended',
    'plugin:jsx-a11y/recommended',
    'plugin:prettier/recommended',
    'prettier'
  ],
  env: {
    node: true,
    browser: true,
    es2022: true
  },
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true
    }
  },
  settings: {
    react: {
      version: 'detect'
    },
    'import/extensions': ['.js', '.jsx']
  },
  ignorePatterns: ['node_modules/*'],
  rules: {
    /**
     * Turn-off recommended rules
     */
    'array-callback-return': 'off',
    'jsx-a11y/click-events-have-key-events': 'off',
    'jsx-a11y/no-autofocus': 'off',
    'react/display-name': 'off',
    'react/prop-types': 'off',
    'eslint-comments/disable-enable-pair': 'off',
    'unicorn/no-array-reduce': 'off',
    'unicorn/filename-case': 'off',
    'unicorn/no-null': 'off',
    'unicorn/prevent-abbreviations': 'off',
    'unicorn/prefer-module': 'off',
    'unicorn/prefer-top-level-await': 'off',
    'unicorn/consistent-function-scoping': 'off',
    'unicorn/no-array-for-each': 'off',
    'unicorn/prefer-spread': 'off',

    /**
     * Adjust recommended rules
     */
    'no-empty': ['error', {allowEmptyCatch: true}],
    'no-unused-vars': ['error', {args: 'none', ignoreRestSiblings: true}],
    'react-hooks/exhaustive-deps': ['error', {additionalHooks: '(useRecoilCallback|useRecoilTransaction)'}],

    /**
     * Use additional rules
     */
    'default-case': 'error',
    eqeqeq: ['error', 'smart'],
    'no-array-constructor': 'error',
    'no-caller': 'error',
    'no-eval': 'error',
    'no-extend-native': 'error',
    'no-extra-bind': 'error',
    'no-extra-label': 'error',
    'no-implied-eval': 'error',
    'no-label-var': 'error',
    'no-labels': 'error',
    'no-lone-blocks': 'error',
    'no-loop-func': 'error',
    'no-multi-str': 'error',
    'no-new-func': 'error',
    'no-new-object': 'error',
    'no-new-wrappers': 'error',
    'no-restricted-globals': ['error', ...require('confusing-browser-globals')],
    'no-script-url': 'error',
    'no-self-compare': 'error',
    'no-sequences': 'error',
    'no-template-curly-in-string': 'error',
    'no-throw-literal': 'error',
    'no-unused-expressions': [
      'error',
      {
        allowShortCircuit: true,
        allowTernary: true,
        allowTaggedTemplates: true
      }
    ],
    'no-useless-computed-key': 'error',
    'no-useless-concat': 'error',
    'no-useless-constructor': 'error',
    'no-useless-rename': 'error',
    strict: ['error', 'never'],
    'react/jsx-pascal-case': ['error', {allowAllCaps: true}],
    'react/no-array-index-key': 'error',
    'react/no-typos': 'error',
    'react/style-prop-object': 'error'
  },
  overrides: [
    {
      files: ['**/__tests__/**/*', '**/*.{spec,test}.*'],
      extends: ['plugin:testing-library/react', 'plugin:jest-dom/recommended']
    },
    {
      files: ['**/*.stories.*'],
      rules: {
        'import/no-anonymous-default-export': 'off'
      }
    },
    {
      files: ['**/*.ts?(x)', '**/*.vue'],
      parser: 'vue-eslint-parser',
      parserOptions: {
        parser: '@typescript-eslint/parser',
        ecmaversion: 2022,
        sourceType: 'module',
        ecmaFeatures: {
          jsx: true
        },
        extraFileExtensions: ['.vue'],
        project: [
          './tsconfig.json',
          './packages/*/tsconfig.json',
          './examples/**/tsconfig.json',
          './playgrounds/**/tsconfig.json'
        ]
      },
      settings: {
        react: {version: 'detect'},
        'import/resolver': {
          typescript: {
            alwaysTryTypes: true
          }
        }
      },
      env: {
        browser: true,
        node: true,
        es6: true
      },
      extends: [
        'plugin:@typescript-eslint/recommended',
        'plugin:@typescript-eslint/recommended-requiring-type-checking',
        'plugin:@typescript-eslint/strict',
        'plugin:import/typescript',
        'plugin:vue/recommended',
        'plugin:prettier/recommended',
        'prettier'
      ],
      rules: {
        /**
         * Turn-off recommended rules
         */
        '@typescript-eslint/no-floating-promises': 'off',
        '@typescript-eslint/non-nullable-type-assertion-style': 'off',
        '@typescript-eslint/no-unsafe-assignment': 'off',
        '@typescript-eslint/no-non-null-assertion': 'off',
        '@typescript-eslint/no-unsafe-call': 'off',
        '@typescript-eslint/no-unnecessary-type-assertion': 'off',
        '@typescript-eslint/prefer-ts-expect-error': 'off',

        // 'tsc' already handles this (https://typescript-eslint.io/docs/linting/troubleshooting/#eslint-plugin-import)
        'import/default': 'off',
        'import/namespace': 'off',
        'import/no-named-as-default-member': 'off',
        'import/no-unresolved': 'off',

        /**
         * Adjust recommended rules
         */
        '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
        '@typescript-eslint/no-misused-promises': ['error', {checksVoidReturn: {arguments: false, attributes: false}}],
        '@typescript-eslint/no-unused-vars': ['error', {args: 'none', ignoreRestSiblings: true}],

        /**
         * Use additional rules
         */
        '@typescript-eslint/consistent-type-imports': 'error',
        'import/first': 'error',
        'import/no-anonymous-default-export': 'error',

        /**
         * Replace additional rules
         */
        'default-case': 'off', // 'tsc' noFallthroughCasesInSwitch option is more robust
        'no-array-constructor': 'off',
        '@typescript-eslint/no-array-constructor': 'error',
        'no-implied-eval': 'off',
        '@typescript-eslint/no-implied-eval': 'error',
        'no-loop-func': 'off',
        '@typescript-eslint/no-loop-func': 'error',
        'no-unused-expressions': 'off',
        '@typescript-eslint/no-unused-expressions': [
          'error',
          {
            allowShortCircuit: true,
            allowTernary: true,
            allowTaggedTemplates: true
          }
        ],
        'no-throw-literal': 'off',
        '@typescript-eslint/no-throw-literal': 'error',
        'no-useless-constructor': 'off',
        '@typescript-eslint/no-useless-constructor': 'error',

        // vue
        'vue/no-v-html': 'off',
        'vue/singleline-html-element-content-newline': 'off',
        'vue/html-self-closing': 'off',
        'vue/max-attributes-per-line': [
          'error',
          {
            singleline: 10,
            multiline: 1
          }
        ],
        'vue/require-default-prop': 'off',
        'vue/html-closing-bracket-spacing': 'error',
        'vue/no-unused-vars': 'warn',
        'vue/multi-word-component-names': 'off',
        'vue/one-component-per-file': 'off',
        'vue/no-v-model-argument': 'off',
        'vue/comment-directive': [
          'warn',
          {
            reportUnusedDisableDirectives: false
          }
        ]
      }
    }
  ]
})


================================================
FILE: .github/FUNDING.yml
================================================
github: [2214962083]


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: "\U0001F41E Bug report"
description: Report an issue with Vr360
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!
  - type: textarea
    id: bug-description
    attributes:
      label: Describe the bug
      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!
      placeholder: Bug description
    validations:
      required: true
  - type: input
    id: reproduction
    attributes:
      label: Reproduction
      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.
      placeholder: Reproduction
    validations:
      required: true
  - type: textarea
    id: system-info
    attributes:
      label: System Info
      description: Output of `npx envinfo --system --npmPackages '{@nicepkg/vr360-core,three}' --binaries --browsers`
      render: Shell
      placeholder: System, Binaries, Browsers
    validations:
      required: true
  - type: dropdown
    id: package-manager
    attributes:
      label: Used Package Manager
      description: Select the used package manager
      options:
        - npm
        - yarn
        - pnpm
    validations:
      required: true
  - type: checkboxes
    id: checkboxes
    attributes:
      label: Validations
      description: Before submitting the issue, please make sure you do the following
      options:
        - label: Follow our [Code of Conduct](https://github.com/nicepkg/vr360/blob/master/CODE_OF_CONDUCT.md)
          required: true
        - label: Read the [Contributing Guidelines](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).
          required: true
        - 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.
          required: true
        - 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.
          required: true
        - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/nicepkg/vr360/discussions).
          required: true
        - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Question
    url: https://github.com/nicepkg/vr360/discussions/new?category=Q-A
    about: Ask a question or discuss about vr360
  - name: Ideas
    url: https://github.com/nicepkg/vr360/discussions/new?category=Ideas
    about: Start a discussion to improve vr360
  - name: GitHub Sponsors
    url: https://github.com/sponsors/2214962083
    about: Like this project? Please consider supporting the author.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: "\U0001F680 New feature proposal"
description: Propose a new feature to be added to Vr360
labels: ['feature request']
body:
  - type: markdown
    attributes:
      value: |
        Thanks for your interest in the project and taking the time to fill out this feature report!
  - type: textarea
    id: feature-description
    attributes:
      label: Clear and concise description of the problem
      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!'
    validations:
      required: true
  - type: textarea
    id: suggested-solution
    attributes:
      label: Suggested solution
      description: 'In module [xy] we could provide following implementation...'
    validations:
      required: true
  - type: textarea
    id: alternative
    attributes:
      label: Alternative
      description: Clear and concise description of any alternative solutions or features you've considered.
  - type: textarea
    id: additional-context
    attributes:
      label: Additional context
      description: Any other context or screenshots about the feature request here.
  - type: checkboxes
    id: checkboxes
    attributes:
      label: Validations
      description: Before submitting the issue, please make sure you do the following
      options:
        - label: Follow our [Code of Conduct](https://github.com/nicepkg/vr360/blob/master/CODE_OF_CONDUCT.md)
          required: true
        - label: Read the [Contributing Guidelines](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).
          required: true
        - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
          required: true


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Thank you for contributing! -->

### Description

<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->

### Additional context

<!-- e.g. is there anything you'd like reviewers to focus on? -->

---

### What is the purpose of this pull request? <!-- (put an "X" next to an item) -->

- [ ] Bug fix
- [ ] New Feature
- [ ] Documentation update
- [ ] Other

### Before submitting the PR, please make sure you do the following

- [ ] Read the [Contributing Guidelines](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).
- [ ] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
- [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`).
- [ ] Ideally, include relevant tests that fail without this PR but pass with it.


================================================
FILE: .github/commit-convention.md
================================================
## Git Commit Message Convention

> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).

#### TL;DR:

Messages must be matched by the following regex:

```text
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,50}/
```

#### Examples

Appears under "Features" header, `link` subheader:

```
feat(link): add `force` option
```

Appears under "Bug Fixes" header, `view` subheader, with a link to issue #28:

```
fix(view): handle keep-alive with aborted navigations

close #28
```

Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:

```
perf: improve guard extraction

BREAKING CHANGE: The 'beforeRouteEnter' option has been removed.
```

The 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.

```
revert: feat(compiler): add 'comments' option

This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
```

### Full Message Format

A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:

```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```

The **header** is mandatory and the **scope** of the header is optional.

### Revert

If 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.

### Type

If 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.

Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.

### Scope

The scope could be anything specifying the place of the commit change. For example `core`, `compiler`, `ssr`, `v-model`, `transition` etc...

### Subject

The subject contains a succinct description of the change:

- use the imperative, present tense: "change" not "changed" nor "changes"
- don't capitalize the first letter
- no dot (.) at the end

### Body

Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.

### Footer

The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.

**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.


================================================
FILE: .github/settings.yml
================================================
labels:
  - name: bug
    color: ee0701
  - name: contribution welcome
    color: 0e8a16
  - name: discussion
    color: 4935ad
  - name: docs
    color: 8be281
  - name: enhancement
    color: a2eeef
  - name: good first issue
    color: 7057ff
  - name: help wanted
    color: 008672
  - name: question
    color: d876e3
  - name: wontfix
    color: ffffff
  - name: WIP
    color: ffffff
  - name: need repro
    color: c9581c
  - name: feature request
    color: fbca04


================================================
FILE: .github/workflows/docs.yml
================================================
name: Docs

on:
  push:
    branches:
      - main
      - master

  pull_request:
    branches:
      - main
      - master
  # workflow_run:
  #   workflows: ['Test']
  #   types:
  #     - completed

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.VERCEL_TOKEN }}
      TURBO_TEAM: ${{ secrets.VERCEL_TEAM }}
      TURBO_CACHE_KEY: ubuntu-latest-16 # reuse cache key from ci workflow
      NODE_OPTIONS: '--max_old_space_size=4096'

    steps:
      - uses: actions/checkout@v2

      - name: Install pnpm
        uses: pnpm/action-setup@v2.2.1

      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 16.x
          registry-url: https://registry.npmjs.org/
          cache: pnpm

      - name: Install Dependencies and build all packages
        run: pnpm bootstrap

      # - name: deploy docs to vercel
      #   uses: BetaHuhn/deploy-to-vercel-action@v1
      #   # see: https://github.com/BetaHuhn/deploy-to-vercel-action
      #   with:
      #     GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      #     VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
      #     VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
      #     VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_DOC_SITE }}
      #     # for team settings
      #     VERCEL_SCOPE: ${{ secrets.VERCEL_ORG_ID }}
      #     # the docs build dist folder
      #     WORKING_DIRECTORY: ./packages/doc/.vuepress/dist
      #     # bind domains
      #     # ALIAS_DOMAINS: |
      #     #   docs.vr360.com
      #     #   docs.vr360.cn

      - name: deploy docs to vercel
        uses: amondnet/vercel-action@v20
        # see: https://github.com/amondnet/vercel-action
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID_DOC_SITE }}
          # for team settings
          scope: ${{ secrets.VERCEL_ORG_ID }}
          # just like npx vercel --prod
          vercel-args: '--prod'
          # the docs build dist folder
          working-directory: ./packages/doc/.vuepress/dist
          # bind domains
          # alias-domains: |
          #   docs.vr360.com
          #   docs.vr360.cn


================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint

on:
  push:
    branches:
      - main
      - master

  pull_request:
    branches:
      - main
      - master

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.VERCEL_TOKEN }}
      TURBO_TEAM: ${{ secrets.VERCEL_TEAM }}
      TURBO_CACHE_KEY: ubuntu-latest-16 # reuse cache key from ci workflow

    steps:
      - uses: actions/checkout@v2

      - name: Install pnpm
        uses: pnpm/action-setup@v2.2.1

      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 16
          registry-url: https://registry.npmjs.org/
          cache: pnpm

      - name: Install Dependencies
        run: pnpm bootstrap

      - name: Lint
        run: pnpm run lint


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.VERCEL_TOKEN }}
      TURBO_TEAM: ${{ secrets.VERCEL_TEAM }}
      TURBO_CACHE_KEY: ubuntu-latest-16 # reuse cache key from ci workflow
      NODE_OPTIONS: '--max_old_space_size=4096'

    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Install pnpm
        uses: pnpm/action-setup@v2.2.1

      - name: Use Node.js v16
        uses: actions/setup-node@v2
        with:
          node-version: 16
          registry-url: https://registry.npmjs.org/
          cache: pnpm

      - run: npx conventional-github-releaser -p angular
        continue-on-error: true
        env:
          CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{secrets.GITHUB_TOKEN}}

      - name: Install Dependencies
        run: pnpm i

      - name: PNPM build
        run: pnpm run build

      - name: Publish to NPM
        run: pnpm -r publish --access public --no-git-checks
        env:
          NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

      # - name: Publish to VSCE & OVSX
      #   run: npm run publish
      #   working-directory: ./packages/vscode
      #   env:
      #     VSCE_TOKEN: ${{secrets.VSCE_TOKEN}}
      #     OVSX_TOKEN: ${{secrets.OVSX_TOKEN}}


================================================
FILE: .github/workflows/test.yml
================================================
name: Test

on:
  push:
    branches:
      - main
      - master

  pull_request:
    branches:
      - main
      - master
  # workflow_run:
  #   workflows: ['Lint']
  #   types:
  #     - completed

jobs:
  build:
    runs-on: ${{ matrix.os }}
    env:
      TURBO_TOKEN: ${{ secrets.VERCEL_TOKEN }}
      TURBO_TEAM: ${{ secrets.VERCEL_TEAM }}
      TURBO_CACHE_KEY: ${{ matrix.os }}-${{ matrix.node-version }}
      NODE_OPTIONS: '--max_old_space_size=4096'
    strategy:
      matrix:
        node-version: [14, 16]
        os: [ubuntu-latest, macOS-latest]
      fail-fast: false

    steps:
      - uses: actions/checkout@v2

      - name: Install pnpm
        uses: pnpm/action-setup@v2.2.1

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v2
        with:
          node-version: ${{ matrix.node-version }}
          registry-url: https://registry.npmjs.org/
          cache: pnpm

      - name: Install Dependencies and build all packages
        run: pnpm bootstrap

      # - name: Build
      #   run: pnpm run build

      - name: Test
        run: pnpm run test


================================================
FILE: .gitignore
================================================
.DS_Store
.vite-ssg-dist
.vite-ssg-temp
*.local
dist
dist-ssr
node_modules
.idea/
*.log
stats.html
.vite-inspect
.history
**/.rollup.cache
**/test/coverage
tsconfig.tsbuildinfo
**/.temp
**/.cache

# testing
/coverage
cypress/videos
cypress/screenshots

# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

.vercel
.turbo


================================================
FILE: .husky/commit-msg
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# lint commit message
npx --no-install commitlint --edit "$1"


================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# lint git stash files
npx --no-install lint-staged


================================================
FILE: .husky/prepare-commit-msg
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "do not 'git commit' please run 'pnpm commit' instead"
# echo "commit lint please see https://juejin.cn/post/6934292467160514567#heading-3"
echo "See .github/commit-convention.md for more details."


================================================
FILE: .npmrc
================================================
# registry = "https://registry.npmmirror.com"

sass_binary_site = "https://cdn.npmmirror.com/binaries/node-sass"
phantomjs_cdnurl = "https://npmmirror.com/package/downloads"
electron_mirror = "https://registry.npmmirror.com/binary.html?path=electron/"
sqlite3_binary_host_mirror = "https://foxgis.oss-cn-shanghai.aliyuncs.com/"
profiler_binary_host_mirror = "https://registry.npmmirror.com/binary.html?path=node-inspector/"
chromedriver_cdnurl = "https://cdn.npmmirror.com/binaries/chromedriver"


# pnpm 子线程交叉打印
stream = true

# pnpm 工作区中的本地包是否优先于注册表中的包
prefer-workspace-packages = true

# pnpm 将 monorepo 工作区中的本地可用包链接到 node_modules,而不是从注册表重新下载它们。
link-workspace-packages = true

# 依赖提升
public-hoist-pattern[] = *types*
public-hoist-pattern[] = *eslint*
public-hoist-pattern[] = *stylelint*
public-hoist-pattern[] = @prettier/plugin-*
public-hoist-pattern[] = *prettier-plugin-*
public-hoist-pattern[] = prettier
public-hoist-pattern[] = *jest*
public-hoist-pattern[] = *rollup*
public-hoist-pattern[] = *babel*
public-hoist-pattern[] = core-js
public-hoist-pattern[] = regenerator-runtime
public-hoist-pattern[] = esbuild
public-hoist-pattern[] = *conventional*
public-hoist-pattern[] = jsdom

hoist-pattern[] = typescript
hoist-pattern[] = @vue/*
hoist-pattern[] = vue-template-compiler
hoist-pattern[] = vue-template-es2015-compiler
hoist-pattern[] = *vuepress*
hoist-pattern[] = *plop*
hoist-pattern[] = vue-demi

# for doc-site
hoist-pattern[] = nprogress


================================================
FILE: .stylelintignore
================================================
node_modules/
**/*.spec.*
es/
lib/
_site/
dist/
**/node_modules/*
**/segi-ant-theme.less
**/.rollup.cache/*
**/index.html


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": [
    "antfu.iconify",
    "antfu.unocss",
    "antfu.goto-alias",
    "csstools.postcss",
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "mrmlnc.vscode-less",
    "mikestead.dotenv",
    "stylelint.vscode-stylelint",
    "vue.volar",
    "streetsidesoftware.code-spell-checker"
  ]
}


================================================
FILE: .vscode/settings.json
================================================
{
  // Use PNPM
  "npm.packageManager": "pnpm",
  // "eslint.packageManager": "pnpm",
  "typescript.tsdk": "./node_modules/typescript/lib",
  "editor.tabSize": 2,

  "stylelint.enable": true,
  "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
  "prettier.enable": false,
  "editor.formatOnSave": false,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.fixAll.stylelint": true
  },

  "files.associations": {
    "*.css": "postcss"
  },

  "typescript.inlayHints.parameterNames.enabled": "all",
  // "typescript.inlayHints.variableTypes.enabled": true,
  // "typescript.inlayHints.propertyDeclarationTypes.enabled": true,
  "typescript.inlayHints.parameterTypes.enabled": true,
  // "typescript.inlayHints.functionLikeReturnTypes.enabled": true,
  "scss.lint.unknownAtRules": "ignore",
  "less.lint.unknownAtRules": "ignore",
  "css.lint.unknownAtRules": "ignore",

  "unocss.root": "playgrounds/vue3",

  "workbench.colorCustomizations": {
    "activityBar.background": "#252F38",
    "titleBar.activeBackground": "#34414F",
    "titleBar.activeForeground": "#F9FAFB"
  },
  "cSpell.words": [
    "alais",
    "amondnet",
    "antfu",
    "apartuser",
    "APPKEY",
    "appr",
    "Attributify",
    "authc",
    "axios",
    "bonuse",
    "bumpp",
    "busi",
    "Cascader",
    "Certi",
    "clsx",
    "commitlint",
    "Compat",
    "cparagraph",
    "csentence",
    "cssnano",
    "cust",
    "cword",
    "datacachesvr",
    "Datetime",
    "demi",
    "docsearch",
    "DOWNFILE",
    "Dtos",
    "ecmaversion",
    "editble",
    "envinfo",
    "esno",
    "Filterbar",
    "globby",
    "iconify",
    "iife",
    "INDIV",
    "innercheckinmember",
    "Inspction",
    "intlify",
    "jsdelivr",
    "Jssdk",
    "jweixin",
    "Lazyload",
    "micromessenger",
    "mockjs",
    "nicepkg",
    "noscript",
    "nouce",
    "nprogress",
    "oper",
    "OVSX",
    "Pagelist",
    "pannellum",
    "pano",
    "pinia",
    "pnpm",
    "poppable",
    "preinstall",
    "prismjs",
    "qrcode",
    "raycaster",
    "Realsee",
    "redrun",
    "restapi",
    "rgba",
    "safelist",
    "Segi",
    "semibold",
    "shiki",
    "Sname",
    "solidjs",
    "stackblitz",
    "stackblitzrc",
    "stylelint",
    "svgr",
    "tabbar",
    "testop",
    "threejs",
    "touchmove",
    "tweenjs",
    "typecheck",
    "typeof",
    "uhome",
    "uhomecp",
    "unocss",
    "unplugin",
    "unproject",
    "unref",
    "userinfo",
    "Vant",
    "vconsole",
    "VERCEL",
    "Vite",
    "vitejs",
    "Vitesse",
    "vitest",
    "vpay",
    "vuepress",
    "vueuse",
    "wcpay",
    "webgl",
    "Wechat",
    "Weixin",
    "wxappid",
    "XFQYGM",
    "XFQYXZ",
    "yangjinming",
    "zhong",
    "ZLZYLX",
    "ZZMM"
  ],
  "commentTranslate.targetLanguage": "zh-CN"
}


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

- The use of sexualized language or imagery and unwelcome sexual attention or
  advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
  address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at 2214962083@qq.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

Thanks for being interested in contributing to this project!

## Development

To 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.

## Setup

The following steps will get you up and running to contribute to Vr360:

1. Fork the repo (click the <kbd>Fork</kbd> button at the top right of
   [this page](https://github.com/nicepkg/vr360))

2. Clone your fork locally

```sh
git clone https://github.com/<your_github_username>/vr360.git
cd vr360
```

3. Install Dependencies. This project depends on node v14+ and pnpm 6.x

If you don't have pnpm installed, you should execute:

```bash
npm i -g pnpm@6.32.17
```

Install the dependencies:

```bash
pnpm bootstrap
```

We use VuePress for rapid development and documenting. You can start it locally by

```bash
cd packages/doc-site
pnpm dev
```

### Commit Convention

Before you create a Pull Request, please check whether your commits comply with
the commit conventions used in this repository.

When you create a commit we kindly ask you to follow the convention
`category(scope or module): message` in your commit message while using one of
the following categories:

- `feat / feature`: all changes that introduce completely new code or new
  features
- `fix`: changes that fix a bug (ideally you will additionally reference an
  issue if present)
- `refactor`: any code related change that is not a fix nor a feature
- `docs`: changing existing or creating new documentation (i.e. README, docs for
  usage of a lib or cli usage)
- `build`: all changes regarding the build of the software, changes to
  dependencies or the addition of new dependencies
- `test`: all changes regarding tests (adding new tests or changing existing
  ones)
- `ci`: all changes regarding the configuration of continuous integration (i.e.
  github actions, ci system)
- `chore`: all changes to the repository that do not fit into any of the above
  categories

If you are interested in the detailed specification you can visit
https://www.conventionalcommits.org/ or check out the
[Angular Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines).

### Steps to PR

1. Fork of the vvr360 repository and clone your fork

2. Create a new branch out of the `master` branch. We follow the convention
   `[type/scope]`. For example `fix/vr360-core` or `docs/vr360-ui`. `type`
   can be either `docs`, `fix`, `feat`, `build`, or any other conventional
   commit type. `scope` is just a short id that describes the scope of work.

3. Make and commit your changes following the
   [commit convention](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md#commit-convention).
   As you develop, you can run `pnpm --filter <module> build` and
   `pnpm --filter <module> test` to make sure everything works as expected. Please
   note that you might have to run `pnpm bootstrap` first in order to build all
   dependencies.

## Code Style

Don'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.

## Thanks

Thank you again for being interested in this project! You are awesome!

## License

By contributing your code to the vr360 GitHub repository, you agree to
license your contribution under the MIT license.


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 YangJinMing <https://github.com/2214962083>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<div align="center">
  <a href="https://vr360.nicepkg.cn/">
    <img src="https://vr360.nicepkg.cn/images/logo-bg.png" width="50%">
  </a>
  <br>
  <br>
  <p>快速实现你的全景开发需求,全景看房、全景街景、全景景点</p>
  <p>
    <img src="https://img.shields.io/github/package-json/v/nicepkg/vr360" alt="version">
    <img src="https://img.shields.io/github/license/nicepkg/vr360" alt="license">
    <img src="https://img.shields.io/github/stars/nicepkg/vr360?style=social" alt="stars">
  </p>
</div>

## 介绍

Vr360 是一个基于 threejs 能让你快速实现业务全景需求的库,比如全景看房、全景街景、全景景点。

它的核心被设计为框架无关性,可以用 json 配置的方式快速实现常见全景需求。

后续还会提供高度封装的 viewer 和 editor 等组件,很适合懒人,会提供 vue2/3 、 react 、web component 版本。

json 驱动视图的特性是好维护,你甚至可以不用接触 threejs。写出来的代码拉条哈士奇过来也能维护。

## 文档

[查看文档](https://vr360.nicepkg.cn/)

## 库列表

| 库名                                                           | 文档                                                  | 版本                                                                                             | 描述                                                                                        |
| -------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| [@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/                  |
| [@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。 |
| [@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 二次封装版                                                     |
| [@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 二次封装版本,开箱即用。                                       |
| [@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 二次封装版本,开箱即用。                                      |

## Contributing

Learn about contribution [here](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).

This project exists thanks to all the people who contribute:

<a href="https://github.com/nicepkg/vr360/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=nicepkg/vr360" />
</a>

## License

[MIT](https://github.com/nicepkg/vr360/blob/master/LICENSE) License © 2022-PRESENT [nicepkg](https://github.com/nicepkg)


================================================
FILE: commitlint.config.js
================================================
module.exports = {
  extends: ['@commitlint/config-conventional']
}


================================================
FILE: lint-staged.config.js
================================================
module.exports = {
  '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
  '*.json': ['prettier --write'],
  '*.vue': ['eslint --fix', 'stylelint --aei --fix', 'prettier --write'],
  '*.{scss,sass,less,css}': ['stylelint --aei --fix', 'prettier --write'],
  '*.md': ['prettier --write']
}


================================================
FILE: netlify.toml
================================================
[build.environment]
  NODE_VERSION = "16"
  NPM_FLAGS = "--version" # prevent Netlify npm install
[build]
  publish = "packages/doc/.vuepress/dist"
  command = "npm i -g pnpm && pnpm build:ci"


================================================
FILE: package.json
================================================
{
  "name": "@nicepkg/vr360",
  "version": "0.3.1",
  "private": true,
  "packageManager": "pnpm@7.0.0",
  "author": "yangjinming",
  "description": "快速实现你的全景开发需求,全景看房、全景街景、全景景点",
  "engines": {
    "node": ">=14",
    "pnpm": ">=7"
  },
  "scripts": {
    "bootstrap": "pnpm i &&pnpm build:all",
    "build": "esno ./scripts/build.ts",
    "build:all": "turbo run build",
    "build:ci": "pnpm i --store=node_modules/.pnpm-store --frozen-lockfile && turbo run build --no-cache",
    "check-update": "esno ./scripts/check-update.ts",
    "clean": "rimraf **/node_modules/**",
    "commit": "git add . &&git-cz",
    "lint": "pnpm lint:es &&pnpm lint:css",
    "lint:change": "lint-staged",
    "lint:css": "stylelint --aei --fix ./**/*.{vue,css,sass,scss,less,html} --cache --cache-location node_modules/.cache/stylelint/",
    "lint:es": "eslint --fix . --ext .jsx,.js,.vue,.ts,.tsx",
    "preinstall": "npx only-allow pnpm",
    "prepare": "husky install",
    "release": "esno ./scripts/release.ts",
    "test": "pnpm run -r test"
  },
  "devDependencies": {
    "@commitlint/cli": "^17.1.2",
    "@commitlint/config-conventional": "^17.1.0",
    "@types/eslint": "^8.4.6",
    "@types/lodash-es": "^4.17.6",
    "@types/node": "*",
    "@types/prettier": "^2.7.1",
    "@typescript-eslint/eslint-plugin": "^5.38.1",
    "@typescript-eslint/parser": "^5.38.1",
    "@vue/eslint-config-typescript": "^11.0.2",
    "bumpp": "^8.2.1",
    "chalk": "4.1.2",
    "commitizen": "^4.2.5",
    "confusing-browser-globals": "^1.0.11",
    "conventional-changelog-cli": "^2.2.2",
    "cross-env": "^7.0.3",
    "cz-conventional-changelog": "^3.3.0",
    "eslint": "^8.24.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-import-resolver-typescript": "^3.5.1",
    "eslint-plugin-eslint-comments": "3.2.0",
    "eslint-plugin-html": "^7.1.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-jest-dom": "^4.0.2",
    "eslint-plugin-jsx-a11y": "^6.6.1",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-react": "7.31.7",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-testing-library": "^5.7.0",
    "eslint-plugin-vue": "^9.5.1",
    "eslint-plugin-unicorn": "43.0.2",
    "esno": "*",
    "globby": "11.1.0",
    "husky": "^8.0.1",
    "ini": "^3.0.1",
    "less": "^4.1.3",
    "lint-staged": "^13.0.3",
    "npm-check-updates": "^16.3.3",
    "npm-run-all": "^4.1.5",
    "ora": "6.1.2",
    "plop": "^3.1.1",
    "postcss": "^8.4.16",
    "postcss-html": "^1.5.0",
    "postcss-less": "^6.0.0",
    "prettier": "^2.7.1",
    "react": "*",
    "rimraf": "^3.0.2",
    "stylelint": "^14.13.0",
    "stylelint-config-prettier": "^9.0.3",
    "stylelint-config-recess-order": "^3.0.0",
    "stylelint-config-recommended": "^9.0.0",
    "stylelint-config-recommended-vue": "^1.4.0",
    "stylelint-config-standard": "^28.0.0",
    "stylelint-declaration-block-no-ignored-properties": "^2.5.0",
    "stylelint-less": "^1.0.6",
    "stylelint-order": "^5.0.0",
    "stylelint-prettier": "^2.0.0",
    "turbo": "^1.5.4",
    "typescript": "*"
  },
  "pnpm": {
    "overrides": {
      "@testing-library/dom": "8.18.1",
      "@testing-library/jest-dom": "5.16.5",
      "@testing-library/react": "13.4.0",
      "@types/node": "18.7.23",
      "@types/react": "18.0.21",
      "@types/react-dom": "18.0.6",
      "@types/react-router-dom": "5.3.3",
      "@vitejs/plugin-react": "2.1.0",
      "concurrently": "7.4.0",
      "conventional-changelog-cli": "2.2.2",
      "esno": "0.16.3",
      "react": "17.0.2",
      "react-dom": "17.0.2",
      "react-router-dom": "6.3.0",
      "typescript": "4.7.4",
      "vite": "3.0.9",
      "vitest": "0.23.4",
      "vue-demi": "0.13.11"
    },
    "peerDependencyRules": {
      "allowedVersions": {
        "react": "17",
        "react-dom": "17",
        "typescript": "4.8"
      }
    },
    "allowedDeprecatedVersions": {
      "stable": "*",
      "core-js": "*",
      "mkdirp": "*",
      "uuid": "*",
      "querystring": "*",
      "sane": "*",
      "chokidar": "*",
      "fsevents": "*",
      "source-map-resolve": "*",
      "source-map-url": "*",
      "resolve-url": "*",
      "urix": "*"
    },
    "packageExtensions": {
      "stylelint-config-recommended-vue": {
        "dependencies": {
          "postcss-html": "^1.4.1"
        }
      },
      "vue-template-compiler": {
        "devDependencies": {
          "vue": "^2.6.14"
        },
        "peerDependencies": {
          "vue": "^2.6.14"
        }
      }
    }
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}


================================================
FILE: packages/doc/.vuepress/client.ts
================================================
/* eslint-disable unicorn/no-await-expression-member */
import type {ProjectFiles} from '@stackblitz/sdk'
import {defineClientConfig} from '@vuepress/client'
import pkg from '../package.json'

export default defineClientConfig({
  enhance({app}) {
    app.config.globalProperties.version = pkg.version

    if (!__VUEPRESS_SSR__) {
      window.loadCodeDemoModeDefaultFiles = async _mode => {
        const mode = _mode as 'node' | 'vue2' | 'vue3' | 'react' | 'html'

        const defaultFiles: ProjectFiles = {
          'src/demo.css': (await import('./public/code-demo-templates/demo.css.txt?raw')).default,
          '.stackblitzrc': `{
            "startCommand": "npm run dev"
          }`
        }

        switch (mode) {
          case 'node':
          case 'vue3':
            return {
              ...defaultFiles,
              'src/App.vue': (await import('./public/code-demo-templates/vue3/src/App.vue.txt?raw')).default,
              'src/main.ts': (await import('./public/code-demo-templates/vue3/src/main.ts.txt?raw')).default,
              'types/module.d.ts': (await import('./public/code-demo-templates/vue3/types/module.d.ts.txt?raw'))
                .default,
              'index.html': (await import('./public/code-demo-templates/vue3/index.html.txt?raw')).default,
              'vite.config.ts': (await import('./public/code-demo-templates/vue3/vite.config.ts.txt?raw')).default,
              'package.json': (await import('./public/code-demo-templates/vue3/package.json.txt?raw')).default,
              'tsconfig.json': (await import('./public/code-demo-templates/vue3/tsconfig.json.txt?raw')).default
            } as ProjectFiles
          case 'vue2':
            return {
              ...defaultFiles,
              'src/App.vue': (await import('./public/code-demo-templates/vue2/src/App.vue.txt?raw')).default,
              'src/main.ts': (await import('./public/code-demo-templates/vue2/src/main.ts.txt?raw')).default,
              'types/module.d.ts': (await import('./public/code-demo-templates/vue2/types/module.d.ts.txt?raw'))
                .default,
              'index.html': (await import('./public/code-demo-templates/vue2/index.html.txt?raw')).default,
              'vite.config.ts': (await import('./public/code-demo-templates/vue2/vite.config.ts.txt?raw')).default,
              'package.json': (await import('./public/code-demo-templates/vue2/package.json.txt?raw')).default,
              'tsconfig.json': (await import('./public/code-demo-templates/vue2/tsconfig.json.txt?raw')).default
            } as ProjectFiles
          case 'react':
            return {
              ...defaultFiles,
              'src/main.tsx': (await import('./public/code-demo-templates/react/src/main.tsx.txt?raw')).default,
              'index.html': (await import('./public/code-demo-templates/react/index.html.txt?raw')).default,
              'package.json': (await import('./public/code-demo-templates/react/package.json.txt?raw')).default,
              'tsconfig.json': (await import('./public/code-demo-templates/react/tsconfig.json.txt?raw')).default,
              'vite.config.ts': (await import('./public/code-demo-templates/react/vite.config.ts.txt?raw')).default
            } as ProjectFiles
          case 'html':
            return {
              'package.json': (await import('./public/code-demo-templates/html/package.json.txt?raw')).default,
              'demo.css': (await import('./public/code-demo-templates/demo.css.txt?raw')).default,
              '.stackblitzrc': `{
                "startCommand": "npm run dev"
              }`
            } as ProjectFiles
          default:
            return {}
        }
      }
    }
  }
})


================================================
FILE: packages/doc/.vuepress/components/DemoA.vue
================================================
<template>
  <div class="demoA">
    <div
      ref="tipRef"
      class="demoA-tip"
      :style="{
        transform: `translate(${tipLeft}px, ${tipTop + 50}px)`,
        zIndex: showTip ? 99 : -1,
        visibility: showTip ? 'visible' : 'hidden'
      }"
    >
      <div class="demoA-tip-title">{{ tipTitle }}</div>
      <div class="demoA-tip-content">{{ tipContent }}</div>
    </div>
    <div ref="containerRef" class="demoA-container"></div>
    <div class="demoA-bottom-bar">
      <div
        v-for="space in spacesConfig"
        :key="space.id"
        class="demoA-bottom-card"
        :style="{
          backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`
        }"
        @click="handleSwitchSpace(space)"
      ></div>
    </div>
  </div>
</template>

<script setup lang="ts">
/* eslint-disable import/no-named-as-default-member */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import {onMounted, ref} from 'vue'
import type {SpaceConfig} from '@nicepkg/vr360-core'
import {Vr360} from '@nicepkg/vr360-core'
import textures from '../../../../textures.json'

const containerRef = ref<HTMLElement>()

const tipRef = ref<HTMLElement>()
const tipLeft = ref(0)
const tipTop = ref(0)
const showTip = ref(false)
const tipTitle = ref('')
const tipContent = ref('')

let vr360: InstanceType<typeof Vr360>

const spacesConfig = ref<SpaceConfig[]>([
  {
    id: 'spaceA',
    tips: [
      {
        id: '1',
        position: {x: 0, y: -10, z: 40},
        content: {
          title: '豪华跑车',
          text: '比奥迪还贵的豪华跑车'
        }
      },
      {
        id: '2',
        textureUrl: textures.hotpot,
        targetSpaceId: 'spaceB',
        position: {x: -10, y: -4, z: 40},
        content: {
          title: '去客厅',
          text: '一起去尊贵的客厅吧'
        }
      }
    ],
    cubeSpaceTextureUrls: textures.firstHouseDoor
  },
  {
    id: 'spaceB',
    tips: [
      {
        id: '3',
        position: {x: -2, y: -25, z: 40},
        content: {
          title: '香奈儿垃圾桶',
          text: '里面装着主人不用的奢侈品'
        }
      },
      {
        id: '4',
        position: {x: -20, y: 0, z: 40},
        content: {
          title: '宇宙牌冰箱',
          text: '装着超级多零食'
        }
      },
      {
        id: '5',
        textureUrl: textures.hotpot,
        targetSpaceId: 'spaceA',
        position: {
          x: -8,
          y: 0,
          z: -40
        },
        content: {
          title: '去门口',
          text: '一起去门口吧'
        }
      }
    ],
    cubeSpaceTextureUrls: textures.firstHouseLivingRoom
  }
])

function handleSwitchSpace(space: SpaceConfig) {
  vr360.switchSpace(space.id)
}

onMounted(() => {
  vr360 = new Vr360({
    container: containerRef.value!,
    tipContainer: tipRef.value!,
    spacesConfig: spacesConfig.value
  })

  vr360.controls.autoRotate = true

  vr360.render()

  vr360.listenResize()

  vr360.on('showTip', e => {
    vr360!.controls.autoRotate = false
    const {top, left, tip} = e
    showTip.value = true
    tipLeft.value = left
    tipTop.value = top
    tipTitle.value = tip.content.title
    tipContent.value = tip.content.text
  })

  vr360.on('hideTip', () => {
    vr360!.controls.autoRotate = true
    showTip.value = false
  })
})
</script>
<style>
.demoA {
  position: relative;
  z-index: 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: calc(100vw - 3rem);
  height: 500px;
  margin-bottom: 2rem;
  overflow: hidden;
  background-color: var(--c-bg);
  border: 1px solid var(--c-border);
  border-radius: 8px;
}

.demoA-tip {
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 240px;
  height: 60px;
  padding: 4px;
  color: #fff;
  cursor: pointer;
  background-color: rgba(0, 0, 0, 0.5);
  border-radius: 4px;
}

.demoA-tip-title {
  font-weight: bold;
}

.demoA-container {
  width: 100%;
  height: 100%;
}

.demoA-bottom-bar {
  display: flex;
  align-items: center;
  width: 100%;
  height: 100px;
}

.demoA-bottom-card {
  width: 140px;
  height: 70px;
  margin-left: 1rem;
  cursor: pointer;
  background-repeat: no-repeat;
  background-size: cover;
  border-radius: 4px;
}
</style>


================================================
FILE: packages/doc/.vuepress/components/NpmBadge.vue
================================================
<script setup lang="ts">
import {computed} from 'vue'

const props = defineProps({
  package: {
    type: String,
    required: true
  },
  distTag: {
    type: String,
    required: false,
    default: 'next'
  }
})

const badgeLink = computed(() => `https://www.npmjs.com/package/${props.package}`)
const badgeLabel = computed(() => {
  if (props.distTag) {
    return `${props.package}@${props.distTag}`
  }

  return props.package
})
const badgeImg = computed(
  () => `https://badgen.net/npm/v/${props.package}/${props.distTag}?label=${encodeURIComponent(badgeLabel.value)}`
)
</script>

<template>
  <a class="npm-badge" :href="badgeLink" :title="package" target="_blank" rel="noopener noreferrer">
    <img :src="badgeImg" :alt="package" />
  </a>
</template>

<style scoped>
.npm-badge {
  margin-right: 0.5rem;
}
</style>


================================================
FILE: packages/doc/.vuepress/configs/index.ts
================================================
export * as navbar from './navbar'
export * as sidebar from './sidebar'


================================================
FILE: packages/doc/.vuepress/configs/navbar/en.ts
================================================
import type {NavbarConfig} from '@vuepress/theme-default'
import {version} from '../../utils/common'

export const en: NavbarConfig = [
  {
    text: 'Guide',
    link: '/en/guide/'
  },
  {
    text: `v${version}`,
    children: [
      {
        text: 'Releases',
        link: 'https://github.com/nicepkg/vr360/releases'
      }
    ]
  }
]


================================================
FILE: packages/doc/.vuepress/configs/navbar/index.ts
================================================
export * from './en'
export * from './zh'


================================================
FILE: packages/doc/.vuepress/configs/navbar/zh.ts
================================================
import type {NavbarConfig} from '@vuepress/theme-default'
import {version} from '../../utils/common'

export const zh: NavbarConfig = [
  {
    text: '指南',
    link: '/guide/'
  },
  {
    text: '库列表',
    children: [
      {
        text: 'vr360-core',
        link: '/libs/vr360-core/README.md'
      }
    ]
  },
  {
    text: `v${version}`,
    children: [
      {
        text: '更新日志',
        link: 'https://github.com/nicepkg/vr360/releases'
      }
    ]
  }
]


================================================
FILE: packages/doc/.vuepress/configs/sidebar/en.ts
================================================
import type {SidebarConfig} from '@vuepress/theme-default'

export const en: SidebarConfig = {
  '/en/guide/': [
    {
      text: 'Guide',
      children: ['/en/guide/README.md', '/en/guide/questions.md']
    }
  ]
}


================================================
FILE: packages/doc/.vuepress/configs/sidebar/index.ts
================================================
export * from './en'
export * from './zh'


================================================
FILE: packages/doc/.vuepress/configs/sidebar/zh.ts
================================================
import type {SidebarConfig} from '@vuepress/theme-default'

export const zh: SidebarConfig = {
  '/guide/': [
    {
      text: '指南',
      children: ['/guide/README.md', '/guide/questions.md']
    }
  ],
  '/libs/vr360-core/': [
    {
      text: 'vr360-core',
      children: [
        '/libs/vr360-core/README.md',
        '/libs/vr360-core/properties.md',
        '/libs/vr360-core/methods.md',
        '/libs/vr360-core/events.md',
        '/libs/vr360-core/example.md'
      ]
    }
  ]
}


================================================
FILE: packages/doc/.vuepress/examples/firstHouse/demo.css
================================================
* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

.demo {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background-color: #fff;
}

.demo-tip {
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 240px;
  height: 60px;
  padding: 4px;
  color: #fff;
  cursor: pointer;
  visibility: hidden;
  background-color: rgba(0, 0, 0, 0.5);
  border-radius: 4px;
}

.demo-tip-title {
  font-weight: bold;
}

.demo-container {
  width: 100%;
  height: 100%;
}

.demo-bottom-bar {
  display: flex;
  align-items: center;
  width: 100%;
  height: 100px;
}

.demo-bottom-card {
  width: 140px;
  height: 70px;
  margin-left: 1rem;
  cursor: pointer;
  background-repeat: no-repeat;
  background-size: cover;
  border-radius: 4px;
}


================================================
FILE: packages/doc/.vuepress/examples/firstHouse/html.html
================================================
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>Html App</title>
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
    />

    <!-- 引入 threejs -->
    <script src="https://unpkg.com/three@0.145.0/build/three.min.js"></script>

    <!-- 引入 vr360-core -->
    <script src="https://unpkg.com/@nicepkg/vr360-core@^0.3.1/dist/index.min.umd.js"></script>

    <!-- 引入自己的 css -->
    <link href="./demo.css" rel="stylesheet" />
  </head>
  <body>
    <div class="demo">
      <!-- 提示 -->
      <div class="demo-tip">

        <!-- 提示标题 -->
        <div class="demo-tip-title"></div>

        <!-- 提示内容 -->
        <div class="demo-tip-content"></div>
      </div>

      <!-- 360全景容器 -->
      <div class="demo-container"></div>

      <!-- 底部切换场景 -->
      <div class="demo-bottom-bar"></div>
    </div>
    <script>
      const {Vr360} = Vr360Core // 原生使用时,所有的导出都在 window.Vr360Core 里
      const container = document.querySelector('.demo-container') // 360全景容器
      const tip = document.querySelector('.demo-tip') // 提示容器
      const tipTitle = document.querySelector('.demo-tip-title') // 提示标题 el
      const tipContent = document.querySelector('.demo-tip-content') // 提示内容 el
      const bottomBar = document.querySelector('.demo-bottom-bar') // 底部切换场景容器

      // 全景空间配置
      const spacesConfig = [
        {
          id: 'spaceA', // 空间 id,用于切换空间,必须唯一
          tips: [
            // 提示,可选
            {
              id: '1', // 提示 id,用于缓存,在当前 tips 数组里要唯一,必须
              position: {x: 0, y: -10, z: 40}, // 提示位置,必须
              content: {
                // 提示内容,在 showTip 事件会暴露,要包含什么你自己决定。
                title: '豪华跑车',
                text: '比奥迪还贵的豪华跑车'
              }
            },
            {
              id: '2',
              // 自定义提示图标贴图,可选
              textureUrl:
                'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
              targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id,可选
              position: {x: -10, y: -4, z: 40},
              content: {
                title: '去客厅',
                text: '一起去尊贵的客厅吧'
              }
            }
          ],
          cubeSpaceTextureUrls: {
            // 立方体贴图,分别为:背侧、下侧、前侧、左侧、右侧、上侧,必须
            back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',
            down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',
            front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',
            left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',
            right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',
            up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'
          }
        },
        {
          id: 'spaceB',
          tips: [
            {
              id: '3',
              position: {x: -2, y: -25, z: 40},
              content: {
                title: '香奈儿垃圾桶',
                text: '里面装着主人不用的奢侈品'
              }
            },
            {
              id: '4',
              position: {x: -20, y: 0, z: 40},
              content: {
                title: '宇宙牌冰箱',
                text: '装着超级多零食'
              }
            },
            {
              id: '5',
              textureUrl:
                'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
              targetSpaceId: 'spaceA',
              position: {
                x: -8,
                y: 0,
                z: -40
              },
              content: {
                title: '去门口',
                text: '一起去门口吧'
              }
            }
          ],
          cubeSpaceTextureUrls: {
            back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',
            down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',
            front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',
            left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',
            right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',
            up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'
          }
        }
      ]

      // 初始化全景实例
      const vr360 = new Vr360({
        container, // 全景挂载容器
        tipContainer: tip, // 提示挂载容器
        spacesConfig // 全景配置
      })

      // 设置全景自动旋转
      vr360.controls.autoRotate = true

      // 开始渲染全景
      vr360.render()

      // 实时监听页面尺寸变化,更新全景尺寸
      vr360.listenResize()

      // 当需要显示提示时
      vr360.on('showTip', e => {
        // 停止旋转场景
        vr360.controls.autoRotate = false

        // 设置提示内容和提示容器位置
        const {top, left} = e
        Object.assign(tip.style, {
          transform: `translate(${left}px, ${top + 50}px)`,
          zIndex: 99,
          visibility: 'visible'
        })
        tip.style.t
        tipTitle.innerText = e.tip.content.title
        tipContent.innerText = e.tip.content.text
      })

      // 当需要隐藏提示时
      vr360.on('hideTip', () => {
        // 重新开启旋转场景
        vr360.controls.autoRotate = true

        // 隐藏提示容器
        tip.style.zIndex = -1
        tip.style.visibility = 'hidden'
      })

      // 页面卸载时注销全景实例
      window.addEventListener('unload', () => {
        vr360.destroy()
      })

      // 切换全景空间
      function handleSwitchSpace(space) {
        vr360.switchSpace(space.id)
      }

      bottomBar.append(
        ...spacesConfig.map(space => {
          const card = document.createElement('div')
          card.className = 'demo-bottom-card'
          Object.assign(card.style, {
            backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`
          })
          card.addEventListener('click', () => {
            handleSwitchSpace(space)
          })
          return card
        })
      )
    </script>
  </body>
</html>


================================================
FILE: packages/doc/.vuepress/examples/firstHouse/react.tsx
================================================
import React, {useEffect, useState, useRef} from 'react'
import {Vr360} from '@nicepkg/vr360-core'
import type {SpaceConfig} from '@nicepkg/vr360-core'
import './demo.css' // 引入自己的样式

function Example() {
  const containerRef = useRef<HTMLDivElement>(null) // 全景容器
  const tipRef = useRef<HTMLDivElement>(null) // 提示容器
  const [tipLeft, setTipLeft] = useState(0) // 提示容器的 left 或 translateX 值
  const [tipTop, setTipTop] = useState(0) // 提示容器的 top 或 translateY 值
  const [showTip, setShowTip] = useState(false) // 是否显示提示容器
  const [tipTitle, setTipTitle] = useState('') // 提示容器的标题
  const [tipContent, setTipContent] = useState('') // 提示容器的内容
  const [vr360, setVr360] = useState<InstanceType<typeof Vr360>>() // 全景实例

  // 全景空间配置
  const spacesConfig: SpaceConfig[] = [
    {
      id: 'spaceA', // 空间 id,用于切换空间,必须唯一
      tips: [
        // 提示,可选
        {
          id: '1', // 提示 id,用于缓存,在当前 tips 数组里要唯一,必须
          position: {x: 0, y: -10, z: 40}, // 提示位置,必须
          content: {
            // 提示内容,在 showTip 事件会暴露,要包含什么你自己决定。
            title: '豪华跑车',
            text: '比奥迪还贵的豪华跑车'
          }
        },
        {
          id: '2',
          // 自定义提示图标贴图,可选
          textureUrl:
            'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
          targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id,可选
          position: {x: -10, y: -4, z: 40},
          content: {
            title: '去客厅',
            text: '一起去尊贵的客厅吧'
          }
        }
      ],
      cubeSpaceTextureUrls: {
        // 立方体贴图,分别为:背侧、下侧、前侧、左侧、右侧、上侧,必须
        back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',
        down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',
        front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',
        left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',
        right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',
        up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'
      }
    },
    {
      id: 'spaceB',
      tips: [
        {
          id: '3',
          position: {x: -2, y: -25, z: 40},
          content: {
            title: '香奈儿垃圾桶',
            text: '里面装着主人不用的奢侈品'
          }
        },
        {
          id: '4',
          position: {x: -20, y: 0, z: 40},
          content: {
            title: '宇宙牌冰箱',
            text: '装着超级多零食'
          }
        },
        {
          id: '5',
          textureUrl:
            'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
          targetSpaceId: 'spaceA',
          position: {
            x: -8,
            y: 0,
            z: -40
          },
          content: {
            title: '去门口',
            text: '一起去门口吧'
          }
        }
      ],
      cubeSpaceTextureUrls: {
        back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',
        down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',
        front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',
        left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',
        right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',
        up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'
      }
    }
  ]

  useEffect(() => {
    // 初始化全景实例
    setVr360(
      new Vr360({
        container: containerRef.current!,
        tipContainer: tipRef.current!,
        spacesConfig
      })
    )

    return () => {
      // 页面卸载时注销全景实例
      vr360?.destroy?.()
    }
  }, [])

  useEffect(() => {
    if (vr360) {
      // 设置全景自动旋转
      vr360.controls.autoRotate = true

      // 开始渲染全景
      vr360.render()

      // 实时监听页面尺寸变化,更新全景尺寸
      vr360.listenResize()

      // 当需要显示提示时
      vr360.on('showTip', e => {
        // 停止旋转场景
        vr360!.controls.autoRotate = false

        // 设置提示内容和提示容器位置
        const {top, left, tip} = e
        setShowTip(true)
        setTipLeft(left)
        setTipTop(top)
        setTipTitle(tip.content.title)
        setTipContent(tip.content.text)
      })

      // 当需要隐藏提示时
      vr360.on('hideTip', () => {
        // 重新开启旋转场景
        vr360!.controls.autoRotate = true

        // 隐藏提示容器
        setShowTip(false)
      })
    }
  }, [vr360])

  // 切换全景空间
  function handleSwitchSpace(space: SpaceConfig) {
    vr360?.switchSpace?.(space.id)
  }

  return (
    <div className="demo">
      {/* 提示 */}
      <div
        ref={tipRef}
        className="demo-tip"
        style={{
          transform: `translate(${tipLeft}px, ${tipTop + 50}px)`,
          zIndex: showTip ? 99 : -1,
          visibility: showTip ? 'visible' : 'hidden'
        }}
      >
        {/* 提示标题 */}
        <div className="demo-tip-title">{tipTitle}</div>

        {/* 提示内容 */}
        <div className="demo-tip-content">{tipContent}</div>
      </div>

      {/* 360全景容器 */}
      <div ref={containerRef} className="demo-container"></div>

      {/* 底部切换场景 */}
      <div className="demo-bottom-bar">
        {spacesConfig.map(space => (
          <div
            key={space.id}
            className="demo-bottom-card"
            onClick={() => handleSwitchSpace(space)}
            style={{
              backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`
            }}
          ></div>
        ))}
      </div>
    </div>
  )
}

export default Example


================================================
FILE: packages/doc/.vuepress/examples/firstHouse/vue2.vue
================================================
<template>
  <div class="demo">
    <!-- 提示 -->
    <div
      ref="tipRef"
      class="demo-tip"
      :style="{
        transform: `translate(${tip.left}px, ${tip.top + 50}px)`,
        zIndex: tip.show ? 99 : -1,
        visibility: tip.show ? 'visible' : 'hidden'
      }"
    >
      <!-- 提示标题 -->
      <div class="demo-tip-title">{{ tip.title }}</div>

      <!-- 提示内容 -->
      <div class="demo-tip-content">{{ tip.content }}</div>
    </div>

    <!-- 360全景容器 -->
    <div ref="containerRef" class="demo-container"></div>

    <!-- 底部切换场景 -->
    <div class="demo-bottom-bar">
      <div
        v-for="space in spacesConfig"
        :key="space.id"
        class="demo-bottom-card"
        @click="handleSwitchSpace(space)"
        :style="{
          backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`
        }"
      ></div>
    </div>
  </div>
</template>
<script lang="ts">
import {Vr360} from '@nicepkg/vr360-core'
import type {SpaceConfig} from '@nicepkg/vr360-core'

export default {
  data() {
    // 全景空间配置
    const spacesConfig: SpaceConfig[] = [
      {
        id: 'spaceA', // 空间 id,用于切换空间,必须唯一
        tips: [
          // 提示,可选
          {
            id: '1', // 提示 id,用于缓存,在当前 tips 数组里要唯一,必须
            position: {x: 0, y: -10, z: 40}, // 提示位置,必须
            content: {
              // 提示内容,在 showTip 事件会暴露,要包含什么你自己决定。
              title: '豪华跑车',
              text: '比奥迪还贵的豪华跑车'
            }
          },
          {
            id: '2',
            // 自定义提示图标贴图,可选
            textureUrl:
              'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
            targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id,可选
            position: {x: -10, y: -4, z: 40},
            content: {
              title: '去客厅',
              text: '一起去尊贵的客厅吧'
            }
          }
        ],
        cubeSpaceTextureUrls: {
          // 立方体贴图,分别为:背侧、下侧、前侧、左侧、右侧、上侧,必须
          back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',
          down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',
          front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',
          left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',
          right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',
          up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'
        }
      },
      {
        id: 'spaceB',
        tips: [
          {
            id: '3',
            position: {x: -2, y: -25, z: 40},
            content: {
              title: '香奈儿垃圾桶',
              text: '里面装着主人不用的奢侈品'
            }
          },
          {
            id: '4',
            position: {x: -20, y: 0, z: 40},
            content: {
              title: '宇宙牌冰箱',
              text: '装着超级多零食'
            }
          },
          {
            id: '5',
            textureUrl:
              'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
            targetSpaceId: 'spaceA',
            position: {
              x: -8,
              y: 0,
              z: -40
            },
            content: {
              title: '去门口',
              text: '一起去门口吧'
            }
          }
        ],
        cubeSpaceTextureUrls: {
          back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',
          down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',
          front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',
          left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',
          right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',
          up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'
        }
      }
    ]

    return {
      vr360: null as InstanceType<typeof Vr360> | null, // 全景实例
      spacesConfig, // 全景空间配置
      tip: {
        // 提示属性
        top: 0, // 提示容器的 top 或 translateY 值
        left: 0, // 提示容器的 left 或 translateX 值
        title: '', // 提示标题
        content: '', // 提示内容
        show: false // 是否显示提示
      }
    }
  },
  mounted() {
    // 初始化全景实例
    this.vr360 = new Vr360({
      container: this.$refs.containerRef!,
      tipContainer: this.$refs.tipRef!,
      spacesConfig: this.spacesConfig
    })

    // 设置全景自动旋转
    this.vr360.controls.autoRotate = true

    // 开始渲染全景
    this.vr360.render()

    // 实时监听页面尺寸变化,更新全景尺寸
    this.vr360.listenResize()

    // 当需要显示提示时
    this.vr360.on('showTip', e => {
      // 停止旋转场景
      this.vr360!.controls.autoRotate = false

      // 设置提示内容和提示容器位置
      const {top, left, tip} = e
      this.tip = {
        top,
        left,
        title: tip.content.title,
        content: tip.content.text,
        show: true
      }
    })

    // 当需要隐藏提示时
    this.vr360.on('hideTip', () => {
      // 重新开启旋转场景
      this.vr360!.controls.autoRotate = true

      // 隐藏提示容器
      this.tip.show = false
    })
  },
  destroy() {
    // 页面卸载时注销全景实例
    this.vr360?.destroy?.()
  },
  methods: {
    // 切换全景空间
    handleSwitchSpace(space: SpaceConfig) {
      this.vr360?.switchSpace?.(space.id)
    }
  }
}
</script>
<style>
/* 引入自己的样式 */
@import './demo.css';
</style>


================================================
FILE: packages/doc/.vuepress/examples/firstHouse/vue3.vue
================================================
<template>
  <div class="demo">
    <!-- 提示 -->
    <div
      ref="tipRef"
      class="demo-tip"
      :style="{
        transform: `translate(${tipLeft}px, ${tipTop + 50}px)`,
        zIndex: showTip ? 99 : -1,
        visibility: showTip ? 'visible' : 'hidden'
      }"
    >
      <!-- 提示标题 -->
      <div class="demo-tip-title">{{ tipTitle }}</div>

      <!-- 提示内容 -->
      <div class="demo-tip-content">{{ tipContent }}</div>
    </div>

    <!-- 360全景容器 -->
    <div ref="containerRef" class="demo-container"></div>

    <!-- 底部切换场景 -->
    <div class="demo-bottom-bar">
      <div
        v-for="space in spacesConfig"
        :key="space.id"
        class="demo-bottom-card"
        @click="handleSwitchSpace(space)"
        :style="{
          backgroundImage: `url(${space.cubeSpaceTextureUrls.left})`
        }"
      ></div>
    </div>
  </div>
</template>

<script setup lang="ts">
import {onMounted, onUnmounted, ref} from 'vue'
import {Vr360} from '@nicepkg/vr360-core'
import type {SpaceConfig} from '@nicepkg/vr360-core'

const containerRef = ref<HTMLElement>() // 全景容器

const tipRef = ref<HTMLElement>() // 提示容器
const tipLeft = ref(0) // 提示容器的 left 或 translateX 值
const tipTop = ref(0) // 提示容器的 top 或 translateY 值
const showTip = ref(false) // 是否显示提示容器
const tipTitle = ref('') // 提示容器的标题
const tipContent = ref('') // 提示容器的内容

let vr360: InstanceType<typeof Vr360> // 全景实例

// 全景空间配置
const spacesConfig: SpaceConfig[] = [
  {
    id: 'spaceA', // 空间 id,用于切换空间,必须唯一
    tips: [
      // 提示,可选
      {
        id: '1', // 提示 id,用于缓存,在当前 tips 数组里要唯一,必须
        position: {x: 0, y: -10, z: 40}, // 提示位置,必须
        content: {
          // 提示内容,在 showTip 事件会暴露,要包含什么你自己决定。
          title: '豪华跑车',
          text: '比奥迪还贵的豪华跑车'
        }
      },
      {
        id: '2',
        // 自定义提示图标贴图,可选
        textureUrl: 'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
        targetSpaceId: 'spaceB', // 提示点击后跳转的空间 id,可选
        position: {x: -10, y: -4, z: 40},
        content: {
          title: '去客厅',
          text: '一起去尊贵的客厅吧'
        }
      }
    ],
    cubeSpaceTextureUrls: {
      // 立方体贴图,分别为:背侧、下侧、前侧、左侧、右侧、上侧,必须
      back: 'https://m.360buyimg.com/babel/jfs/t1/40814/31/19646/41953/63398297E0707fe35/4e831e60cf579899.jpg',
      down: 'https://m.360buyimg.com/babel/jfs/t1/43941/23/19369/84038/633982d7E838acd9a/9e7a89cc910d3409.jpg',
      front: 'https://m.360buyimg.com/babel/jfs/t1/150573/35/27827/113528/633982faE8556c0c0/b455284fd91885c6.jpg',
      left: 'https://m.360buyimg.com/babel/jfs/t1/189204/25/29491/61430/63398310E3c180e43/26d9b459cf714f9f.jpg',
      right: 'https://m.360buyimg.com/babel/jfs/t1/184948/34/28448/131232/63398323E61ff80fb/89ab84eda0421260.jpg',
      up: 'https://m.360buyimg.com/babel/jfs/t1/86258/24/33602/70616/63398334Ea2dcf448/e2f5c275792fb3d6.jpg'
    }
  },
  {
    id: 'spaceB',
    tips: [
      {
        id: '3',
        position: {x: -2, y: -25, z: 40},
        content: {
          title: '香奈儿垃圾桶',
          text: '里面装着主人不用的奢侈品'
        }
      },
      {
        id: '4',
        position: {x: -20, y: 0, z: 40},
        content: {
          title: '宇宙牌冰箱',
          text: '装着超级多零食'
        }
      },
      {
        id: '5',
        textureUrl: 'https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png',
        targetSpaceId: 'spaceA',
        position: {
          x: -8,
          y: 0,
          z: -40
        },
        content: {
          title: '去门口',
          text: '一起去门口吧'
        }
      }
    ],
    cubeSpaceTextureUrls: {
      back: 'https://m.360buyimg.com/babel/jfs/t1/48117/28/21445/120448/63398366Ede81497b/a46e362df5f7d0ed.jpg',
      down: 'https://m.360buyimg.com/babel/jfs/t1/101209/24/26762/106253/63398376Eedb0db22/4f335c4ecd72ad74.jpg',
      front: 'https://m.360buyimg.com/babel/jfs/t1/154056/6/26449/110652/63398388E8ecda044/22e1646534839a95.jpg',
      left: 'https://m.360buyimg.com/babel/jfs/t1/198990/28/28201/74687/6339839aE28806a5e/43b311d3379397df.jpg',
      right: 'https://m.360buyimg.com/babel/jfs/t1/209711/14/25233/92186/633983b0E8f4df687/750ba84061ea64a6.jpg',
      up: 'https://m.360buyimg.com/babel/jfs/t1/186545/35/29054/29678/633983c2E72ef4848/92043b945a03fc29.jpg'
    }
  }
]

onMounted(() => {
  // 初始化全景实例
  vr360 = new Vr360({
    container: containerRef.value!,
    tipContainer: tipRef.value!,
    spacesConfig
  })

  // 设置全景自动旋转
  vr360.controls.autoRotate = true

  // 开始渲染全景
  vr360.render()

  // 实时监听页面尺寸变化,更新全景尺寸
  vr360.listenResize()

  // 当需要显示提示时
  vr360.on('showTip', e => {
    // 停止旋转场景
    vr360!.controls.autoRotate = false

    // 设置提示内容和提示容器位置
    const {top, left, tip} = e
    showTip.value = true
    tipLeft.value = left
    tipTop.value = top
    tipTitle.value = tip.content.title
    tipContent.value = tip.content.text
  })

  // 当需要隐藏提示时
  vr360.on('hideTip', () => {
    // 重新开启旋转场景
    vr360!.controls.autoRotate = true

    // 隐藏提示容器
    showTip.value = false
  })
})

// 切换全景空间
function handleSwitchSpace(space: SpaceConfig) {
  vr360.switchSpace(space.id)
}

onUnmounted(() => {
  // 页面卸载时注销全景实例
  vr360?.destroy?.()
})
</script>
<style>
/* 引入自己的样式 */
@import './demo.css';
</style>


================================================
FILE: packages/doc/.vuepress/index.build.html
================================================
<!DOCTYPE html>
<html lang="{{ lang }}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="generator" content="VuePress {{ version }}">
    <!--vuepress-ssr-head-->
    <!--vuepress-ssr-resources-->
    <!--vuepress-ssr-styles-->
  </head>
  <body>
    <div id="vr-teleport"></div>
    <div id="app"><!--vuepress-ssr-app--></div>
    <!--vuepress-ssr-scripts-->
  </body>
</html>


================================================
FILE: packages/doc/.vuepress/plugins/code-demo/CodeDemo.props.ts
================================================
import type {Project} from '@stackblitz/sdk'
import type {ExtractPropTypes, PropType} from 'vue'

export const getProps = () => ({
  mode: {
    type: String,
    default: 'node'
  },
  project: {
    type: Object as PropType<Project>
  },
  previewOnly: {
    type: Boolean,
    default: false
  },
  clickToLoad: {
    type: Boolean,
    default: false
  },
  openFile: {
    type: String,
    default: 'src/main.ts'
  }
})

export type CodeDemoProps = Partial<ExtractPropTypes<ReturnType<typeof getProps>>>


================================================
FILE: packages/doc/.vuepress/plugins/code-demo/CodeDemo.vue
================================================
<template>
  <div
    ref="codeDemoRef"
    class="code-demo"
    :style="{
      border: isFullScreen ? 'none' : '1px solid var(--c-border)',
      borderRadius: isFullScreen ? '0px' : '8px'
    }"
  >
    <div class="code-demo-top">
      <div class="code-demo-title">{{ project?.title ?? DEFAULT_EDITOR_TITLE }}</div>
      <div class="code-demo-fullscreen" @click="toggleFullScreen">
        <ExitFullscreenIcon v-if="isFullScreen" />
        <FullscreenIcon v-else />
      </div>
    </div>
    <div ref="codeDemoEditorRef" class="code-demo-editor"></div>
  </div>
</template>

<script setup lang="ts">
import {onMounted, ref} from 'vue'
import sdk from '@stackblitz/sdk'
import type {ProjectFiles, Project} from '@stackblitz/sdk'
import {getProps} from './CodeDemo.props'
import {DEFAULT_EDITOR_TITLE} from './constant'
import {ExitFullscreenIcon, FullscreenIcon} from './Icons'

const props = defineProps(getProps())

const codeDemoRef = ref<HTMLElement>()
const codeDemoEditorRef = ref<HTMLIFrameElement>()
const isFullScreen = ref(false)

const toggleFullScreen = () => {
  if (!codeDemoRef.value) return
  if (isFullScreen.value) {
    document.exitFullscreen()
    isFullScreen.value = false
  } else {
    codeDemoRef.value.requestFullscreen()
    isFullScreen.value = true
  }
}

onMounted(async () => {
  if (!codeDemoEditorRef.value || !props.project) return

  codeDemoEditorRef.value.setAttribute('frameborder', '0')

  try {
    // eslint-disable-next-line no-restricted-globals
    const defaultFiles: ProjectFiles = (await self.loadCodeDemoModeDefaultFiles?.(props.mode)) ?? {}
    const finalProject = {
      ...props.project,
      files: {
        ...defaultFiles,
        ...props.project.files
      }
    } as Project

    await sdk.embedProject(codeDemoEditorRef.value, finalProject, {
      forceEmbedLayout: true,
      openFile: props.openFile,
      hideNavigation: true,
      height: '100%',
      view: props.previewOnly ? 'preview' : undefined,
      clickToLoad: props.clickToLoad
    })
  } catch (error) {
    console.error('CodeDemoError:', error)
  }
})
</script>
<style>
.code-demo {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background-color: #2e3138;
}
.code-demo-top {
  box-sizing: border-box;
  display: flex;
  flex-shrink: 0;
  align-items: center;
  width: 100%;
  height: 2rem;
  padding: 0 0.5em;
  overflow: hidden;
  font-size: 14px;
  color: #ccc;
}
.code-demo-title {
  flex: 1;
}
.code-demo-fullscreen {
  flex-shrink: 0;
  font-size: 20px;
  cursor: pointer;
}
.code-demo-editor {
  box-sizing: border-box;
  display: block;
  height: 100%;
  min-height: 500px;
  border: none;
}
</style>


================================================
FILE: packages/doc/.vuepress/plugins/code-demo/Icons.tsx
================================================
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {VNode} from 'vue'
import {defineComponent, h} from 'vue'

const createComponent = (name: string, render: (props: Record<string, any>) => VNode) =>
  defineComponent({
    name,
    render() {
      const props = {...this.$props, ...this.$attrs}
      return render(props)
    }
  })

export const FullscreenIcon = createComponent('FullscreenIcon', props => {
  //
  //   <svg width="1em" height="1em" viewBox="0 0 24 24" {...props}>
  //     <path d="M5 5h5v2H7v3H5V5m9 0h5v5h-2V7h-3V5m3 9h2v5h-5v-2h3v-3m-7 3v2H5v-5h2v3h3z" fill="currentColor"></path>
  //   </svg>
  //

  return h(
    'svg',
    {
      width: '1em',
      height: '1em',
      viewBox: '0 0 24 24',
      ...props
    },
    [
      h('path', {
        d: 'M5 5h5v2H7v3H5V5m9 0h5v5h-2V7h-3V5m3 9h2v5h-5v-2h3v-3m-7 3v2H5v-5h2v3h3z',
        fill: 'currentColor'
      })
    ]
  )
})

export const ExitFullscreenIcon = createComponent('ExitFullscreenIcon', props => {
  // <svg width="1em" height="1em" viewBox="0 0 24 24" {...props}>
  //   <path d="M14 14h5v2h-3v3h-2v-5m-9 0h5v5H8v-3H5v-2m3-9h2v5H5V8h3V5m11 3v2h-5V5h2v3h3z" fill="currentColor"></path>
  // </svg>

  return h(
    'svg',
    {
      width: '1em',
      height: '1em',
      viewBox: '0 0 24 24',
      ...props
    },
    [
      h('path', {
        d: 'M14 14h5v2h-3v3h-2v-5m-9 0h5v5H8v-3H5v-2m3-9h2v5H5V8h3V5m11 3v2h-5V5h2v3h3z',
        fill: 'currentColor'
      })
    ]
  )
})


================================================
FILE: packages/doc/.vuepress/plugins/code-demo/clientConfigFile.ts
================================================
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {CodeDemoProps} from './CodeDemo.props'
import {defineClientConfig} from '@vuepress/client'
import type {ConcreteComponent, DefineComponent} from 'vue'
import {h, resolveComponent} from 'vue'
import * as base64 from 'js-base64'

export default defineClientConfig({
  async enhance({app}) {
    // eslint-disable-next-line @typescript-eslint/ban-types
    let CodeDemo: DefineComponent<{}, {}, any> | undefined

    if (!__VUEPRESS_SSR__) {
      // eslint-disable-next-line unicorn/no-await-expression-member
      CodeDemo = (await import('./CodeDemo.vue')).default

      // set global css
      const styleEl = document.createElement('style')
      styleEl.innerHTML = `
      .code-demo-wrapper {
        margin-top: 1rem;
        margin-bottom: 1rem;
      }
      .code-demo-wrapper + .code-demo-wrapper {
        margin-top: 3rem;
      }
    `
      document.head.append(styleEl)
    }
    if (CodeDemo) {
      app.component('CodeDemo', CodeDemo)
    }

    // wrap the component with default options
    app.component('CodeDemoClient', (defaultProps: CodeDemoProps) => {
      if (!CodeDemo) return null
      const ClientOnly = resolveComponent('ClientOnly')
      // eslint-disable-next-line no-restricted-globals
      const codeDemoOptions = self.loadCodeDemoOptions?.(defaultProps) ?? defaultProps

      return h(ClientOnly, {}, () =>
        h(CodeDemo as ConcreteComponent, {
          ...codeDemoOptions
        })
      )
    })

    app.config.globalProperties.base64 = base64 // decode the options in sandbox component
  }
})


================================================
FILE: packages/doc/.vuepress/plugins/code-demo/constant.ts
================================================
// 用于 Stackblitz 示例的默认标题(未覆盖时)
export const DEFAULT_EDITOR_TITLE = 'Vr360 Example'

// 用于 Stackblitz 示例的默认描述(未覆盖时)
export const DEFAULT_EDITOR_DESCRIPTION = ''

// 用于所有 @nicepkg/vr360-*包的默认包版本。
export const DEFAULT_VR360_VERSION = '^6.0.0'


================================================
FILE: packages/doc/.vuepress/plugins/code-demo/global.d.ts
================================================
import type {ProjectFiles} from '@stackblitz/sdk'
import type {CodeDemoProps} from './CodeDemo.props'

declare global {
  // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
  interface Window {
    loadCodeDemoOptions?: (preOptions: CodeDemoProps) => CodeDemoProps
    loadCodeDemoModeDefaultFiles?: (mode: string) => ProjectFiles | Promise<ProjectFiles>
  }
}


================================================
FILE: packages/doc/.vuepress/plugins/code-demo/index.ts
================================================
export * from './plugin'


================================================
FILE: packages/doc/.vuepress/plugins/code-demo/plugin.ts
================================================
/* eslint-disable @typescript-eslint/no-explicit-any */
import path from 'node:path'
import fs from 'node:fs'
import type {Plugin} from '@vuepress/core'
import markdownItContainer from 'markdown-it-container'
import type {MarkdownEnv} from '@vuepress/markdown'
import type * as Token from 'markdown-it/lib/token'
import type * as Renderer from 'markdown-it/lib/renderer'
import type {CodeDemoProps} from './CodeDemo.props'
import type {ProjectFiles} from '@stackblitz/sdk'
import * as base64 from 'js-base64'
import {DEFAULT_EDITOR_DESCRIPTION, DEFAULT_EDITOR_TITLE} from './constant'

const pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)

export type MarkdownItRenderFn = (
  tokens: Token[],
  index: number,
  options: any,
  env: MarkdownEnv,
  self: Renderer
) => string

export type CodeDemoOptions = {
  codeDemoMark?: string
  clickToLoad?: boolean
  resolvePath?: (filePath: string) => string
}

export type RenderPlaceFunction = (description: string, codeBlockTokens?: Token[]) => string

export const codeDemoPlugin = (options: CodeDemoOptions = {}): Plugin => {
  const {codeDemoMark = 'demo', clickToLoad = false, resolvePath = p => p} = options

  // const START_TYPE = `container_${codeDemoMark}_open`
  const END_TYPE = `container_${codeDemoMark}_close`
  const CODE_BLOCK_TYPE = 'fence'
  const START_NESTING = 1
  // const END_NESTING = -1

  const renderBefore: RenderPlaceFunction = (des, codeBlockTokens) => {
    const files: ProjectFiles = {}
    let openFile: string | undefined
    const [mode, title, description] = des.split('--').map(s => s.trim())

    if (codeBlockTokens && codeBlockTokens.length > 0) {
      codeBlockTokens.map(token => {
        const [lang, filename] = token.info.split(/\s+/)

        // 找出代码内容需要插入的部分 /*# 路径 #*/
        const importPathReg = /\/\*#\s*(.+)\s*#\*\//g

        // 代码内容
        const codeContent = token.content.replace(importPathReg, (match, p: string) => {
          const currentImportPath = pathResolve(resolvePath(p.trim()))
          if (!fs.existsSync(currentImportPath)) return match
          const importCode = fs.readFileSync(currentImportPath, 'utf8')
          return importCode || match
        })

        // 最终文件名
        const _filename = filename.endsWith(`.${lang}`) ? filename : `${filename}.${lang}`

        if (!openFile) openFile = _filename

        files[_filename] = codeContent
      })
    }

    const options: CodeDemoProps = {
      mode,
      openFile,
      clickToLoad,
      project: {
        template: 'node',
        title: title || DEFAULT_EDITOR_TITLE,
        description: description || DEFAULT_EDITOR_DESCRIPTION,
        files
      }
    }
    const optionsBase64 = base64.encode(JSON.stringify(options))

    return `<div class="code-demo-wrapper ${codeDemoMark}"><code-demo-client v-bind="JSON.parse(base64.decode('${optionsBase64}'))">\n`
  }

  const renderAfter: RenderPlaceFunction = () => '</code-demo-client></div>\n'

  const descriptionsStack: string[] = []
  const render: MarkdownItRenderFn = (tokens, index, opts, env) => {
    const token = tokens[index]
    if (token.nesting === START_NESTING) {
      // `before` tag

      // resolve description (title)
      const description = token.info.trim().slice(codeDemoMark.length).trim() || DEFAULT_EDITOR_TITLE
      descriptionsStack.push(description)

      let i = index + 1
      const codeBlockTokens: Token[] = []
      while (tokens[i].type !== END_TYPE) {
        const nextToken = tokens[i]
        if (nextToken.type === CODE_BLOCK_TYPE) {
          codeBlockTokens.push(nextToken)
        }
        i++
      }

      return renderBefore(description, codeBlockTokens)
    } else {
      // `after` tag

      // pop the description from stack
      // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
      const description = descriptionsStack.pop() || ''

      // render
      return renderAfter(description)
    }
  }

  const plugin: Plugin = {
    name: 'vuepress-plugin-code-demo',
    clientConfigFile: pathResolve('./clientConfigFile.ts').replace(/\\/g, '/'),
    extendsMarkdown: md => {
      md.use(markdownItContainer, codeDemoMark, {render})
    }
  }

  return plugin
}


================================================
FILE: packages/doc/.vuepress/plugins/index.ts
================================================
import type {PluginConfig} from 'vuepress'
import {registerComponentsPlugin} from '@vuepress/plugin-register-components'
import {googleAnalyticsPlugin} from '@vuepress/plugin-google-analytics'
import {shikiPlugin} from '@vuepress/plugin-shiki'
import {path} from '@vuepress/utils'
import {isProd} from '../utils/common'
import {codeDemoPlugin} from './code-demo'

const pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)

const vuepressPlugins: PluginConfig = [
  // for google search
  googleAnalyticsPlugin({
    id: ''
  }),
  // auto register globally components
  registerComponentsPlugin({
    componentsDir: pathResolve('../components')
  }),
  codeDemoPlugin({
    clickToLoad: true,
    resolvePath: str => str.replace(/^@/, pathResolve('../'))
  })
]

if (isProd) {
  vuepressPlugins.push(
    // code highlighting
    shikiPlugin({
      theme: 'dark-plus'
    })
  )
}

export const plugins = vuepressPlugins


================================================
FILE: packages/doc/.vuepress/public/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
    <msapplication>
        <tile>
            <square150x150logo src="/images/icons/mstile-150x150.png"/>
            <TileColor>#0c4dc4</TileColor>
        </tile>
    </msapplication>
</browserconfig>


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/demo.css.txt
================================================
* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

.demo {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

.demo-tip {
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 240px;
  height: 60px;
  padding: 4px;
  color: #fff;
  cursor: pointer;
  background-color: rgba(0, 0, 0, 0.5);
  border-radius: 4px;
}

.demo-tip-title {
  font-weight: bold;
}

.demo-container {
  width: 100%;
  height: 100%;
}

.demo-bottom-bar {
  display: flex;
  align-items: center;
  width: 100%;
  height: 100px;
}

.demo-bottom-card {
  width: 140px;
  height: 70px;
  margin-left: 1rem;
  cursor: pointer;
  background-repeat: no-repeat;
  background-size: cover;
  border-radius: 4px;
}


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/html/package.json.txt
================================================
{
  "name": "playground-html",
  "private": true,
  "scripts": {
    "dev": "http-server ./ -p 8000 -o"
  },
  "devDependencies": {
    "http-server": "^14.1.1"
  }
}


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/react/index.html.txt
================================================
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico" />
  <title>React App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
  <script>
    (function () {
      // ios safari 禁止缩放
      document.addEventListener('touchmove', function(event) {
        event = event.originalEvent || event;
        if(event.scale !== undefined && event.scale !== 1) {
          event.preventDefault();
        }
      }, false);
    })()
  </script>
</head>
<body class="font-sans">
  <noscript>
    <strong>We're sorry but web-cli doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
  </noscript>
  <div id="app"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>
</html>


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/react/package.json.txt
================================================
{
  "name": "playground-react",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview"
  },
  "dependencies": {
    "@nicepkg/vr360-core": "latest",
    "clsx": "^1.2.1",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "three": "^0.145.0"
  },
  "devDependencies": {
    "@types/node": "^18.7.23",
    "@types/react": "18.0.21",
    "@types/react-dom": "18.0.6",
    "@types/three": "^0.144.0",
    "@vitejs/plugin-react": "^2.1.0",
    "typescript": "4.7.4",
    "vite": "3.0.9"
  }
}


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/react/src/main.tsx.txt
================================================
import React from 'react'
import ReactDOM from 'react-dom'
import Example from './Example'

ReactDOM.render(
  <React.StrictMode>
    <Example />
  </React.StrictMode>,
  document.querySelector('#app')
)


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/react/tsconfig.json.txt
================================================
{
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist",
    "target": "esnext",
    "composite": true,
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "react-jsx",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "skipLibCheck": true,
    "types": ["vite/client"],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*", "types/**/*", "*.ts", "*.js"]
}


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/react/vite.config.ts.txt
================================================
import {defineConfig} from 'vite'
import path from 'node:path'
import viteReact from '@vitejs/plugin-react'

const pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)

export default defineConfig({
  resolve: {
    alias: {
      '@/': `${pathResolve('./src')}/`
    }
  },

  plugins: [
    viteReact()
  ]
})


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue2/index.html.txt
================================================
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico" />
  <title>Vue2 App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
  <script>
    (function () {
      // ios safari 禁止缩放
      document.addEventListener('touchmove', function(event) {
        event = event.originalEvent || event;
        if(event.scale !== undefined && event.scale !== 1) {
          event.preventDefault();
        }
      }, false);
    })()
  </script>
</head>
<body class="font-sans">
  <noscript>
    <strong>We're sorry but web-cli doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
  </noscript>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue2/package.json.txt
================================================
{
  "name": "playground-vue2",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview"
  },
  "dependencies": {
    "@nicepkg/vr360-core": "latest",
    "three": "^0.145.0",
    "vue": "2.6.14"
  },
  "devDependencies": {
    "@types/node": "18.7.23",
    "@types/three": "^0.144.0",
    "@vue/runtime-dom": "latest",
    "typescript": "4.7.4",
    "vite": "^2.9.9",
    "vite-plugin-vue2": "^2.0.2",
    "vue-template-compiler": "2.6.14"
  }
}


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue2/src/App.vue.txt
================================================
<template>
  <Example></Example>
</template>

<script lang="ts">
import Example from './Example.vue'

export default {
  name: 'App',
  components: {
    Example
  }
}
</script>


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue2/src/main.ts.txt
================================================
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App)
}).$mount('#app')


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue2/tsconfig.json.txt
================================================
{
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist",
    "target": "esnext",
    "composite": true,
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "skipLibCheck": true,
    "types": ["vite/client"],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*", "types/**/*", "*.ts", "*.js"]
}


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue2/types/module.d.ts.txt
================================================
declare module '*.vue' {
  import type {VueConstructor} from 'vue'
  const component: VueConstructor
  export default component
}


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue2/vite.config.ts.txt
================================================
import path from 'node:path'
import {defineConfig} from 'vite'
import {createVuePlugin} from 'vite-plugin-vue2'

const pathResolve = (...args: string[]) => path.resolve(__dirname, ...args)

export default defineConfig({
  resolve: {
    alias: {
      '@/': `${pathResolve('./src')}/`
    }
  },

  plugins: [
    createVuePlugin({
      jsx: true,
      jsxOptions: {
        compositionAPI: true
      }
    })
  ]
})


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue3/index.html.txt
================================================
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico" />
  <title>Vue3 App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
  <script>
    (function () {
      // ios safari 禁止缩放
      document.addEventListener('touchmove', function(event) {
        event = event.originalEvent || event;
        if(event.scale !== undefined && event.scale !== 1) {
          event.preventDefault();
        }
      }, false);
    })()
  </script>
</head>
<body class="font-sans">
  <noscript>
    <strong>We're sorry but web-cli doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
  </noscript>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue3/package.json.txt
================================================
{
  "name": "playground-vue3",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview"
  },
  "dependencies": {
    "@nicepkg/vr360-core": "latest",
    "three": "^0.145.0",
    "vue": "^3.2.40"
  },
  "devDependencies": {
    "@types/node": "18.7.23",
    "@types/three": "^0.144.0",
    "@vitejs/plugin-vue": "^3.1.0",
    "@vitejs/plugin-vue-jsx": "^2.0.1",
    "typescript": "4.7.4",
    "vite": "3.0.9"
  }
}


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue3/src/App.vue.txt
================================================
<template>
  <Example></Example>
</template>

<script setup lang="ts">
import Example from './Example.vue'
</script>


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue3/src/main.ts.txt
================================================
import {createApp} from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

app.config.globalProperties.productionTip = false


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue3/tsconfig.json.txt
================================================
{
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist",
    "target": "esnext",
    "composite": true,
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "skipLibCheck": true,
    "types": ["vite/client"],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*", "types/**/*", "*.ts", "*.js"]
}


================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue3/types/module.d.ts.txt
================================================
declare module '*.vue' {
  import type {DefineComponent} from 'vue-demi'
  const Component: DefineComponent<{}, {}, any>
  export default Component
}



================================================
FILE: packages/doc/.vuepress/public/code-demo-templates/vue3/vite.config.ts.txt
================================================
import path from 'node:path'
import {defineConfig} from 'vite'
import Vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

const pathResolve = (...args: string[]) => path.resolve(__dirname, ...args)

export default defineConfig({
  resolve: {
    alias: {
      '@/': `${pathResolve('./src')}/`
    }
  },

  plugins: [
    Vue({
      include: [/\.vue$/]
    }),
    vueJsx()
  ]
})


================================================
FILE: packages/doc/.vuepress/public/manifest.webmanifest
================================================
{
  "name": "vr360",
  "short_name": "vr360",
  "description": "快速实现你的全景开发需求",
  "theme_color": "#ffffff",
  "background_color": "#ffffff",
  "display": "standalone",
  "scope": "./",
  "start_url": "./",
  "icons": [
    {
      "src": "/images/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/images/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/images/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable any"
    }
  ]
}


================================================
FILE: packages/doc/.vuepress/styles/index.scss
================================================
:root {
  // brand colors
  --c-brand: #0c4dc4;
  --c-brand-light: #0b3788;

  scroll-behavior: smooth;
}

html.dark {
  // brand colors
  --c-brand: #1fa8f8;
  --c-brand-light: #3bb3f8;
}

.sidebar-item.collapsible {
  cursor: pointer;
}

.site-name.can-hide {
  display: none;
}

body {
  position: relative;
  width: 100vw;
  min-height: 100vh;
  overflow-x: hidden;
  overflow-y: auto;
  background-color: var(--c-bg);
}

#app {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 99;
  min-width: 100vw;
  min-height: 100vh;
}

.code-group {
  width: 100%;
  max-width: calc(100vw - 3rem);
}

.theme-default-content div[class*='language-'] {
  width: calc(100% + 3rem);
  max-width: 100vw;
}

.theme-default-content .code-group div[class*='language-'] {
  width: auto;
  max-width: auto;
}


================================================
FILE: packages/doc/.vuepress/theme/components/Home.vue
================================================
<script setup lang="ts">
import HomeContent from '@vuepress/theme-default/components/HomeContent.vue'
import HomeFeatures from './HomeFeatures.vue'
import HomeFooter from '@vuepress/theme-default/components/HomeFooter.vue'
import HomeHero from '@vuepress/theme-default/components/HomeHero.vue'
import HomeVrBg from './HomeVrBg.vue'
</script>

<template>
  <main class="home">
    <HomeHero />
    <HomeFeatures />
    <HomeContent />
    <HomeFooter />
    <ClientOnly>
      <teleport to="#vr-teleport">
        <HomeVrBg :show-mask="true"></HomeVrBg>
      </teleport>
    </ClientOnly>
  </main>
</template>


================================================
FILE: packages/doc/.vuepress/theme/components/HomeFeatures.vue
================================================
<script setup lang="ts">
import {usePageFrontmatter} from '@vuepress/client'
import {isArray} from '@vuepress/shared'
import type {DefaultThemeHomePageFrontmatter} from '@vuepress/theme-default'
import {computed} from 'vue'
import AutoLink from '@vuepress/theme-default/components/AutoLink.vue'

type HomePageFormatter = {
  features?: {
    title: string
    details: string
    link: string
    disabled: boolean
  }[]
} & Omit<DefaultThemeHomePageFrontmatter, 'features'>

// eslint-disable-next-line react-hooks/rules-of-hooks
const frontmatter = usePageFrontmatter<HomePageFormatter>()
const features = computed(() => {
  if (isArray(frontmatter.value.features)) {
    return frontmatter.value.features
  }
  return []
})
</script>

<template>
  <div v-if="features.length > 0" class="features">
    <div
      v-for="feature in features"
      :key="feature.title"
      class="feature"
      :style="{
        cursor: feature.disabled ? 'not-allowed' : 'pointer',
        opacity: feature.disabled ? 0.8 : 1
      }"
    >
      <h4>
        <AutoLink
          v-if="!feature.disabled"
          :item="{
            text: feature.title,
            link: feature.link || './'
          }"
        >
          {{ feature.title }}
        </AutoLink>
        <div v-else>
          {{ feature.title }}
        </div>
      </h4>

      <p>{{ feature.details }}</p>
    </div>
  </div>
</template>


================================================
FILE: packages/doc/.vuepress/theme/components/HomeVrBg.vue
================================================
<template>
  <div class="vr-container-wrapper" :style="{height: height + 'px'}">
    <div
      ref="tipRef"
      class="vr-tip"
      :style="{
        left: tipLeft + 'px',
        top: tipTop + 50 + 'px',
        zIndex: showTip ? 99 : -1
      }"
    >
      <div class="vr-tip-title">{{ tipTitle }}</div>
      <div class="vr-tip-content">{{ tipContent }}</div>
    </div>
    <div ref="containerRef" class="vr-container"></div>

    <div v-if="showMask" class="vr-mask"></div>
  </div>
</template>

<script setup lang="ts">
/* eslint-disable import/no-named-as-default-member */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import {onMounted, ref, watch, nextTick, onUnmounted} from 'vue'
import type {SpaceConfig} from '@nicepkg/vr360-core'
import {Vr360} from '@nicepkg/vr360-core'
import {useElementSize} from '@vueuse/core'
import textures from '../../../../../textures.json'

defineProps({
  showMask: {
    type: Boolean,
    default: false
  }
})

const appRef = ref<HTMLElement>()

const containerRef = ref<HTMLElement>()

const tipRef = ref<HTMLElement>()
const tipLeft = ref(0)
const tipTop = ref(0)
const showTip = ref(false)
const tipTitle = ref('')
const tipContent = ref('')

let vr360: InstanceType<typeof Vr360>

const spacesConfig = ref<SpaceConfig[]>([
  {
    id: 'spaceA',
    camera: {
      position: {
        x: 0,
        y: 0,
        z: 0
      }
    },
    tips: [],
    cubeSpaceTextureUrls: textures.beijing
  }
])

onMounted(() => {
  vr360 = new Vr360({
    container: containerRef.value!,
    tipContainer: tipRef.value!,
    spacesConfig: spacesConfig.value
  })

  vr360.controls.autoRotate = true
  vr360.controls.autoRotateSpeed = 0.2

  vr360.render()

  vr360.listenResize()

  vr360.on('showTip', e => {
    vr360!.controls.autoRotate = false
    const {top, left, tip} = e
    showTip.value = true
    tipLeft.value = left
    tipTop.value = top
    tipTitle.value = tip.content.title
    tipContent.value = tip.content.text
  })

  vr360.on('hideTip', () => {
    vr360!.controls.autoRotate = true
    showTip.value = false
  })

  appRef.value = document.querySelector<HTMLElement>('#app')!
})

onUnmounted(() => {
  vr360.destroy()
})

// eslint-disable-next-line react-hooks/rules-of-hooks
const {height} = useElementSize(appRef)

watch(height, async () => {
  await nextTick()
  vr360.updateContainerSize()
})
</script>
<style scoped>
.vr-container-wrapper {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  width: 100vw;
  min-height: 100vh;
  overflow: hidden;
}

.vr-tip {
  position: absolute;
  z-index: 99;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 240px;
  height: 60px;
  padding: 1rem;
  color: #fff;
  cursor: pointer;
  background-color: rgba(0, 0, 0, 0.5);
}

.vr-tip-title {
  font-weight: bold;
}

.vr-container {
  width: 100%;
  height: 100%;

  /* 修复 edge canvas底部空白,我也不知道为什么,但是有效 */
  border: 0.5px solid;
}

.vr-container :deep(canvas) {
  background-color: #fff;
}

.vr-mask {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  background: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 1) 60%,
    rgba(255, 255, 255, 0.8) 90%,
    rgba(255, 255, 255, 0.2)
  );
}

html.dark .vr-mask {
  background: linear-gradient(to bottom, rgba(34, 39, 46, 1) 60%, rgba(34, 39, 46, 0.8) 90%, rgba(34, 39, 46, 0.2));
}
</style>


================================================
FILE: packages/doc/.vuepress/theme/index.ts
================================================
import type {Theme} from '@vuepress/core'
import {defaultTheme} from '@vuepress/theme-default'
import {path} from '@vuepress/utils'
import {navbar, sidebar} from '../configs'
import {isProd} from '../utils/common'

const pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)

export const localTheme = (): Theme => {
  return {
    name: 'vuepress-theme-local',
    extends: defaultTheme({
      logo: '/images/logo.png',
      logoDark: '/images/logo-dark.png',

      repo: 'nicepkg/vr360',

      docsBranch: 'master',

      docsDir: 'packages/doc',

      // theme-level locales config
      locales: {
        /**
         * Chinese locale config
         */
        '/': {
          // navbar
          navbar: navbar.zh,
          selectLanguageName: '简体中文',
          selectLanguageText: '选择语言',
          selectLanguageAriaLabel: '选择语言',

          // sidebar
          sidebar: sidebar.zh,

          // page meta
          editLinkText: '在 GitHub 上编辑此页',
          lastUpdatedText: '上次更新',
          contributorsText: '贡献者',

          // custom containers
          tip: '提示',
          warning: '注意',
          danger: '警告',

          // 404 page
          notFound: ['这里什么都没有', '我们怎么到这来了?', '这是一个 404 页面', '看起来我们进入了错误的链接'],
          backToHome: '返回首页',

          // a11y
          openInNewWindow: '在新窗口打开',
          toggleColorMode: '切换主题',
          toggleSidebar: '切换侧边栏'
        },

        /**
         * English locale config
         *
         * As the default locale of @vuepress/theme-default is English,
         * we don't need to set all of the locale fields
         */
        '/en/': {
          // navbar
          navbar: navbar.en,

          // sidebar
          sidebar: sidebar.en,

          // page meta
          editLinkText: 'Edit this page on GitHub'
        }
      },

      themePlugins: {
        // only enable git plugin in production mode
        git: isProd,
        // use shiki plugin in production mode instead
        prismjs: !isProd,
        // disable the @vuepress/plugin-nprogress plugin to fix the bug of `Cannot set properties of undefined (setting 'NProgress')`
        nprogress: false
      }
    }),
    alias: {
      '@theme/Home.vue': pathResolve('components/Home.vue'),
      '@theme/HomeFeatures.vue': pathResolve('components/HomeFeatures.vue')
    }
  }
}


================================================
FILE: packages/doc/.vuepress/types/module.d.ts
================================================
/* eslint-disable @typescript-eslint/no-explicit-any */

declare module '*.vue' {
  import type {DefineComponent} from 'vue'
  // eslint-disable-next-line @typescript-eslint/ban-types
  const Component: DefineComponent<{}, {}, any>
  export default Component
}


================================================
FILE: packages/doc/.vuepress/utils/common.ts
================================================
import rootPkg from '../../../../package.json'

export const version = rootPkg.version as string
export const isProd = process.env.NODE_ENV === 'production'


================================================
FILE: packages/doc/CHANGELOG.md
================================================
# 0.3.0 (2022-10-08)

### Bug Fixes

- **@nicepkg/vr360-core:** 修复移动端的提示移动 bug ([7090a98](https://github.com/nicepkg/vr360/commit/7090a9805bc472240de6ad4467bbadc14fd6151d))

# 0.3.0 (2022-10-02)

## 0.2.1 (2022-10-02)

# 0.2.0 (2022-10-02)

### Bug Fixes

- 修复 vr360-core tips 在滑动时的 bug ([e179ebc](https://github.com/nicepkg/vr360/commit/e179ebc2697314bc455320eecf8beb6182a53ded))

### Features

- 暴露更多的事件,设置默认自动旋转 ([4e21c53](https://github.com/nicepkg/vr360/commit/4e21c53ac945532020a7fbbfa46644294c33b49d))
- 初始化项目 ([06f39d1](https://github.com/nicepkg/vr360/commit/06f39d141004a1d0b1a125ad598298baf15ffee8))
- 添加标签提示和空间切换功能 ([6aa67d3](https://github.com/nicepkg/vr360/commit/6aa67d39113b06d05036ecc1d66ab1d70a2f4cf5))
- 添加如视 vr 导入支持 ([5a9eb5d](https://github.com/nicepkg/vr360/commit/5a9eb5d7a33d092be8cea5565490f268376d2f79))
- 文档添加示例插件、core 的纹理 top、bottom 改为 up 和 down ([70ccb2c](https://github.com/nicepkg/vr360/commit/70ccb2c40a06079ffb3cf50b27f986ab19b1f7db))
- 修改 package.json 说明 ([e5e4ad0](https://github.com/nicepkg/vr360/commit/e5e4ad04b1c7a9cddff3af6f73d438fd1de85b25))
- 优化代码 ([4519c4a](https://github.com/nicepkg/vr360/commit/4519c4a0fb230bb62bed49f97e0824c8977be3ca))
- 优化 sdk,添加纹理缓存预加载 ([9dd57e7](https://github.com/nicepkg/vr360/commit/9dd57e71b8f5a38ecc9901395a9b189481172edb))
- 重写核心,添加最小化更新配置支持 ([289091b](https://github.com/nicepkg/vr360/commit/289091b13dfe0495b44ae9e5353b78d2712e9762))


================================================
FILE: packages/doc/README.md
================================================
---
home: true
title: 首页
heroImage: /images/logo.png
heroImageDark: /images/logo-dark.png
heroText: null
tagline: 快速实现你的全景开发需求
actions:
  - text: 快速上手
    link: /guide/
    type: primary
  - text: Github
    link: https://github.com/nicepkg/vr360
    type: secondary
features:
  - title: vr360-core
    details: json 驱动的全景浏览库,设计框架无关性,可用于任何框架,如vue/react/angular/svelte/solidjs...
    link: '/libs/vr360-core/README.md'

  - title: vr360-ui
    details: (开发中...)提供一个现成的 vr360 viewer 和 editor 组件,基于 stencil 构建的 web component。
    disabled: true
    link: ''

  - title: vr360-ui-vue2
    details: (开发中...)vr360-ui 的 vue2 二次封装版本,开箱即用。
    disabled: true
    link: ''

  - title: vr360-ui-vue3
    details: (开发中...)vr360-ui 的 vue3 二次封装版本,开箱即用。
    disabled: true
    link: ''

  - title: vr360-ui-react
    details: (开发中...)vr360-ui 的 react 二次封装版本,开箱即用。
    disabled: true
    link: ''

  - title:
    details:
footer: MIT License | Copyright © 2022-present YangJinMing
---


================================================
FILE: packages/doc/bundler.config.ts
================================================
import {path} from '@vuepress/utils'
import type {ViteBundlerOptions} from 'vuepress'

const pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)
export const bundlerConfig = {
  viteOptions: {
    resolve: {
      alias: [
        {
          find: /@(?=\/)/,
          replacement: pathResolve('./.vuepress')
        }
      ]
    },
    build: {
      chunkSizeWarningLimit: Number.POSITIVE_INFINITY
    }
  }
} as ViteBundlerOptions


================================================
FILE: packages/doc/guide/README.md
================================================
# 介绍

Vr360 是一个基于 threejs 能让你快速实现业务全景需求的库,比如全景看房、全景街景、全景景点。

它的核心被设计为框架无关性,可以用 json 配置的方式快速实现常见全景需求。

后续还会提供高度封装的 viewer 和 editor 等组件,很适合懒人,会提供 vue2/3 、 react 、web component 版本。

json 驱动视图的特性是好维护,你甚至可以不用接触 threejs。写出来的代码拉条哈士奇过来也能维护。

## 库列表

#### 核心 [vr360-core](/libs/vr360-core/)

vr360 核心库,提供了基础的全景浏览功能,你可以用它来快速实现你的全景需求。

#### web 组件 [vr360-ui](/libs/vr360-ui/)

提供一个现成的 vr360 viewer 和 editor 组件,基于 stencil 构建的 web component。(开发中...)

#### vue2 组件 [vr360-ui-vue2](/libs/vr360-ui-vue2/)

vr360-ui 的 vue2 二次封装版本,开箱即用。(开发中...)

#### vue3 组件 [vr360-ui-vue3](/libs/vr360-ui-vue2/)

vr360-ui 的 vue2 二次封装版本,开箱即用。(开发中...)

#### react 组件 [vr360-ui-react](/libs/vr360-ui-react/)

vr360-ui 的 react 二次封装版本,开箱即用。(开发中...)


================================================
FILE: packages/doc/guide/questions.md
================================================
# 常见问题

## vr360-ui 为什么没有 angular 版

vr360-ui 是基于 stencil.js 开发,适配 angular 是很容易的,但是由于本人没怎么用过 angular,适配完也不知道有没有 bug,所以欢迎提 pr 。


================================================
FILE: packages/doc/libs/vr360-core/README.md
================================================
# 介绍

`@nicepkg/vr360-core` 是一个基于 [threejs](https://github.com/mrdoob/three.js/) 的全景库,非常适合用来做全景看房、全景街景、全景景点等业务需求。

它支持 json 配置驱动视图,你可以用 json 配置的方式快速实现常见全景需求。

## 特性

- json 驱动视图,快速实现全景业务需求
- 高性能,支持自动找出 json 变更的部分,最小化更新到 3d 全景里,不会整个场景重建。
- 支持增删改提示点,hover 提示点的弹窗支持自定义定制,支持自定义提示点的贴图。
- 内置场景切换穿梭动画
- 暴露 scene、camera、renderer 等 [threejs](https://github.com/mrdoob/three.js/) 对象,方便你更高的定制化
- 基于事件驱动的数据交互,设计框架无关性
- 完整的 ts 支持

## 安装

:::: code-group
::: code-group-item npm

```bash
npm i @nicepkg/vr360-core threejs
```

:::
::: code-group-item yarn

```bash
yarn add @nicepkg/vr360-core threejs
```

:::
::: code-group-item pnpm

```bash
pnpm add @nicepkg/vr360-core threejs
```

:::
::::

## 浏览器(CDN)

:::: code-group
::: code-group-item Unpkg

```html:no-v-pre
<!-- 引入 threejs -->
<script src="https://unpkg.com/three@0.145.0/build/three.min.js"></script>

<!-- 引入 vr360-core -->
<script src="https://unpkg.com/@nicepkg/vr360-core@{{version}}"></script>

<script>
// 使用
const {Vr360} = Vr360Core // 原生使用时,所有的导出都在 window.Vr360Core 里

// 初始化全景实例
const vr360 = new Vr360({...})

// 开始渲染全景
vr360.render()
</script>
```

:::
::: code-group-item JsDelivr

```html:no-v-pre
<!-- 引入 threejs -->
<script src="https://cdn.jsdelivr.net/npm/three@0.145.0/build/three.min.js"></script>

<!-- 引入 vr360-core -->
<script src="https://cdn.jsdelivr.net/npm/@nicepkg/vr360-core@{{version}}"></script>

<script>
// 使用
const {Vr360} = Vr360Core // 原生使用时,所有的导出都在 window.Vr360Core 里

// 初始化全景实例
const vr360 = new Vr360({...})

// 开始渲染全景
vr360.render()
</script>
```

:::
::::

## 为什么

如果产品叫你实现一个类似全景看房的功能,而且时间紧,你会怎么做?

学 threejs 知识,然后徒手撸出全景,然后自己抽象出一个 json 配置驱动和后端数据联调对接?

可能还要自己写一个全景编辑器?

可以但没必要,你完全可以把时间省下来做些有意义的事,你只要用 `@nicepkg/vr360-core` 就可以了。

简单点的业务需求仅需 json 配置就能实现,重要是这种代码拉条哈士奇也能维护。

编辑器还在开发中,复杂度不会消失,只会转移,当你岁月静好,一定是作者在为你负重前行。

## 使用

请阅读我们的 [属性文档](./properties.md)、 [函数文档](./methods.md)、[事件文档](./events.md),了解如何使用 `@nicepkg/vr360-core`。

除了下面的简易示例,你也可以浏览我们的 [完整项目示例](./example.md)

#### 简易示例效果

<br>
<DemoA></DemoA>

#### 简易示例代码(cv 专用)

<br>
<CodeGroup>
  <CodeGroupItem title="vue2" active>

@[code vue](@/examples/firstHouse/vue2.vue)

  </CodeGroupItem>
  <CodeGroupItem title="vue3">

@[code vue](@/examples/firstHouse/vue3.vue)

  </CodeGroupItem>
  <CodeGroupItem title="react">

@[code tsx](@/examples/firstHouse/react.tsx)

  </CodeGroupItem>
  <CodeGroupItem title="原生">

@[code html](@/examples/firstHouse/html.html)

  </CodeGroupItem>
  <CodeGroupItem title="demo.css">

@[code css](@/examples/firstHouse/demo.css)

  </CodeGroupItem>
</CodeGroup>


================================================
FILE: packages/doc/libs/vr360-core/events.md
================================================
# 事件

## 显示提示

#### 介绍

```ts
interface Vr360Events {
  /**
   * 触发提示时的回调
   */
  showTip: (e: {
    /**
     * 提示配置信息
     */
    tip: Tip

    /**
     * 相对于 container 的 left
     */
    left: number

    /**
     * 相对于 container 的 top
     */
    top: number
  }) => void
}
```

点击查看 [tip](./methods.md#tip-空间配置里的提示配置) 类型

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

vr360.on('showTip', (e) => {
  console.log('触发提示时的回调', e)
})
```

## 隐藏提示

#### 介绍

```ts
interface Vr360Events {
  /**
   * 隐藏提示时的回调
   */
  hideTip: (e: {
    /**
     * 提示配置信息
     */
    tip: Tip
  }) => void
}
```

点击查看 [tip](./methods.md#tip-空间配置里的提示配置) 类型

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

vr360.on('hideTip', (e) => {
  console.log('隐藏提示时的回调', e)
})
```

## 点击提示图标

#### 介绍

```ts
interface Vr360Events {
  /**
   * 点击提示时的回调
   */
  clickTip: (e: {
    /**
     * 提示配置信息
     */
    tip: Tip
  }) => void
}
```

点击查看 [tip](./methods.md#tip-空间配置里的提示配置) 类型

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

vr360.on('clickTip', (e) => {
  console.log('点击提示时的回调', e)
})
```

## 每一帧更新

#### 介绍

```ts
interface Vr360Events {
  /**
   * 每帧更新回调
   */
  update: () => void
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

vr360.on('update', () => {
  console.log('每帧更新回调')
})
```

## 切换全景空间完成

#### 介绍

```ts
interface Vr360Events {
  /**
   * 完成跳转空间时的回调
   */
  afterSwitchSpace: (e: {
    /**
     * 跳转的目标空间配置
     */
    spaceConfig: SpaceConfig
  }) => void
}
```

点击查看 [SpaceConfig](./methods.md#spaceconfig-构造参数里的空间配置) 类型

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

vr360.on('afterSwitchSpace', (e) => {
  console.log('完成跳转空间时的回调', e)
})
```


================================================
FILE: packages/doc/libs/vr360-core/example.md
================================================
# 示例

## 效果

<br/>
<DemoA></DemoA>

### vue2 实现

::: demo vue2 -- vue2 示例代码 -- 这里是示例代码的描述

```vue src/Example.vue
/*# @/examples/firstHouse/vue2.vue #*/
```

:::

### vue3 实现

::: demo vue3 -- vue3 示例代码 -- 这里是示例代码的描述

```vue src/Example.vue
/*# @/examples/firstHouse/vue3.vue #*/
```

:::

### react 实现

::: demo react -- react 示例代码 -- 这里是示例代码的描述

```tsx src/Example.tsx
/*# @/examples/firstHouse/react.tsx #*/
```

:::

### 原生实现

::: demo html -- html 示例代码 -- 这里是示例代码的描述

```html index.html
/*# @/examples/firstHouse/html.html #*/
```

:::


================================================
FILE: packages/doc/libs/vr360-core/methods.md
================================================
# 方法

## 构造器

#### 介绍

```ts
class Vr360 {
  /**
   * @param options 构造参数配置
   * @returns Vr360 实例
   */
  constructor(options: Vr360Options) {
    ...
  }
}
```

点击查看 [Vr360Options](#vr360options-构造参数) 类型

#### 使用

```ts
import {Vr360, Vr360Options} from '@nicepkg/vr360-core'

const vr360Options: Vr360Options = {
  container: document.querySelector('#container'),
  tipContainer: document.querySelector('#tip-container'),
  spacesConfig: []
}
const vr360 = new Vr360(vr360Options)
```

#### 相关类型

##### Vr360Options 构造参数

```ts
/**
 *  vr360 的构造参数
 */
interface Vr360Options {
  /**
   * 容器
   */
  container: HTMLElement

  /**
   * 提示的 element 节点
   */
  tipContainer?: HTMLElement

  /**
   * 初始显示的空间 id
   */
  initSpaceId?: string

  /**
   * 空间配置
   */
  spacesConfig: SpaceConfig[]
}
```

##### SpaceConfig 构造参数里的空间配置

```ts
/**
 * 空间配置
 */
interface SpaceConfig {
  /**
   * 空间 id
   */
  id: string

  /**
   * 相机配置
   */
  camera?: {
    /**
     * 位置
     */
    position: {
      x: number
      y: number
      z: number
    }

    /**
     * 缩放
     */
    scale?: {
      x: number
      y: number
      z: number
    }

    /**
     * 旋转
     */
    rotate?: {
      x: number
      y: number
      z: number
    }
  }

  /**
   * 提示配置列表
   */
  tips?: Tip[]

  /**
   * 空间贴图列表
   */
  cubeSpaceTextureUrls: {
    /**
     * 左侧贴图 url
     */
    left: string

    /**
     * 右侧贴图 url
     */
    right: string

    /**
     * 上侧贴图 url
     */
    up: string

    /**
     * 下侧贴图 url
     */
    down: string

    /**
     * 前侧贴图 url
     */
    front: string

    /**
     * 后侧贴图 url
     */
    back: string
  }
}
```

##### Tip 空间配置里的提示配置

```ts
interface Tip {
  /**
   * 提示 id
   */
  id: string

  /**
   * 跳转的空间 id
   */
  targetSpaceId?: string

  /**
   * 提示图案纹理贴图
   */
  textureUrl?: string

  /**
   * 位置
   */
  position: {
    x: number
    y: number
    z: number
  }

  /**
   * 缩放
   */
  scale?: {
    x: number
    y: number
    z: number
  }

  /**
   * 旋转
   */
  rotate?: {
    x: number
    y: number
    z: number
  }

  /**
   * 其它携带信息,比如你可以附加 title 、 content,在提示相关事件回调时你可以拿到
   */
  [key: string]: any
}
```

## 监听页面尺寸变化

#### 介绍

```ts
class Vr360 {
  /**
   * 监听页面尺寸变化,更新画布尺寸
   * @returns 返回取消监听函数
   */
  public listenResize(): () => void {
    ...
  }
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 让渲染器监听页面尺寸变化,实时更新画布尺寸
const removeResizeListener = vr360.listenResize()

// 你可以随时移除监听
removeResizeListener()
```

## 刷新容器渲染宽高

#### 介绍

```ts
class Vr360 {
  /**
   * 刷新容器渲染宽高
   */
  public updateContainerSize(): void {
    ...
  }
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 手动更新画布尺寸
vr360.updateContainerSize()
```

## 更新全景空间配置

#### 介绍

```ts
class Vr360 {
  /**
   * 更新 spacesConfig
   * @param newSpacesConfig 新的空间配置
   */
  public updateSpacesConfig(newSpacesConfig: SpaceConfig[]): void {
    ...
  }
}
```

点击查看 [SpaceConfig](#spaceconfig-构造参数里的空间配置) 类型

#### 使用

```ts
import {Vr360, SpaceConfig} from '@nicepkg/vr360-core'

const spacesConfig: SpaceConfig[] = []
const vr360 = new Vr360({
  container: document.querySelector('#container'),
  tipContainer: document.querySelector('#tip-container'),
  spacesConfig
})

vr360.render()

spacesConfig.push([
  {
    ....
  }
])

// 手动更新空间配置
vr360.updateSpacesConfig(spacesConfig)
```

## 切换全景空间

#### 介绍

```ts
class Vr360 {
   /**
   * 切换空间
   * @param id 空间 id
   */
  public switchSpace(id: string): void {
    ...
  }
}
```

#### 使用

```ts
import {Vr360, SpaceConfig} from '@nicepkg/vr360-core'

const spacesConfig: SpaceConfig[] = [
  {
    id: 'spaceA',
    ...
  },
  {
    id: 'spaceB',
    ...
  }
]

const vr360 = new Vr360({
  container: document.querySelector('#container'),
  tipContainer: document.querySelector('#tip-container'),
  spacesConfig
})

vr360.render()

// 切换到 spaceB 空间
vr360.switchSpace('spaceB')
```

## 开始渲染全景

#### 介绍

```ts
class Vr360 {
  /**
   * 渲染
   */
  public render(): void {
    ...
  }
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()
```

## 把鼠标位置转换成 threejs 位置

#### 介绍

```ts
class Vr360 {
  /**
   * 根据鼠标位置获取当前空间的位置
   * @param x 鼠标 x 坐标
   * @param y 鼠标 y 坐标
   * @returns 返回当前空间的位置
   */
  public getPositionFromMouseXY(x: number, y: number): {
    x: number
    y: number
    z: number
  } {
    ...
  }
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({
  container: document.querySelector('#container'),
  tipContainer: document.querySelector('#tip-container'),
  spacesConfig: [....]
})

vr360.render()

// 获取当前空间的位置
document.querySelector('#container').addEventListener('click', (e) => {
  const {x, y, z} = vr360.getPositionFromMouseXY(e.clientX, e.clientY)
  console.log('点击的画布里的空间位置为:', x, y, z)
})
```

## 注销全景实例

#### 介绍

```ts
class Vr360 {
  /**
   * 销毁实例
   */
  public destroy(): void {
    ...
  }
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 销毁实例
vr360.destroy()
```


================================================
FILE: packages/doc/libs/vr360-core/properties.md
================================================
# 属性

## 全景容器

##### 介绍

```ts
class Vr360 {
  /**
   * 容器
   */
  public container: HTMLElement
}
```

##### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 获取容器
console.log('3d 渲染容器是:', vr360.container)
```

## 相机

##### 介绍

```ts
class Vr360 {
  /**
   * 相机
   */
  public camera: THREE.PerspectiveCamera
}
```

##### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 获取相机
console.log('3d 相机是:', vr360.camera)
```

## 渲染器

#### 介绍

```ts
class Vr360 {
  /**
   * 渲染器
   */
  public renderer: THREE.WebGLRenderer
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 获取渲染器
console.log('3d 渲染器是:', vr360.renderer)
```

## 场景

#### 介绍

```ts
class Vr360 {
  /**
   * 场景
   */
  public scene: THREE.Scene
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 获取场景
console.log('3d 场景是:', vr360.scene)
```

## 控制器

#### 介绍

```ts
class Vr360 {
  /**
   * 控制器
   */
  public controls: THREE.OrbitControls
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 获取控制器
console.log('3d 控制器是:', vr360.controls)
```

## 全景空间配置

#### 介绍

```ts
class Vr360 {
  /**
   * 空间配置
   */
  public spacesConfig: SpaceConfig[]
}
```

点击查看 [SpaceConfig](./methods.md#spaceconfig-构造参数里的空间配置) 类型

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 获取空间配置
console.log('3d 空间配置是:', vr360.spacesConfig)
```

## 提示容器

#### 介绍

```ts
class Vr360 {
  /**
   * 提示容器
   */
  public tipContainer?: HTMLElement
}
```

#### 使用

```ts
import {Vr360} from '@nicepkg/vr360-core'

const vr360 = new Vr360({....})

vr360.render()

// 获取提示容器
console.log('3d 提示容器是:', vr360.tipContainer)
```


================================================
FILE: packages/doc/libs/vr360-ui/README.md
================================================
# 介绍

(开发中...)提供一个现成的 vr360 viewer 和 editor 组件,基于 stencil 构建的 web component。


================================================
FILE: packages/doc/libs/vr360-ui-react/README.md
================================================
# 介绍

(开发中...)提供一个现成的 vr360 viewer 和 editor 组件,适配 react 框架版。


================================================
FILE: packages/doc/libs/vr360-ui-vue2/README.md
================================================
# 介绍

(开发中...)提供一个现成的 vr360 viewer 和 editor 组件,适配 vue2 框架版。


================================================
FILE: packages/doc/libs/vr360-ui-vue3/README.md
================================================
# 介绍

(开发中...)提供一个现成的 vr360 viewer 和 editor 组件,适配 vue3 框架版。


================================================
FILE: packages/doc/package.json
================================================
{
  "name": "doc",
  "version": "0.3.1",
  "private": true,
  "description": "the libs docs website",
  "scripts": {
    "build": "vuepress build --clean-cache",
    "clean": "rimraf .vuepress/.temp .vuepress/.cache .vuepress/dist",
    "dev": "vuepress dev --clean-cache",
    "serve": "http-server --port 8080 .vuepress/dist"
  },
  "dependencies": {
    "@nicepkg/vr360-core": "workspace:*",
    "@stackblitz/sdk": "^1.8.0",
    "@vueuse/core": "^9.3.0",
    "vue-router": "^4.1.5"
  },
  "devDependencies": {
    "@types/markdown-it-container": "^2.0.5",
    "@vuepress/bundler-vite": "2.0.0-beta.51",
    "@vuepress/cli": "2.0.0-beta.51",
    "@vuepress/client": "2.0.0-beta.51",
    "@vuepress/core": "2.0.0-beta.51",
    "@vuepress/markdown": "2.0.0-beta.51",
    "@vuepress/plugin-google-analytics": "2.0.0-beta.51",
    "@vuepress/plugin-register-components": "2.0.0-beta.51",
    "@vuepress/plugin-shiki": "2.0.0-beta.51",
    "@vuepress/shared": "2.0.0-beta.51",
    "@vuepress/theme-default": "2.0.0-beta.51",
    "@vuepress/utils": "2.0.0-beta.51",
    "http-server": "^14.1.1",
    "js-base64": "^3.7.2",
    "markdown-it": "^13.0.1",
    "markdown-it-container": "^3.0.0",
    "rimraf": "^3.0.2",
    "sass": "^1.55.0",
    "vite": "*",
    "vue": "^3.2.40",
    "vuepress": "2.0.0-beta.51"
  }
}


================================================
FILE: packages/doc/tsconfig.json
================================================
{
  "extends": "../../tsconfig-base.json",
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist",
    "types": ["vite/client", "@vuepress/client/types"],
    "paths": {
      "@/*": ["./.vuepress/*"]
    }
  },
  "include": ["./.vuepress/**/*", "./*.js", "./*.ts"],
  "exclude": ["node_modules", "dist", ".vuepress/examples/**/*"]
}


================================================
FILE: packages/doc/vuepress.config.ts
================================================
import {defineUserConfig} from 'vuepress'
import {path} from '@vuepress/utils'
import {viteBundler} from '@vuepress/bundler-vite'
import {plugins} from './.vuepress/plugins'
import {bundlerConfig} from './bundler.config'
import {localTheme} from './.vuepress/theme'

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const pathResolve = (..._path: string[]) => path.resolve(__dirname, ..._path)

export default defineUserConfig({
  base: '/',

  head: [
    [
      'link',
      {
        rel: 'icon',
        type: 'image/png',
        sizes: '16x16',
        href: `/images/icons/favicon-16x16.png`
      }
    ],
    [
      'link',
      {
        rel: 'icon',
        type: 'image/png',
        sizes: '32x32',
        href: `/images/icons/favicon-32x32.png`
      }
    ],
    ['link', {rel: 'manifest', href: '/manifest.webmanifest'}],
    ['meta', {name: 'application-name', content: 'Vr360'}],
    ['meta', {name: 'apple-mobile-web-app-title', content: 'Vr360'}],
    ['meta', {name: 'apple-mobile-web-app-status-bar-style', content: 'black'}],
    ['link', {rel: 'apple-touch-icon', href: `/images/icons/apple-touch-icon.png`}],
    [
      'link',
      {
        rel: 'mask-icon',
        href: '/images/icons/safari-pinned-tab.svg',
        color: '#0c4dc4'
      }
    ],
    ['meta', {name: 'msapplication-TileColor', content: '#0c4dc4'}],
    ['meta', {name: 'theme-color', content: '#0c4dc4'}]
  ],

  // site-level locales config
  locales: {
    '/': {
      lang: 'zh-CN',
      title: 'Vr360',
      description: '快速实现你的全景浏览开发需求'
    }
    // '/en/': {
    //   lang: 'en-US',
    //   title: 'Vr360',
    //   description: 'Quickly realize your panoramic browsing development needs'
    // }
  },

  theme: localTheme(),
  bundler: viteBundler(bundlerConfig),
  templateDev: pathResolve('.vuepress/index.build.html'),
  templateBuild: pathResolve('.vuepress/index.build.html'),
  markdown: {
    importCode: {
      handleImportPath: str => str.replace(/^@/, pathResolve('.vuepress'))
    }
  },
  plugins
})


================================================
FILE: packages/vr360-core/CHANGELOG.md
================================================
# 0.3.0 (2022-10-08)

### Bug Fixes

- **@nicepkg/vr360-core:** 修复移动端的提示移动 bug ([7090a98](https://github.com/nicepkg/vr360/commit/7090a9805bc472240de6ad4467bbadc14fd6151d))

# 0.3.0 (2022-10-02)

## 0.2.1 (2022-10-02)

# 0.2.0 (2022-10-02)

### Bug Fixes

- 修复 vr360-core tips 在滑动时的 bug ([e179ebc](https://github.com/nicepkg/vr360/commit/e179ebc2697314bc455320eecf8beb6182a53ded))

### Features

- 暴露更多的事件,设置默认自动旋转 ([4e21c53](https://github.com/nicepkg/vr360/commit/4e21c53ac945532020a7fbbfa46644294c33b49d))
- 初始化项目 ([06f39d1](https://github.com/nicepkg/vr360/commit/06f39d141004a1d0b1a125ad598298baf15ffee8))
- 添加标签提示和空间切换功能 ([6aa67d3](https://github.com/nicepkg/vr360/commit/6aa67d39113b06d05036ecc1d66ab1d70a2f4cf5))
- 添加如视 vr 导入支持 ([5a9eb5d](https://github.com/nicepkg/vr360/commit/5a9eb5d7a33d092be8cea5565490f268376d2f79))
- 文档添加示例插件、core 的纹理 top、bottom 改为 up 和 down ([70ccb2c](https://github.com/nicepkg/vr360/commit/70ccb2c40a06079ffb3cf50b27f986ab19b1f7db))
- 修改 package.json 说明 ([e5e4ad0](https://github.com/nicepkg/vr360/commit/e5e4ad04b1c7a9cddff3af6f73d438fd1de85b25))
- 优化代码 ([4519c4a](https://github.com/nicepkg/vr360/commit/4519c4a0fb230bb62bed49f97e0824c8977be3ca))
- 优化 sdk,添加纹理缓存预加载 ([9dd57e7](https://github.com/nicepkg/vr360/commit/9dd57e71b8f5a38ecc9901395a9b189481172edb))
- 重写核心,添加最小化更新配置支持 ([289091b](https://github.com/nicepkg/vr360/commit/289091b13dfe0495b44ae9e5353b78d2712e9762))


================================================
FILE: packages/vr360-core/README.md
================================================
<div align="center">
  <a href="https://vr360.nicepkg.cn/libs/vr360-core/">
    <img src="https://vr360.nicepkg.cn/images/logo-bg.png" width="50%">
  </a>
  <h1>@nicepkg/vr360-core</h1>
  <p>json 驱动的全景浏览库核心,设计框架无关性,可用于任何框架,如vue/react/angular/svelte/solidjs...</p>
  <p>
    <img src="https://img.shields.io/npm/v/@nicepkg/vr360-core?style=flat-square" alt="version">
    <img src="https://img.shields.io/npm/dependency-version/@nicepkg/vr360-core/peer/three" alt="three">
    <img src="https://img.shields.io/npm/l/@nicepkg/vr360-core.svg" alt="license">
    <img src="https://img.shields.io/codecov/c/github/nicepkg/vr360" alt="coverage">
    <img src="https://img.badgesize.io/https://unpkg.com/@nicepkg/vr360-core?compression=gzip&label=gzip" alt="gzip" />
    <img src="https://img.shields.io/github/stars/nicepkg/vr360?style=social" alt="stars">
  </p>
</div>

## 介绍

`@nicepkg/vr360-core` 是一个基于 [threejs](https://github.com/mrdoob/three.js/) 的全景库,非常适合用来做全景看房、全景街景、全景景点等业务需求。

它支持 json 配置驱动视图,你可以用 json 配置的方式快速实现常见全景需求。

## 特性

- json 驱动视图,快速实现全景业务需求
- 高性能,支持自动找出 json 变更的部分,最小化更新到 3d 全景里,不会整个场景重建。
- 支持增删改提示点,hover 提示点的弹窗支持自定义定制,支持自定义提示点的贴图。
- 内置场景切换穿梭动画
- 暴露 scene、camera、renderer 等 [threejs](https://github.com/mrdoob/three.js/) 对象,方便你更高的定制化
- 基于事件驱动的数据交互,设计框架无关性
- 完整的 ts 支持

## 安装

for npm

```bash
npm i @nicepkg/vr360-core threejs
```

for yarn

```bash
yarn add @nicepkg/vr360-core threejs
```

for pnpm

```bash
pnpm add @nicepkg/vr360-core threejs
```

## 使用

点击查看[使用文档](https://vr360.nicepkg.cn/libs/vr360-core/)。

## Contributing

Learn about contribution [here](https://github.com/nicepkg/vr360/blob/master/CONTRIBUTING.md).

This project exists thanks to all the people who contribute:

<a href="https://github.com/nicepkg/vr360/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=nicepkg/vr360" />
</a>

## License

[MIT](https://github.com/nicepkg/vr360/blob/master/LICENSE) License © 2022-PRESENT [nicepkg](https://github.com/nicepkg)


================================================
FILE: packages/vr360-core/package.json
================================================
{
  "name": "@nicepkg/vr360-core",
  "version": "0.3.1",
  "description": "快速实现你的全景开发需求,全景看房、全景街景、全景景点",
  "keywords": [
    "vr360",
    "vr360-core",
    "panorama",
    "pannellum",
    "photo-sphere-viewer",
    "photograph",
    "nicepkg",
    "webgl",
    "threejs"
  ],
  "author": "yangjinming <https://github.com/2214962083>",
  "funding": "https://github.com/sponsors/2214962083",
  "license": "MIT",
  "private": false,
  "scripts": {
    "build": "pnpm type-check &&esno ./scripts/build.ts",
    "build:watch": "cross-env WATCH=true pnpm build",
    "clean": "rimraf ./dist/**/*",
    "dev": "esno ./src/playground.ts",
    "test": "vitest run --silent --passWithNoTests",
    "test:watch": "pnpm test -- --watch",
    "type-check": "tsc --noEmit"
  },
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "unpkg": "./dist/index.min.umd.js",
  "jsdelivr": "./dist/index.min.umd.js",
  "exports": {
    ".": {
      "require": "./dist/index.cjs",
      "import": "./dist/index.mjs",
      "types": "./dist/index.d.ts"
    },
    "./*": "./*"
  },
  "files": [
    "dist"
  ],
  "sideEffects": false,
  "repository": {
    "type": "git",
    "url": "git+https://github.com/nicepkg/vr360.git",
    "directory": "packages/vr360-core"
  },
  "bugs": {
    "url": "https://github.com/nicepkg/vr360/issues"
  },
  "homepage": "https://github.com/nicepkg/vr360#readme",
  "peerDependencies": {
    "three": "*"
  },
  "dependencies": {
    "@tweenjs/tween.js": "^18.6.4",
    "eventemitter3": "^4.0.7"
  },
  "devDependencies": {
    "@nicepkg/vr360-shared": "workspace:*",
    "@types/three": "^0.144.0",
    "@types/node": "*",
    "esno": "*",
    "jsdom": "^20.0.1",
    "three": "^0.145.0",
    "typescript": "*",
    "vite": "*",
    "vitest": "*"
  }
}


================================================
FILE: packages/vr360-core/scripts/build.ts
================================================
import {buildUtils} from '@nicepkg/vr360-shared'
import {minifyConfig, unMinifyConfig, packagePath} from '../vite.config'

buildUtils.build({
  minifyConfig,
  unMinifyConfig,
  packagePath,
  dtsOptions: {
    // merge all .d.ts files
    rollupTypes: true
  }
})


================================================
FILE: packages/vr360-core/src/helper.ts
================================================
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
import * as THREE from 'three'
import type {DeepRequired, DeepPartial, Position, ThreeObjectBase} from './types'

let raycaster: THREE.Raycaster
let mouse: THREE.Vector2
function getSingleValue() {
  if (!raycaster) raycaster = new THREE.Raycaster()
  if (!mouse) mouse = new THREE.Vector2()
  return {
    raycaster,
    mouse
  }
}

export type ConvertMousePositionToThreePositionOptions = {
  /**
   * 鼠标 x 坐标
   */
  mouseX: number

  /**
   * 鼠标 y 坐标
   */
  mouseY: number

  /**
   * 获取依赖函数
   */
  getDeps: GetAddListenerToThreeObjectDeps
}

/**
 * 转换鼠标位置到 three 坐标
 * @param options 配置
 * @returns x,y,z 坐标
 */
export function convertMousePositionToThreePosition(options: ConvertMousePositionToThreePositionOptions): Position {
  const {mouseX, mouseY, getDeps} = options
  const {camera, scene, renderer} = getDeps()
  if (!camera || !scene || !renderer) return {x: 0, y: 0, z: 0}
  const {raycaster, mouse} = getSingleValue()
  const renderDomBound = renderer.domElement.getBoundingClientRect()

  mouse.x = ((mouseX - renderDomBound.left) / renderer.domElement.clientWidth) * 2 - 1
  mouse.y = -((mouseY - renderDomBound.top) / renderer.domElement.clientHeight) * 2 + 1

  raycaster.setFromCamera(mouse, camera)

  const intersects = raycaster.intersectObjects(scene.children)
  const firstIntersect = intersects[0] ?? undefined
  return firstIntersect.point ?? {x: 0, y: 0, z: 0}
}

/**
 * 获取依赖函数
 */
export type GetAddListenerToThreeObjectDeps = () => {
  /**
   * 摄像机实例
   */
  camera?: THREE.PerspectiveCamera

  /**
   * 场景实例
   */
  scene?: THREE.Scene

  /**
   * 渲染器实例
   */
  renderer?: THREE.WebGLRenderer
}

export type ThreeObjectDispatchEvent<T extends keyof HTMLElementEventMap> = {
  type: T
  intersect: THREE.Intersection
  sourceEvent: HTMLElementEventMap[T]
  isMouseDown: boolean
}

/**
 * 为 threejs 对象添加事件监听器
 * @param getDeps 获取依赖函数
 * @param events 监听的事件名称列表
 */
export function addListenerToThreeObject(
  getDeps: GetAddListenerToThreeObjectDeps,
  events: (keyof HTMLElementEventMap)[] = ['click', 'mousemove', 'touchmove', 'mousedown', 'mouseup']
) {
  const renderElement = getDeps().renderer?.domElement
  if (!renderElement) return
  const {raycaster, mouse} = getSingleValue()

  // 上一个点击的对象
  let mouseoverPreIntersect: THREE.Intersection | undefined

  // 鼠标是否处于按压状态,用于传给 tip 判断,在按下鼠标滑动时不显示 tip
  let isMouseDown = false

  ;['mousedown'].map(eventName =>
    renderElement.addEventListener(eventName, () => {
      isMouseDown = true
    })
  )
  ;['mouseup'].map(eventName =>
    renderElement.addEventListener(eventName, () => {
      isMouseDown = false
    })
  )

  function handleEvent(eventName: string, event: MouseEvent | TouchEvent) {
    event.preventDefault()
    const {camera, scene, renderer} = getDeps()
    if (!camera || !scene || !renderer) return
    const renderDomBound = renderer.domElement.getBoundingClientRect()

    const clientX = (event as TouchEvent)?.changedTouches?.[0]?.clientX ?? (event as MouseEvent).clientX
    const clientY = (event as TouchEvent)?.changedTouches?.[0]?.clientY ?? (event as MouseEvent).clientY

    mouse.x = ((clientX - renderDomBound.left) / renderer.domElement.clientWidth) * 2 - 1
    mouse.y = -((clientY - renderDomBound.top) / renderer.domElement.clientHeight) * 2 + 1

    raycaster.setFromCamera(mouse, camera)

    const intersects = raycaster.intersectObjects(scene.children)
    const firstIntersect = intersects[0] ?? undefined

    if (!firstIntersect) return

    if (
      ['mousemove', 'touchmove'].includes(eventName) &&
      mouseoverPreIntersect?.object.uuid !== firstIntersect.object.uuid
    ) {
      // 在 mousemove 事件时, 并且本次鼠标指中的 three 对象和上一个对象不相同时
      // 触发上个 hover 对象的 mouseout 事件
      mouseoverPreIntersect?.object.dispatchEvent({
        type: 'mouseout',
        intersect: mouseoverPreIntersect,
        sourceEvent: event,
        isMouseDown
      } as ThreeObjectDispatchEvent<'mouseout'>)
      mouseoverPreIntersect = firstIntersect

      // 设置鼠标样式
      const cursor = firstIntersect.object.userData.cursor || 'default'
      if (renderElement && renderElement.style.cursor !== cursor) renderElement.style.cursor = cursor

      // 触发本次 hover 对象的 mouseover 事件
      firstIntersect.object.dispatchEvent({
        type: 'mouseover',
        intersect: firstIntersect,
        sourceEvent: event,
        isMouseDown
      } as ThreeObjectDispatchEvent<'mouseover'>)
    }

    // 射线范围内没有东西,终止流程
    if (intersects.length <= 0) return

    // if (eventName === 'click') {
    //   console.log(`当前点击位置: x=${firstIntersect.point.x}, y=${firstIntersect.point.y}, z=${firstIntersect.point.z}`)
    // }

    firstIntersect.object.dispatchEvent({
      type: eventName === 'touchmove' ? 'mousemove' : eventName,
      intersect: firstIntersect,
      sourceEvent: event,
      isMouseDown
    } as ThreeObjectDispatchEvent<keyof HTMLElementEventMap>)
  }

  for (const eventName of events) {
    renderElement.addEventListener(eventName, event => handleEvent(eventName, event as MouseEvent))
  }
}

/**
 * 纹理缓存加载器
 */
export class TextureCacheLoader {
  static instance: TextureCacheLoader
  static getInstance() {
    if (!TextureCacheLoader.instance) TextureCacheLoader.instance = new TextureCacheLoader()
    return TextureCacheLoader.instance
  }

  /**
   * 缓存
   */
  private cache = new Map<string, THREE.Texture>()

  /**
   * 加载器
   */
  private loader = new THREE.TextureLoader()

  /**
   * 根据单个链接加载纹理
   * @param url 图片链接
   * @returns 返回纹理
   */
  loadUrl(url: string) {
    if (this.cache.has(url)) return this.cache.get(url)
    const texture = this.loader.load(url)
    this.cache.set(url, texture)
    return texture
  }

  /**
   * 根据多个链接加载纹理
   * @param urls 图片链接数组
   * @returns 返回纹理数组
   */
  loadUrls(urls: string[]) {
    return urls.map(url => this.loadUrl(url))
  }
}

/**
 * 判断该对象是否含有该键名
 * @param obj 对象
 * @param key 键名
 * @returns 返回该对象是否含有该键名
 */
export function hasOwnProperty(obj: any, key: string): boolean {
  return Object.prototype.hasOwnProperty.call(obj, key)
}

/**
 * 判断两个值是否相等
 * @param a 值 a
 * @param b 值 b
 * @returns 值 a 和 值 b 是否相等
 */
export function isEqual(a: any, b: any): boolean {
  if (a === b) return true
  if (typeof a !== typeof b) return false
  if (typeof a !== 'object') return false
  if (a === null || b === null) return false
  if (Array.isArray(a) !== Array.isArray(b)) return false
  if (Array.isArray(a)) {
    if (a.length !== b.length) return false
    for (const [i, element] of a.entries()) {
      if (!isEqual(element, b[i])) return false
    }
    return true
  }
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
  const aKeys = Object.keys(a)
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
  const bKeys = Object.keys(b)
  if (aKeys.length !== bKeys.length) return false
  for (const key of aKeys) {
    if (!isEqual(a[key], b[key])) return false
  }
  return true
}

/**
 * 防抖
 * @param func 函数
 * @param wait 延迟时间,单位毫秒
 * @param immediate 是否立即执行
 * @returns 返回一个新的函数
 */
export function debounce<T extends (...args: any[]) => any>(func: T, wait: number, immediate = true) {
  let timeout: number | undefined
  let hasRunImmediate = false
  return function (this: any, ...args: Parameters<T>) {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-return
    const run = () => func.apply(this, args)

    if (timeout) clearTimeout(timeout)
    if (immediate && !hasRunImmediate) {
      hasRunImmediate = true
      run()
    } else {
      timeout = window.setTimeout(run, wait)
    }
  } as T
}

/**
 * 更新 threejs 3d 对象的位置、缩放、旋转等基础信息
 * @param object threejs 3d 对象
 * @param baseInfo 位置、缩放、旋转信息
 */
export function update3dObjectBaseInfo(object: THREE.Object3D, baseInfo: Partial<ThreeObjectBase>): void {
  const {position, rotate, scale} = baseInfo
  if (position && !new THREE.Vector3(position.x, position.y, position.z).equals(object.position)) {
    object.position.set(position.x, position.y, position.z)
  }

  if (scale && !new THREE.Vector3(scale.x, scale.y, scale.z).equals(object.scale)) {
    object.scale.set(scale.x, scale.y, scale.z)
  }

  if (rotate && !new THREE.Euler(rotate.x, rotate.y, rotate.z).equals(object.rotation)) {
    object.rotation.set(rotate.x, rotate.y, rotate.z)
  }
}

/**
 * 获取 threejs 3d 对象的位置、缩放、旋转等基础信息
 * @param object threejs 3d 对象
 * @returns 返回 threejs 3d 对象的位置、缩放、旋转等基础信息
 */
export function get3dObjectBaseInfo(object?: THREE.Object3D): ThreeObjectBase {
  return {
    position: {
      x: object?.position?.x ?? 0,
      y: object?.position?.y ?? 0,
      z: object?.position?.z ?? 0
    },
    rotate: {
      x: object?.rotation?.x ?? 0,
      y: object?.rotation?.y ?? 0,
      z: object?.rotation?.z ?? 0
    },
    scale: {
      x: object?.scale?.x ?? 1,
      y: object?.scale?.y ?? 1,
      z: object?.scale?.z ?? 1
    }
  }
}

/**
 * 处理位置、缩放、旋转等基础信息默认值
 * @param baseInfo 位置、缩放、旋转信息
 * @returns 返回位置、缩放、旋转等基础信息
 */
export function formatBaseInfo(baseInfo: DeepPartial<ThreeObjectBase> | undefined): DeepRequired<ThreeObjectBase> {
  return {
    position: {
      x: baseInfo?.position?.x ?? 0,
      y: baseInfo?.position?.y ?? 0,
      z: baseInfo?.position?.z ?? 0
    },
    rotate: {
      x: baseInfo?.rotate?.x ?? 0,
      y: baseInfo?.rotate?.y ?? 0,
      z: baseInfo?.rotate?.z ?? 0
    },
    scale: {
      x: baseInfo?.scale?.x ?? 1,
      y: baseInfo?.scale?.y ?? 1,
      z: baseInfo?.scale?.z ?? 1
    }
  }
}


================================================
FILE: packages/vr360-core/src/index.ts
================================================
export * from './vr360'
export * from './types'
export * from './manager'


================================================
FILE: packages/vr360-core/src/manager/index.ts
================================================
export * from './space'
export * from './tip'

export type ConfigModel = {
  id: string
}

export type ConfigModelManager<CM extends ConfigModel, TM> = {
  create(configModel: CM): TM
  add(configModel: CM): TM
  update(configModel: Partial<CM> & Pick<CM, 'id'>): TM | undefined
  find(configModel: string | (Partial<CM> & Pick<CM, 'id'>)): TM | undefined
  findOrCreate(configModel: CM): TM
  remove(configModel: string | (Partial<CM> & Pick<CM, 'id'>)): void
  removeAll(): void
  destroy(): void
}


================================================
FILE: packages/vr360-core/src/manager/space.ts
================================================
/* eslint-disable @typescript-eslint/ban-ts-comment */
import * as THREE from 'three'
import type {TextureCacheLoader} from '../helper'
import {isEqual} from '../helper'
import {EventEmitter} from 'eventemitter3'
import type {ConfigModelManager} from './index'
import type {CubeSpaceTextureUrls, SpaceConfig} from '../types'
import type {TipManagerEvents, TipManagerOptions} from './tip'
import {tipEventNames, TipManager} from './tip'

/**
 * 空间管理器事件列表
 */
export type SpaceManagerEvents = TipManagerEvents

export type SpaceEventName = keyof SpaceManagerEvents
export const spaceEventNames: SpaceEventName[] = [...tipEventNames]

export type SpaceManagerOptions = Omit<TipManagerOptions, 'tipContainer'> & {
  tipContainer?: HTMLElement
}

/**
 * curd SpaceConfig to threejs
 */
export class SpaceManager
  extends EventEmitter<SpaceManagerEvents>
  implements ConfigModelManager<SpaceConfig, THREE.Group>
{
  private container: HTMLElement
  private camera: THREE.PerspectiveCamera
  private scene: THREE.Scene
  private renderer: THREE.WebGLRenderer
  private parent: THREE.Object3D
  private tipContainer?: HTMLElement
  private textureCacheLoader: TextureCacheLoader

  private spaceIdGroupMap = new Map<string, THREE.Group>()
  private spaceIdTipManager = new Map<string, TipManager>()

  constructor(options: SpaceManagerOptions) {
    super()
    this.container = options.container
    this.camera = options.camera
    this.scene = options.scene
    this.renderer = options.renderer
    this.parent = options.parent
    this.tipContainer = options.tipContainer
    this.textureCacheLoader = options.textureCacheLoader
  }

  /**
   * 创建提示管理器
   * @param parent threejs 组
   * @returns 提示管理器
   */
  private createTipManager(parent: THREE.Object3D): TipManager | undefined {
    if (!this.tipContainer) return

    const tipManager = new TipManager({
      container: this.container,
      camera: this.camera,
      scene: this.scene,
      renderer: this.renderer,
      tipContainer: this.tipContainer,
      textureCacheLoader: this.textureCacheLoader,
      parent
    })

    // 提示的事件继承
    tipEventNames.forEach(eventName => {
      tipManager.on(eventName, e => {
        // @ts-ignore
        this.emit(eventName, e)
      })
    })

    return tipManager
  }

  /**
   * 创建正方体空间 mesh
   * @param cubeSpaceTextureUrls 空间贴图
   */
  private createCubeSpaceMesh(cubeSpaceTextureUrls: CubeSpaceTextureUrls) {
    // 创建空间
    const boxGeometry = new THREE.BoxGeometry(100, 100, 100)

    // 随机挑选一个面翻转扩大,使得贴图能够正常渲染
    boxGeometry.scale(-1, 1, 1)

    // 贴材质
    const boxMaterials = this.createCubeSpaceMaterials(cubeSpaceTextureUrls)
    const spaceMesh = new THREE.Mesh(boxGeometry, boxMaterials)
    return spaceMesh
  }

  /**
   * 创建正方体空间材料
   * @param cubeSpaceTextureUrls 空间贴图
   */
  private createCubeSpaceMaterials(cubeSpaceTextureUrls: CubeSpaceTextureUrls) {
    const directions = ['right', 'left', 'up', 'down', 'front', 'back'] as const
    const boxMaterials: THREE.MeshBasicMaterial[] = directions.map(direction => {
      const texture = this.textureCacheLoader.loadUrl(
        cubeSpaceTextureUrls[direction as keyof typeof cubeSpaceTextureUrls]
      )
      return new THREE.MeshBasicMaterial({map: texture})
    })
    return boxMaterials
  }

  public create(spaceConfig: SpaceConfig): THREE.Group {
    const {cubeSpaceTextureUrls, tips = [], id} = spaceConfig
    const group = new THREE.Group()

    // 创建提示精灵
    if (this.tipContainer) {
      const tipManager = this.createTipManager(group)!
      tips.forEach(tip => {
        const sprite = tipManager.findOrCreate(tip)
        group.add(sprite)
      })
      this.spaceIdTipManager.set(id, tipManager)
    }

    // 创建空间
    const spaceMesh = this.createCubeSpaceMesh(cubeSpaceTextureUrls)
    spaceMesh.userData.type = 'spaceMesh'

    // 挂载当前 spaceConfig 到 group 上,在事件里面使用(更新时会覆盖这个挂载)
    group.userData.type = 'spaceGroup'
    group.userData.spaceConfig = spaceConfig

    group.add(spaceMesh)
    this.spaceIdGroupMap.set(id, group)

    return group
  }

  public add(spaceConfig: SpaceConfig): THREE.Group {
    const group = this.findOrCreate(spaceConfig)
    this.parent.add(group)
    return group
  }

  public update(spaceConfig: Partial<SpaceConfig> & Pick<SpaceConfig, 'id'>): THREE.Group | undefined {
    const {id, tips, cubeSpaceTextureUrls} = spaceConfig
    const group = this.spaceIdGroupMap.get(id)

    if (!group) return

    if (tips && this.tipContainer) {
      const tipManager = this.spaceIdTipManager.get(id) ?? this.createTipManager(group)!

      const sprites = new Set(
        tips.map(tip => {
          const sprite = tipManager.update(tip) ?? tipManager.create(tip)
          return sprite
        })
      )

      const children = [...group.children]

      // 找出需要删除的精灵并删除
      children.forEach(child => {
        if (child.type === 'Sprite' && child.userData.type === 'tipSprite' && !sprites.has(child as THREE.Sprite)) {
          group.remove(child)
        }
      })

      // 找出需要添加的精灵并添加
      sprites.forEach(sprite => {
        if (!group.children.includes(sprite)) {
          group.add(sprite)
        }
      })
    }

    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    if (cubeSpaceTextureUrls && !isEqual(group.userData.spaceConfig?.cubeSpaceTextureUrls, cubeSpaceTextureUrls)) {
      let spaceMesh = group.children.find(child => child.type === 'Mesh' && child.userData.type === 'spaceMesh') as
        | THREE.Mesh
        | undefined

      if (!spaceMesh) {
        spaceMesh = this.createCubeSpaceMesh(cubeSpaceTextureUrls)
      } else {
        const boxMaterials = this.createCubeSpaceMaterials(cubeSpaceTextureUrls)
        spaceMesh.material = boxMaterials
      }
    }

    group.userData.spaceConfig = spaceConfig
    // this.spaceIdGroupMap.set(id, group)
    return group
  }

  public find(spaceConfig: string | (Partial<SpaceConfig> & Pick<SpaceConfig, 'id'>)): THREE.Group | undefined {
    const id = typeof spaceConfig === 'string' ? spaceConfig : spaceConfig.id
    return this.spaceIdGroupMap.get(id)
  }

  public findOrCreate(spaceConfig: SpaceConfig): THREE.Group {
    const group = this.find(spaceConfig)
    if (group) return group
    return this.create(spaceConfig)
  }

  public remove(spaceConfig: string | (Partial<SpaceConfig> & Pick<SpaceConfig, 'id'>)): void {
    const id = typeof spaceConfig === 'string' ? spaceConfig : spaceConfig.id
    const group = this.spaceIdGroupMap.get(id)

    if (!group) return
    this.parent.remove(group)
    this.spaceIdGroupMap.delete(id)
    this.spaceIdTipManager.delete(id)
  }

  public removeAll(): void {
    this.spaceIdGroupMap.forEach(group => {
      this.parent.remove(group)
    })

    this.spaceIdTipManager.forEach(tipManager => {
      tipManager.destroy()
    })

    this.spaceIdGroupMap.clear()
    this.spaceIdTipManager.clear()
  }

  public destroy(): void {
    this.removeAll()
    this.removeAllListeners()
  }
}


================================================
FILE: packages/vr360-core/src/manager/tip.ts
================================================
import * as THREE from 'three'
import type {TextureCacheLoader, ThreeObjectDispatchEvent} from '../helper'
import {update3dObjectBaseInfo} from '../helper'
import type {Tip} from '../types'
import defaultTipUrl from '../assets/tips.png'
import {EventEmitter} from 'eventemitter3'
import type {ConfigModelManager} from '../manager'

/**
 * 触发提示时的回调 event
 */
export type ShowTipEvent = {
  /**
   * 提示配置信息
   */
  tip: Tip

  /**
   * 相对于 container 的 left
   */
  left: number

  /**
   * 相对于 container 的 top
   */
  top: number
}

/**
 * 隐藏提示时的回调 event
 */
export type HideTipEvent = {
  tip: Tip
}

/**
 * 点击提示时的回调 event
 */
export type ClickTipEvent = {
  tip: Tip
}

/**
 * 切换空间事件
 */
export type SwitchSpaceEvent = {
  /**
   * 跳转目标空间的 id
   */
  targetSpaceId: string

  /**
   * 点击切换的位置
   */
  clickPosition?: THREE.Vector3
}

/**
 * 提示管理器事件列表
 */
export type TipManagerEvents = {
  /**
   * 触发提示时的回调
   */
  showTip: (e: ShowTipEvent) => void

  /**
   * 隐藏提示时的回调
   */
  hideTip: (e: HideTipEvent) => void

  /**
   * 点击提示时的回调
   */
  clickTip: (e: ClickTipEvent) => void

  /**
   * 切换空间时的回调
   */
  switchSpace: (e: SwitchSpaceEvent) => void
}

export type TipEventName = keyof TipManagerEvents
export const tipEventNames: TipEventName[] = ['showTip', 'hideTip', 'clickTip', 'switchSpace']

export type TipManagerOptions = {
  /**
   * 容器
   */
  container: HTMLElement

  /**
   * 相机
   */
  camera: THREE.PerspectiveCamera

  /**
   * 场景
   */
  scene: THREE.Scene

  /**
   * 渲染器
   */
  renderer: THREE.WebGLRenderer

  /**
   * 3d 组, 用于添加提示 sprite
   */
  parent: THREE.Object3D

  /**
   * texture 缓存器
   */
  textureCacheLoader: TextureCacheLoader

  /**
   * 提示容器
   */
  tipContainer: HTMLElement
}

/**
 * 提示配置管理器
 */
export class TipManager extends EventEmitter<TipManagerEvents> implements ConfigModelManager<Tip, THREE.Sprite> {
  private container: HTMLElement
  private camera: THREE.PerspectiveCamera
  private scene: THREE.Scene
  private renderer: THREE.WebGLRenderer
  private parent: THREE.Object3D
  private textureCacheLoader: TextureCacheLoader
  private tipContainer: HTMLElement

  private tipIdSpriteMap = new Map<string, THREE.Sprite>()

  constructor(options: TipManagerOptions) {
    super()
    this.container = options.container
    this.camera = options.camera
    this.scene = options.scene
    this.renderer = options.renderer
    this.parent = options.parent
    this.textureCacheLoader = options.textureCacheLoader
    this.tipContainer = options.tipContainer
  }

  public create(tip: Tip): THREE.Sprite {
    const {position, textureUrl = defaultTipUrl, scale, rotate, id} = tip
    const texture = this.textureCacheLoader.loadUrl(textureUrl)
    const material = new THREE.SpriteMaterial({map: texture})
    const sprite = new THREE.Sprite(material)

    // 调整位置大小旋转角度
    sprite.scale.set(scale?.x ?? 3, scale?.y ?? 3, scale?.z ?? 3)
    sprite.position.set(position.x, position.y, position.z)
    if (rotate) {
      sprite.rotation.set(rotate.x, rotate.y, rotate.z)
    }

    // 设置鼠标样式
    sprite.userData.cursor = 'pointer'

    // 挂载当前 tip 到 sprite 上,在事件里面使用(更新时会覆盖这个挂载)
    sprite.userData.type = 'tipSprite'
    sprite.userData.tip = tip

    // 触发展示 tips 事件
    const emitShowTip = (e: ThreeObjectDispatchEvent<'mouseover'> | ThreeObjectDispatchEvent<'mouseup'>) => {
      // hover 的对象
      const intersect = e.intersect
      const tipFromUserData = intersect.object.userData.tip as Tip

      const containerHalfWidth = this.container.clientWidth / 2
      const containerHalfHeight = this.container.clientHeight / 2

      // 注意:如果外面是使用 display none 控制 tip 显隐,这里的 tipContainer 宽高均为 0,导致 tip 偏移
      const tipContainerWidth = this.tipContainer.clientWidth
      const tipContainerHeight = this.tipContainer.clientHeight
      const rendererOffsetLeft = this.renderer.domElement.offsetLeft
      const rendererOffsetTop = this.renderer.domElement.offsetTop
      const percentPosition = intersect.object.position.clone().project(this.camera)
      const left = (percentPosition.x + 1) * containerHalfWidth - tipContainerWidth / 2 + rendererOffsetLeft
      const top = (1 - percentPosition.y) * containerHalfHeight - tipContainerHeight / 2 + rendererOffsetTop

      const showTipEvent: ShowTipEvent = {tip: tipFromUserData, left, top}
      // console.log('展示提示', showTipEvent)
      this.emit('showTip', showTipEvent)
    }

    // 鼠标 hover 时展示提示
    sprite.addEventListener('mouseover', _e => {
      const e = _e as unknown as ThreeObjectDispatchEvent<'mouseover'>

      // 如果鼠标处于按压下状态,不展示提示,此举是为了防止和 controls 滑动冲突导致位置偏差
      if (e.isMouseDown) return

      emitShowTip(e)
    })

    // 如果在 tip 上松开鼠标,则判断为需要展示 tips,因为用户可能 controls 滑倒 tips 再松开
    sprite.addEventListener('mouseup', _e => {
      const e = _e as unknown as ThreeObjectDispatchEvent<'mouseup'>

      emitShowTip(e)
    })

    // 鼠标移出时移除提示
    sprite.addEventListener('mouseout', _e => {
      const e = _e as unknown as ThreeObjectDispatchEvent<'mouseout'>

      const intersect = e.intersect
      const tipFromUserData = intersect.object.userData.tip as Tip

      const hideTipEvent: HideTipEvent = {tip: tipFromUserData}
      // console.log('隐藏提示', hideTipEvent)
      this.emit('hideTip', hideTipEvent)
    })

    sprite.addEventListener('click', _e => {
      const e = _e as unknown as ThreeObjectDispatchEvent<'click'>

      const intersect = e.intersect
      const tipFromUserData = intersect.object.userData.tip as Tip

      this.emit('clickTip', {tip: tipFromUserData})

      if (tipFromUserData.targetSpaceId) {
        // 存在目标空间 id,点击跳转
        // 跳转时隐藏提示
        this.emit('hideTip', {tip: tipFromUserData})

        const switchSpaceEvent: SwitchSpaceEvent = {
          targetSpaceId: tipFromUserData.targetSpaceId,
          clickPosition: intersect.point
        }

        // console.log('调转到空间', switchSpaceEvent)
        this.emit('switchSpace', switchSpaceEvent)
      }
    })

    this.tipIdSpriteMap.set(id, sprite)
    return sprite
  }

  public add(tip: Tip): THREE.Sprite {
    const sprite = this.findOrCreate(tip)
    this.parent.add(sprite)
    return sprite
  }

  public update(tip: Partial<Tip> & Pick<Tip, 'id'>): THREE.Sprite | undefined {
    const {id, textureUrl, position, scale, rotate} = tip
    const sprite = this.tipIdSpriteMap.get(id)

    if (!sprite) return

    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    if (textureUrl && textureUrl !== sprite.material.map?.image?.src) {
      const texture = this.textureCacheLoader.loadUrl(textureUrl)
      const material = new THREE.SpriteMaterial({map: texture})
      sprite.material = material
    }

    update3dObjectBaseInfo(sprite, {
      position,
      scale,
      rotate
    })

    sprite.userData.tip = tip
    // this.tipIdSpriteMap.set(id, sprite)
    return sprite
  }

  public find(tip: string | (Partial<Tip> & Pick<Tip, 'id'>)): THREE.Sprite | undefined {
    const id = typeof tip === 'string' ? tip : tip.id
    return this.tipIdSpriteMap.get(id)
  }

  public findOrCreate(tip: Tip): THREE.Sprite {
    const sprite = this.find(tip)
    if (sprite) return sprite
    return this.create(tip)
  }

  public remove(tip: string | (Partial<Tip> & Pick<Tip, 'id'>)): void {
    const id = typeof tip === 'string' ? tip : tip.id
    const sprite = this.tipIdSpriteMap.get(id)
    if (!sprite) return

    this.parent.remove(sprite)
    this.tipIdSpriteMap.delete(id)
  }

  public removeAll(): void {
    this.tipIdSpriteMap.forEach(sprite => {
      this.parent.remove(sprite)
    })
    this.tipIdSpriteMap.clear()
  }

  public destroy(): void {
    this.removeAll()
    this.removeAllListeners()
  }
}


================================================
FILE: packages/vr360-core/src/types.ts
================================================
/* eslint-disable @typescript-eslint/no-explicit-any */

import type {ConfigModel} from './manager'
import type {SpaceManagerEvents} from './manager/space'

/**
 * 完成跳转空间时的回调 event
 */
export type AfterSwitchSpaceEvent = {
  /**
   * 跳转的目标空间配置
   */
  spaceConfig: SpaceConfig
}

/**
 * vr360 暴露的自定义事件
 */
export type Vr360Events = {
  /**
   * 每帧更新回调
   */
  update: () => void

  /**
   * 完成跳转空间时的回调
   */
  afterSwitchSpace: (e: AfterSwitchSpaceEvent) => void
} & Omit<SpaceManagerEvents, 'switchSpace'>

/**
 * 通用坐标
 */
export type Vector3Position = {
  /**
   * x 轴坐标
   */
  x: number

  /**
   * y 轴坐标
   */
  y: number

  /**
   * z 轴坐标
   */
  z: number
}

/**
 * 位置
 */
export type Position = Vector3Position

/**
 * 缩放
 */
export type Scale = Vector3Position

/**
 * 旋转
 */
export type Rotate = Vector3Position

/**
 * 每个 threejs 对象的通用配置
 */
export type ThreeObjectBase = {
  /**
   * 位置
   */
  position: Position

  /**
   * 缩放
   */
  scale?: Scale

  /**
   * 旋转
   */
  rotate?: Rotate
}

/**
 * 立方体空间纹理贴图 url 列表
 */
export type CubeSpaceTextureUrls = {
  /**
   * 左侧贴图 url
   */
  left: string

  /**
   * 右侧贴图 url
   */
  right: string

  /**
   * 上侧贴图 url
   */
  up: string

  /**
   * 下侧贴图 url
   */
  down: string

  /**
   * 前侧贴图 url
   */
  front: string

  /**
   * 后侧贴图 url
   */
  back: string
}

/**
 * 提示配置
 */
export type Tip = {
  /**
   * 提示 id
   */
  id: string

  /**
   * 跳转的空间 id
   */
  targetSpaceId?: string

  /**
   * 提示图案纹理贴图
   */
  textureUrl?: string

  /**
   * 其它携带信息,比如你可以附加 title 、 content,在提示相关事件回调时你可以拿到
   */
  [key: string]: any
} & ThreeObjectBase &
  ConfigModel

/**
 * 空间配置
 */
export type SpaceConfig = {
  /**
   * 空间 id
   */
  id: string

  /**
   * 相机配置
   */
  camera?: ThreeObjectBase

  /**
   * 提示配置列表
   */
  tips?: Tip[]

  /**
   * 空间贴图列表
   */
  cubeSpaceTextureUrls: CubeSpaceTextureUrls
} & ConfigModel

/**
 * vr360 构造参数
 */
export type Vr360Options = {
  /**
   * 容器
   */
  container: HTMLElement

  /**
   * 提示的 element 节点
   */
  tipContainer?: HTMLElement

  /**
   * 初始显示的空间 id
   */
  initSpaceId?: string

  /**
   * 空间配置
   */
  spacesConfig: SpaceConfig[]
}

/**
 * 深度处理 interface key,使所有 key 为必填项
 */
export type DeepRequired<T> = {
  [P in keyof T]-?: T[P] extends (infer U)[] ? DeepRequired<U>[] : T[P] extends object ? DeepRequired<T[P]> : T[P]
}

/**
 * 深度处理 interface key,使所有 key 为可选项
 */
export type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends (infer U)[] ? DeepPartial<U>[] : T[P] extends object ? DeepPartial<T[P]> : T[P]
}


================================================
FILE: packages/vr360-core/src/vr360.ts
================================================
/* eslint-disable unicorn/no-array-callback-reference */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
import * as THREE from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
import TWEEN from '@tweenjs/tween.js'
import {EventEmitter} from 'eventemitter3'
import {
  addListenerToThreeObject,
  convertMousePositionToThreePosition,
  formatBaseInfo,
  get3dObjectBaseInfo,
  TextureCacheLoader,
  update3dObjectBaseInfo
} from './helper'
import type {Position, SpaceConfig, ThreeObjectBase, Vr360Events, Vr360Options} from './types'
import {spaceEventNames, SpaceManager} from './manager/space'
import type {SpaceEventName} from './manager/space'

export type Vr360EventName = keyof Vr360Events

/**
 * 继承的空间管理器事件列表
 */
const extendsSpaceEventNames = spaceEventNames.filter(eventName => !['switchSpace'].includes(eventName)) as Exclude<
  SpaceEventName,
  'switchSpace'
>[]

/**
 * vr360 的事件列表
 */
export const vr360EventNames: Vr360EventName[] = [...extendsSpaceEventNames, 'update', 'afterSwitchSpace']

export class Vr360 extends EventEmitter<Vr360Events> {
  /**
   * 销毁队列
   */
  private destroyTasks: (() => void)[] = []

  /**
   * 当前空间 id
   */
  private currentSpaceId = ''

  /**
   * 初始化空间 id
   */
  public initSpaceId?: string

  /**
   * texture 缓存器
   */
  private textureCacheLoader: TextureCacheLoader

  /**
   * 容器
   */
  public container: HTMLElement

  /**
   * 相机
   */
  public camera: THREE.PerspectiveCamera

  /**
   * 场景
   */
  public scene: THREE.Scene

  /**
   * 渲染器
   */
  public renderer: THREE.WebGLRenderer

  /**
   * 控制器
   */
  public controls: OrbitControls

  /**
   * 提示容器
   */
  public tipContainer?: HTMLElement

  /**
   * 空间管理器
   */
  public spaceManager: SpaceManager

  /**
   * 空间配置
   */
  public spacesConfig: SpaceConfig[]

  /**
   * 容器宽
   */
  public get containerWidth() {
    return this.container.clientWidth
  }

  /**
   * 容器高
   */
  public get containerHeight() {
    return this.container.clientHeight
  }

  constructor(options: Vr360Options) {
    super()
    const {container, tipContainer, initSpaceId, spacesConfig = []} = options

    this.textureCacheLoader = TextureCacheLoader.getInstance()
    this.container = container
    this.camera = this.createCamera()
    this.scene = this.createScene()
    this.renderer = this.createRenderer()
    this.updateContainerSize()
    this.controls = this.createControls()
    this.container.append(this.renderer.domElement)

    this.tipContainer = tipContainer
    this.spacesConfig = [...spacesConfig]
    this.initSpaceId = String(initSpaceId || '')

    // 为 mesh 添加事件支持
    addListenerToThreeObject(() => {
      return {
        camera: this.camera,
        scene: this.scene,
        renderer: this.renderer
      }
    })

    // 缓存所有纹理
    this.cacheAllTextures()

    // 创建空间管理器
    this.spaceManager = this.createSpaceManager()

    // 实例化空间
    this.updateSpacesConfig(this.spacesConfig)

    // 初始化切换到第一个空间
    // this.switchSpace(this.currentSpaceId)
  }

  /**
   * 一次性加载并配置里所有的纹理图片
   */
  public cacheAllTextures(): void {
    const urls = this.spacesConfig.reduce<string[]>((urls, spaceConfig) => {
      const {cubeSpaceTextureUrls} = spaceConfig
      return [...urls, ...Object.values(cubeSpaceTextureUrls)]
    }, [])
    this.textureCacheLoader.loadUrls(urls)
  }

  /**
   * 监听页面尺寸变化,更新画布尺寸
   * @returns 返回取消监听函数
   */
  public listenResize(): () => void {
    const resizeFn = () => {
      this.updateContainerSize()
    }
    window.addEventListener('resize', resizeFn)

    const remove = () => {
      window.removeEventListener('resize', resizeFn)
    }

    this.destroyTasks.push(remove)
    return remove
  }

  /**
   * 更新容器宽高
   */
  public updateContainerSize(): void {
    const width = this.containerWidth
    const height = this.containerHeight

    if (this.camera) {
      this.camera.aspect = width / height
      this.camera.updateProjectionMatrix()
    }

    if (this.renderer) {
      this.renderer.setSize(width, height)
    }
  }

  /**
   * 更新 spacesConfig
   * @param newSpacesConfig 新的空间配置
   */
  public updateSpacesConfig(newSpacesConfig: SpaceConfig[]): void {
    const oldSpacesConfig = this.spacesConfig

    const newSpacesIds = new Set<string>()
    newSpacesConfig.map(spaceConfig => {
      // 如果空间已经存在,则更新空间配置,否则创建空间
      this.spaceManager.update(spaceConfig) ?? this.spaceManager.create(spaceConfig)
      newSpacesIds.add(String(spaceConfig.id))
    })

    oldSpacesConfig.map(spaceConfig => {
      // 删掉旧的空间
      if (!newSpacesIds.has(String(spaceConfig.id))) {
        this.spaceManager.remove(String(spaceConfig.id))
      }
    })

    this.spacesConfig = [...newSpacesConfig]

    // 如果当前空间不存在,则切换到另一个空间
    if (!this.currentSpaceId || !newSpacesIds.has(this.currentSpaceId)) {
      // 默认空间 id
      const defaultSpaceId = String(newSpacesConfig[0].id || '')

      // 要跳转的空间 id
      const switchSpaceId =
        !this.currentSpaceId && this.initSpaceId ? this.initSpaceId || defaultSpaceId : defaultSpaceId

      this.switchSpace(switchSpaceId)
    }
  }

  /**
   * 切换空间
   * @param id 空间 id
   * @param clickPosition 点击切换的位置,如果提高则会自动计算过渡动画
   */
  public switchSpace(id: string, clickPosition?: THREE.Vector3): void {
    const targetSpaceId = String(id)

    // 相同的房间就不执行跳转了
    if (this.currentSpaceId === targetSpaceId || !targetSpaceId) return

    const spaceGroup = this.spaceManager.find(targetSpaceId)

    // 找不到空间 id 相关的空间组,不执行跳转
    if (!spaceGroup) return

    // 找到空间组,但是挂载的信息丢失了,抛错
    if (!spaceGroup.userData.spaceConfig) throw new Error('vr360: spaceConfig 不存在')

    this.currentSpaceId = targetSpaceId

    const spaceConfig = spaceGroup.userData.spaceConfig as SpaceConfig
    const {camera} = spaceConfig

    // 添加目标空间进场景
    if (!this.scene.children.includes(spaceGroup)) {
      this.scene.add(spaceGroup)
    }

    // 从场景里移除其它空间
    this.scene.children.forEach(child => {
      if (child instanceof THREE.Group && child.userData.type === 'spaceGroup' && child !== spaceGroup) {
        this.scene.remove(child)
      }
    })

    // 场景动画结束时,添加小标签和白点
    const handleCompleteTween = () => {
      // 触发切换空间完成事件
      this.emit('afterSwitchSpace', {spaceConfig: spaceConfig})
    }

    // 下一个镜头的信息
    const nextCameraInfo = formatBaseInfo(camera)

    if (clickPosition) {
      // 当前相机的信息
      const currentCameraInfo: ThreeObjectBase = {
        ...get3dObjectBaseInfo(this.camera),
        position: {
          x: nextCameraInfo.position.x - (clickPosition.x - this.camera.position.x),
          y: nextCameraInfo.position.y - (clickPosition.y - this.camera.position.y),
          z: nextCameraInfo.position.z - (clickPosition.z - this.camera.position.z)
        }
      }

      // 用 tween.js 添加镜头切换动画
      new TWEEN.Tween(currentCameraInfo)
        .to(nextCameraInfo, 500)
        .onUpdate(cameraInfo => {
          update3dObjectBaseInfo(this.camera, cameraInfo)
        })
        .easing(TWEEN.Easing.Linear.None)
        .start()
        .onComplete(() => {
          handleCompleteTween()
        })
    } else {
      update3dObjectBaseInfo(this.camera, nextCameraInfo)
      handleCompleteTween()
    }
  }

  /**
   * 渲染
   */
  public render(): void {
    requestAnimationFrame(this.render.bind(this))
    this.controls.update()
    this.handleUpdate()
    TWEEN.update()
  }

  /**
   * 每次渲染更新时执行一些东西
   */
  private handleUpdate(): void {
    this.emit('update')
    this.renderer.render(this.scene, this.camera)
  }

  /**
   * 创建场景
   * @returns 返回场景
   */
  private createScene(): THREE.Scene {
    const scene = new THREE.Scene()

    this.destroyTasks.push(() => {
      scene.remove(...scene.children)
    })

    return scene
  }

  /**
   * 创建相机
   * @returns 返回相机
   */
  private createCamera(): THREE.PerspectiveCamera {
    const camera = new THREE.PerspectiveCamera(75, this.containerWidth / this.containerHeight, 0.01, 10_000)
    camera.position.set(0, 0, 0)

    return camera
  }

  /**
   * 创建 renderer 渲染器
   * @returns 返回渲染器
   */
  private createRenderer(): THREE.WebGLRenderer {
    const renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true
    })
    renderer.setClearColor(0xff_ff_ff, 0)
    renderer.setPixelRatio(window.devicePixelRatio)
    renderer.setSize(this.containerWidth, this.containerHeight)

    this.destroyTasks.push(() => {
      renderer.dispose()
      renderer.forceContextLoss()
      renderer.domElement.remove()
    })

    return renderer
  }

  /**
   * 创建控制器
   * @returns 返回控制器
   */
  private createControls(): OrbitControls {
    const controls = new OrbitControls(this.camera, this.renderer.domElement)
    controls.listenToKeyEvents(window)
    controls.autoRotate = false
    controls.autoRotateSpeed = 0.5
    controls.enableZoom = false
    controls.enableDamping = true
    controls.enablePan = true
    controls.enableRotate = true
    controls.rotateSpeed = 0.5
    controls.minDistance = 1
    controls.maxDistance = 100
    // controls.maxPolarAngle = Math.PI / 2
    // controls.screenSpacePanning = false

    // 控制器初始化位置为相机看到的位置
    controls.target = new THREE.Vector3(0, 0, 1)
    controls.update()

    this.destroyTasks.push(() => {
      controls.dispose()
    })

    return controls
  }

  /**
   * 创建空间管理器
   */
  private createSpaceManager(): SpaceManager {
    const spaceManager = new SpaceManager({
      container: this.container,
      camera: this.camera,
      scene: this.scene,
      renderer: this.renderer,
      tipContainer: this.tipContainer,
      textureCacheLoader: this.textureCacheLoader,
      parent: this.scene
    })

    // 空间事件继承
    extendsSpaceEventNames.forEach(eventName => {
      spaceManager.on(eventName, e => {
        // @ts-ignore
        this.emit(eventName, e)
      })
    })

    // 特殊处理
    spaceManager.on('switchSpace', e => {
      this.switchSpace(e.targetSpaceId, e.clickPosition)
    })

    return spaceManager
  }

  /**
   * 把鼠标 x、y坐标转换为画布内的三维坐标
   * @param x 鼠标 x 坐标
   * @param y 鼠标 y 坐标
   * @returns 返回三维坐标
   */
  public getPositionFromMouseXY(x: number, y: number): Position {
    return convertMousePositionToThreePosition({
      mouseX: x,
      mouseY: y,
      getDeps: () => ({
        camera: this.camera,
        renderer: this.renderer,
        scene: this.scene
      })
    })
  }

  /**
   * 销毁实例
   */
  public destroy(): void {
    this.destroyTasks.forEach(task => task())
    this.removeAllListeners()
  }
}


================================================
FILE: packages/vr360-core/test/index.ts
================================================
import {polyfillFetch} from '@nicepkg/vr360-shared/test-utils'
export * from '@nicepkg/vr360-shared/test-utils'
export * from '@nicepkg/vr360-shared/test-vue-utils'

polyfillFetch()


================================================
FILE: packages/vr360-core/test/setup.ts
================================================
import {polyfillFetch, polyfillPointerEvents} from '@nicepkg/vr360-shared/test-utils'
import {setupVueSwitch} from '@nicepkg/vr360-shared/test-vue-utils'
import {beforeAll, beforeEach} from 'vitest'

polyfillFetch()
polyfillPointerEvents()

setupVueSwitch()

beforeAll(() => {
  setupVueSwitch()
})

beforeEach(() => {
  document.body.innerHTML = ''
  document.head.innerHTML = ''
})


================================================
FILE: packages/vr360-core/tsconfig.json
================================================
{
  "extends": "../../tsconfig-base.json",
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist",
    "types": ["vite/client", "vitest", "vitest/globals"],
    "paths": {
      "@/*": ["./src/*"],
      "@test/*": ["./test/*"]
    }
  },
  "include": ["./src/**/*", "./scripts/**/*", "./types/**/*", "./test/**/*", "./*.js", "./*.ts"],
  "exclude": ["node_modules", "dist"]
}


================================================
FILE: packages/vr360-core/vite.config.ts
================================================
/// <reference types="vitest" />
import type {UserConfig} from 'vite'
import {defineConfig} from 'vite'
import {buildUtils} from '@nicepkg/vr360-shared'

export const packagePath = __dirname

type CreateViteConfigOptions = {
  minify?: boolean
}

const createViteConfig = (options: CreateViteConfigOptions = {}): UserConfig => {
  const {minify = false} = options
  return buildUtils.createViteConfig({
    packagePath,
    minify,
    externalMap: {
      three: 'THREE'
    },
    dedupe: ['three'] // use the same version
  })
}

// default config and build prod config
export const unMinifyConfig = createViteConfig({minify: false})

// build prod and build prod config
export const minifyConfig = createViteConfig({minify: true})

// for vitest
export default defineConfig(unMinifyConfig)


================================================
FILE: packages/vr360-shared/CHANGELOG.md
================================================
# 0.3.0 (2022-10-08)

### Bug Fixes

- **@nicepkg/vr360-core:** 修复移动端的提示移动 bug ([7090a98](https://github.com/nicepkg/vr360/commit/7090a9805bc472240de6ad4467bbadc14fd6151d))

# 0.3.0 (2022-10-02)

## 0.2.1 (2022-10-02)

# 0.2.0 (2022-10-02)

### Bug Fixes

- 修复 vr360-core tips 在滑动时的 bug ([e179ebc](https://github.com/nicepkg/vr360/commit/e179ebc2697314bc455320eecf8beb6182a53ded))

### Features

- 暴露更多的事件,设置默认自动旋转 ([4e21c53](https://github.com/nicepkg/vr360/commit/4e21c53ac945532020a7fbbfa46644294c33b49d))
- 初始化项目 ([06f39d1](https://github.com/nicepkg/vr360/commit/06f39d141004a1d0b1a125ad598298baf15ffee8))
- 添加标签提示和空间切换功能 ([6aa67d3](https://github.com/nicepkg/vr360/commit/6aa67d39113b06d05036ecc1d66ab1d70a2f4cf5))
- 添加如视 vr 导入支持 ([5a9eb5d](https://github.com/nicepkg/vr360/commit/5a9eb5d7a33d092be8cea5565490f268376d2f79))
- 文档添加示例插件、core 的纹理 top、bottom 改为 up 和 down ([70ccb2c](https://github.com/nicepkg/vr360/commit/70ccb2c40a06079ffb3cf50b27f986ab19b1f7db))
- 修改 package.json 说明 ([e5e4ad0](https://github.com/nicepkg/vr360/commit/e5e4ad04b1c7a9cddff3af6f73d438fd1de85b25))
- 优化代码 ([4519c4a](https://github.com/nicepkg/vr360/commit/4519c4a0fb230bb62bed49f97e0824c8977be3ca))
- 优化 sdk,添加纹理缓存预加载 ([9dd57e7](https://github.com/nicepkg/vr360/commit/9dd57e71b8f5a38ecc9901395a9b189481172edb))
- 重写核心,添加最小化更新配置支持 ([289091b](https://github.com/nicepkg/vr360/commit/289091b13dfe0495b44ae9e5353b78d2712e9762))


================================================
FILE: packages/vr360-shared/README.md
================================================
# Vr360-shared

内部实用功能的共享包,包括构建和测试功能。


================================================
FILE: packages/vr360-shared/build.config.ts
================================================
import {defineBuildConfig} from 'unbuild'

export default defineBuildConfig({
  entries: ['src/index', 'src/test-utils', 'src/test-vue-utils', 'src/test-react-utils'],
  clean: true,
  declaration: true,
  externals: ['vite', 'vitest', 'vue-demi', 'react'],
  rollup: {
    emitCJS: true,
    cjsBridge: true
  }
})


================================================
FILE: packages/vr360-shared/package.json
================================================
{
  "name": "@nicepkg/vr360-shared",
  "version": "0.3.1",
  "description": "A shared package of internal utility functions, including build and test functions.",
  "keywords": [
    "vr360-shared",
    "nicepkg",
    "utils"
  ],
  "author": "yangjinming <https://github.com/2214962083>",
  "funding": "https://github.com/sponsors/2214962083",
  "license": "MIT",
  "private": false,
  "scripts": {
    "build": "pnpm type-check &&unbuild",
    "build:watch": "pnpm build -- --stub",
    "clean": "rimraf ./dist/**/*",
    "type-check": "tsc --noEmit"
  },
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "require": "./dist/index.cjs",
      "import": "./dist/index.mjs",
      "types": "./dist/index.d.ts"
    },
    "./*": "./*",
    "./test-utils": {
      "require": "./dist/test-utils.cjs",
      "import": "./dist/test-utils.mjs",
      "types": "./dist/test-utils.d.ts"
    },
    "./test-vue-utils": {
      "require": "./dist/test-vue-utils.cjs",
      "import": "./dist/test-vue-utils.mjs",
      "types": "./dist/test-vue-utils.d.ts"
    },
    "./test-react-utils": {
      "require": "./dist/test-react-utils.cjs",
      "import": "./dist/test-react-utils.mjs",
      "types": "./dist/test-react-utils.d.ts"
    }
  },
  "files": [
    "dist"
  ],
  "sideEffects": false,
  "repository": {
    "type": "git",
    "url": "git+https://github.com/nicepkg/vr360.git",
    "directory": "packages/vr360-shared"
  },
  "bugs": {
    "url": "https://github.com/nicepkg/vr360/issues"
  },
  "homepage": "https://github.com/nicepkg/vr360#readme",
  "peerDependencies": {
    "vite": "*",
    "vitest": "*",
    "vue-demi": "*"
  },
  "peerDependenciesMeta": {
    "vite": {
      "optional": true
    },
    "vitest": {
      "optional": true
    },
    "vue-demi": {
      "optional": true
    },
    "react": {
      "optional": true
    },
    "react-dom": {
      "optional": true
    },
    "react-router-dom": {
      "optional": true
    },
    "@testing-library/jest-dom": {
      "optional": true
    }
  },
  "dependencies": {
    "@testing-library/react": "*",
    "@testing-library/user-event": "^14.4.3",
    "@types/fs-extra": "^9.0.13",
    "@types/mockjs": "^1.0.7",
    "@types/node": "*",
    "@types/node-fetch": "^2.6.2",
    "@types/rimraf": "^3.0.2",
    "@vitejs/plugin-react": "^2.1.0",
    "chalk": "4.1.2",
    "fs-extra": "^10.1.0",
    "globby": "11.1.0",
    "mockjs": "^1.1.0",
    "msw": "^0.39.2",
    "node-fetch": "2.6.7",
    "ora": "5.4.1",
    "rimraf": "^3.0.2",
    "rollup-plugin-visualizer": "^5.8.2",
    "unplugin-auto-import": "^0.11.2",
    "vite-plugin-dts": "^1.6.1",
    "vite-plugin-inspect": "0.6.1",
    "vite-plugin-mock": "^2.9.6",
    "vite-plugin-svgr": "^2.2.1",
    "vite-tsconfig-paths": "^3.5.1",
    "vitest-fetch-mock": "0.2.1"
  },
  "devDependencies": {
    "@testing-library/dom": "*",
    "@testing-library/jest-dom": "*",
    "@types/react": "*",
    "@types/react-dom": "*",
    "@types/react-router-dom": "*",
    "autoprefixer": "^10.4.12",
    "conventional-changelog-cli": "*",
    "cross-env": "^7.0.3",
    "cssnano": "^5.1.13",
    "esno": "*",
    "postcss": "^8.4.16",
    "react": "*",
    "react-dom": "*",
    "react-router-dom": "*",
    "rollup": "^2.79.1",
    "typescript": "*",
    "unbuild": "^0.8.11",
    "vite": "*",
    "vitest": "*",
    "vue": "^3.2.40",
    "vue-demi": "*",
    "vue2": "npm:vue@2.6.14"
  }
}


================================================
FILE: packages/vr360-shared/src/build-utils/build-script.util.ts
================================================
import type {InlineConfig, UserConfig, BuildOptions as ViteBuildOptions} from 'vite'
import {build as viteBuild} from 'vite'
import rimraf from 'rimraf'
import path from 'node:path'
import dts from 'vite-plugin-dts'
import type {PluginVisualizerOptions} f
Download .txt
gitextract_4oa5dsxh/

├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── commit-convention.md
│   ├── settings.yml
│   └── workflows/
│       ├── docs.yml
│       ├── lint.yml
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   ├── pre-commit
│   └── prepare-commit-msg
├── .npmrc
├── .stylelintignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── commitlint.config.js
├── lint-staged.config.js
├── netlify.toml
├── package.json
├── packages/
│   ├── doc/
│   │   ├── .vuepress/
│   │   │   ├── client.ts
│   │   │   ├── components/
│   │   │   │   ├── DemoA.vue
│   │   │   │   └── NpmBadge.vue
│   │   │   ├── configs/
│   │   │   │   ├── index.ts
│   │   │   │   ├── navbar/
│   │   │   │   │   ├── en.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── zh.ts
│   │   │   │   └── sidebar/
│   │   │   │       ├── en.ts
│   │   │   │       ├── index.ts
│   │   │   │       └── zh.ts
│   │   │   ├── examples/
│   │   │   │   └── firstHouse/
│   │   │   │       ├── demo.css
│   │   │   │       ├── html.html
│   │   │   │       ├── react.tsx
│   │   │   │       ├── vue2.vue
│   │   │   │       └── vue3.vue
│   │   │   ├── index.build.html
│   │   │   ├── plugins/
│   │   │   │   ├── code-demo/
│   │   │   │   │   ├── CodeDemo.props.ts
│   │   │   │   │   ├── CodeDemo.vue
│   │   │   │   │   ├── Icons.tsx
│   │   │   │   │   ├── clientConfigFile.ts
│   │   │   │   │   ├── constant.ts
│   │   │   │   │   ├── global.d.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── plugin.ts
│   │   │   │   └── index.ts
│   │   │   ├── public/
│   │   │   │   ├── browserconfig.xml
│   │   │   │   ├── code-demo-templates/
│   │   │   │   │   ├── demo.css.txt
│   │   │   │   │   ├── html/
│   │   │   │   │   │   └── package.json.txt
│   │   │   │   │   ├── react/
│   │   │   │   │   │   ├── index.html.txt
│   │   │   │   │   │   ├── package.json.txt
│   │   │   │   │   │   ├── src/
│   │   │   │   │   │   │   └── main.tsx.txt
│   │   │   │   │   │   ├── tsconfig.json.txt
│   │   │   │   │   │   └── vite.config.ts.txt
│   │   │   │   │   ├── vue2/
│   │   │   │   │   │   ├── index.html.txt
│   │   │   │   │   │   ├── package.json.txt
│   │   │   │   │   │   ├── src/
│   │   │   │   │   │   │   ├── App.vue.txt
│   │   │   │   │   │   │   └── main.ts.txt
│   │   │   │   │   │   ├── tsconfig.json.txt
│   │   │   │   │   │   ├── types/
│   │   │   │   │   │   │   └── module.d.ts.txt
│   │   │   │   │   │   └── vite.config.ts.txt
│   │   │   │   │   └── vue3/
│   │   │   │   │       ├── index.html.txt
│   │   │   │   │       ├── package.json.txt
│   │   │   │   │       ├── src/
│   │   │   │   │       │   ├── App.vue.txt
│   │   │   │   │       │   └── main.ts.txt
│   │   │   │   │       ├── tsconfig.json.txt
│   │   │   │   │       ├── types/
│   │   │   │   │       │   └── module.d.ts.txt
│   │   │   │   │       └── vite.config.ts.txt
│   │   │   │   └── manifest.webmanifest
│   │   │   ├── styles/
│   │   │   │   └── index.scss
│   │   │   ├── theme/
│   │   │   │   ├── components/
│   │   │   │   │   ├── Home.vue
│   │   │   │   │   ├── HomeFeatures.vue
│   │   │   │   │   └── HomeVrBg.vue
│   │   │   │   └── index.ts
│   │   │   ├── types/
│   │   │   │   └── module.d.ts
│   │   │   └── utils/
│   │   │       └── common.ts
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── bundler.config.ts
│   │   ├── guide/
│   │   │   ├── README.md
│   │   │   └── questions.md
│   │   ├── libs/
│   │   │   ├── vr360-core/
│   │   │   │   ├── README.md
│   │   │   │   ├── events.md
│   │   │   │   ├── example.md
│   │   │   │   ├── methods.md
│   │   │   │   └── properties.md
│   │   │   ├── vr360-ui/
│   │   │   │   └── README.md
│   │   │   ├── vr360-ui-react/
│   │   │   │   └── README.md
│   │   │   ├── vr360-ui-vue2/
│   │   │   │   └── README.md
│   │   │   └── vr360-ui-vue3/
│   │   │       └── README.md
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── vuepress.config.ts
│   ├── vr360-core/
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── scripts/
│   │   │   └── build.ts
│   │   ├── src/
│   │   │   ├── helper.ts
│   │   │   ├── index.ts
│   │   │   ├── manager/
│   │   │   │   ├── index.ts
│   │   │   │   ├── space.ts
│   │   │   │   └── tip.ts
│   │   │   ├── types.ts
│   │   │   └── vr360.ts
│   │   ├── test/
│   │   │   ├── index.ts
│   │   │   └── setup.ts
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   ├── vr360-shared/
│   │   ├── CHANGELOG.md
│   │   ├── README.md
│   │   ├── build.config.ts
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── build-utils/
│   │   │   │   ├── build-script.util.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── vite-config-common.util.ts
│   │   │   ├── index.ts
│   │   │   ├── test-react-utils.ts
│   │   │   ├── test-utils/
│   │   │   │   ├── common/
│   │   │   │   │   ├── helper.util.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── mock-global-api.ts
│   │   │   │   │   ├── mock-server.util.ts
│   │   │   │   │   ├── polyfill-fetch.util.ts
│   │   │   │   │   └── polyfill-pointer-events.util.ts
│   │   │   │   ├── react/
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── react-helper.util.ts
│   │   │   │   │   └── react-mount.util.ts
│   │   │   │   └── vue/
│   │   │   │       ├── index.ts
│   │   │   │       ├── vue-helper.util.ts
│   │   │   │       └── vue-mount.util.ts
│   │   │   ├── test-utils.ts
│   │   │   └── test-vue-utils.ts
│   │   ├── test-react-utils.d.ts
│   │   ├── test-utils.d.ts
│   │   ├── test-vue-utils.d.ts
│   │   ├── tsconfig.json
│   │   └── types/
│   │       └── global.d.ts
│   ├── vr360-ui/
│   │   └── README.md
│   ├── vr360-ui-react/
│   │   └── README.md
│   ├── vr360-ui-vue2/
│   │   └── README.md
│   └── vr360-ui-vue3/
│       └── README.md
├── playgrounds/
│   ├── react/
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── Example.tsx
│   │   │   └── main.tsx
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   ├── vue2/
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── App.vue
│   │   │   └── main.ts
│   │   ├── tsconfig.json
│   │   ├── types/
│   │   │   └── module.d.ts
│   │   ├── unocss.config.ts
│   │   └── vite.config.ts
│   └── vue3/
│       ├── index.html
│       ├── package.json
│       ├── src/
│       │   ├── App.vue
│       │   ├── ContextMenu.vue
│       │   ├── Editor.vue
│       │   ├── EditorHotPointManager.vue
│       │   ├── EditorLeftBar.vue
│       │   ├── EditorSceneManager.vue
│       │   ├── EditorSettings.vue
│       │   ├── EditorTipsManager.vue
│       │   ├── EditorTopBar.vue
│       │   ├── Icons.tsx
│       │   ├── helper.ts
│       │   ├── main.ts
│       │   └── useVr360.ts
│       ├── tsconfig.json
│       ├── types/
│       │   └── module.d.ts
│       ├── unocss.config.ts
│       └── vite.config.ts
├── pnpm-workspace.yaml
├── prettier.config.js
├── scripts/
│   ├── build.ts
│   ├── check-update.ts
│   ├── release.ts
│   └── utils.ts
├── stylelint.config.js
├── textures.json
├── tsconfig-base.json
├── tsconfig.json
└── turbo.json
Download .txt
SYMBOL INDEX (150 symbols across 29 files)

FILE: packages/doc/.vuepress/client.ts
  method enhance (line 7) | enhance({app}) {

FILE: packages/doc/.vuepress/examples/firstHouse/react.tsx
  function Example (line 6) | function Example() {

FILE: packages/doc/.vuepress/plugins/code-demo/CodeDemo.props.ts
  type CodeDemoProps (line 26) | type CodeDemoProps = Partial<ExtractPropTypes<ReturnType<typeof getProps>>>

FILE: packages/doc/.vuepress/plugins/code-demo/Icons.tsx
  method render (line 8) | render() {

FILE: packages/doc/.vuepress/plugins/code-demo/clientConfigFile.ts
  method enhance (line 9) | async enhance({app}) {

FILE: packages/doc/.vuepress/plugins/code-demo/constant.ts
  constant DEFAULT_EDITOR_TITLE (line 2) | const DEFAULT_EDITOR_TITLE = 'Vr360 Example'
  constant DEFAULT_EDITOR_DESCRIPTION (line 5) | const DEFAULT_EDITOR_DESCRIPTION = ''
  constant DEFAULT_VR360_VERSION (line 8) | const DEFAULT_VR360_VERSION = '^6.0.0'

FILE: packages/doc/.vuepress/plugins/code-demo/global.d.ts
  type Window (line 6) | interface Window {

FILE: packages/doc/.vuepress/plugins/code-demo/plugin.ts
  type MarkdownItRenderFn (line 16) | type MarkdownItRenderFn = (
  type CodeDemoOptions (line 24) | type CodeDemoOptions = {
  type RenderPlaceFunction (line 30) | type RenderPlaceFunction = (description: string, codeBlockTokens?: Token...

FILE: packages/vr360-core/src/helper.ts
  function getSingleValue (line 9) | function getSingleValue() {
  type ConvertMousePositionToThreePositionOptions (line 18) | type ConvertMousePositionToThreePositionOptions = {
  function convertMousePositionToThreePosition (line 40) | function convertMousePositionToThreePosition(options: ConvertMousePositi...
  type GetAddListenerToThreeObjectDeps (line 60) | type GetAddListenerToThreeObjectDeps = () => {
  type ThreeObjectDispatchEvent (line 77) | type ThreeObjectDispatchEvent<T extends keyof HTMLElementEventMap> = {
  function addListenerToThreeObject (line 89) | function addListenerToThreeObject(
  class TextureCacheLoader (line 183) | class TextureCacheLoader {
    method getInstance (line 185) | static getInstance() {
    method loadUrl (line 205) | loadUrl(url: string) {
    method loadUrls (line 217) | loadUrls(urls: string[]) {
  function hasOwnProperty (line 228) | function hasOwnProperty(obj: any, key: string): boolean {
  function isEqual (line 238) | function isEqual(a: any, b: any): boolean {
  function debounce (line 269) | function debounce<T extends (...args: any[]) => any>(func: T, wait: numb...
  function update3dObjectBaseInfo (line 291) | function update3dObjectBaseInfo(object: THREE.Object3D, baseInfo: Partia...
  function get3dObjectBaseInfo (line 311) | function get3dObjectBaseInfo(object?: THREE.Object3D): ThreeObjectBase {
  function formatBaseInfo (line 336) | function formatBaseInfo(baseInfo: DeepPartial<ThreeObjectBase> | undefin...

FILE: packages/vr360-core/src/manager/index.ts
  type ConfigModel (line 4) | type ConfigModel = {
  type ConfigModelManager (line 8) | type ConfigModelManager<CM extends ConfigModel, TM> = {

FILE: packages/vr360-core/src/manager/space.ts
  type SpaceManagerEvents (line 14) | type SpaceManagerEvents = TipManagerEvents
  type SpaceEventName (line 16) | type SpaceEventName = keyof SpaceManagerEvents
  type SpaceManagerOptions (line 19) | type SpaceManagerOptions = Omit<TipManagerOptions, 'tipContainer'> & {
  class SpaceManager (line 26) | class SpaceManager
    method constructor (line 41) | constructor(options: SpaceManagerOptions) {
    method createTipManager (line 57) | private createTipManager(parent: THREE.Object3D): TipManager | undefin...
    method createCubeSpaceMesh (line 85) | private createCubeSpaceMesh(cubeSpaceTextureUrls: CubeSpaceTextureUrls) {
    method createCubeSpaceMaterials (line 102) | private createCubeSpaceMaterials(cubeSpaceTextureUrls: CubeSpaceTextur...
    method create (line 113) | public create(spaceConfig: SpaceConfig): THREE.Group {
    method add (line 141) | public add(spaceConfig: SpaceConfig): THREE.Group {
    method update (line 147) | public update(spaceConfig: Partial<SpaceConfig> & Pick<SpaceConfig, 'i...
    method find (line 199) | public find(spaceConfig: string | (Partial<SpaceConfig> & Pick<SpaceCo...
    method findOrCreate (line 204) | public findOrCreate(spaceConfig: SpaceConfig): THREE.Group {
    method remove (line 210) | public remove(spaceConfig: string | (Partial<SpaceConfig> & Pick<Space...
    method removeAll (line 220) | public removeAll(): void {
    method destroy (line 233) | public destroy(): void {

FILE: packages/vr360-core/src/manager/tip.ts
  type ShowTipEvent (line 12) | type ShowTipEvent = {
  type HideTipEvent (line 32) | type HideTipEvent = {
  type ClickTipEvent (line 39) | type ClickTipEvent = {
  type SwitchSpaceEvent (line 46) | type SwitchSpaceEvent = {
  type TipManagerEvents (line 61) | type TipManagerEvents = {
  type TipEventName (line 83) | type TipEventName = keyof TipManagerEvents
  type TipManagerOptions (line 86) | type TipManagerOptions = {
  class TipManager (line 126) | class TipManager extends EventEmitter<TipManagerEvents> implements Confi...
    method constructor (line 137) | constructor(options: TipManagerOptions) {
    method create (line 148) | public create(tip: Tip): THREE.Sprite {
    method add (line 247) | public add(tip: Tip): THREE.Sprite {
    method update (line 253) | public update(tip: Partial<Tip> & Pick<Tip, 'id'>): THREE.Sprite | und...
    method find (line 277) | public find(tip: string | (Partial<Tip> & Pick<Tip, 'id'>)): THREE.Spr...
    method findOrCreate (line 282) | public findOrCreate(tip: Tip): THREE.Sprite {
    method remove (line 288) | public remove(tip: string | (Partial<Tip> & Pick<Tip, 'id'>)): void {
    method removeAll (line 297) | public removeAll(): void {
    method destroy (line 304) | public destroy(): void {

FILE: packages/vr360-core/src/types.ts
  type AfterSwitchSpaceEvent (line 9) | type AfterSwitchSpaceEvent = {
  type Vr360Events (line 19) | type Vr360Events = {
  type Vector3Position (line 34) | type Vector3Position = {
  type Position (line 54) | type Position = Vector3Position
  type Scale (line 59) | type Scale = Vector3Position
  type Rotate (line 64) | type Rotate = Vector3Position
  type ThreeObjectBase (line 69) | type ThreeObjectBase = {
  type CubeSpaceTextureUrls (line 89) | type CubeSpaceTextureUrls = {
  type Tip (line 124) | type Tip = {
  type SpaceConfig (line 150) | type SpaceConfig = {
  type Vr360Options (line 175) | type Vr360Options = {
  type DeepRequired (line 200) | type DeepRequired<T> = {
  type DeepPartial (line 207) | type DeepPartial<T> = {

FILE: packages/vr360-core/src/vr360.ts
  type Vr360EventName (line 21) | type Vr360EventName = keyof Vr360Events
  class Vr360 (line 36) | class Vr360 extends EventEmitter<Vr360Events> {
    method containerWidth (line 100) | public get containerWidth() {
    method containerHeight (line 107) | public get containerHeight() {
    method constructor (line 111) | constructor(options: Vr360Options) {
    method cacheAllTextures (line 153) | public cacheAllTextures(): void {
    method listenResize (line 165) | public listenResize(): () => void {
    method updateContainerSize (line 182) | public updateContainerSize(): void {
    method updateSpacesConfig (line 200) | public updateSpacesConfig(newSpacesConfig: SpaceConfig[]): void {
    method switchSpace (line 237) | public switchSpace(id: string, clickPosition?: THREE.Vector3): void {
    method render (line 308) | public render(): void {
    method handleUpdate (line 318) | private handleUpdate(): void {
    method createScene (line 327) | private createScene(): THREE.Scene {
    method createCamera (line 341) | private createCamera(): THREE.PerspectiveCamera {
    method createRenderer (line 352) | private createRenderer(): THREE.WebGLRenderer {
    method createControls (line 374) | private createControls(): OrbitControls {
    method createSpaceManager (line 403) | private createSpaceManager(): SpaceManager {
    method getPositionFromMouseXY (line 436) | public getPositionFromMouseXY(x: number, y: number): Position {
    method destroy (line 451) | public destroy(): void {

FILE: packages/vr360-core/vite.config.ts
  type CreateViteConfigOptions (line 8) | type CreateViteConfigOptions = {

FILE: packages/vr360-shared/src/build-utils/build-script.util.ts
  constant WATCH (line 9) | const WATCH = Boolean(process.env.WATCH)
  constant REPORT (line 10) | const REPORT = Boolean(process.env.REPORT)
  type DtsOptions (line 12) | type DtsOptions = Parameters<typeof dts>[0]
  type GetItemType (line 14) | type GetItemType<T> = T extends (infer U)[] ? U : T
  type RollupOutput (line 15) | type RollupOutput = NonNullable<NonNullable<ViteBuildOptions['rollupOpti...
  type OutputOptions (line 16) | type OutputOptions = GetItemType<RollupOutput>
  type ChangeConfigOptions (line 18) | type ChangeConfigOptions = {
  function changeViteConfig (line 27) | function changeViteConfig(config: UserConfig, options: ChangeConfigOptio...
  type ChangeConfigFn (line 74) | type ChangeConfigFn = (config: UserConfig, options: ChangeConfigOptions)...
  type BuildOptions (line 75) | type BuildOptions = {
  function build (line 84) | async function build(config: BuildOptions) {

FILE: packages/vr360-shared/src/build-utils/vite-config-common.util.ts
  type CreateViteConfigOptions (line 23) | type CreateViteConfigOptions = {

FILE: packages/vr360-shared/src/test-utils/common/mock-global-api.ts
  function mockGlobalApi (line 1) | function mockGlobalApi() {

FILE: packages/vr360-shared/src/test-utils/common/polyfill-pointer-events.util.ts
  class PointerEvent (line 6) | class PointerEvent extends MouseEvent {
    method constructor (line 9) | constructor(type: string, params: PointerEventInit = {}) {

FILE: packages/vr360-shared/src/test-utils/react/react-helper.util.ts
  function commonSetup (line 4) | function commonSetup(options?: {title?: string}) {

FILE: packages/vr360-shared/src/test-utils/react/react-mount.util.ts
  function mount (line 2) | function mount() {

FILE: packages/vr360-shared/src/test-utils/vue/vue-helper.util.ts
  function setProps (line 13) | function setProps(props: Record<string, any>) {
  function createFunctionComponent (line 28) | function createFunctionComponent(fn: (...args: any[]) => any) {
  function getSlots (line 42) | function getSlots(ctx: any, slotName: string) {
  function setupVueSwitch (line 50) | function setupVueSwitch() {

FILE: packages/vr360-shared/src/test-utils/vue/vue-mount.util.ts
  type InstanceType (line 6) | type InstanceType<V> = V extends new (...arg: any[]) => infer X ? X : never
  type VM (line 7) | type VM<V> = InstanceType<V> & {unmount(): void}
  function find (line 9) | function find(selector: string, target?: HTMLElement | null | undefined) {
  type SnapType (line 23) | type SnapType = 'all' | 'vue2' | 'vue3'
  type MountResult (line 25) | type MountResult<V> = VM<V> & {
  function mount (line 30) | function mount<V extends Component>(Comp: V): MountResult<V> {
  function getComponent (line 67) | function getComponent(tagName: string) {
  function useSetup (line 71) | function useSetup<V>(setup: () => V) {
  function useInjectedSetup (line 84) | function useInjectedSetup<V>(setup: () => V) {

FILE: packages/vr360-shared/types/global.d.ts
  type Writable (line 4) | type Writable<T> = {
  type DeepPartial (line 8) | type DeepPartial<T> = {
  type TimeoutHandle (line 12) | type TimeoutHandle = ReturnType<typeof setTimeout>
  type IntervalHandle (line 13) | type IntervalHandle = ReturnType<typeof setInterval>

FILE: playgrounds/react/src/Example.tsx
  function Example (line 1) | function Example() {

FILE: playgrounds/vue3/src/helper.ts
  type HttpOptions (line 6) | type HttpOptions = {
  type DeepPartial (line 140) | type DeepPartial<T> = {
  type RealseeVrInfo (line 148) | type RealseeVrInfo = {
  type RealseeVrInfoInitial (line 166) | type RealseeVrInfoInitial = {
  type RealseeVrInfoModel (line 174) | type RealseeVrInfoModel = {
  type RealseeVrInfoObserver (line 182) | type RealseeVrInfoObserver = {
  type RealseeVrInfoObserverQuaternion (line 193) | type RealseeVrInfoObserverQuaternion = {
  type RealseeVrInfoPanorama (line 200) | type RealseeVrInfoPanorama = {
  type RealseeVrInfoPanoramaList (line 205) | type RealseeVrInfoPanoramaList = {

FILE: playgrounds/vue3/src/useVr360.ts
  type MaybeRef (line 8) | type MaybeRef<T> = T | Ref<T>
  type UseVr360Options (line 10) | type UseVr360Options = {
  function useVr360 (line 70) | function useVr360(options: UseVr360Options) {

FILE: scripts/check-update.ts
  function checkNpmPkgUpdate (line 5) | async function checkNpmPkgUpdate() {

FILE: scripts/utils.ts
  function copyFiles (line 13) | function copyFiles() {
  type PackageInfo (line 26) | type PackageInfo = {path: string; name: string}
  function getPackagesInfo (line 27) | function getPackagesInfo(type: 'public' | 'all'): PackageInfo[] {
  function build (line 56) | function build() {
  function generateChangelog (line 62) | function generateChangelog() {
  function release (line 75) | function release() {
Condensed preview — 193 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (287K chars).
[
  {
    "path": ".editorconfig",
    "chars": 147,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".eslintignore",
    "chars": 230,
    "preview": "**/node_modules/\n*.html\nes/\n**/lib/\n**/dist/\n**/lib-types/\n_site/\n**/dist/\nCHANGELOG.md\n.rollup.cache\ntsconfig.tsbuildin"
  },
  {
    "path": ".eslintrc.js",
    "chars": 7316,
    "preview": "//@ts-check\n\nmodule.exports = /** @type { import('eslint').Linter.Config } */ ({\n  root: true,\n  extends: [\n    'eslint:"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 21,
    "preview": "github: [2214962083]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 2655,
    "preview": "name: \"\\U0001F41E Bug report\"\ndescription: Report an issue with Vr360\nbody:\n  - type: markdown\n    attributes:\n      val"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 461,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Question\n    url: https://github.com/nicepkg/vr360/discussions/new?"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 1797,
    "preview": "name: \"\\U0001F680 New feature proposal\"\ndescription: Propose a new feature to be added to Vr360\nlabels: ['feature reques"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 924,
    "preview": "<!-- Thank you for contributing! -->\n\n### Description\n\n<!-- Please insert your description here and provide especially i"
  },
  {
    "path": ".github/commit-convention.md",
    "chars": 2930,
    "preview": "## Git Commit Message Convention\n\n> This is adapted from [Angular's commit convention](https://github.com/conventional-c"
  },
  {
    "path": ".github/settings.yml",
    "chars": 474,
    "preview": "labels:\n  - name: bug\n    color: ee0701\n  - name: contribution welcome\n    color: 0e8a16\n  - name: discussion\n    color:"
  },
  {
    "path": ".github/workflows/docs.yml",
    "chars": 2305,
    "preview": "name: Docs\n\non:\n  push:\n    branches:\n      - main\n      - master\n\n  pull_request:\n    branches:\n      - main\n      - ma"
  },
  {
    "path": ".github/workflows/lint.yml",
    "chars": 746,
    "preview": "name: Lint\n\non:\n  push:\n    branches:\n      - main\n      - master\n\n  pull_request:\n    branches:\n      - main\n      - ma"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1321,
    "preview": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    env:\n      TURBO_TOKE"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1117,
    "preview": "name: Test\n\non:\n  push:\n    branches:\n      - main\n      - master\n\n  pull_request:\n    branches:\n      - main\n      - ma"
  },
  {
    "path": ".gitignore",
    "chars": 514,
    "preview": ".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.histor"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 104,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\n# lint commit message\nnpx --no-install commitlint --edit \"$1\"\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 94,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\n# lint git stash files\nnpx --no-install lint-staged\n"
  },
  {
    "path": ".husky/prepare-commit-msg",
    "chars": 245,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\necho \"do not 'git commit' please run 'pnpm commit' instead\"\n# echo \"commit lin"
  },
  {
    "path": ".npmrc",
    "chars": 1462,
    "preview": "# registry = \"https://registry.npmmirror.com\"\n\nsass_binary_site = \"https://cdn.npmmirror.com/binaries/node-sass\"\nphantom"
  },
  {
    "path": ".stylelintignore",
    "chars": 122,
    "preview": "node_modules/\n**/*.spec.*\nes/\nlib/\n_site/\ndist/\n**/node_modules/*\n**/segi-ant-theme.less\n**/.rollup.cache/*\n**/index.htm"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 325,
    "preview": "{\n  \"recommendations\": [\n    \"antfu.iconify\",\n    \"antfu.unocss\",\n    \"antfu.goto-alias\",\n    \"csstools.postcss\",\n    \"d"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 2840,
    "preview": "{\n  // Use PNPM\n  \"npm.packageManager\": \"pnpm\",\n  // \"eslint.packageManager\": \"pnpm\",\n  \"typescript.tsdk\": \"./node_modul"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3352,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3428,
    "preview": "# Contributing\n\nThanks for being interested in contributing to this project!\n\n## Development\n\nTo improve our development"
  },
  {
    "path": "LICENSE",
    "chars": 1100,
    "preview": "MIT License\n\nCopyright (c) 2021 YangJinMing <https://github.com/2214962083>\n\nPermission is hereby granted, free of charg"
  },
  {
    "path": "README.md",
    "chars": 3308,
    "preview": "<div align=\"center\">\n  <a href=\"https://vr360.nicepkg.cn/\">\n    <img src=\"https://vr360.nicepkg.cn/images/logo-bg.png\" w"
  },
  {
    "path": "commitlint.config.js",
    "chars": 68,
    "preview": "module.exports = {\n  extends: ['@commitlint/config-conventional']\n}\n"
  },
  {
    "path": "lint-staged.config.js",
    "chars": 296,
    "preview": "module.exports = {\n  '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],\n  '*.json': ['prettier --write'],\n  '*.v"
  },
  {
    "path": "netlify.toml",
    "chars": 193,
    "preview": "[build.environment]\n  NODE_VERSION = \"16\"\n  NPM_FLAGS = \"--version\" # prevent Netlify npm install\n[build]\n  publish = \"p"
  },
  {
    "path": "package.json",
    "chars": 4858,
    "preview": "{\n  \"name\": \"@nicepkg/vr360\",\n  \"version\": \"0.3.1\",\n  \"private\": true,\n  \"packageManager\": \"pnpm@7.0.0\",\n  \"author\": \"ya"
  },
  {
    "path": "packages/doc/.vuepress/client.ts",
    "chars": 3707,
    "preview": "/* eslint-disable unicorn/no-await-expression-member */\nimport type {ProjectFiles} from '@stackblitz/sdk'\nimport {define"
  },
  {
    "path": "packages/doc/.vuepress/components/DemoA.vue",
    "chars": 4221,
    "preview": "<template>\n  <div class=\"demoA\">\n    <div\n      ref=\"tipRef\"\n      class=\"demoA-tip\"\n      :style=\"{\n        transform: "
  },
  {
    "path": "packages/doc/.vuepress/components/NpmBadge.vue",
    "chars": 831,
    "preview": "<script setup lang=\"ts\">\nimport {computed} from 'vue'\n\nconst props = defineProps({\n  package: {\n    type: String,\n    re"
  },
  {
    "path": "packages/doc/.vuepress/configs/index.ts",
    "chars": 72,
    "preview": "export * as navbar from './navbar'\nexport * as sidebar from './sidebar'\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/navbar/en.ts",
    "chars": 344,
    "preview": "import type {NavbarConfig} from '@vuepress/theme-default'\nimport {version} from '../../utils/common'\n\nexport const en: N"
  },
  {
    "path": "packages/doc/.vuepress/configs/navbar/index.ts",
    "chars": 42,
    "preview": "export * from './en'\nexport * from './zh'\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/navbar/zh.ts",
    "chars": 469,
    "preview": "import type {NavbarConfig} from '@vuepress/theme-default'\nimport {version} from '../../utils/common'\n\nexport const zh: N"
  },
  {
    "path": "packages/doc/.vuepress/configs/sidebar/en.ts",
    "chars": 218,
    "preview": "import type {SidebarConfig} from '@vuepress/theme-default'\n\nexport const en: SidebarConfig = {\n  '/en/guide/': [\n    {\n "
  },
  {
    "path": "packages/doc/.vuepress/configs/sidebar/index.ts",
    "chars": 42,
    "preview": "export * from './en'\nexport * from './zh'\n"
  },
  {
    "path": "packages/doc/.vuepress/configs/sidebar/zh.ts",
    "chars": 495,
    "preview": "import type {SidebarConfig} from '@vuepress/theme-default'\n\nexport const zh: SidebarConfig = {\n  '/guide/': [\n    {\n    "
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/demo.css",
    "chars": 886,
    "preview": "* {\n  box-sizing: border-box;\n  padding: 0;\n  margin: 0;\n}\n\n.demo {\n  position: relative;\n  display: flex;\n  flex-direct"
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/html.html",
    "chars": 6427,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Html App</title>\n    <meta\n      na"
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/react.tsx",
    "chars": 5887,
    "preview": "import React, {useEffect, useState, useRef} from 'react'\nimport {Vr360} from '@nicepkg/vr360-core'\nimport type {SpaceCon"
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/vue2.vue",
    "chars": 5699,
    "preview": "<template>\n  <div class=\"demo\">\n    <!-- 提示 -->\n    <div\n      ref=\"tipRef\"\n      class=\"demo-tip\"\n      :style=\"{\n     "
  },
  {
    "path": "packages/doc/.vuepress/examples/firstHouse/vue3.vue",
    "chars": 5245,
    "preview": "<template>\n  <div class=\"demo\">\n    <!-- 提示 -->\n    <div\n      ref=\"tipRef\"\n      class=\"demo-tip\"\n      :style=\"{\n     "
  },
  {
    "path": "packages/doc/.vuepress/index.build.html",
    "chars": 454,
    "preview": "<!DOCTYPE html>\n<html lang=\"{{ lang }}\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=de"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/CodeDemo.props.ts",
    "chars": 510,
    "preview": "import type {Project} from '@stackblitz/sdk'\nimport type {ExtractPropTypes, PropType} from 'vue'\n\nexport const getProps "
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/CodeDemo.vue",
    "chars": 2697,
    "preview": "<template>\n  <div\n    ref=\"codeDemoRef\"\n    class=\"code-demo\"\n    :style=\"{\n      border: isFullScreen ? 'none' : '1px s"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/Icons.tsx",
    "chars": 1494,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {VNode} from 'vue'\nimport {defineComponent, h} from "
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/clientConfigFile.ts",
    "chars": 1616,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {CodeDemoProps} from './CodeDemo.props'\nimport {defi"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/constant.ts",
    "chars": 240,
    "preview": "// 用于 Stackblitz 示例的默认标题(未覆盖时)\nexport const DEFAULT_EDITOR_TITLE = 'Vr360 Example'\n\n// 用于 Stackblitz 示例的默认描述(未覆盖时)\nexpor"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/global.d.ts",
    "chars": 385,
    "preview": "import type {ProjectFiles} from '@stackblitz/sdk'\nimport type {CodeDemoProps} from './CodeDemo.props'\n\ndeclare global {\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/index.ts",
    "chars": 25,
    "preview": "export * from './plugin'\n"
  },
  {
    "path": "packages/doc/.vuepress/plugins/code-demo/plugin.ts",
    "chars": 4219,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport typ"
  },
  {
    "path": "packages/doc/.vuepress/plugins/index.ts",
    "chars": 942,
    "preview": "import type {PluginConfig} from 'vuepress'\nimport {registerComponentsPlugin} from '@vuepress/plugin-register-components'"
  },
  {
    "path": "packages/doc/.vuepress/public/browserconfig.xml",
    "chars": 259,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/demo.css.txt",
    "chars": 838,
    "preview": "* {\n  box-sizing: border-box;\n  padding: 0;\n  margin: 0;\n}\n\n.demo {\n  position: relative;\n  display: flex;\n  flex-direct"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/html/package.json.txt",
    "chars": 167,
    "preview": "{\n  \"name\": \"playground-html\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"http-server ./ -p 8000 -o\"\n  },\n  \"devDepen"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/index.html.txt",
    "chars": 852,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>R"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/package.json.txt",
    "chars": 545,
    "preview": "{\n  \"name\": \"playground-react\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"ser"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/src/main.tsx.txt",
    "chars": 204,
    "preview": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport Example from './Example'\n\nReactDOM.render(\n  <React.St"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/tsconfig.json.txt",
    "chars": 561,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"target\": \"esnext\",\n    \"composite\": true,\n   "
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/react/vite.config.ts.txt",
    "chars": 331,
    "preview": "import {defineConfig} from 'vite'\nimport path from 'node:path'\nimport viteReact from '@vitejs/plugin-react'\n\nconst pathR"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/index.html.txt",
    "chars": 850,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>V"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/package.json.txt",
    "chars": 497,
    "preview": "{\n  \"name\": \"playground-vue2\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"serv"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/src/App.vue.txt",
    "chars": 178,
    "preview": "<template>\n  <Example></Example>\n</template>\n\n<script lang=\"ts\">\nimport Example from './Example.vue'\n\nexport default {\n "
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/src/main.ts.txt",
    "chars": 135,
    "preview": "import Vue from 'vue'\nimport App from './App.vue'\n\nVue.config.productionTip = false\n\nnew Vue({\n  render: h => h(App)\n})."
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/tsconfig.json.txt",
    "chars": 560,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"target\": \"esnext\",\n    \"composite\": true,\n   "
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/types/module.d.ts.txt",
    "chars": 130,
    "preview": "declare module '*.vue' {\n  import type {VueConstructor} from 'vue'\n  const component: VueConstructor\n  export default co"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue2/vite.config.ts.txt",
    "chars": 420,
    "preview": "import path from 'node:path'\nimport {defineConfig} from 'vite'\nimport {createVuePlugin} from 'vite-plugin-vue2'\n\nconst p"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/index.html.txt",
    "chars": 850,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>V"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/package.json.txt",
    "chars": 466,
    "preview": "{\n  \"name\": \"playground-vue3\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"serv"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/src/App.vue.txt",
    "chars": 117,
    "preview": "<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",
    "chars": 155,
    "preview": "import {createApp} from 'vue'\nimport App from './App.vue'\n\nconst app = createApp(App)\napp.mount('#app')\n\napp.config.glob"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/tsconfig.json.txt",
    "chars": 560,
    "preview": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"target\": \"esnext\",\n    \"composite\": true,\n   "
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/types/module.d.ts.txt",
    "chars": 151,
    "preview": "declare module '*.vue' {\n  import type {DefineComponent} from 'vue-demi'\n  const Component: DefineComponent<{}, {}, any>"
  },
  {
    "path": "packages/doc/.vuepress/public/code-demo-templates/vue3/vite.config.ts.txt",
    "chars": 406,
    "preview": "import path from 'node:path'\nimport {defineConfig} from 'vite'\nimport Vue from '@vitejs/plugin-vue'\nimport vueJsx from '"
  },
  {
    "path": "packages/doc/.vuepress/public/manifest.webmanifest",
    "chars": 1375,
    "preview": "{\n  \"name\": \"vr360\",\n  \"short_name\": \"vr360\",\n  \"description\": \"快速实现你的全景开发需求\",\n  \"theme_color\": \"#ffffff\",\n  \"background"
  },
  {
    "path": "packages/doc/.vuepress/styles/index.scss",
    "chars": 797,
    "preview": ":root {\n  // brand colors\n  --c-brand: #0c4dc4;\n  --c-brand-light: #0b3788;\n\n  scroll-behavior: smooth;\n}\n\nhtml.dark {\n "
  },
  {
    "path": "packages/doc/.vuepress/theme/components/Home.vue",
    "chars": 611,
    "preview": "<script setup lang=\"ts\">\nimport HomeContent from '@vuepress/theme-default/components/HomeContent.vue'\nimport HomeFeature"
  },
  {
    "path": "packages/doc/.vuepress/theme/components/HomeFeatures.vue",
    "chars": 1404,
    "preview": "<script setup lang=\"ts\">\nimport {usePageFrontmatter} from '@vuepress/client'\nimport {isArray} from '@vuepress/shared'\nim"
  },
  {
    "path": "packages/doc/.vuepress/theme/components/HomeVrBg.vue",
    "chars": 3445,
    "preview": "<template>\n  <div class=\"vr-container-wrapper\" :style=\"{height: height + 'px'}\">\n    <div\n      ref=\"tipRef\"\n      class"
  },
  {
    "path": "packages/doc/.vuepress/theme/index.ts",
    "chars": 2350,
    "preview": "import type {Theme} from '@vuepress/core'\nimport {defaultTheme} from '@vuepress/theme-default'\nimport {path} from '@vuep"
  },
  {
    "path": "packages/doc/.vuepress/types/module.d.ts",
    "chars": 261,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\ndeclare module '*.vue' {\n  import type {DefineComponent} from '"
  },
  {
    "path": "packages/doc/.vuepress/utils/common.ts",
    "chars": 157,
    "preview": "import rootPkg from '../../../../package.json'\n\nexport const version = rootPkg.version as string\nexport const isProd = p"
  },
  {
    "path": "packages/doc/CHANGELOG.md",
    "chars": 1410,
    "preview": "# 0.3.0 (2022-10-08)\n\n### Bug Fixes\n\n- **@nicepkg/vr360-core:** 修复移动端的提示移动 bug ([7090a98](https://github.com/nicepkg/vr3"
  },
  {
    "path": "packages/doc/README.md",
    "chars": 969,
    "preview": "---\nhome: true\ntitle: 首页\nheroImage: /images/logo.png\nheroImageDark: /images/logo-dark.png\nheroText: null\ntagline: 快速实现你的"
  },
  {
    "path": "packages/doc/bundler.config.ts",
    "chars": 456,
    "preview": "import {path} from '@vuepress/utils'\nimport type {ViteBundlerOptions} from 'vuepress'\n\nconst pathResolve = (..._path: st"
  },
  {
    "path": "packages/doc/guide/README.md",
    "chars": 711,
    "preview": "# 介绍\n\nVr360 是一个基于 threejs 能让你快速实现业务全景需求的库,比如全景看房、全景街景、全景景点。\n\n它的核心被设计为框架无关性,可以用 json 配置的方式快速实现常见全景需求。\n\n后续还会提供高度封装的 viewer"
  },
  {
    "path": "packages/doc/guide/questions.md",
    "chars": 127,
    "preview": "# 常见问题\n\n## vr360-ui 为什么没有 angular 版\n\nvr360-ui 是基于 stencil.js 开发,适配 angular 是很容易的,但是由于本人没怎么用过 angular,适配完也不知道有没有 bug,所以欢迎"
  },
  {
    "path": "packages/doc/libs/vr360-core/README.md",
    "chars": 2540,
    "preview": "# 介绍\n\n`@nicepkg/vr360-core` 是一个基于 [threejs](https://github.com/mrdoob/three.js/) 的全景库,非常适合用来做全景看房、全景街景、全景景点等业务需求。\n\n它支持 j"
  },
  {
    "path": "packages/doc/libs/vr360-core/events.md",
    "chars": 1943,
    "preview": "# 事件\n\n## 显示提示\n\n#### 介绍\n\n```ts\ninterface Vr360Events {\n  /**\n   * 触发提示时的回调\n   */\n  showTip: (e: {\n    /**\n     * 提示配置信息\n "
  },
  {
    "path": "packages/doc/libs/vr360-core/example.md",
    "chars": 541,
    "preview": "# 示例\n\n## 效果\n\n<br/>\n<DemoA></DemoA>\n\n### vue2 实现\n\n::: demo vue2 -- vue2 示例代码 -- 这里是示例代码的描述\n\n```vue src/Example.vue\n/*# @/"
  },
  {
    "path": "packages/doc/libs/vr360-core/methods.md",
    "chars": 5154,
    "preview": "# 方法\n\n## 构造器\n\n#### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * @param options 构造参数配置\n   * @returns Vr360 实例\n   */\n  constructor(o"
  },
  {
    "path": "packages/doc/libs/vr360-core/properties.md",
    "chars": 1909,
    "preview": "# 属性\n\n## 全景容器\n\n##### 介绍\n\n```ts\nclass Vr360 {\n  /**\n   * 容器\n   */\n  public container: HTMLElement\n}\n```\n\n##### 使用\n\n```ts\n"
  },
  {
    "path": "packages/doc/libs/vr360-ui/README.md",
    "chars": 77,
    "preview": "# 介绍\n\n(开发中...)提供一个现成的 vr360 viewer 和 editor 组件,基于 stencil 构建的 web component。\n"
  },
  {
    "path": "packages/doc/libs/vr360-ui-react/README.md",
    "chars": 61,
    "preview": "# 介绍\n\n(开发中...)提供一个现成的 vr360 viewer 和 editor 组件,适配 react 框架版。\n"
  },
  {
    "path": "packages/doc/libs/vr360-ui-vue2/README.md",
    "chars": 60,
    "preview": "# 介绍\n\n(开发中...)提供一个现成的 vr360 viewer 和 editor 组件,适配 vue2 框架版。\n"
  },
  {
    "path": "packages/doc/libs/vr360-ui-vue3/README.md",
    "chars": 60,
    "preview": "# 介绍\n\n(开发中...)提供一个现成的 vr360 viewer 和 editor 组件,适配 vue3 框架版。\n"
  },
  {
    "path": "packages/doc/package.json",
    "chars": 1312,
    "preview": "{\n  \"name\": \"doc\",\n  \"version\": \"0.3.1\",\n  \"private\": true,\n  \"description\": \"the libs docs website\",\n  \"scripts\": {\n   "
  },
  {
    "path": "packages/doc/tsconfig.json",
    "chars": 346,
    "preview": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"type"
  },
  {
    "path": "packages/doc/vuepress.config.ts",
    "chars": 2044,
    "preview": "import {defineUserConfig} from 'vuepress'\nimport {path} from '@vuepress/utils'\nimport {viteBundler} from '@vuepress/bund"
  },
  {
    "path": "packages/vr360-core/CHANGELOG.md",
    "chars": 1410,
    "preview": "# 0.3.0 (2022-10-08)\n\n### Bug Fixes\n\n- **@nicepkg/vr360-core:** 修复移动端的提示移动 bug ([7090a98](https://github.com/nicepkg/vr3"
  },
  {
    "path": "packages/vr360-core/README.md",
    "chars": 1972,
    "preview": "<div align=\"center\">\n  <a href=\"https://vr360.nicepkg.cn/libs/vr360-core/\">\n    <img src=\"https://vr360.nicepkg.cn/image"
  },
  {
    "path": "packages/vr360-core/package.json",
    "chars": 1807,
    "preview": "{\n  \"name\": \"@nicepkg/vr360-core\",\n  \"version\": \"0.3.1\",\n  \"description\": \"快速实现你的全景开发需求,全景看房、全景街景、全景景点\",\n  \"keywords\": ["
  },
  {
    "path": "packages/vr360-core/scripts/build.ts",
    "chars": 265,
    "preview": "import {buildUtils} from '@nicepkg/vr360-shared'\nimport {minifyConfig, unMinifyConfig, packagePath} from '../vite.config"
  },
  {
    "path": "packages/vr360-core/src/helper.ts",
    "chars": 9628,
    "preview": "/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n"
  },
  {
    "path": "packages/vr360-core/src/index.ts",
    "chars": 74,
    "preview": "export * from './vr360'\nexport * from './types'\nexport * from './manager'\n"
  },
  {
    "path": "packages/vr360-core/src/manager/index.ts",
    "chars": 501,
    "preview": "export * from './space'\nexport * from './tip'\n\nexport type ConfigModel = {\n  id: string\n}\n\nexport type ConfigModelManage"
  },
  {
    "path": "packages/vr360-core/src/manager/space.ts",
    "chars": 6989,
    "preview": "/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport * as THREE from 'three'\nimport type {TextureCacheLoader} f"
  },
  {
    "path": "packages/vr360-core/src/manager/tip.ts",
    "chars": 7689,
    "preview": "import * as THREE from 'three'\nimport type {TextureCacheLoader, ThreeObjectDispatchEvent} from '../helper'\nimport {updat"
  },
  {
    "path": "packages/vr360-core/src/types.ts",
    "chars": 2524,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type {ConfigModel} from './manager'\nimport type {SpaceMa"
  },
  {
    "path": "packages/vr360-core/src/vr360.ts",
    "chars": 10593,
    "preview": "/* eslint-disable unicorn/no-array-callback-reference */\n/* eslint-disable @typescript-eslint/ban-ts-comment */\n/* eslin"
  },
  {
    "path": "packages/vr360-core/test/index.ts",
    "chars": 182,
    "preview": "import {polyfillFetch} from '@nicepkg/vr360-shared/test-utils'\nexport * from '@nicepkg/vr360-shared/test-utils'\nexport *"
  },
  {
    "path": "packages/vr360-core/test/setup.ts",
    "chars": 384,
    "preview": "import {polyfillFetch, polyfillPointerEvents} from '@nicepkg/vr360-shared/test-utils'\nimport {setupVueSwitch} from '@nic"
  },
  {
    "path": "packages/vr360-core/tsconfig.json",
    "chars": 389,
    "preview": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"type"
  },
  {
    "path": "packages/vr360-core/vite.config.ts",
    "chars": 794,
    "preview": "/// <reference types=\"vitest\" />\nimport type {UserConfig} from 'vite'\nimport {defineConfig} from 'vite'\nimport {buildUti"
  },
  {
    "path": "packages/vr360-shared/CHANGELOG.md",
    "chars": 1410,
    "preview": "# 0.3.0 (2022-10-08)\n\n### Bug Fixes\n\n- **@nicepkg/vr360-core:** 修复移动端的提示移动 bug ([7090a98](https://github.com/nicepkg/vr3"
  },
  {
    "path": "packages/vr360-shared/README.md",
    "chars": 38,
    "preview": "# Vr360-shared\n\n内部实用功能的共享包,包括构建和测试功能。\n"
  },
  {
    "path": "packages/vr360-shared/build.config.ts",
    "chars": 316,
    "preview": "import {defineBuildConfig} from 'unbuild'\n\nexport default defineBuildConfig({\n  entries: ['src/index', 'src/test-utils',"
  },
  {
    "path": "packages/vr360-shared/package.json",
    "chars": 3484,
    "preview": "{\n  \"name\": \"@nicepkg/vr360-shared\",\n  \"version\": \"0.3.1\",\n  \"description\": \"A shared package of internal utility functi"
  },
  {
    "path": "packages/vr360-shared/src/build-utils/build-script.util.ts",
    "chars": 3232,
    "preview": "import type {InlineConfig, UserConfig, BuildOptions as ViteBuildOptions} from 'vite'\nimport {build as viteBuild} from 'v"
  },
  {
    "path": "packages/vr360-shared/src/build-utils/index.ts",
    "chars": 78,
    "preview": "export * from './build-script.util'\nexport * from './vite-config-common.util'\n"
  },
  {
    "path": "packages/vr360-shared/src/build-utils/vite-config-common.util.ts",
    "chars": 3359,
    "preview": "/* eslint-disable @typescript-eslint/no-var-requires */\n/// <reference types=\"vitest\" />\nimport type {InlineConfig, Libr"
  },
  {
    "path": "packages/vr360-shared/src/index.ts",
    "chars": 787,
    "preview": "/* eslint-disable unicorn/prefer-export-from */\n\nimport chalk from 'chalk'\nimport * as msw from 'msw'\nimport * as globby"
  },
  {
    "path": "packages/vr360-shared/src/test-react-utils.ts",
    "chars": 71,
    "preview": "// this file will build as a bundle\nexport * from './test-utils/react'\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/helper.util.ts",
    "chars": 666,
    "preview": "// Like `until` but works off of any assertion, not application code.\nexport const retry = (assertion: () => void, {inte"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/index.ts",
    "chars": 186,
    "preview": "// this file will build as a bundle\nexport * from './helper.util'\nexport * from './mock-server.util'\nexport * from './po"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/mock-global-api.ts",
    "chars": 320,
    "preview": "export function mockGlobalApi() {\n  // Mock matchMedia\n  vi.stubGlobal('matchMedia', (query: string) => ({\n    matches: "
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/mock-server.util.ts",
    "chars": 2454,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * Network mocking with MSW.\n * Import this helper into the "
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/polyfill-fetch.util.ts",
    "chars": 133,
    "preview": "import nodeFetch from 'node-fetch'\n\nexport const polyfillFetch = () => {\n  // @ts-expect-error override\n  window.fetch ="
  },
  {
    "path": "packages/vr360-shared/src/test-utils/common/polyfill-pointer-events.util.ts",
    "chars": 552,
    "preview": "export const polyfillPointerEvents = () => {\n  /* eslint-disable @typescript-eslint/no-explicit-any */\n  // polyfill for"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/react/index.ts",
    "chars": 338,
    "preview": "/* eslint-disable unicorn/prefer-export-from */\n// this file will build as a bundle\n// all of react utils\nimport * as te"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/react/react-helper.util.ts",
    "chars": 290,
    "preview": "import {mockGlobalApi} from '../common/mock-global-api'\nimport createFetchMock from 'vitest-fetch-mock'\n\nexport function"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/react/react-mount.util.ts",
    "chars": 107,
    "preview": "/* 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",
    "chars": 128,
    "preview": "// this file will build as a bundle\n// all of vue-demi utils\nexport * from './vue-helper.util'\nexport * from './vue-moun"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/vue/vue-helper.util.ts",
    "chars": 1477,
    "preview": "/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils/vue/vue-mount.util.ts",
    "chars": 2534,
    "preview": "/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n"
  },
  {
    "path": "packages/vr360-shared/src/test-utils.ts",
    "chars": 72,
    "preview": "// this file will build as a bundle\nexport * from './test-utils/common'\n"
  },
  {
    "path": "packages/vr360-shared/src/test-vue-utils.ts",
    "chars": 69,
    "preview": "// this file will build as a bundle\nexport * from './test-utils/vue'\n"
  },
  {
    "path": "packages/vr360-shared/test-react-utils.d.ts",
    "chars": 40,
    "preview": "export * from './dist/test-react-utils'\n"
  },
  {
    "path": "packages/vr360-shared/test-utils.d.ts",
    "chars": 34,
    "preview": "export * from './dist/test-utils'\n"
  },
  {
    "path": "packages/vr360-shared/test-vue-utils.d.ts",
    "chars": 38,
    "preview": "export * from './dist/test-vue-utils'\n"
  },
  {
    "path": "packages/vr360-shared/tsconfig.json",
    "chars": 389,
    "preview": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"type"
  },
  {
    "path": "packages/vr360-shared/types/global.d.ts",
    "chars": 449,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\ndeclare global {\n  type Writable<T> = {\n    -readonly [P in key"
  },
  {
    "path": "packages/vr360-ui/README.md",
    "chars": 6,
    "preview": "# 开发中\n"
  },
  {
    "path": "packages/vr360-ui-react/README.md",
    "chars": 6,
    "preview": "# 开发中\n"
  },
  {
    "path": "packages/vr360-ui-vue2/README.md",
    "chars": 6,
    "preview": "# 开发中\n"
  },
  {
    "path": "packages/vr360-ui-vue3/README.md",
    "chars": 6,
    "preview": "# 开发中\n"
  },
  {
    "path": "playgrounds/react/index.html",
    "chars": 852,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>R"
  },
  {
    "path": "playgrounds/react/package.json",
    "chars": 688,
    "preview": "{\n  \"name\": \"playground-react\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"concurrently -r -k -g 'npm:build:deps' 'np"
  },
  {
    "path": "playgrounds/react/src/Example.tsx",
    "chars": 99,
    "preview": "function Example() {\n  return <div className=\"bg-gray-100\">Example</div>\n}\n\nexport default Example\n"
  },
  {
    "path": "playgrounds/react/src/main.tsx",
    "chars": 258,
    "preview": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport Example from './Example'\n\nimport '@unocss/reset/tailwi"
  },
  {
    "path": "playgrounds/react/tsconfig.json",
    "chars": 354,
    "preview": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"jsx\""
  },
  {
    "path": "playgrounds/react/vite.config.ts",
    "chars": 441,
    "preview": "import {defineConfig} from 'vite'\nimport path from 'node:path'\nimport viteReact from '@vitejs/plugin-react'\nimport viteU"
  },
  {
    "path": "playgrounds/vue2/index.html",
    "chars": 850,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>V"
  },
  {
    "path": "playgrounds/vue2/package.json",
    "chars": 717,
    "preview": "{\n  \"name\": \"playground-vue2\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"concurrently -r -k -g 'npm:build:deps' 'npm"
  },
  {
    "path": "playgrounds/vue2/src/App.vue",
    "chars": 3538,
    "preview": "<template>\n  <div class=\"relative w-100vw h-100vh overflow-hidden flex flex-col\">\n    <div\n      ref=\"tipRef\"\n      clas"
  },
  {
    "path": "playgrounds/vue2/src/main.ts",
    "chars": 277,
    "preview": "import Vue from 'vue'\nimport App from './App.vue'\nimport VueCompositionAPI from '@vue/composition-api'\n\n// css\nimport 'u"
  },
  {
    "path": "playgrounds/vue2/tsconfig.json",
    "chars": 493,
    "preview": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"type"
  },
  {
    "path": "playgrounds/vue2/types/module.d.ts",
    "chars": 274,
    "preview": "declare module '*.vue' {\n  import type {VueConstructor} from 'vue'\n  const component: VueConstructor\n  export default co"
  },
  {
    "path": "playgrounds/vue2/unocss.config.ts",
    "chars": 791,
    "preview": "import {\n  defineConfig,\n  presetAttributify,\n  presetIcons,\n  presetUno,\n  transformerDirectives,\n  transformerVariantG"
  },
  {
    "path": "playgrounds/vue2/vite.config.ts",
    "chars": 973,
    "preview": "import path from 'node:path'\nimport {defineConfig} from 'vite'\nimport {createVuePlugin} from 'vite-plugin-vue2'\nimport S"
  },
  {
    "path": "playgrounds/vue3/index.html",
    "chars": 850,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <link rel=\"icon\" href=\"/favicon.ico\" />\n  <title>V"
  },
  {
    "path": "playgrounds/vue3/package.json",
    "chars": 669,
    "preview": "{\n  \"name\": \"playground-vue3\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"concurrently -r -k -g 'npm:build:deps' 'npm"
  },
  {
    "path": "playgrounds/vue3/src/App.vue",
    "chars": 3297,
    "preview": "<template>\n  <div class=\"w-100vw h-100vh\">\n    <Editor>\n      <div class=\"relative w-full h-full overflow-hidden flex fl"
  },
  {
    "path": "playgrounds/vue3/src/ContextMenu.vue",
    "chars": 2146,
    "preview": "<template>\n  <div\n    v-show=\"showContextMenu\"\n    ref=\"contextMenuRef\"\n    class=\"vr360-context-menu absolute z-999 tex"
  },
  {
    "path": "playgrounds/vue3/src/Editor.vue",
    "chars": 918,
    "preview": "<template>\n  <div class=\"vr360-editor relative w-full h-full overflow-hidden flex flex-col\">\n    <div class=\"vr360-edito"
  },
  {
    "path": "playgrounds/vue3/src/EditorHotPointManager.vue",
    "chars": 161,
    "preview": "<template>\n  <div class=\"editor-hot-point-manager w-full h-full flex flex-col\">传送白点</div>\n</template>\n\n<script setup lan"
  },
  {
    "path": "playgrounds/vue3/src/EditorLeftBar.vue",
    "chars": 1476,
    "preview": "<template>\n  <div class=\"vr360-editor-left-bar w-full h-full flex\">\n    <div class=\"vr360-editor-left-bar-menu-list h-fu"
  },
  {
    "path": "playgrounds/vue3/src/EditorSceneManager.vue",
    "chars": 2566,
    "preview": "<template>\n  <div class=\"editor-scene-manager w-full h-full flex flex-col\">\n    <input\n      v-model=\"realseeVrUrl\"\n    "
  },
  {
    "path": "playgrounds/vue3/src/EditorSettings.vue",
    "chars": 150,
    "preview": "<template>\n  <div class=\"editor-settings w-full h-full flex flex-col\">设置</div>\n</template>\n\n<script setup lang=\"ts\"></sc"
  },
  {
    "path": "playgrounds/vue3/src/EditorTipsManager.vue",
    "chars": 156,
    "preview": "<template>\n  <div class=\"editor-tips-manager w-full h-full flex flex-col\">文本标签</div>\n</template>\n\n<script setup lang=\"ts"
  },
  {
    "path": "playgrounds/vue3/src/EditorTopBar.vue",
    "chars": 215,
    "preview": "<template>\n  <div class=\"vr360-editor-top-bar w-full h-full flex items-center\">\n    <span class=\"text-2xl font-bold ml-8"
  },
  {
    "path": "playgrounds/vue3/src/Icons.tsx",
    "chars": 1475,
    "preview": "/* eslint-disable react/no-unknown-property */\nexport const DeleteIcon = () => (\n  <svg xmlns=\"http://www.w3.org/2000/sv"
  },
  {
    "path": "playgrounds/vue3/src/helper.ts",
    "chars": 5865,
    "preview": "/* eslint-disable unicorn/prefer-add-event-listener */\nimport type {CubeSpaceTextureUrls} from '@nicepkg/vr360-core'\nimp"
  },
  {
    "path": "playgrounds/vue3/src/main.ts",
    "chars": 216,
    "preview": "import {createApp} from 'vue'\nimport App from './App.vue'\n\n// css\nimport 'uno.css'\nimport '@unocss/reset/tailwind.css'\n\n"
  },
  {
    "path": "playgrounds/vue3/src/useVr360.ts",
    "chars": 2580,
    "preview": "/* eslint-disable @typescript-eslint/no-unnecessary-type-arguments */\n/* eslint-disable @typescript-eslint/no-unsafe-mem"
  },
  {
    "path": "playgrounds/vue3/tsconfig.json",
    "chars": 315,
    "preview": "{\n  \"extends\": \"../../tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist\",\n    \"type"
  },
  {
    "path": "playgrounds/vue3/types/module.d.ts",
    "chars": 409,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\ndeclare module '*.vue' {\n  import type {DefineComponent} from 'v"
  },
  {
    "path": "playgrounds/vue3/unocss.config.ts",
    "chars": 791,
    "preview": "import {\n  defineConfig,\n  presetAttributify,\n  presetIcons,\n  presetUno,\n  transformerDirectives,\n  transformerVariantG"
  },
  {
    "path": "playgrounds/vue3/vite.config.ts",
    "chars": 1037,
    "preview": "import path from 'node:path'\nimport type {UserConfig} from 'vite'\nimport {defineConfig, loadEnv} from 'vite'\nimport Vue "
  },
  {
    "path": "pnpm-workspace.yaml",
    "chars": 85,
    "preview": "packages:\n  - 'packages/**'\n  - 'examples/**'\n  - 'playgrounds/**'\n  - '!**/test/**'\n"
  },
  {
    "path": "prettier.config.js",
    "chars": 296,
    "preview": "// @ts-check\n\nmodule.exports = /** @type { import ('prettier').RequiredOptions }  */ ({\n  printWidth: 120,\n  semi: false"
  },
  {
    "path": "scripts/build.ts",
    "chars": 63,
    "preview": "import {build, copyFiles} from './utils'\n\ncopyFiles()\n\nbuild()\n"
  },
  {
    "path": "scripts/check-update.ts",
    "chars": 536,
    "preview": "import * as ncu from 'npm-check-updates'\nimport fs from 'node:fs'\nimport {packagesPaths, pathResolve} from './utils'\n\nas"
  },
  {
    "path": "scripts/release.ts",
    "chars": 43,
    "preview": "import {release} from './utils'\n\nrelease()\n"
  },
  {
    "path": "scripts/utils.ts",
    "chars": 2520,
    "preview": "import {copyFileSync, readFileSync, existsSync} from 'node:fs'\nimport {execSync} from 'node:child_process'\nimport path f"
  },
  {
    "path": "stylelint.config.js",
    "chars": 2180,
    "preview": "//@ts-check\n\nmodule.exports = /** @type { Partial<import('stylelint').Config> } */ ({\n  customSyntax: 'postcss-less',\n  "
  },
  {
    "path": "textures.json",
    "chars": 3648,
    "preview": "{\n  \"hotpot\": \"https://m.360buyimg.com/babel/jfs/t1/125314/12/31594/6260/6339b149E14068522/5c0d35a3e149936a.png\",\n  \"bei"
  },
  {
    "path": "tsconfig-base.json",
    "chars": 1044,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"module\": \"esnext\",\n    \"typeRoots\": [\"./node_modules/@types\", \"types\""
  },
  {
    "path": "tsconfig.json",
    "chars": 236,
    "preview": "{\n  \"extends\": \"./tsconfig-base.json\",\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\"\n  },\n  \"include\": [\"./scripts/**/*\", \""
  },
  {
    "path": "turbo.json",
    "chars": 708,
    "preview": "{\n  \"$schema\": \"https://turborepo.org/schema.json\",\n  \"baseBranch\": \"origin/master\",\n  \"pipeline\": {\n    \"@nicepkg/vr360"
  }
]

About this extraction

This page contains the full source code of the nicepkg/vr360 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 193 files (241.0 KB), approximately 79.8k tokens, and a symbol index with 150 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!