Compare commits
5 Commits
feature/co
...
main
Author | SHA1 | Date | |
---|---|---|---|
46b08a46b5 | |||
![]() |
184eba34e1 | ||
![]() |
b76affe2f7 | ||
![]() |
5c9c5ba9e0 | ||
![]() |
2a65449959 |
246
docs/ar-xr.md
Normal file
246
docs/ar-xr.md
Normal file
@ -0,0 +1,246 @@
|
||||
# [開発]AR/XRの利用ガイドライン
|
||||
|
||||
## AR/XEについて定義
|
||||
|
||||
空間の概念
|
||||
|
||||
* 空間の中に仮想オブジェクトが配置
|
||||
* ユーザーが空間内を自由に移動または視点操作できる
|
||||
|
||||
## 利用例と対応技術
|
||||
|
||||
1. **GPS、カメラなどのセンサ情報と連動**
|
||||
- 仮想オブジェクトを現実に重ねる
|
||||
- 例:ポケモンGOのように位置情報と連動して表示される仮想キャラクター。
|
||||
2. **ユーザーとのインタラクション**
|
||||
- タップ、ジェスチャー、視線、音声などを通じた操作が可能。
|
||||
- 例:展覧会アプリでアート作品に近づくと詳細情報が表示される。
|
||||
3. **空間認識 / 環境マッピング**
|
||||
- ARKitやARCoreなどで現実の床・壁・物体を認識して、仮想オブジェクトを自然に配置。
|
||||
- 例:家具ARアプリが部屋の床にテーブルを正確に置く
|
||||
4. **空間を活かしたUX設計**
|
||||
- 単なるUI配置ではなく、「空間に存在する意味」を考慮した設計。
|
||||
- 例:不動産カタログがモデルルーム内を歩き回るUXで部屋の広さや窓の位置を体験できる。
|
||||
|
||||
| 利用例 | 空間性 | 使用技術 | 特記事項 |
|
||||
| ---------------- | -------- | ----------------- | -------------------------------- |
|
||||
| 歩行型Web展覧会 | あり | WebXR + A-Frame等 | 空間移動・近接操作あり |
|
||||
| ポケモンGO | あり | ARKit/ARCore | GPS連動・カメラ合成あり |
|
||||
| 商品カタログ(3D) | 条件付き | WebAR / Unity等 | 操作・空間性の有無で評価分かれる |
|
||||
|
||||
### 類似技術(ARではない例)
|
||||
|
||||
| 例 | 理由 |
|
||||
| -------------------------------------------------- | -------------------------------------------------------- |
|
||||
| カメラ映像の上にキャラクターを表示するだけのアプリ | 空間と連動していないため「ただの合成」 |
|
||||
| 背景がカメラ映像の3Dゲーム | 現実との関係がないのでARではない |
|
||||
| Instagramのエフェクトの一部(目に星を重ねるなど) | 空間ARというよりはフェイストラッキング or エフェクト処理 |
|
||||
|
||||
## スタック
|
||||
|
||||
### フロントエンド(AR体験を提供する側)
|
||||
|
||||
| デバイス | 技術・ライブラリ |
|
||||
| -------- | -------------------- |
|
||||
| iOS | ARKit(Apple純正) |
|
||||
| Android | ARCore(Google純正) |
|
||||
|
||||
**(Web向けAR)**
|
||||
|
||||
| ライブラリ | 特徴 | 難易度 | 拡張性 | 主な用途 |
|
||||
| ---------- | --------------------------------------------- | ------ | -------- | -------------------- |
|
||||
| A-Frame | HTMLベースで簡単に3Dコンテンツ。初心者向き | 低 | 中 | プロトタイピングなど |
|
||||
| Three.js | 高機能3Dライブラリ。低レベル制御が可能 | 中〜高 | 高 | 複雑な3D表現 |
|
||||
| AR.js | WebAR用ライブラリ。Three.js/A-Frameと併用可能 | 中 | 中 | マーカーAR、GPS AR |
|
||||
| 8thWall | 高精度WebARプラットフォーム(有料) | 低 | 高 | 実用ARアプリ |
|
||||
| WebXR API | ブラウザ標準のAR/VR API | 高 | 非常に高 | 本格的なAR/VRアプリ |
|
||||
|
||||
|
||||
|
||||
* iOSの制約ポイント(2025年)
|
||||
* カメラアクセス Safariのみが原則的に対応(PWAやChromeは制限あり)
|
||||
* WebXR API:サポートされていない(2025年現在)
|
||||
* WebRTC/MediaDevices getUserMediaはOK。(AR.jsの一部機能が動作不安定)
|
||||
|
||||
その他の仕様する技術について
|
||||
|
||||
| Flutter | |
|
||||
| ------- | --------------------------------------------- |
|
||||
| Unity | AR Foundation(ARKit + ARCoreの抽象化) |
|
||||
| Flutter | ar_flutter_plugin(公式ARSDKのFlutterラッパー) |
|
||||
| | |
|
||||
|
||||
|
||||
### バックエンド
|
||||
|
||||
必要な場合のみ実装
|
||||
|
||||
*クラウド処理
|
||||
* 画像認識AI
|
||||
* 地図情報
|
||||
* マルチユーザー同期
|
||||
|
||||
WebSocketやMQTTでリアルタイム連携する
|
||||
|
||||
|
||||
### A-Frame(エーフレーム)
|
||||
|
||||
Webブラウザ上で3DコンテンツやVR/AR体験を作れるHTMLベースのフレームワーク
|
||||
JavaScriptが得意でなくても、**HTMLタグ感覚で簡単に使える**
|
||||
|
||||
* 公式チュートリアル: https://aframe.io/docs/master/introduction/
|
||||
|
||||
#### 使用方法について
|
||||
|
||||
A-FrameはCDNで使えます。
|
||||
HTMLの<head>に以下を追加する
|
||||
|
||||
```html
|
||||
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
||||
```
|
||||
|
||||
## Three.js + AR.js
|
||||
|
||||
Three.jsは WebGLを抽象化し、高機能な3D描画をJavaScriptで行える軽量ライブラリです。
|
||||
ARやVRだけでなくゲームやインタラクティブなグラフィック表現にも使われます
|
||||
|
||||
|
||||
- 公式: https://threejs.org/
|
||||
- GitHub: https://github.com/mrdoob/three.js/
|
||||
|
||||
|
||||
AR.jsは、Three.jsベースで動作する**WebARライブラリ**。
|
||||
マーカー認識や位置情報を使ったAR体験を提供。
|
||||
|
||||
- 公式: https://ar-js-org.github.io/AR.js-Docs/
|
||||
|
||||
#### 特徴
|
||||
|
||||
**Three.js**
|
||||
- カスタマイズ性が高く、細かい3D制御が可能
|
||||
- WebAR/VR対応にも拡張しやすい
|
||||
- A-Frameに比べて**記述量は多め**だが、**柔軟性が高い**
|
||||
|
||||
**AR.js**
|
||||
- モバイルブラウザでも軽快に動作(60fpsも実現可能)
|
||||
- **マーカーベースAR**(Hiroマーカーなど)と**位置情報ベースAR**の両方に対応
|
||||
- **A-Frameとも連携可能**
|
||||
|
||||
---
|
||||
|
||||
### 使用方法について(Three.js + AR.js)
|
||||
|
||||
Three.jsのシーンを作成し、AR.jsと連携してカメラ映像と重ねて表示します。
|
||||
主に以下のステップで構成されます:
|
||||
|
||||
1. カメラの取得(`ARjs.Context`)
|
||||
2. Three.jsのシーン、カメラ、レンダラーの準備
|
||||
3. マーカー検出(`ARjs.MarkerControls`)を使って3Dモデル表示
|
||||
|
||||
#### サンプルコード(マーカーベースAR)
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AR.js マーカーベース サンプル</title>
|
||||
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/AR-js-org/AR.js@3.3.2/aframe/build/aframe-ar.min.js"></script>
|
||||
<style>
|
||||
body { margin: 0; overflow: hidden; }
|
||||
#info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
color: white;
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 5px 10px;
|
||||
font-family: sans-serif;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="info">「Hiroマーカー」をカメラにかざしてください</div>
|
||||
|
||||
<a-scene embedded arjs="sourceType: webcam; debugUIEnabled: false;">
|
||||
<!-- マーカーが認識されたときに表示される青い箱 -->
|
||||
<a-marker preset="hiro">
|
||||
<a-box position="0 0.5 0" material="color: blue;" shadow></a-box>
|
||||
<a-text value="Hello AR!" position="-0.5 1 0" color="white"></a-text>
|
||||
</a-marker>
|
||||
|
||||
<!-- カメラ -->
|
||||
<a-entity camera></a-entity>
|
||||
</a-scene>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
* Webカメラ対応のPCまたはスマホのブラウザ
|
||||
*
|
||||
|
||||
## A-Frameの基本
|
||||
|
||||
### A-Frameの導入
|
||||
A-FrameはCDNで使えます。HTMLの`<head>`に以下を追加するだけ:
|
||||
|
||||
```html
|
||||
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
||||
```
|
||||
|
||||
| タグ | 説明 |
|
||||
| -------------- | ------------------------------------------ |
|
||||
| `<a-scene>` | A-Frameの世界のルート(必須) |
|
||||
| `<a-box>` | 箱を表示(位置、回転、色、サイズを指定可) |
|
||||
| `<a-sphere>` | 球体 |
|
||||
| `<a-cylinder>` | 円柱 |
|
||||
| `<a-plane>` | 平面。地面に使われることが多い |
|
||||
| `<a-text>` | テキスト表示 |
|
||||
| `<a-camera>` | 視点を定義。省略すると自動的に追加される |
|
||||
| `<a-light>` | 光源。明るさや影の描画に必要 |
|
||||
|
||||
|
||||
## サンプルコード
|
||||
|
||||
### A-Frameで作る歩行型Webページ
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>歩行型Webページ</title>
|
||||
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a-scene>
|
||||
<!-- 地面 -->
|
||||
<a-plane position="0 0 0" rotation="-90 0 0" width="50" height="50" color="#7BC8A4"></a-plane>
|
||||
|
||||
<!-- カメラとコントロール(WASDキーで歩行可能) -->
|
||||
<a-entity camera wasd-controls look-controls position="0 1.6 5"></a-entity>
|
||||
|
||||
<!-- 建物風のオブジェクト -->
|
||||
<a-box position="0 0.5 -5" depth="5" height="1" width="5" color="#4CC3D9"></a-box>
|
||||
<a-box position="10 0.5 -5" depth="5" height="1" width="5" color="#FFC65D"></a-box>
|
||||
<a-sphere position="-10 1 -5" radius="1" color="#EF2D5E"></a-sphere>
|
||||
</a-scene>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
#### 特徴
|
||||
- **マウス視点移動**:マウスで見回せます。
|
||||
- **WASDキーで歩行**:前後左右に移動できます。
|
||||
- **HTMLだけで構成**:JavaScriptなしでA-Frameがほとんど自動処理。
|
||||
|
||||
#### 移動の制御
|
||||
|
||||
- `wasd-controls`: W/A/S/Dキーで歩けるようにする
|
||||
- `look-controls`: マウスで視点を変える
|
||||
|
||||
#### 応用例
|
||||
|
||||
* [ホバーイベント:](../src\front\ar\afreame\aframe-hover.html)
|
4
docs/blockcheen.md
Normal file
4
docs/blockcheen.md
Normal file
@ -0,0 +1,4 @@
|
||||
# [技術][2025]ブロックチェーンの利用ガイドライン
|
||||
|
||||
サプライチェーンにブロックチェーンを導入することで
|
||||
トレーサビリティの確保、改ざん防止、透明性の向上といった効果が得られます
|
42
docs/front/ar/WebXR.html
Normal file
42
docs/front/ar/WebXR.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!-- index.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Simple WebXR AR</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.152.2/examples/jsm/webxr/ARButton.js"></script>
|
||||
</head>
|
||||
|
||||
<body style="margin: 0; overflow: hidden;">
|
||||
<script type="module">
|
||||
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.module.js';
|
||||
import { ARButton } from 'https://cdn.jsdelivr.net/npm/three@0.152.2/examples/jsm/webxr/ARButton.js';
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.xr.enabled = true;
|
||||
document.body.appendChild(renderer.domElement);
|
||||
document.body.appendChild(ARButton.createButton(renderer));
|
||||
|
||||
const geometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
|
||||
const material = new THREE.MeshNormalMaterial();
|
||||
const cube = new THREE.Mesh(geometry, material);
|
||||
cube.position.set(0, 0, -0.5);
|
||||
scene.add(cube);
|
||||
|
||||
function animate() {
|
||||
renderer.setAnimationLoop(() => {
|
||||
cube.rotation.y += 0.01;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
}
|
||||
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
74
docs/front/ar/WebXRCamera.html
Normal file
74
docs/front/ar/WebXRCamera.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>WebXR AR Sample</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="module">
|
||||
import * as THREE from 'https://cdn.skypack.dev/three@0.152.2';
|
||||
import { ARButton } from 'https://cdn.skypack.dev/three@0.152.2/examples/jsm/webxr/ARButton.js';
|
||||
|
||||
let camera, scene, renderer;
|
||||
let controller;
|
||||
|
||||
init();
|
||||
animate();
|
||||
|
||||
function init() {
|
||||
// Renderer
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.xr.enabled = true;
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
// ARボタンを追加(Hit-test機能必要)
|
||||
document.body.appendChild(
|
||||
ARButton.createButton(renderer, { requiredFeatures: ['hit-test'] })
|
||||
);
|
||||
|
||||
// SceneとCamera
|
||||
scene = new THREE.Scene();
|
||||
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
|
||||
|
||||
// 光源
|
||||
const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1);
|
||||
light.position.set(0.5, 1, 0.25);
|
||||
scene.add(light);
|
||||
|
||||
// 立方体を作成
|
||||
const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
|
||||
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
|
||||
const cube = new THREE.Mesh(geometry, material);
|
||||
cube.position.set(0, 0, -0.5); // カメラの前に表示
|
||||
scene.add(cube);
|
||||
|
||||
// コントローラ
|
||||
controller = renderer.xr.getController(0);
|
||||
scene.add(controller);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
renderer.setAnimationLoop(render);
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
21
docs/front/ar/aframe.html
Normal file
21
docs/front/ar/aframe.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>歩行型Webページ</title>
|
||||
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a-scene>
|
||||
<!-- 地面 -->
|
||||
<a-plane position="0 0 0" rotation="-90 0 0" width="50" height="50" color="#7BC8A4"></a-plane>
|
||||
|
||||
<!-- カメラとコントロール(WASDキーで歩行可能) -->
|
||||
<a-entity camera wasd-controls look-controls position="0 1.6 5"></a-entity>
|
||||
|
||||
<!-- 建物風のオブジェクト -->
|
||||
<a-box position="0 0.5 -5" depth="5" height="1" width="5" color="#4CC3D9"></a-box>
|
||||
<a-box position="10 0.5 -5" depth="5" height="1" width="5" color="#FFC65D"></a-box>
|
||||
<a-sphere position="-10 1 -5" radius="1" color="#EF2D5E"></a-sphere>
|
||||
</a-scene>
|
||||
</body>
|
||||
</html>
|
0
docs/front/assets/cloud-storage.js
Normal file
0
docs/front/assets/cloud-storage.js
Normal file
11
docs/front/assets/data/test_data_1.csv
Normal file
11
docs/front/assets/data/test_data_1.csv
Normal file
@ -0,0 +1,11 @@
|
||||
ID,Name,Age,Email
|
||||
1,User_1,44,user1@example.com
|
||||
2,User_2,20,user2@example.com
|
||||
3,User_3,67,user3@example.com
|
||||
4,User_4,49,user4@example.com
|
||||
5,User_5,56,user5@example.com
|
||||
6,User_6,63,user6@example.com
|
||||
7,User_7,31,user7@example.com
|
||||
8,User_8,67,user8@example.com
|
||||
9,User_9,41,user9@example.com
|
||||
10,User_10,30,user10@example.com
|
|
11
docs/front/assets/data/test_data_2.csv
Normal file
11
docs/front/assets/data/test_data_2.csv
Normal file
@ -0,0 +1,11 @@
|
||||
ID,Name,Age,Email
|
||||
1,User_1,37,user1@example.com
|
||||
2,User_2,30,user2@example.com
|
||||
3,User_3,63,user3@example.com
|
||||
4,User_4,42,user4@example.com
|
||||
5,User_5,31,user5@example.com
|
||||
6,User_6,63,user6@example.com
|
||||
7,User_7,28,user7@example.com
|
||||
8,User_8,20,user8@example.com
|
||||
9,User_9,42,user9@example.com
|
||||
10,User_10,69,user10@example.com
|
|
79
docs/front/assets/download.js
Normal file
79
docs/front/assets/download.js
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @description downloadボタンを押下したらCSVファイルをzip圧縮してダウンロードする
|
||||
*/
|
||||
|
||||
|
||||
// ダウンロードボタン
|
||||
const downloadButton = document.getElementById('download-button');
|
||||
const downloadButtonFile = document.getElementById('download-button-file');
|
||||
|
||||
// ダウンロードボタンを押下した時の処理を記載する
|
||||
downloadButton.addEventListener('click', async () => {
|
||||
// ダウンロードボタンを無効化する
|
||||
downloadButton.disabled = true;
|
||||
// ダウンロード処理を実行する
|
||||
try {
|
||||
const zip = new JSZip();
|
||||
// ZIPにファイルを追加
|
||||
zip.file("hello.txt", "Hello, this is a ZIP file!");
|
||||
zip.file("world.txt", "Hello, this is a ZIP file!");
|
||||
|
||||
// ZIPを生成してダウンロード
|
||||
const zipBlob = await zip.generateAsync({ type: "blob" });
|
||||
const a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(zipBlob);
|
||||
a.download = "example.zip";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
} catch (e) {
|
||||
// エラーが発生した場合
|
||||
console.error(e);
|
||||
alert('ダウンロードに失敗しました');
|
||||
} finally {
|
||||
// ダウンロードボタンを有効化する
|
||||
downloadButton.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
downloadButtonFile.addEventListener('click', async () => {
|
||||
// ダウンロードボタンを無効化する
|
||||
downloadButtonFile.disabled = true;
|
||||
try {
|
||||
const files = [
|
||||
{ name: "text1.csv", url: "assets/data/test_data_1.csv" },
|
||||
{ name: "text2.csv", url: "assets/data/test_data_2.csv" }
|
||||
];
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
// CSV ファイルを取得して ZIP に追加
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const response = await fetch(file.url);
|
||||
if (!response.ok) throw new Error(`Failed to fetch ${file.name}`);
|
||||
const text = await response.text();
|
||||
zip.file(file.name, text);
|
||||
})
|
||||
);
|
||||
|
||||
// ZIP を生成してダウンロード
|
||||
zip.generateAsync({ type: "blob" }).then((blob) => {
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = "files.zip";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}).catch(console.error)
|
||||
} catch (e) {
|
||||
// エラーが発生した場合
|
||||
console.error(e);
|
||||
alert('ダウンロードに失敗しました');
|
||||
}
|
||||
finally {
|
||||
// ダウンロードボタンを有効化する
|
||||
downloadButtonFile.disabled = false;
|
||||
}
|
||||
});
|
72
docs/front/assets/downloadFromServer.js
Normal file
72
docs/front/assets/downloadFromServer.js
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @description downloadボタンを押下したらCSVファイルをzip圧縮してダウンロードする
|
||||
*/
|
||||
|
||||
|
||||
// ダウンロードボタン
|
||||
const downloadButtonServer = document.getElementById('download-button-server');
|
||||
|
||||
// ダウンロードボタンを押下した時の処理を記載する
|
||||
downloadButtonServer.addEventListener('click', async () => {
|
||||
// Blobで取得する方法
|
||||
console.log('downloadButtonServer');
|
||||
// ダウンロードボタンを無効化する
|
||||
downloadButtonServer.disabled = true;
|
||||
// ダウンロード処理を実行する
|
||||
try {
|
||||
// localhost:3000/downdload にリクエストを送る
|
||||
const response = await fetch('http://localhost:3000/downdload');
|
||||
if (!response.ok) throw new Error('Failed to fetch');
|
||||
|
||||
// ZIPファイルを取得
|
||||
const zipBlob = await response.blob();
|
||||
const a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(zipBlob);
|
||||
a.download = "serverFile.zip";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
} catch (e) {
|
||||
// エラーが発生した場合
|
||||
console.error(e);
|
||||
alert('ダウンロードに失敗しました');
|
||||
} finally {
|
||||
// ダウンロードボタンを有効化する
|
||||
downloadButtonServer.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// ダウンロードボタン(アップロード)
|
||||
const downloadButtonUpload = document.getElementById('download-button-upload');
|
||||
|
||||
// ダウンロードボタンを押下した時の処理を記載する
|
||||
downloadButtonUpload.addEventListener('click', async () => {
|
||||
console.log('downloadButtonUpload');
|
||||
// ダウンロードボタンを無効化する
|
||||
downloadButtonUpload.disabled = true;
|
||||
// サーバーにアップロード処理APIを送信する
|
||||
try {
|
||||
// localhost:3000/generate-zip にリクエストを送る
|
||||
const response = await fetch('http://localhost:3000/generate-zip');
|
||||
if (!response.ok) throw new Error('Failed to fetch');
|
||||
|
||||
// レスポンスからURLを取得
|
||||
const { url } = await response.json();
|
||||
// 取得したURLを開く
|
||||
window.open(url);
|
||||
|
||||
} catch (e) {
|
||||
// エラーが発生した場合
|
||||
console.error(e);
|
||||
alert('ダウンロードに失敗しました');
|
||||
} finally {
|
||||
// ダウンロードボタンを有効化する
|
||||
downloadButtonUpload.disabled = false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
5
docs/front/assets/index.js
Normal file
5
docs/front/assets/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
// Message
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.getElementById("message").textContent = "Hello, JavaScript!";
|
||||
});
|
13
docs/front/assets/lib/jszip.min.js
vendored
Normal file
13
docs/front/assets/lib/jszip.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
21
docs/front/download.html
Normal file
21
docs/front/download.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
||||
<title>Download</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>JavaScript Develop Download To Front Side</h1>
|
||||
<p >Zip File Download</p>
|
||||
<button id="download-button">Zip Download(From Content)</button>
|
||||
<button id="download-button-file">Zip Download(From File)</button>
|
||||
<p >Server </p>
|
||||
<button id="download-button-server">Zip Download(From Server)</button>
|
||||
<button id="download-button-upload">Zip Download(upload)</button>
|
||||
<p >Google Storage Link</p>
|
||||
<script src="assets/download.js"></script>
|
||||
<script src="assets/downloadFromServer.js"></script>
|
||||
</body>
|
||||
</html>
|
15
docs/front/gcs.html
Normal file
15
docs/front/gcs.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
||||
<title>Google Cloud Storage</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>JavaScript Develop Download To Google Cloud Storage</h1>
|
||||
<p >Zip File Download</p>
|
||||
<button id="download-button">Zip Download(GCS)</button>
|
||||
<script src="assets/cloud-storage.js"></script>
|
||||
</body>
|
||||
</html>
|
13
docs/front/index.html
Normal file
13
docs/front/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>JavaScript Develop Debug</h1>
|
||||
<p id="message">Open the console to see the output</p>
|
||||
<script src="assets/index.js"></script>
|
||||
</body>
|
||||
</html>
|
462
docs/plantuml.md
Normal file
462
docs/plantuml.md
Normal file
@ -0,0 +1,462 @@
|
||||
# 【PlantUML】概要及び基本的な使い方
|
||||
|
||||
- [【PlantUML】概要及び基本的な使い方](#plantuml概要及び基本的な使い方)
|
||||
- [各図形の基本的な書き方](#各図形の基本的な書き方)
|
||||
- [フローチャート図](#フローチャート図)
|
||||
- [シーケンス図](#シーケンス図)
|
||||
- [クラス図](#クラス図)
|
||||
- [ER図](#er図)
|
||||
- [リレーションシンボルの意味](#リレーションシンボルの意味)
|
||||
- [アーキテクチャ図(簡易的な表現)](#アーキテクチャ図簡易的な表現)
|
||||
- [WBS](#wbs)
|
||||
- [業務フロー図](#業務フロー図)
|
||||
- [スキンやスタイルの変更(PlantUML)](#スキンやスタイルの変更plantuml)
|
||||
- [全体をグレースケールにする](#全体をグレースケールにする)
|
||||
- [特定のアクティビティに色を付ける](#特定のアクティビティに色を付ける)
|
||||
- [VSCodeでのPlantUMLスニペット設定](#vscodeでのplantumlスニペット設定)
|
||||
- [関連リンク](#関連リンク)
|
||||
|
||||
|
||||
## 各図形の基本的な書き方
|
||||
|
||||
### フローチャート図
|
||||
|
||||
````
|
||||
```plantuml
|
||||
@startuml
|
||||
start
|
||||
:Christmas;
|
||||
:Go shopping;
|
||||
if (Let me think) then (One)
|
||||
:Laptop;
|
||||
elseif (Two)
|
||||
:iPhone;
|
||||
else (Three)
|
||||
:Car;
|
||||
endif
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
````
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
start
|
||||
:Christmas;
|
||||
:Go shopping;
|
||||
if (Let me think) then (One)
|
||||
:Laptop;
|
||||
elseif (Two)
|
||||
:iPhone;
|
||||
else (Three)
|
||||
:Car;
|
||||
endif
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
### シーケンス図
|
||||
|
||||
````
|
||||
```plantuml
|
||||
@startuml
|
||||
actor User
|
||||
participant Front
|
||||
participant Server
|
||||
|
||||
User ->> Front : URLをリンクする
|
||||
Front -->> User : 一覧画面を表示する
|
||||
User ->> Front : 検索する
|
||||
loop 対象商品
|
||||
Front ->> Server : 商品情報を取得する
|
||||
Server -->> Front : レスポンス
|
||||
end
|
||||
Front -->> User : 検索結果を表示する
|
||||
note right of Front
|
||||
Product Find\nsequence
|
||||
end note
|
||||
@enduml
|
||||
```
|
||||
````
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
actor User
|
||||
participant Front
|
||||
participant Server
|
||||
|
||||
User ->> Front : URLをリンクする
|
||||
Front -->> User : 一覧画面を表示する
|
||||
User ->> Front : 検索する
|
||||
loop 対象商品
|
||||
Front ->> Server : 商品情報を取得する
|
||||
Server -->> Front : レスポンス
|
||||
end
|
||||
Front -->> User : 検索結果を表示する
|
||||
note right of Front
|
||||
Product Find\nsequence
|
||||
end note
|
||||
@enduml
|
||||
```
|
||||
|
||||
APIのサンプル例
|
||||
````
|
||||
```plantuml
|
||||
@startuml
|
||||
participant App
|
||||
participant API
|
||||
|
||||
App ->> API: request
|
||||
alt OK
|
||||
API -->> App: 200
|
||||
else error
|
||||
API -->> App: 400
|
||||
end
|
||||
@enduml
|
||||
```
|
||||
````
|
||||
|
||||
### クラス図
|
||||
````
|
||||
```plantuml
|
||||
@startuml
|
||||
class Animal {
|
||||
+int age
|
||||
+String gender
|
||||
+mate()
|
||||
}
|
||||
|
||||
class Duck {
|
||||
+String beakColor
|
||||
+swim()
|
||||
+quack()
|
||||
}
|
||||
|
||||
Animal <|-- Duck
|
||||
@enduml
|
||||
```
|
||||
````
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
class Animal {
|
||||
+int age
|
||||
+String gender
|
||||
+mate()
|
||||
}
|
||||
|
||||
class Duck {
|
||||
+String beakColor
|
||||
+swim()
|
||||
+quack()
|
||||
}
|
||||
|
||||
Animal <|-- Duck
|
||||
@enduml
|
||||
```
|
||||
### ER図
|
||||
|
||||
データベース設計に特化したツールみたいには細かく設定ができない
|
||||
|
||||
* 関係性で1なのか0なのかなどはカーディナリティで表現できない
|
||||
* 外部キーをfield単位で結びつけれない
|
||||
|
||||
````
|
||||
## 基本的な構文
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
entity "エンティティ名" as 物理名 {
|
||||
+ フィールド名 : データ型 <<制約>> -- 論理名(コメント)
|
||||
}
|
||||
@enduml
|
||||
```
|
||||
````
|
||||
|
||||
#### リレーションシンボルの意味
|
||||
|
||||
* `||--||` : 1対1 (One to One)
|
||||
* `}o--||` : 多対1 (Many to One)
|
||||
* `o{--||` : 1対多 (One to Many)
|
||||
* `}o--o{` : 多対多 (Many to Many)
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
entity "USER" as ユーザー {
|
||||
+ user_id : int <<PK>> -- ユーザーID
|
||||
--
|
||||
name : string -- 名前
|
||||
email : string -- メールアドレス
|
||||
}
|
||||
|
||||
entity "ORDER" as 注文 {
|
||||
+ order_id : int <<PK>> -- 注文ID
|
||||
--
|
||||
user_id : int <<FK>> -- ユーザーID (FK)
|
||||
total_price : float -- 合計金額
|
||||
order_date : date -- 注文日
|
||||
}
|
||||
|
||||
ユーザー ||--o{ 注文 : "places"
|
||||
@enduml
|
||||
```
|
||||
|
||||
### アーキテクチャ図(簡易的な表現)
|
||||
````
|
||||
```plantuml
|
||||
@startuml
|
||||
package "Public" {
|
||||
package "Private" {
|
||||
[Server] --> [Database]
|
||||
}
|
||||
}
|
||||
@enduml
|
||||
```
|
||||
````
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
package "Public" {
|
||||
package "Private" {
|
||||
[Server] --> [Database]
|
||||
}
|
||||
}
|
||||
@enduml
|
||||
```
|
||||
|
||||
(図形を読み込む)
|
||||
|
||||
https://github.com/davidholsgrove/gcp-icons-for-plantuml/tree/master/dist
|
||||
|
||||
````
|
||||
```plantuml
|
||||
@startuml
|
||||
!define GCPPuml https://raw.githubusercontent.com/davidholsgrove/gcp-icons-for-plantuml/master/dist
|
||||
!includeurl GCPPuml/GCPCommon.puml
|
||||
!includeurl GCPPuml/DeveloperTools/all.puml
|
||||
!includeurl GCPPuml/Storage/CloudStorage.puml
|
||||
!includeurl GCPPuml/Compute/CloudFunctions.puml
|
||||
|
||||
|
||||
actor "Person" as personAlias
|
||||
CloudToolsforVisualStudio(desktopAlias, "Label", "Technology", "Optional Description")
|
||||
CloudStorage(storageAlias, "Label", "Technology", "Optional Description")
|
||||
CloudFunctions(functionsAlias, "Label", "Function", "Optional Description")
|
||||
|
||||
personAlias --> desktopAlias
|
||||
personAlias --> functionsAlias
|
||||
desktopAlias --> storageAlias
|
||||
|
||||
@enduml
|
||||
```
|
||||
````
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
!define GCPPuml https://raw.githubusercontent.com/davidholsgrove/gcp-icons-for-plantuml/master/dist
|
||||
!includeurl GCPPuml/GCPCommon.puml
|
||||
!includeurl GCPPuml/DeveloperTools/all.puml
|
||||
!includeurl GCPPuml/Storage/CloudStorage.puml
|
||||
!includeurl GCPPuml/Compute/CloudFunctions.puml
|
||||
|
||||
|
||||
actor "Person" as personAlias
|
||||
CloudToolsforVisualStudio(desktopAlias, "Label", "Technology", "Optional Description")
|
||||
CloudStorage(storageAlias, "Label", "Technology", "Optional Description")
|
||||
CloudFunctions(functionsAlias, "Label", "Function", "Optional Description")
|
||||
|
||||
personAlias --> desktopAlias
|
||||
personAlias --> functionsAlias
|
||||
desktopAlias --> storageAlias
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
|
||||
### WBS
|
||||
|
||||
````
|
||||
```plantuml
|
||||
@startwbs
|
||||
* work
|
||||
** work_A
|
||||
*** 準備
|
||||
*** 作業
|
||||
*** リリース
|
||||
** work_B
|
||||
*** 準備
|
||||
*** 作業_1
|
||||
*** 作業_2
|
||||
*** リリース
|
||||
@endwbs
|
||||
```
|
||||
````
|
||||
|
||||
|
||||
```plantuml
|
||||
@startwbs
|
||||
* work
|
||||
** work_A
|
||||
*** 準備
|
||||
*** 作業
|
||||
*** リリース
|
||||
** work_B
|
||||
*** 準備
|
||||
*** 作業_1
|
||||
*** 作業_2
|
||||
*** リリース
|
||||
@endwbs
|
||||
```
|
||||
### 業務フロー図
|
||||
|
||||
````
|
||||
```plantuml
|
||||
@startuml
|
||||
|__顧客__|
|
||||
:注文する;
|
||||
|
||||
|#AntiqueWhite|__販売部門__|
|
||||
:在庫を確認する;
|
||||
:出荷を確認する;
|
||||
|
||||
|__出荷部門__|
|
||||
:出荷する;
|
||||
fork
|
||||
:出荷を報告する;
|
||||
|
||||
|#AntiqueWhite|__経理部門__|
|
||||
:請求する;
|
||||
|
||||
|__顧客__|
|
||||
forkagain
|
||||
:商品を受け取る;
|
||||
|
||||
|__顧客__|
|
||||
end fork
|
||||
:支払う;
|
||||
|
||||
|#AntiqueWhite|__経理部門__|
|
||||
:入金を確認する;
|
||||
|
||||
@endum
|
||||
```
|
||||
````
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
|__顧客__|
|
||||
:注文する;
|
||||
|
||||
|#AntiqueWhite|__販売部門__|
|
||||
:在庫を確認する;
|
||||
:出荷を確認する;
|
||||
|
||||
|__出荷部門__|
|
||||
:出荷する;
|
||||
fork
|
||||
:出荷を報告する;
|
||||
|
||||
|#AntiqueWhite|__経理部門__|
|
||||
:請求する;
|
||||
|
||||
|__顧客__|
|
||||
forkagain
|
||||
:商品を受け取る;
|
||||
|
||||
|__顧客__|
|
||||
end fork
|
||||
:支払う;
|
||||
|
||||
|#AntiqueWhite|__経理部門__|
|
||||
:入金を確認する;
|
||||
@endum
|
||||
```
|
||||
|
||||
|
||||
## スキンやスタイルの変更(PlantUML)
|
||||
|
||||
PlantUMLではスキン(skinparam)でスタイルを制御します。
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
skinparam backgroundColor #EEEBDC
|
||||
skinparam handwritten true
|
||||
|
||||
actor User
|
||||
User -> System: Hello
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 全体をグレースケールにする
|
||||
|
||||
````
|
||||
```plantuml
|
||||
@startuml
|
||||
' 全体をグレースケールにする
|
||||
skinparam monochrome true
|
||||
```
|
||||
````
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
skinparam monochrome true
|
||||
|
||||
actor User
|
||||
User -> System: Hello
|
||||
@enduml
|
||||
```
|
||||
|
||||
### 特定のアクティビティに色を付ける
|
||||
|
||||
```
|
||||
'色を指定する
|
||||
#ffccff:処理名;
|
||||
|
||||
'グラデーションの設定
|
||||
#white-ffccff:処理名;
|
||||
```
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
|
||||
start
|
||||
#ffccff :処理A;
|
||||
#white-ffccff :処理B;
|
||||
#aaffaa :処理C;
|
||||
stop
|
||||
@enduml
|
||||
```
|
||||
|
||||
|
||||
|
||||
## VSCodeでのPlantUMLスニペット設定
|
||||
|
||||
1. [Ctrl] + [Shift] + [P]を押下
|
||||
2. "Snippets: Configure Snippets" を選択
|
||||
3. `plantuml.json`という名前で作成
|
||||
|
||||
```json
|
||||
{
|
||||
"PlantUML Sequence Diagram": {
|
||||
"prefix": "plantuml:sequence",
|
||||
"body": [
|
||||
"@startuml",
|
||||
"actor User",
|
||||
"participant Front",
|
||||
"participant Server",
|
||||
"User ->> Front: Click Button",
|
||||
"Front ->> Server: Request Data",
|
||||
"Server -->> Front: Return Data",
|
||||
"Front -->> User: Display Data",
|
||||
"@enduml"
|
||||
],
|
||||
"description": "Create a sequence diagram"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 関連リンク
|
||||
- [PlantUML 公式サイト](https://plantuml.com/)
|
||||
- [PlantUML Online Editor](https://www.planttext.com/)
|
||||
- [Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml)
|
||||
|
44
docs/trend2025.md
Normal file
44
docs/trend2025.md
Normal file
@ -0,0 +1,44 @@
|
||||
## 空間コンピューティング / AR / XRの技術選定
|
||||
|
||||
|
||||
```html
|
||||
<!-- index.html -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Simple WebXR AR</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.152.2/examples/jsm/webxr/ARButton.js"></script>
|
||||
</head>
|
||||
<body style="margin: 0; overflow: hidden;">
|
||||
<script type="module">
|
||||
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.module.js';
|
||||
import { ARButton } from 'https://cdn.jsdelivr.net/npm/three@0.152.2/examples/jsm/webxr/ARButton.js';
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.xr.enabled = true;
|
||||
document.body.appendChild(renderer.domElement);
|
||||
document.body.appendChild(ARButton.createButton(renderer));
|
||||
|
||||
const geometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
|
||||
const material = new THREE.MeshNormalMaterial();
|
||||
const cube = new THREE.Mesh(geometry, material);
|
||||
cube.position.set(0, 0, -0.5);
|
||||
scene.add(cube);
|
||||
|
||||
function animate() {
|
||||
renderer.setAnimationLoop(() => {
|
||||
cube.rotation.y += 0.01;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
}
|
||||
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
4
examles/sampleCommon.js
Normal file
4
examles/sampleCommon.js
Normal file
@ -0,0 +1,4 @@
|
||||
const common = require('../src/lib/common.js');
|
||||
|
||||
console.log(common.formatDatetime(new Date(), 'yyyy/MM/dd hh:mm:ss'));
|
||||
console.log(common.formatDatetime(new Date(), 'yyyy-MM-ddThh:mm:ss'));
|
42
src/front/ar/WebXR.html
Normal file
42
src/front/ar/WebXR.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!-- index.html -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Simple WebXR AR</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.152.2/examples/jsm/webxr/ARButton.js"></script>
|
||||
</head>
|
||||
|
||||
<body style="margin: 0; overflow: hidden;">
|
||||
<script type="module">
|
||||
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.module.js';
|
||||
import { ARButton } from 'https://cdn.jsdelivr.net/npm/three@0.152.2/examples/jsm/webxr/ARButton.js';
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.xr.enabled = true;
|
||||
document.body.appendChild(renderer.domElement);
|
||||
document.body.appendChild(ARButton.createButton(renderer));
|
||||
|
||||
const geometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
|
||||
const material = new THREE.MeshNormalMaterial();
|
||||
const cube = new THREE.Mesh(geometry, material);
|
||||
cube.position.set(0, 0, -0.5);
|
||||
scene.add(cube);
|
||||
|
||||
function animate() {
|
||||
renderer.setAnimationLoop(() => {
|
||||
cube.rotation.y += 0.01;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
}
|
||||
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
74
src/front/ar/WebXRCamera.html
Normal file
74
src/front/ar/WebXRCamera.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>WebXR AR Sample</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="module">
|
||||
import * as THREE from 'https://cdn.skypack.dev/three@0.152.2';
|
||||
import { ARButton } from 'https://cdn.skypack.dev/three@0.152.2/examples/jsm/webxr/ARButton.js';
|
||||
|
||||
let camera, scene, renderer;
|
||||
let controller;
|
||||
|
||||
init();
|
||||
animate();
|
||||
|
||||
function init() {
|
||||
// Renderer
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.xr.enabled = true;
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
// ARボタンを追加(Hit-test機能必要)
|
||||
document.body.appendChild(
|
||||
ARButton.createButton(renderer, { requiredFeatures: ['hit-test'] })
|
||||
);
|
||||
|
||||
// SceneとCamera
|
||||
scene = new THREE.Scene();
|
||||
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 20);
|
||||
|
||||
// 光源
|
||||
const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1);
|
||||
light.position.set(0.5, 1, 0.25);
|
||||
scene.add(light);
|
||||
|
||||
// 立方体を作成
|
||||
const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
|
||||
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
|
||||
const cube = new THREE.Mesh(geometry, material);
|
||||
cube.position.set(0, 0, -0.5); // カメラの前に表示
|
||||
scene.add(cube);
|
||||
|
||||
// コントローラ
|
||||
controller = renderer.xr.getController(0);
|
||||
scene.add(controller);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
renderer.setAnimationLoop(render);
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
48
src/front/ar/afreame/aframe-hover.html
Normal file
48
src/front/ar/afreame/aframe-hover.html
Normal file
@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ホバーで説明表示</title>
|
||||
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<a-scene cursor="rayOrigin: mouse" raycaster="objects: .hoverable">
|
||||
<!-- 地面 -->
|
||||
<a-plane position="0 0 0" rotation="-90 0 0" width="50" height="50" color="#7BC8A4"></a-plane>
|
||||
|
||||
<!-- カメラ -->
|
||||
<a-entity wasd-controls camera look-controls position="0 1.6 5"></a-entity>
|
||||
|
||||
<!-- 展示物 -->
|
||||
<a-box id="exhibit" class="hoverable" position="0 0.5 -5" color="#4CC3D9" depth="1" height="1" width="1"></a-box>
|
||||
|
||||
<!-- 説明テキスト(最初は非表示) -->
|
||||
<a-text id="tooltip" value="これは展示物です" visible="false"
|
||||
position="0 1.5 -5" color="#000" align="center" width="4">
|
||||
</a-text>
|
||||
|
||||
<!-- イベントスクリプト -->
|
||||
<script>
|
||||
AFRAME.registerComponent('show-tooltip', {
|
||||
init: function () {
|
||||
const tooltip = document.querySelector('#tooltip');
|
||||
this.el.addEventListener('mouseenter', () => {
|
||||
tooltip.setAttribute('visible', 'true');
|
||||
});
|
||||
this.el.addEventListener('mouseleave', () => {
|
||||
tooltip.setAttribute('visible', 'false');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const exhibit = document.querySelector('#exhibit');
|
||||
exhibit.setAttribute('show-tooltip', '');
|
||||
});
|
||||
</script>
|
||||
</a-scene>
|
||||
</body>
|
||||
|
||||
</html>
|
44
src/front/ar/afreame/aframe-three.html
Normal file
44
src/front/ar/afreame/aframe-three.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AR.js マーカーベース サンプル</title>
|
||||
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/AR-js-org/AR.js@3.3.2/aframe/build/aframe-ar.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 5px 10px;
|
||||
font-family: sans-serif;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="info">「Hiroマーカー」をカメラにかざしてください</div>
|
||||
|
||||
<a-scene embedded arjs="sourceType: webcam; debugUIEnabled: false;">
|
||||
<!-- マーカーが認識されたときに表示される青い箱 -->
|
||||
<a-marker preset="hiro">
|
||||
<a-box position="0 0.5 0" material="color: blue;" shadow></a-box>
|
||||
<a-text value="Hello AR!" position="-0.5 1 0" color="white"></a-text>
|
||||
</a-marker>
|
||||
|
||||
<!-- カメラ -->
|
||||
<a-entity camera></a-entity>
|
||||
</a-scene>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
F
|
21
src/front/ar/afreame/aframe.html
Normal file
21
src/front/ar/afreame/aframe.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>歩行型Webページ</title>
|
||||
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a-scene>
|
||||
<!-- 地面 -->
|
||||
<a-plane position="0 0 0" rotation="-90 0 0" width="50" height="50" color="#7BC8A4"></a-plane>
|
||||
|
||||
<!-- カメラとコントロール(WASDキーで歩行可能) -->
|
||||
<a-entity camera wasd-controls look-controls position="0 1.6 5"></a-entity>
|
||||
|
||||
<!-- 建物風のオブジェクト -->
|
||||
<a-box position="0 0.5 -5" depth="5" height="1" width="5" color="#4CC3D9"></a-box>
|
||||
<a-box position="10 0.5 -5" depth="5" height="1" width="5" color="#FFC65D"></a-box>
|
||||
<a-sphere position="-10 1 -5" radius="1" color="#EF2D5E"></a-sphere>
|
||||
</a-scene>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @description downloadボタンを押下したらCSVファイルをzip圧縮してダウンロードする
|
||||
*/
|
||||
|
||||
|
||||
// ダウンロードボタン
|
||||
const downloadButton = document.getElementById('download-button');
|
||||
const downloadButtonBase64 = document.getElementById('download-button-base64');
|
||||
|
||||
// ダウンロードボタンを押下した時の処理を記載する
|
||||
downloadButton.addEventListener('click', async () => {
|
||||
// URLを別のウィンドウで開く
|
||||
// GCSからZIPファイルのコンテンツが返る
|
||||
window.open('http://localhost:3000/downdload-gcs');
|
||||
|
||||
});
|
||||
|
||||
|
||||
downloadButtonBase64.addEventListener('click', async () => {
|
||||
// ダウンロードボタンを無効化する
|
||||
downloadButtonBase64.disabled = true;
|
||||
try {
|
||||
// localhost:3000/downdload-gcs-json にリクエストを送る
|
||||
const response = await fetch('http://localhost:3000/downdload-gcs-json');
|
||||
if (!response.ok ) throw new Error('Failed to fetch');
|
||||
// JSONでdataにbase64が返ってくる
|
||||
const res = await response.json();
|
||||
const base64 = res.data;
|
||||
|
||||
// Base64をデコードしてZIPファイルを生成
|
||||
const zipBlob = await fetch(`data:application/zip;base64,${base64}`).then(res => res.blob());
|
||||
const a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(zipBlob);
|
||||
a.download = "serverFile.zip";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
} catch (e) {
|
||||
// エラーが発生した場合
|
||||
console.error(e);
|
||||
alert('ダウンロードに失敗しました');
|
||||
}
|
||||
finally {
|
||||
// ダウンロードボタンを有効化する
|
||||
downloadButtonBase64.disabled = false;
|
||||
}
|
||||
});
|
@ -9,7 +9,9 @@
|
||||
<body>
|
||||
<h1>JavaScript Develop Download To Google Cloud Storage</h1>
|
||||
<p >Zip File Download</p>
|
||||
|
||||
<button id="download-button">Zip Download(GCS)</button>
|
||||
<button id="download-button-base64">Zip Download(GCS JSON Base64)</button>
|
||||
<script src="assets/cloud-storage.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,22 @@
|
||||
const common = {
|
||||
/**
|
||||
* 日時データを指定フォーマットで文字列化する
|
||||
* @param {Date} dt
|
||||
* @param {String} fmt
|
||||
* @returns {String}
|
||||
*/
|
||||
formatDatetime(dt,fmt="yyyy-MM-dd hh:mm:ss"){
|
||||
const pad = (n) => n < 10 ? '0' + n : n;
|
||||
const replacements = {
|
||||
"yyyy": dt.getFullYear(),
|
||||
"MM": pad(dt.getMonth() + 1),
|
||||
"dd": pad(dt.getDate()),
|
||||
"hh": pad(dt.getHours()),
|
||||
"mm": pad(dt.getMinutes()),
|
||||
"ss": pad(dt.getSeconds())
|
||||
};
|
||||
return fmt.replace(/yyyy|MM|dd|hh|mm|ss/g, (match) => replacements[match]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = common;
|
53
src/script/uploadFileFromSignadUrl.js
Normal file
53
src/script/uploadFileFromSignadUrl.js
Normal file
@ -0,0 +1,53 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
async function uploadFile(filePath) {
|
||||
try {
|
||||
// Step 1: Request a signed URL from the API
|
||||
const apiResponse = await fetch('http://localhost:3000/api/get-signed-url', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fileName: path.basename(filePath),
|
||||
contentType: 'application/octet-stream',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
throw new Error(`Failed to retrieve signed URL: ${apiResponse.statusText}`);
|
||||
}
|
||||
|
||||
const { signedUrl } = await apiResponse.json();
|
||||
|
||||
if (!signedUrl) {
|
||||
throw new Error('Signed URL is missing in the response');
|
||||
}
|
||||
|
||||
console.log('Signed URL received:', signedUrl);
|
||||
|
||||
// Step 2: Upload the file to the signed URL using PUT
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
const uploadResponse = await fetch(signedUrl, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
},
|
||||
body: fileStream,
|
||||
});
|
||||
|
||||
if (!uploadResponse.ok) {
|
||||
throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);
|
||||
}
|
||||
|
||||
console.log('File uploaded successfully:', uploadResponse.status);
|
||||
} catch (error) {
|
||||
console.error('Error uploading file:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage
|
||||
const filePath = './example-file.bin'; // Replace with your file path
|
||||
uploadFile(filePath);
|
@ -32,6 +32,7 @@ const downloadFilesFromGCS = async (req, res) => {
|
||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||
|
||||
archive.on('error', (err) => res.status(500).send({ error: err.message }));
|
||||
// レスポンスにアーカイブをパイプする
|
||||
archive.pipe(res);
|
||||
|
||||
|
||||
|
65
src/server/download/gcsArchiveJson.js
Normal file
65
src/server/download/gcsArchiveJson.js
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @fileoverview Google Cloud Storage (GCS) download module.
|
||||
*/
|
||||
const { Storage } = require('@google-cloud/storage');
|
||||
const { PassThrough } = require('stream');
|
||||
const { finished } = require('stream/promises');
|
||||
|
||||
const archiver = require('archiver');
|
||||
const KEY_FILE_PATH = './keys/service-account.json'
|
||||
const storage = new Storage({
|
||||
keyFilename: KEY_FILE_PATH});
|
||||
|
||||
// Load environment variables
|
||||
require('dotenv').config();
|
||||
|
||||
// バケット名を.envから取得する
|
||||
const BUCKET_NAME = process.env.BUCKET_NAME;
|
||||
console.log(`BUCKET_NAME: ${BUCKET_NAME}`);
|
||||
|
||||
|
||||
/**
|
||||
* GCStorageからファイルをダウンロードする
|
||||
*
|
||||
* @param {http.IncomingMessage} req
|
||||
* @param {http.ServerResponse} res
|
||||
*/
|
||||
const downloadFilesFromGCSJson = async (req, res) => {
|
||||
// バケットからファイル一覧を取得する
|
||||
const [files] = await storage.bucket(BUCKET_NAME).getFiles();
|
||||
const filesToZip = files.map((file) => file.name);
|
||||
|
||||
// レスポンスはJSONでBase64のZIP情報を返す
|
||||
res.setHeader('Access-Control-Allow-Origin', '*'); // すべてのオリジンを許可
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
// Streamを作成する
|
||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||
const pass = new PassThrough();
|
||||
const chunks = [];
|
||||
|
||||
// チャンク収集
|
||||
pass.on('data', chunk => chunks.push(chunk));
|
||||
|
||||
for (const fileName of filesToZip) {
|
||||
const file = storage.bucket(BUCKET_NAME).file(fileName);
|
||||
archive.append(file.createReadStream(), { name: fileName });
|
||||
}
|
||||
|
||||
archive.pipe(pass);
|
||||
archive.on('error', (err) => res.status(500).send({ error: err.message }));
|
||||
|
||||
//
|
||||
await archive.finalize();
|
||||
|
||||
// アーカイブ(zipなど)の入力の完了を宣言して書き込みを開始させる命令です。
|
||||
await finished(pass);
|
||||
|
||||
// Base64エンコードしてJSONで返す
|
||||
const base64 = Buffer.concat(chunks).toString('base64');
|
||||
res.end(JSON.stringify({ data: base64 }));
|
||||
};
|
||||
|
||||
|
||||
module.exports = downloadFilesFromGCSJson;
|
@ -2,6 +2,7 @@ const http = require('http');
|
||||
|
||||
const { downloadContents, generateZipUrl } = require('./download/download');
|
||||
const downloadFilesFromGCS = require('./download/gcsArchive');
|
||||
const downloadFilesFromGCSJson = require('./download/gcsArchiveJson');
|
||||
|
||||
|
||||
|
||||
@ -24,6 +25,9 @@ const server = http.createServer((req, res) => {
|
||||
} else if (req.url === '/downdload-gcs') {
|
||||
// GCSからZIPファイルのコンテンツをそのまま返す
|
||||
downloadFilesFromGCS(req, res);
|
||||
} else if (req.url === '/downdload-gcs-json') {
|
||||
// GCSからZIPファイルのコンテンツをそのまま返す
|
||||
downloadFilesFromGCSJson(req, res);
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
|
0
tests/test_common.js
Normal file
0
tests/test_common.js
Normal file
Loading…
x
Reference in New Issue
Block a user