| <!DOCTYPE html> |
| <html lang="ja"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> |
| <title>スタディーサプリ</title> |
| <script src="https://cdn.jsdelivr.net/npm/eruda@3.4.3/eruda.min.js"></script><script>eruda.init();</script> |
| <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900&display=swap" rel="stylesheet"> |
| <link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet"> |
| <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet"> |
| <style> |
| body { |
| margin: 0; |
| padding: 0; |
| font-family: 'Roboto', sans-serif; |
| background-color: #f5f5f5; |
| } |
| .toolbar { |
| background-color: #01579B; |
| color: white; |
| padding: 8px; |
| display: flex; |
| flex-wrap: wrap; |
| gap: 8px; |
| align-items: center; |
| } |
| .client-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); |
| gap: 12px; |
| padding: 16px; |
| } |
| .client-card { |
| background: white; |
| border-radius: 8px; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| overflow: hidden; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| .client-card.selected { |
| background-color: #e3f2fd; |
| border: 2px solid #2196f3; |
| } |
| .client-header { |
| background-color: #f0f0f0; |
| padding: 8px; |
| font-weight: bold; |
| border-bottom: 1px solid #ddd; |
| display: flex; |
| justify-content: space-between; |
| } |
| .client-body { |
| padding: 8px; |
| text-align: center; |
| } |
| .client-status { |
| font-size: 12px; |
| margin-top: 8px; |
| } |
| .video-preview { |
| width: 100%; |
| background: #000; |
| border-radius: 4px; |
| } |
| button { |
| background-color: #01579B; |
| color: white; |
| border: none; |
| padding: 6px 12px; |
| border-radius: 4px; |
| cursor: pointer; |
| margin: 2px; |
| } |
| button:disabled { |
| background-color: #9e9e9e; |
| cursor: not-allowed; |
| } |
| .lock-dialog, .settings-dialog { |
| position: fixed; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background: white; |
| padding: 20px; |
| border-radius: 8px; |
| box-shadow: 0 4px 20px rgba(0,0,0,0.2); |
| z-index: 1000; |
| width: 300px; |
| } |
| .overlay { |
| position: fixed; |
| top: 0; left: 0; right: 0; bottom: 0; |
| background: rgba(0,0,0,0.5); |
| z-index: 999; |
| } |
| .hidden { |
| display: none; |
| } |
| .status-badge { |
| display: inline-block; |
| width: 10px; |
| height: 10px; |
| border-radius: 50%; |
| margin-right: 6px; |
| } |
| .status-online { background-color: #4caf50; } |
| .status-offline { background-color: #9e9e9e; } |
| .status-wait { background-color: #ff9800; } |
| .lock-info { |
| font-size: 12px; |
| margin-top: 4px; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="app"> |
| <v-app> |
| <v-main> |
| <div class="toolbar"> |
| <div> |
| <input type="text" v-model="teacherEmail" placeholder="教師メールアドレス" style="width:200px;"> |
| <input type="text" v-model="className" placeholder="クラス名"> |
| <button @click="initializeSystem">初期化・接続開始</button> |
| </div> |
| <div style="margin-left: auto;"> |
| 選択中: {{ selectedCount }}台 |
| <button @click="allSelect">全選択</button> |
| <button @click="allUnselect">全解除</button> |
| <button @click="refreshStatus">状態更新</button> |
| </div> |
| </div> |
| <div class="toolbar" style="background-color:#e0e0e0; color:black;"> |
| <button @click="startDesktopShare" :disabled="!canShare">画面提示開始</button> |
| <button @click="stopDesktopShare">画面提示停止</button> |
| <button @click="openLockDialog">ロック</button> |
| <button @click="unlock">ロック解除</button> |
| <button @click="fetchDeviceInfo">デバイス情報取得</button> |
| <span style="margin-left:auto;">状態モード |
| <label><input type="radio" value="status" v-model="dispMode"> ステータス</label> |
| <label><input type="radio" value="desktop" v-model="dispMode"> 画面表示</label> |
| </span> |
| </div> |
|
|
| <div class="client-grid"> |
| <div v-for="client in clients" :key="client.mailAddress" |
| class="client-card" :class="{selected: client.selected}" |
| @click="toggleSelect(client)"> |
| <div class="client-header"> |
| <span>{{ client.dispName || client.mailAddress.split('@')[0] }}</span> |
| <span> |
| <span class="status-badge" :class="statusClass(client)"></span> |
| {{ statusText(client) }} |
| </span> |
| </div> |
| <div class="client-body"> |
| <div v-if="dispMode === 'desktop' && client.p2p === 'successful' && client.power && client.joinState === 'consent'"> |
| <video :id="'video_'+client.mailAddress" autoplay muted class="video-preview" style="width:100%; height:auto;"></video> |
| </div> |
| <div v-else> |
| <div v-if="client.activeTab && client.activeTab.title"> |
| <img :src="client.activeTab.favicon" width="32" height="32" style="vertical-align: middle;"> |
| {{ client.activeTab.title.substring(0,30) }} |
| </div> |
| <div v-else-if="!client.power">電源OFF</div> |
| <div v-else-if="client.joinState === 'wait'">参加待ち</div> |
| <div v-else-if="client.joinState === 'refusal'">参加拒否</div> |
| <div v-else-if="client.p2p === 'unknown'">接続確認中</div> |
| <div v-else>情報なし</div> |
| </div> |
| <div class="client-status"> |
| {{ client.pVer ? 'Ver:'+client.pVer : '' }} {{ client.battery.level ? '🔋'+client.battery.level+'%' : '' }} |
| <span v-if="client.mouseLock">🔒</span> |
| <span v-if="client.urlLimit">🌐制限</span> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div v-if="showLockDlg" class="overlay" @click.self="showLockDlg=false"></div> |
| <div v-if="showLockDlg" class="lock-dialog"> |
| <h3>ロック設定</h3> |
| <div> |
| <label><input type="radio" value="image" v-model="lockType"> 画像ロック</label> |
| <label><input type="radio" value="text" v-model="lockType"> テキストロック</label> |
| </div> |
| <div v-if="lockType === 'image'"> |
| <select v-model="lockImage"> |
| <option value="image1">ロック画面1</option> |
| <option value="image2">ロック画面2</option> |
| <option value="image3">ロック画面3</option> |
| </select> |
| </div> |
| <div v-else> |
| <input type="text" v-model="lockText" placeholder="表示文字"> |
| 文字色: <input type="color" v-model="lockFColor"> |
| 背景色: <input type="color" v-model="lockBColor"> |
| </div> |
| <div> |
| <label><input type="checkbox" v-model="timeLimited"> 時間指定</label> |
| <div v-if="timeLimited"> |
| <select v-model="lockLimitedTipe"> |
| <option value="rdoLockTime">経過時間</option> |
| <option value="rdoLockMoment">時刻指定</option> |
| </select> |
| <input v-if="lockLimitedTipe === 'rdoLockTime'" type="text" v-model="lockTime" placeholder="HH:MM:SS"> |
| <input v-else type="text" v-model="lockMoment" placeholder="HH:MM:SS"> |
| </div> |
| </div> |
| <div style="margin-top:16px;"> |
| <button @click="executeLock">実行</button> |
| <button @click="showLockDlg=false">キャンセル</button> |
| </div> |
| </div> |
| </v-main> |
| </v-app> |
| </div> |
|
|
| <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script> |
| <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script> |
| <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-database-compat.js"></script> |
| <script src="firebase-config.js"></script> |
| <script src="main.js"></script> |
| </body> |
| </html> |