マスター編集とプレビュー再生を実装
This commit is contained in:
parent
f6dc93a9ab
commit
1afeaa6022
@ -138,7 +138,7 @@ async def send_quiz_message(channel, intro_audio_name, volume):
|
||||
await disable_button_after_timeout(message)
|
||||
|
||||
async def disable_button_after_timeout(message):
|
||||
await asyncio.sleep(20)
|
||||
await asyncio.sleep(30)
|
||||
view = discord.ui.View()
|
||||
for action_row in message.components:
|
||||
for item in action_row.children:
|
||||
|
13
web/app.py
13
web/app.py
@ -4,7 +4,7 @@ import traceback
|
||||
import secrets
|
||||
import random
|
||||
import requests
|
||||
from flask import Flask, session, redirect, url_for, g, request, render_template, jsonify, flash
|
||||
from flask import Flask, session, redirect, url_for, g, request, render_template, jsonify, flash, send_from_directory
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from db import get_db, close_connection, initialize_db, reset_active_sessions
|
||||
@ -331,6 +331,16 @@ def process_csv(file_path):
|
||||
except Exception as e:
|
||||
logger.error(f'Error processing CSV file: {str(e)}')
|
||||
|
||||
# プレビュー用のエンドポイントを追加
|
||||
@app.route('/preview_audio/<filename>', methods=['GET'])
|
||||
@login_required
|
||||
def preview_audio_route(filename):
|
||||
try:
|
||||
# ファイルがアップロードされているディレクトリからファイルを返す
|
||||
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
||||
except Exception as e:
|
||||
return jsonify({'error': 'ファイルのプレビュー中にエラーが発生しました'}), 500
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
app.run(debug=True, host='0.0.0.0', port=80)
|
||||
@ -340,4 +350,3 @@ if __name__ == "__main__":
|
||||
except Exception as e:
|
||||
logger.error(f"例外が発生しました: {e}")
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
|
@ -90,14 +90,33 @@ def update_quiz_master():
|
||||
db = get_db()
|
||||
quiz_master = db.query(QuizMaster).filter_by(id=data['id']).first()
|
||||
if quiz_master:
|
||||
old_full_audio_name = quiz_master.full_audio_name
|
||||
old_intro_audio_name = quiz_master.intro_audio_name
|
||||
|
||||
# フル音源名とイントロ音源名を更新
|
||||
quiz_master.title = data['title']
|
||||
quiz_master.full_audio_name = data['full_audio_name']
|
||||
quiz_master.intro_audio_name = data['intro_audio_name']
|
||||
quiz_master.keyword1 = data['keyword1']
|
||||
quiz_master.keyword2 = data['keyword2']
|
||||
quiz_master.keyword3 = data['keyword3']
|
||||
|
||||
# ファイル名を変更
|
||||
upload_folder = current_app.config['UPLOAD_FOLDER']
|
||||
if old_full_audio_name != data['full_audio_name']:
|
||||
os.rename(
|
||||
os.path.join(upload_folder, old_full_audio_name),
|
||||
os.path.join(upload_folder, data['full_audio_name'])
|
||||
)
|
||||
if old_intro_audio_name != data['intro_audio_name']:
|
||||
os.rename(
|
||||
os.path.join(upload_folder, old_intro_audio_name),
|
||||
os.path.join(upload_folder, data['intro_audio_name'])
|
||||
)
|
||||
|
||||
db.commit()
|
||||
return jsonify({'message': '更新が完了しました'})
|
||||
return jsonify({'message': '更新が完了しました'})
|
||||
return jsonify({'error': 'クイズマスタが見つかりません'})
|
||||
|
||||
def add_quiz_master():
|
||||
data = request.json
|
||||
|
@ -36,10 +36,28 @@
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
#updateModal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0px 0px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* 音量スライダーのスタイル */
|
||||
#volumeSlider {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
let files = [];
|
||||
let usedFiles = [];
|
||||
let audio = new Audio();
|
||||
|
||||
async function fetchFiles() {
|
||||
try {
|
||||
@ -63,7 +81,6 @@
|
||||
|
||||
function filterFiles() {
|
||||
const fullAudioInput = document.getElementById('full_audio_name_input').value.toLowerCase();
|
||||
|
||||
const fullAudioSelect = document.getElementById('full_audio_name_select');
|
||||
const introAudioSelect = document.getElementById('intro_audio_name_select');
|
||||
fullAudioSelect.innerHTML = '';
|
||||
@ -83,11 +100,9 @@
|
||||
introAudioSelect.appendChild(optionIntro);
|
||||
});
|
||||
|
||||
// タイトルフィールドを更新(拡張子を削除)
|
||||
const title = fullAudioInput.split('.').slice(0, -1).join('.');
|
||||
document.getElementById('title').value = title;
|
||||
|
||||
// イントロ音源名を自動入力
|
||||
const extension = fullAudioInput.split('.').pop();
|
||||
const introAudioName = `${title}_intro.${extension}`;
|
||||
if (files.includes(introAudioName)) {
|
||||
@ -95,7 +110,6 @@
|
||||
document.getElementById('intro_audio_name_hidden').value = introAudioName;
|
||||
}
|
||||
|
||||
// キーワード1を自動入力
|
||||
const keyword1 = fullAudioInput.split(' - ')[0];
|
||||
document.getElementById('keyword1').value = keyword1;
|
||||
}
|
||||
@ -107,12 +121,10 @@
|
||||
input.value = select.value;
|
||||
hidden.value = select.value;
|
||||
|
||||
// タイトルフィールドを更新(拡張子を削除)
|
||||
if (inputId === 'full_audio_name_input') {
|
||||
const title = select.value.split('.').slice(0, -1).join('.');
|
||||
document.getElementById('title').value = title;
|
||||
|
||||
// イントロ音源名を自動入力
|
||||
const extension = select.value.split('.').pop();
|
||||
const introAudioName = `${title}_intro.${extension}`;
|
||||
if (files.includes(introAudioName)) {
|
||||
@ -120,12 +132,84 @@
|
||||
document.getElementById('intro_audio_name_hidden').value = introAudioName;
|
||||
}
|
||||
|
||||
// キーワード1を自動入力
|
||||
const keyword1 = select.value.split(' - ')[0];
|
||||
document.getElementById('keyword1').value = keyword1;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchQuizMasters() {
|
||||
try {
|
||||
const response = await fetch('/quiz_master');
|
||||
const quizMasters = await response.json();
|
||||
const quizMasterList = document.getElementById('quizMasterList');
|
||||
quizMasterList.innerHTML = '';
|
||||
quizMasters.forEach(qm => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${qm.title}</td>
|
||||
<td>${qm.full_audio_name} <button onclick="previewAudio('${qm.full_audio_name}')">再生</button></td>
|
||||
<td>${qm.intro_audio_name} <button onclick="previewAudio('${qm.intro_audio_name}')">再生</button></td>
|
||||
<td>${qm.keyword1}</td>
|
||||
<td>${qm.keyword2}</td>
|
||||
<td>${qm.keyword3}</td>
|
||||
<td>
|
||||
<button onclick="editQuizMaster('${qm.id}')">編集</button>
|
||||
<form onsubmit="handleFormSubmit(event, '/delete_quiz_master')">
|
||||
<input type="hidden" name="id" value="${qm.id}">
|
||||
<button type="submit">削除</button>
|
||||
</form>
|
||||
</td>
|
||||
`;
|
||||
quizMasterList.appendChild(tr);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching quiz masters:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function previewAudio(filename) {
|
||||
const audioUrl = `/preview_audio/${encodeURIComponent(filename)}`;
|
||||
audio.src = audioUrl;
|
||||
audio.volume = document.getElementById('volumeSlider').value / 100; // スライダーから音量を設定
|
||||
audio.play().catch(error => {
|
||||
console.error('Audio playback failed:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function stopAudio() {
|
||||
audio.pause();
|
||||
audio.currentTime = 0; // 再生位置をリセット
|
||||
}
|
||||
|
||||
function changeVolume(value) {
|
||||
audio.volume = value / 100; // 音量を変更
|
||||
}
|
||||
|
||||
async function editQuizMaster(id) {
|
||||
try {
|
||||
const response = await fetch('/select_quiz', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ quiz_id: id })
|
||||
});
|
||||
const data = await response.json();
|
||||
document.getElementById('edit_title').value = data.title;
|
||||
document.getElementById('edit_full_audio_name_input').value = data.full_audio_name;
|
||||
document.getElementById('edit_intro_audio_name_input').value = data.intro_audio_name;
|
||||
document.getElementById('edit_keyword1').value = data.keyword1;
|
||||
document.getElementById('edit_keyword2').value = data.keyword2;
|
||||
document.getElementById('edit_keyword3').value = data.keyword3;
|
||||
document.getElementById('edit_quiz_id').value = data.id;
|
||||
|
||||
document.getElementById('addQuizMasterForm').style.display = 'none';
|
||||
document.getElementById('updateModal').style.display = 'block';
|
||||
} catch (error) {
|
||||
console.error('Error fetching quiz master:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFormSubmit(event, endpoint) {
|
||||
event.preventDefault();
|
||||
const form = event.target;
|
||||
@ -150,39 +234,19 @@
|
||||
fetchQuizMasters();
|
||||
await fetchUsedFiles();
|
||||
filterFiles();
|
||||
if (endpoint === '/update_quiz_master') {
|
||||
document.getElementById('updateModal').style.display = 'none';
|
||||
document.getElementById('addQuizMasterForm').style.display = 'block';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('message').innerText = 'エラーが発生しました: ' + error.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchQuizMasters() {
|
||||
try {
|
||||
const response = await fetch('/quiz_master');
|
||||
const quizMasters = await response.json();
|
||||
const quizMasterList = document.getElementById('quizMasterList');
|
||||
quizMasterList.innerHTML = '';
|
||||
quizMasters.forEach(qm => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${qm.title}</td>
|
||||
<td>${qm.full_audio_name}</td>
|
||||
<td>${qm.intro_audio_name}</td>
|
||||
<td>${qm.keyword1}</td>
|
||||
<td>${qm.keyword2}</td>
|
||||
<td>${qm.keyword3}</td>
|
||||
<td>
|
||||
<form onsubmit="handleFormSubmit(event, '/delete_quiz_master')">
|
||||
<input type="hidden" name="id" value="${qm.id}">
|
||||
<button type="submit">削除</button>
|
||||
</form>
|
||||
</td>
|
||||
`;
|
||||
quizMasterList.appendChild(tr);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching quiz masters:', error);
|
||||
}
|
||||
function closeUpdateModal() {
|
||||
document.getElementById('updateModal').style.display = 'none';
|
||||
document.getElementById('addQuizMasterForm').style.display = 'block';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
@ -196,6 +260,8 @@
|
||||
<h1>クイズマスタデータ管理</h1>
|
||||
<div id="message" style="color: red; margin-top: 20px;"></div>
|
||||
<button onclick="location.href='/'">トップページに戻る</button>
|
||||
|
||||
<!-- 新規追加フォーム -->
|
||||
<form id="addQuizMasterForm" onsubmit="handleFormSubmit(event, '/add_quiz_master')">
|
||||
<div class="form-group">
|
||||
<label for="title">タイトル:</label>
|
||||
@ -231,24 +297,48 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
<h2>CSVアップロード</h2>
|
||||
<form action="/upload_quiz_master" method="post" enctype="multipart/form-data">
|
||||
<!-- 編集用モーダル -->
|
||||
<div id="updateModal">
|
||||
<form id="updateQuizMasterForm" onsubmit="handleFormSubmit(event, '/update_quiz_master')">
|
||||
<input type="hidden" name="id" id="edit_quiz_id">
|
||||
<div class="form-group">
|
||||
<label for="file">CSVファイル:</label>
|
||||
<input type="file" name="file" id="file" accept=".csv" required>
|
||||
<label for="edit_title">タイトル:</label>
|
||||
<input type="text" name="title" id="edit_title" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="edit_full_audio_name_input">フル音源名:</label>
|
||||
<input type="text" id="edit_full_audio_name_input" name="full_audio_name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="edit_intro_audio_name_input">イントロ音源名:</label>
|
||||
<input type="text" id="edit_intro_audio_name_input" name="intro_audio_name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="edit_keyword1">キーワード1:</label>
|
||||
<input type="text" name="keyword1" id="edit_keyword1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="edit_keyword2">キーワード2:</label>
|
||||
<input type="text" name="keyword2" id="edit_keyword2">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="edit_keyword3">キーワード3:</label>
|
||||
<input type="text" name="keyword3" id="edit_keyword3">
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button type="submit">アップロード</button>
|
||||
</div>
|
||||
</form>
|
||||
<form action="/import_quiz_master" method="post">
|
||||
<div class="button-group">
|
||||
<button type="submit">インポート</button>
|
||||
<button type="submit">更新</button>
|
||||
<button type="button" onclick="closeUpdateModal()">キャンセル</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 音量調整と停止ボタン -->
|
||||
<div style="margin-top: 20px;">
|
||||
<label for="volumeSlider">プレビュー音量:</label>
|
||||
<input type="range" id="volumeSlider" min="0" max="100" value="10" oninput="changeVolume(this.value)">
|
||||
<button onclick="stopAudio()">レビュー再生停止</button>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<h2>クイズマスタ一覧</h2>
|
||||
<table>
|
||||
@ -270,4 +360,3 @@
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user