soiz1's picture
Update templates/dev.html
76f2fc4 verified
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin File Manager</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.breadcrumb {
padding: 10px 0;
margin-bottom: 20px;
font-size: 16px;
}
.breadcrumb a {
color: #3498db;
text-decoration: none;
}
.search-box {
margin-bottom: 20px;
}
.search-box input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.file-list {
list-style: none;
padding: 0;
margin-top: 20px;
}
.file-item, .folder-item {
padding: 12px 15px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.2s;
}
.folder-item {
background-color: #f9f9f9;
}
.file-item:hover, .folder-item:hover {
background-color: #f0f0f0;
}
.item-info {
flex: 1;
display: flex;
align-items: center;
}
.item-icon {
margin-right: 10px;
font-size: 20px;
}
.folder-icon {
color: #ffb74d;
}
.file-icon {
color: #64b5f6;
}
.item-name {
font-weight: bold;
}
.item-path {
color: #666;
font-size: 0.9em;
margin-left: 10px;
}
.item-actions {
display: flex;
gap: 8px;
}
.action-btn {
padding: 6px 12px;
border-radius: 4px;
text-decoration: none;
font-size: 14px;
color: white;
cursor: pointer;
border: none;
}
.open-btn {
background-color: #4caf50;
}
.delete-btn {
background-color: #f44336;
}
.rename-btn {
background-color: #ff9800;
}
.move-btn {
background-color: #9c27b0;
}
.upload-form {
margin: 25px 0;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #eee;
}
.form-title {
margin-top: 0;
margin-bottom: 15px;
font-size: 18px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.form-actions {
margin-top: 15px;
}
.btn {
padding: 10px 15px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn:hover {
background-color: #2980b9;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.7);
}
.modal-content {
background-color: white;
margin: 5% auto;
padding: 25px;
border-radius: 8px;
width: 80%;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.modal-title {
margin-top: 0;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #333;
}
.dialog {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 0 20px rgba(0,0,0,0.2);
z-index: 1001;
width: 450px;
max-width: 90%;
}
.dialog-title {
margin-top: 0;
margin-bottom: 20px;
}
.dialog-footer {
margin-top: 20px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn-secondary {
background-color: #6c757d;
}
.no-items {
padding: 30px;
text-align: center;
color: #666;
font-size: 16px;
}
</style>
</head>
<body>
<div class="container">
<h1>Admin File Manager</h1>
<div class="breadcrumb" id="breadcrumb">
<a href="#" onclick="loadDirectory('')">Home</a>
</div>
<div class="search-box">
<input type="text" id="searchInput" class="form-control" placeholder="ファイルまたはフォルダを検索..." oninput="filterFiles()">
</div>
<form id="uploadForm" action="{{ url_for('upload_file') }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="dir" id="currentDir" value="">
<div class="form-group">
<input type="file" name="file" class="form-control" required>
</div>
<div class="form-actions">
<button type="submit" class="btn">アップロード</button>
</div>
</form>
<div class="upload-form">
<h3 class="form-title">フォルダを作成</h3>
<form id="createFolderForm" action="{{ url_for('create_folder') }}" method="post">
<div class="form-group">
<input type="text" name="folder_name" class="form-control" placeholder="フォルダ名を入力" required>
</div>
<div class="form-actions">
<button type="submit" class="btn">作成</button>
</div>
</form>
</div>
<ul class="file-list" id="fileList"></ul>
</div>
<!-- プレビューモーダル -->
<div id="previewModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<div id="modalContent"></div>
<div id="modalCaption" style="margin-top: 15px; text-align: center;"></div>
</div>
</div>
<!-- リネームダイアログ -->
<div id="renameDialog" class="dialog">
<h3 class="dialog-title">名前を変更</h3>
<form id="renameForm" method="post" action="{{ url_for('rename_file') }}">
<input type="hidden" name="old_name" id="oldName">
<div class="form-group">
<label for="newName">新しい名前:</label>
<input type="text" name="new_name" id="newName" class="form-control" required>
</div>
<div class="dialog-footer">
<button type="button" class="btn btn-secondary" onclick="closeDialog('renameDialog')">キャンセル</button>
<button type="submit" class="btn">変更</button>
</div>
</form>
</div>
<!-- 移動ダイアログ -->
<div id="moveDialog" class="dialog">
<h3 class="dialog-title">移動</h3>
<form id="moveForm" method="post" action="{{ url_for('move_file') }}">
<input type="hidden" name="filename" id="moveName">
<div class="form-group">
<label for="destination">移動先のパス:</label>
<input type="text" name="destination" id="destination" class="form-control" required>
</div>
<div class="dialog-footer">
<button type="button" class="btn btn-secondary" onclick="closeDialog('moveDialog')">キャンセル</button>
<button type="submit" class="btn">移動</button>
</div>
</form>
</div>
<script>
let allFiles = [];
let allDirs = [];
let currentDir = '';
// ページ読み込み時にディレクトリを読み込む
document.addEventListener('DOMContentLoaded', function() {
const params = new URLSearchParams(window.location.search);
const dir = params.get('dir') || '';
loadDirectory(dir);
// フォームの送信をインターセプトしてリロードを防ぐ
document.getElementById('uploadForm').addEventListener('submit', function(e) {
e.preventDefault();
uploadFile(this);
});
document.getElementById('createFolderForm').addEventListener('submit', function(e) {
e.preventDefault();
createFolder(this);
});
});
// ディレクトリを読み込む関数
function loadDirectory(dir = '') {
document.getElementById('currentDir').value = dir;
fetch(`/get_file_json?dir=${encodeURIComponent(dir)}`)
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
return;
}
currentDir = dir;
allFiles = data.all_files || [];
allDirs = data.all_dirs || [];
updateBreadcrumb(data.current_dir);
renderFileList(data.files, data.dirs, data.current_dir);
// URLを更新
const newUrl = new URL(window.location);
newUrl.searchParams.set('dir', dir);
window.history.pushState({}, '', newUrl);
})
.catch(error => {
console.error('Error loading directory:', error);
alert('ディレクトリの読み込みに失敗しました。');
});
}
// ファイルをアップロード
function uploadFile(form) {
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
loadDirectory(currentDir);
form.reset();
} else {
alert('ファイルのアップロードに失敗しました。');
}
})
.catch(error => {
console.error('Error:', error);
alert('ファイルのアップロード中にエラーが発生しました。');
});
}
// フォルダを作成
function createFolder(form) {
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 変更: 作成したフォルダのあるディレクトリを再読み込み
const parentDir = data.folder_path.split('/').slice(0, -1).join('/');
loadDirectory(parentDir);
form.reset();
} else {
alert('フォルダ作成エラー: ' + (data.error || '不明なエラー'));
}
})
.catch(error => {
console.error('Error:', error);
alert('フォルダ作成中にエラーが発生しました。');
});
}
// ファイルをフィルタリング
function filterFiles() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
if (!searchTerm) {
loadDirectory(currentDir);
return;
}
const filteredFiles = allFiles.filter(file =>
file.name.toLowerCase().includes(searchTerm) ||
file.path.toLowerCase().includes(searchTerm)
);
const filteredDirs = allDirs.filter(dir =>
dir.name.toLowerCase().includes(searchTerm) ||
dir.path.toLowerCase().includes(searchTerm)
);
renderFileList(filteredFiles, filteredDirs, currentDir);
}
// パンくずリストを更新
function updateBreadcrumb(currentDir) {
const breadcrumb = document.getElementById('breadcrumb');
let html = '<a href="#" onclick="loadDirectory(\'\')">Home</a>';
let path = '';
if (currentDir) {
currentDir.split('/').forEach(part => {
if (part) {
path = path ? `${path}/${part}` : part;
html += ` / <a href="#" onclick="loadDirectory('${path}')">${part}</a>`;
}
});
}
breadcrumb.innerHTML = html;
}
// ファイルリストを表示
function renderFileList(files, dirs, currentDir) {
const fileList = document.getElementById('fileList');
if (files.length === 0 && dirs.length === 0) {
fileList.innerHTML = '<li class="no-items">ファイルまたはフォルダが見つかりません</li>';
return;
}
let html = '';
// フォルダを表示
dirs.forEach(dir => {
html += `
<li class="folder-item" data-path="${dir.path}">
<div class="item-info">
<span class="item-icon folder-icon">📁</span>
<span class="item-name">${dir.name}</span>
<span class="item-path">${dir.path}</span>
</div>
<div class="item-actions">
<button class="action-btn open-btn" onclick="loadDirectory('${dir.path}')">開く</button>
<button class="action-btn rename-btn" onclick="showRenameDialog('${dir.name}', '${dir.path}')">名前変更</button>
<button class="action-btn delete-btn" onclick="deleteItem('${dir.path}')">削除</button>
<button class="action-btn move-btn" onclick="showMoveDialog('${dir.path}')">移動</button>
</div>
</li>
`;
});
// ファイルを表示
files.forEach(file => {
html += `
<li class="file-item" data-path="${file.path}">
<div class="item-info">
<span class="item-icon file-icon">📄</span>
<span class="item-name">${file.name}</span>
<span class="item-path">${file.path}</span>
</div>
<div class="item-actions">
<button class="action-btn open-btn" onclick="previewFile('${file.path}', '${file.name}')">開く</button>
<a href="/download/${file.path}" class="action-btn open-btn" download>ダウンロード</a>
<button class="action-btn rename-btn" onclick="showRenameDialog('${file.name}', '${file.path}')">名前変更</button>
<button class="action-btn delete-btn" onclick="deleteItem('${file.path}')">削除</button>
<button class="action-btn move-btn" onclick="showMoveDialog('${file.path}')">移動</button>
</div>
</li>
`;
});
fileList.innerHTML = html;
}
// プレビューを表示
function previewFile(filepath, filename) {
const modal = document.getElementById('previewModal');
const modalContent = document.getElementById('modalContent');
const modalCaption = document.getElementById('modalCaption');
const ext = filename.split('.').pop().toLowerCase();
modalCaption.textContent = `${filename} (${filepath})`;
if (['jpg', 'jpeg', 'png', 'gif'].includes(ext)) {
modalContent.innerHTML = `<img src="/shared_files/${filepath}" style="max-width:100%; max-height:70vh;">`;
} else if (['mp4', 'webm', 'ogg'].includes(ext)) {
modalContent.innerHTML = `
<video controls autoplay style="max-width:100%; max-height:70vh;">
<source src="/shared_files/${filepath}" type="video/${ext}">
</video>
`;
} else if (['mp3', 'wav', 'ogg'].includes(ext)) {
modalContent.innerHTML = `
<audio controls autoplay>
<source src="/shared_files/${filepath}" type="audio/${ext}">
</audio>
`;
} else if (ext === 'pdf') {
modalContent.innerHTML = `
<iframe src="https://mozilla.github.io/pdf.js/web/viewer.html?file=${encodeURIComponent(window.location.origin + '/shared_files/' + filepath)}"
style="width:100%; height:70vh; border:none;"></iframe>
`;
} else {
modalContent.innerHTML = `
<p>プレビューできません。 <a href="/download/${filepath}">ダウンロード</a></p>
`;
}
modal.style.display = "block";
}
// モーダルを閉じる
function closeModal() {
document.getElementById('previewModal').style.display = "none";
const mediaElements = document.querySelectorAll('#modalContent video, #modalContent audio');
mediaElements.forEach(el => {
el.pause();
el.currentTime = 0;
});
}
// ダイアログを表示
function showRenameDialog(name, path) {
document.getElementById('oldName').value = path;
document.getElementById('newName').value = name;
document.getElementById('renameDialog').style.display = 'block';
}
function showMoveDialog(path) {
document.getElementById('moveName').value = path;
document.getElementById('destination').value = '';
document.getElementById('moveDialog').style.display = 'block';
}
// ダイアログを閉じる
function closeDialog(id) {
document.getElementById(id).style.display = 'none';
}
// アイテムを削除
function deleteItem(path) {
if (confirm(`"${path}"を削除してもよろしいですか?`)) {
fetch(`/delete/${path}`, {
method: 'GET'
})
.then(response => {
if (response.ok) {
loadDirectory(currentDir);
} else {
alert('削除に失敗しました。');
}
})
.catch(error => {
console.error('Error:', error);
alert('削除中にエラーが発生しました。');
});
}
}
// ブラウザの戻る/進むボタン対応
window.addEventListener('popstate', () => {
const params = new URLSearchParams(window.location.search);
const dir = params.get('dir') || '';
loadDirectory(dir);
});
// モーダルの外側をクリックで閉じる
window.onclick = function(event) {
const modal = document.getElementById('previewModal');
if (event.target === modal) {
closeModal();
}
const renameDialog = document.getElementById('renameDialog');
const moveDialog = document.getElementById('moveDialog');
if (event.target === renameDialog) {
closeDialog('renameDialog');
}
if (event.target === moveDialog) {
closeDialog('moveDialog');
}
};
// 移動処理の修正
function moveItem(path) {
const destination = document.getElementById('destination').value;
if (!destination) {
alert('移動先を指定してください');
return;
}
const formData = new FormData();
formData.append('filename', path);
formData.append('destination', destination);
fetch("{{ url_for('move_file') }}", {
method: 'POST',
body: formData
})
.then(response => {
if (response.redirected) {
window.location.href = response.url;
} else {
return response.json();
}
})
.then(data => {
if (data && !data.success) {
alert('移動エラー: ' + (data.error || '不明なエラー'));
}
})
.catch(error => {
console.error('Error:', error);
alert('移動中にエラーが発生しました。');
});
}
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
// 定期的にキャッシュを更新
setInterval(() => {
registration.update();
}, 60 * 60 * 1000); // 1時間ごとに更新
})
.catch(function(err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
</script>
</body>
</html>