File size: 9,767 Bytes
9d97138
 
 
 
 
2de9fdc
9d97138
 
50e1d54
9d97138
 
 
 
 
 
5c1450a
9d97138
 
 
7547388
9d97138
 
 
 
 
2de9fdc
9d97138
 
 
7547388
9d97138
 
 
 
 
 
 
 
 
50e1d54
 
 
 
 
 
 
 
 
9d97138
 
 
 
 
 
 
 
 
 
5dd93eb
9d97138
5dd93eb
9d97138
 
 
 
 
 
 
7547388
9d97138
 
 
7547388
9d97138
 
 
 
 
 
5dd93eb
9d97138
 
7547388
9d97138
 
 
 
b88dc75
9d97138
 
5dd93eb
 
 
 
 
 
7547388
5dd93eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7547388
5dd93eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9d97138
 
 
8807cbe
 
 
 
 
 
 
 
 
9d97138
 
 
 
7547388
50e1d54
 
 
 
 
 
 
 
 
 
 
 
8807cbe
 
 
50e1d54
9d97138
 
50e1d54
9d97138
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Telearn Arcade Player - {{ project_display_name }}</title>
    <!-- Google Fonts -->
    <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&family=Outfit:wght@400;600;800&family=Noto+Sans+TC:wght@400;700&display=swap" rel="stylesheet">
        <link rel="stylesheet" href="/static/css/player.css">
</head>
<body>

    <!-- Top Navigation Bar -->
    <div class="top-bar">
        <div class="project-info">
            <a class="brand-logo" {% if student_token %}href="/student/{{ student_token }}"{% else %}href="/"{% endif %}>
                <img src="/images/logo-light.png" alt="Telearn Logo" class="logo-img" />
                <span class="brand-name">Telearn 得能教育</span>
            </a>
            <button class="btn-back" id="btn-back">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
                返回大廳
            </button>
            <div class="project-title-wrapper" style="margin-left: 1.2rem; border-left: 1px solid rgba(255, 255, 255, 0.08); padding-left: 1.2rem;">
                <span class="project-path">{{ class_name }} / {{ student_name }}</span>
                <span class="project-name">{{ project_display_name }}</span>
            </div>
        </div>

        <div class="status-wrapper status-connecting" id="server-status">
            <div class="status-dot"></div>
            <span id="status-text">連線中...</span>
        </div>
    </div>

    <!-- Main Content Grid -->
    <div class="main-container" id="player-layout">
        <!-- 1. Screen Viewport -->
        <div class="viewport-section">
            <div class="loading-overlay" id="loading-overlay" style="display:none;">
                <div class="loading-spinner"></div>
                <p id="loading-text">正在啟動專案執行器…</p>
                <p class="loading-sub" id="loading-sub">MediaPipe / 模型載入可能需要數秒</p>
            </div>
            <div class="webcam-denied" id="webcam-denied" style="display:none;">
                <p><strong>無法使用攝影機</strong></p>
                <p id="webcam-denied-msg">請在瀏覽器允許鏡頭權限,或使用 HTTPS / localhost。</p>
            </div>
            <div class="screen-frame" id="viewport-frame">
                <!-- Will be dynamically populated with either <img> or <iframe> -->
            </div>

            <div class="focus-prompt" id="focus-prompt-el" style="display: none;">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>
                <span>點選畫面啟用鍵盤/滑鼠控制</span>
            </div>
        </div>

        <!-- 2. Side Panel -->
        <div class="control-panel" id="side-panel">
            <div class="device-section" id="python-device-section">
                <h3 class="device-title">虛擬硬體設定</h3>
                <button class="btn-webcam" id="btn-toggle-webcam">
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
                    啟用 Webcam 影像串流
                </button>

                <div class="action-buttons">
                    <button class="btn-action btn-restart" id="btn-restart">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67"/></svg>
                        重設
                    </button>
                    <button class="btn-action btn-stop" id="btn-stop">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/></svg>
                        終止
                    </button>
                </div>
            </div>

            <div class="terminal-section" id="python-terminal-section">
                <div class="terminal-header-bar">
                    <h3 class="device-title">終端機日誌</h3>
                    <button class="btn-clear" id="btn-clear-console">清除</button>
                </div>
                <div class="terminal-box" id="console-logs"></div>
                <div class="terminal-input-wrapper">
                    <span class="terminal-prompt">&gt;</span>
                    <input type="text" id="terminal-input-field" autocomplete="off" />
                </div>
            </div>

            <!-- APK Info Section -->
            <div id="apk-info-section" style="display: none; padding: 1.5rem; flex-direction: column; gap: 1.5rem; overflow-y: auto; height: 100%;">
                <div style="display: flex; flex-direction: column; gap: 1rem;">
                    <h3 class="device-title">Android APK 專案</h3>
                    <div class="apk-download-card">
                        <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#ef8068" stroke-width="1.5">
                            <path d="M12 2v16m0 0l-4-4m4 4l4-4M4 22h16" stroke-linecap="round" stroke-linejoin="round"/>
                        </svg>
                        <div class="apk-info-text">
                            <div class="apk-file-name" id="apk-file-label">app.apk</div>
                            <div class="apk-file-size" id="apk-size-label">大小估算中...</div>
                        </div>
                        <a id="btn-apk-download" href="#" class="btn-launch btn-launch-apk" style="text-decoration: none; font-size: 0.85rem;" download>
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M12 5v14M5 12h14"/></svg>
                            下載 APK 檔案
                        </a>
                    </div>
                </div>

                <div class="qr-section">
                    <h3 class="device-title">掃描二維碼安裝</h3>
                    <div class="qr-code-wrapper">
                        <div id="apk-qr-image"></div>
                    </div>
                    <span class="qr-tip">使用 Android 裝置掃描此條碼,即可直接下載並安裝此應用程式</span>
                </div>

                <div class="guide-section">
                    <h3 class="device-title">安裝與模擬說明</h3>
                    
                    <div class="guide-item">
                        <div class="guide-step-number">1</div>
                        <p class="guide-text">下載 APK 檔案後,在裝置上點擊安裝。若系統提示「安全性警告」,請允許瀏覽器「安裝未知來源的應用程式」。</p>
                    </div>
                    
                    <div class="guide-item" style="border-top: 1px dashed var(--border-color); padding-top: 0.8rem; margin-top: 0.8rem;">
                        <div class="guide-step-number">2</div>
                        <p class="guide-text">
                            <strong>啟用網頁模擬器</strong>:<br>
                            本平台支援整合 <a href="https://appetize.io" target="_blank" class="guide-link">Appetize.io</a>。將您的 APK 上傳至該網站後,在專案目錄下建立 <code>appetize.txt</code> 寫入 Public Key,即可在此頁面直接操作模擬器。
                        </p>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div class="idle-dialog-backdrop" id="idle-dialog" role="dialog" aria-modal="true" aria-labelledby="idle-dialog-title" hidden>
        <div class="idle-dialog-card">
            <div class="idle-dialog-icon" aria-hidden="true">⏳</div>
            <h2 id="idle-dialog-title">還在觀看嗎?</h2>
            <p>這個作品已一段時間沒有操作。為了讓其他家長也能使用展示名額,系統將在 <strong id="idle-countdown">2:00</strong> 後自動結束。</p>
            <button type="button" id="btn-continue-session">繼續觀看</button>
        </div>
    </div>

    <!-- Hidden audio/video capturing items -->
    <video id="webcam-video" autoplay playsinline width="640" height="480"></video>
    <canvas id="webcam-canvas" width="640" height="480"></canvas>

    <script nonce="{{ request.state.csp_nonce }}">
        window.TELEARN_PLAYER = {
            className: {{ class_name|tojson }},
            studentName: {{ student_name|tojson }},
            studentToken: {{ student_token|tojson }},
            projectName: {{ project_name|tojson }},
            projectType: {{ project_type|tojson }},
            externalUrl: {{ external_url|tojson }},
            apkName: {{ apk_name|tojson }},
            apkSize: {{ apk_size|tojson }},
            appetizeKey: {{ appetize_key|tojson }},
            viewOnly: {{ view_only|default(false)|tojson }},
            sessionTimeoutSec: {{ session_timeout_sec|default(900) }},
            idleWarningSec: {{ idle_warning_sec|default(600) }},
            idleTimeoutSec: {{ idle_timeout_sec|default(720) }},
            heartbeatIntervalSec: {{ heartbeat_interval_sec|default(20) }},
            maxConcurrentRunners: {{ max_concurrent_runners|default(3) }}
        };
    </script>
    <script src="/static/js/player.js"></script>
</body>
</html>