peertube-plugin-transposer-connector
Version:
Transposer connector is a PeerTube language tool plugin to transcribe and translate with Whisper
543 lines (465 loc) • 18.2 kB
JavaScript
async function register({ registerClientRoute, peertubeHelpers }) {
const { notifier } = peertubeHelpers;
function getInstanceBaseUrl() {
return window.location.origin;
}
function getQueryParam(name) {
var url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return "";
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
registerClientRoute({
route: "/transposer/translate",
onMount: ({ rootEl }) => {
rootEl.innerHTML = getPluginContainerHTML();
function getPluginContainerHTML() {
return `
<div id="plugincontainer">
<h1>Translate Video Caption</h1>
<button id="addCaptionButton">Add Caption</button><button id="backButton">Back</button><br><br>
</div>`;
}
function getTranslationPage() {
var videoId = getQueryParam("videoId");
if (videoId == null) {
console.log("No Video Id in query param");
window.open(`${getInstanceBaseUrl()}/p/transposer`, "_self");
return;
}
const addCaptionButton = rootEl.querySelector("#addCaptionButton");
addCaptionButton.addEventListener("click", () => {
console.log("New caption button clicked");
window.open(
`${getInstanceBaseUrl()}/p/transposer/translation/add?videoId=${videoId}`,
"_self"
);
});
const backButton = rootEl.querySelector("#backButton");
backButton.addEventListener("click", () => {
console.log("back button clicked");
window.open(`${getInstanceBaseUrl()}/p/transposer`, "_self");
});
fetch(`${getInstanceBaseUrl()}/api/v1/videos/${videoId}/captions`, {
method: "GET",
headers: peertubeHelpers.getAuthHeader(),
})
.then((captionResponse) => {
if (!captionResponse.ok) {
throw new Error(
`Failed to fetch captions: ${captionResponse.status} ${captionResponse.statusText}`
);
}
return captionResponse.json();
})
.then((captionData) => {
const captions = captionData.data;
const table = document.createElement("table");
table.classList.add("caption-table");
const thead = document.createElement("thead");
const headerRow = document.createElement("tr");
["Language", "Uploaded", "Edit", "Delete"].forEach((headerText) => {
const th = document.createElement("th");
th.textContent = headerText;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
const tbody = document.createElement("tbody");
if (captions.length === 0) {
const noCaptionRow = document.createElement("tr");
const noCaptionCell = document.createElement("td");
noCaptionCell.setAttribute("colspan", "4");
noCaptionCell.textContent = "No captions available.";
noCaptionRow.appendChild(noCaptionCell);
tbody.appendChild(noCaptionRow);
} else {
captions.forEach((caption) => {
const row = document.createElement("tr");
const languageCell = document.createElement("td");
languageCell.textContent = caption.language.label; // Assuming 'language' is an object with a 'label' property
row.appendChild(languageCell);
const uploadedCell = document.createElement("td");
uploadedCell.textContent = caption.updatedAt; // Assuming 'updatedAt' contains the uploaded timestamp
row.appendChild(uploadedCell);
const editCell = document.createElement("td");
const editButton = document.createElement("button");
editButton.textContent = "Edit";
editButton.addEventListener("click", () => {
console.log("Edit button clicked for caption:", caption);
window.open(
`${getInstanceBaseUrl()}/p/transposer/translation/edit?videoId=${videoId}&lang=${
caption.language.id
}`,
"_self"
);
});
editCell.appendChild(editButton);
row.appendChild(editCell);
// Delete button
const deleteCell = document.createElement("td");
const deleteButton = document.createElement("button");
deleteButton.textContent = "Delete";
deleteButton.addEventListener("click", () => {
const videoId = getQueryParam("videoId");
if (
!confirm("Are you sure you want to delete this caption?")
) {
return;
}
fetch(
`${getInstanceBaseUrl()}/api/v1/videos/${videoId}/captions/${
caption.language.id
}`,
{
method: "DELETE",
headers: peertubeHelpers.getAuthHeader(),
}
)
.then((response) => {
if (!response.ok) {
throw new Error(
`Failed to delete caption: ${response.status} ${response.statusText}`
);
}
notifier.success("Caption deleted successfully.");
window.location.reload();
})
.catch((error) => {
notifier.error(`Error deleting caption: ${error}`);
console.error("Error deleting caption:", error);
});
});
deleteCell.appendChild(deleteButton);
row.appendChild(deleteCell);
tbody.appendChild(row);
});
}
table.appendChild(tbody);
const pluginContainer = document.getElementById("plugincontainer");
pluginContainer.appendChild(table);
})
.catch((error) => {
console.error("Error fetching captions:", error);
const pluginContainer = document.getElementById("plugincontainer");
pluginContainer.textContent = "Error fetching captions.";
});
}
function getThemeStyles() {
return `
#plugincontainer {
max-width: 100%;
padding: 20px;
position: relative;
}
#plugincontainer h1 {
color: var(--mainColor);
}
#plugincontainer #formcontainer {
display: flex;
flex-wrap: wrap;
}
#plugincontainer .modifyButton,
#plugincontainer .saveButton,
#plugincontainer .cancelButton,
#plugincontainer .deleteButton {
cursor: pointer;
padding: 20px;
}
#plugincontainer input,
#plugincontainer textarea,
#plugincontainer select {
display: block;
font-size: 16px;
padding: var(--input-y-padding, 8px) var(--input-x-padding, 12px);
width: 100%;
max-width: 100%;
color: var(--input-fg, #000);
background-color: var(--input-bg, #fff);
border: 1px solid var(--input-border-color, #ccc);
border-radius: var(--input-border-radius, 4px);
line-height: 24px;
transition: background-color 0.3s ease;
}
#plugincontainer p {
font-size: 18px;
}
#plugincontainer table {
width: 100%;
border-collapse: collapse;
margin: 25px 0;
font-size: 0.9em;
min-width: 130px;
min-height: 130px;
}
#plugincontainer table tr {
text-align: left;
}
#plugincontainer table th,
#plugincontainer table td {
padding: 12px 15px;
border-bottom: 1px solid var(--inputPlaceholderColor, #ccc);
}
#plugincontainer table th {
background-color: var(--greyBackgroundColor, #f8f9fa);
color: var(--mainForegroundColor);
}
/* General button styling */
#plugincontainer button {
color: var(--on-primary, var(--mainForegroundColor));
background-color: var(--primary, var(--mainColor));
border: 1px solid var(--primary, var(--mainColor));
padding: var(--input-y-padding, 8px) var(--input-x-padding, 12px);
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
vertical-align: top;
}
/* Top-level action buttons */
#plugincontainer > button {
width: auto;
min-width: 100px;
margin-right: 10px;
}
/* Table buttons */
#plugincontainer td button {
width: auto;
min-width: 80px;
margin: 0 5px;
}
#plugincontainer button:hover {
background-color: var(--secondaryColor);
}
#plugincontainer button:disabled {
background-color: var(--greyBackgroundColor, #cccccc);
color: var(--greyForegroundColor, #666666);
cursor: not-allowed;
}
/* Action buttons like in the video list */
#plugincontainer .action-button {
position: relative;
width: 56px;
height: 56px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: unset;
padding: 8px;
}
#plugincontainer .action-button.success {
background-color: var(--primary, var(--mainColor));
border-color: var(--primary, var(--mainColor));
}
#plugincontainer .action-button.not-triggered {
background-color: var(--greyBackgroundColor, #f8f9fa);
border-color: var(--greyForegroundColor, #ccc);
color: var(--greyForegroundColor, #666);
}
#plugincontainer .action-button svg {
width: 24px;
height: 24px;
margin-bottom: 6px;
fill: var(--on-primary, var(--mainForegroundColor));
stroke: var(--on-primary, var(--mainForegroundColor));
}
#plugincontainer .action-button.not-triggered svg {
fill: var(--greyForegroundColor, #666);
stroke: var(--greyForegroundColor, #666);
}
/* Specialized button styles */
#plugincontainer .translate-field-btn {
background-color: var(--greyBackgroundColor, #6c757d);
font-size: 0.8em;
width: auto;
padding: 2px 5px;
margin-left: 5px;
min-width: unset;
}
#plugincontainer .translate-field-btn:hover {
background-color: var(--secondaryColor, #5a6268);
}
#plugincontainer #triggerDetail {
background-color: var(--primary, var(--mainColor));
border: 1px solid var(--primary, var(--mainColor));
border-radius: 4px;
padding: 6px;
display: flex;
align-items: center;
justify-content: center;
}
#plugincontainer #triggerDetail svg {
fill: var(--on-primary, var(--mainForegroundColor));
stroke: var(--on-primary, var(--mainForegroundColor));
}
#plugincontainer .edit-icon path {
stroke: var(--on-primary, var(--mainForegroundColor));
}
/* Rest of the styles remain the same */
#plugincontainer #pageButtonsContainer {
margin: 15px 0;
text-align: center;
}
#plugincontainer #confirmationBox {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--primary, var(--mainColor));
padding: 20px;
border-radius: 5px;
}
#plugincontainer #confirmationBox p {
margin-bottom: 20px;
color: var(--on-primary, var(--mainForegroundColor));
}
#plugincontainer #confirmationBox button {
margin-right: 10px;
padding: 8px 16px;
border: none;
border-radius: 3px;
background-color: var(--on-primary, var(--mainForegroundColor));
color: var(--primary, var(--mainColor));
cursor: pointer;
}
#plugincontainer .page-button {
background-color: var(--primary, var(--mainColor));
min-width: unset;
width: auto;
}
#plugincontainer .current-page-button {
background-color: var(--secondaryColor);
}
/* Action buttons and Header Styles */
#plugincontainer .action-column-header {
padding: 0;
}
#plugincontainer .action-headers-container {
display: flex;
justify-content: space-between;
width: 100%;
}
#plugincontainer .action-header {
flex: 1;
text-align: center;
font-size: 0.9em;
font-weight: bold;
padding: 8px 4px;
white-space: nowrap;
}
#plugincontainer .action-buttons-row {
display: flex;
justify-content: space-between;
width: 100%;
}
#plugincontainer .action-cell {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 4px;
}
#plugincontainer .action-button.label-xl {
font-size: 90%;
padding: var(--input-y-padding) calc(var(--input-x-padding) / 2) !important;
}
#plugincontainer .counter-label {
font-size: 0.75em;
margin-top: 4px;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Language notice styles */
#plugincontainer .language-notice {
margin: 10px 0;
padding: 10px;
border-radius: 4px;
}
#plugincontainer .language-notice.success {
background-color: #e6ffe6;
border: 1px solid #b3ffb3;
}
#plugincontainer .language-notice.warning {
background-color: #fff8e6;
border: 1px solid #ffe0b3;
}
#plugincontainer .language-notice p {
margin: 0;
font-size: 14px;
}
/* Field container styles */
#plugincontainer .field-container {
display: flex;
align-items: center;
width: 100%;
}
#plugincontainer .field-container input,
#plugincontainer .field-container textarea {
flex: 1;
min-width: 200px;
}
/* Modal styles */
#plugincontainer .modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
#plugincontainer .modal-content {
background-color: var(--backgroundPrimary, white);
padding: 20px;
border-radius: 5px;
width: 400px;
text-align: center;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#plugincontainer .modal-content h3 {
margin-top: 0;
color: var(--primary, var(--mainColor));
}
#plugincontainer .modal-content button {
margin: 10px;
padding: 10px 15px;
min-width: 100px;
}
/* For the translation status colors */
#plugincontainer input[style*="background-color: #e6ffe6"],
#plugincontainer textarea[style*="background-color: #e6ffe6"] {
background-color: #e6ffe6 !important;
}
#plugincontainer input[style*="background-color: #ffe6e6"],
#plugincontainer textarea[style*="background-color: #ffe6e6"] {
background-color: #ffe6e6 !important;
}
#plugincontainer input[style*="background-color: #fff8e6"],
#plugincontainer textarea[style*="background-color: #fff8e6"] {
background-color: #fff8e6 !important;
}
`;
}
function initialize() {
getTranslationPage();
const style = document.createElement("style");
style.innerHTML = getThemeStyles();
rootEl.appendChild(style);
}
initialize();
},
});
}
export { register };