melhorando build
Deploy / Deploy (push) Successful in 3m14s Details

This commit is contained in:
JotaChina 2026-04-04 13:38:14 -03:00
parent 26cb5630b4
commit b969be724a
2 changed files with 427 additions and 176 deletions

View File

@ -3,170 +3,177 @@ layout: default
title: "Visualizando Projeto"
---
<div id="project-loader" class="section has-text-centered">
<button class="button is-loading is-large is-ghost">
Carregando Projeto...
</button>
</div>
<div id="project-content" class="publication is-hidden">
<div class="container">
<br />
<div class="tabs is-centered is-boxed is-medium mb-6">
{% include sidebar.html %}
</div>
<div
id="project-header"
class="box has-background-white-ter mb-5"
style="
border-top: 4px solid #3273dc;
padding: 8px 16px;
display: flex;
align-items: center;
justify-content: space-between;
"
></div>
<div class="columns">
<div class="column is-8" id="main-content"></div>
<div class="column is-4" id="sidebar-content"></div>
</div>
<div id="project-root">
<div class="section has-text-centered py-6">
<button class="button is-loading is-large is-ghost">Carregando detalhes do projeto...</button>
</div>
</div>
<script type="module">
import { InstrumentFactory } from '{{ "/assets/js/audio/InstrumentFactory.js" | relative_url }}';
import { InstrumentFactory } from '{{ "/assets/js/audio/InstrumentFactory.js" | relative_url }}';
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("DOMContentLoaded", () => {
const factory = new InstrumentFactory();
const params = new URLSearchParams(window.location.search);
const projectId = params.get("id");
const projectId = params.get('id');
if (!projectId) {
window.location.href = '{{ "/projetos/" | relative_url }}';
return;
}
fetch(
'{{ "/mmpSearch/src_mmpSearch/saida_analises/db_final_completo.json" | relative_url }}',
)
.then((r) => r.json())
.then((data) => {
const project = data.find(
(p) =>
p.file === projectId ||
p.arquivo === projectId + ".ogg" ||
p.arquivo === projectId,
fetch('{{ "src_mmpSearch/saida_analises/db_final_completo.json" | relative_url }}')
.then(r => r.json())
.then(data => {
// Busca o projeto pelo nome do arquivo (sem extensão ou com .ogg)
const p = data.find(item =>
item.file === projectId ||
(item.arquivo && item.arquivo.replace('.ogg', '').replace('.mp3', '') === projectId)
);
if (!project) {
document.getElementById("project-loader").innerHTML =
"<h1 class='title'>Projeto não encontrado 😢</h1>";
if (!p) {
document.getElementById('project-root').innerHTML = `
<div class="container has-text-centered">
<h1 class="title is-2 mt-6">😢 Projeto não encontrado</h1>
<p>O arquivo <code>${projectId}</code> não foi localizado na base de dados.</p>
<a href="{{ '/projetos/' | relative_url }}" class="button is-info mt-4">Voltar para a lista</a>
</div>`;
return;
}
renderProjectPage(project, factory);
});
renderProjectTemplate(p, factory);
})
.catch(err => {
console.error("Erro ao carregar JSON:", err);
document.getElementById('project-root').innerHTML = "<div class='notification is-danger'>Erro ao carregar dados do projeto.</div>";
});
});
function renderProjectPage(p, factory) {
const header = document.getElementById("project-header");
const main = document.getElementById("main-content");
const sidebar = document.getElementById("sidebar-content");
function renderProjectTemplate(page, factory) {
const root = document.getElementById('project-root');
const creationUrl = `{{ '/mmpSearch/creation.html?project=' | relative_url }}${page.file}`;
const embedUrl = `${creationUrl}&embed=true`;
const audioFilePath = `{{ 'src_mmpSearch/wav/' | relative_url }}${page.file}.ogg`;
// 1. Render Header
header.innerHTML = `
<div style="display: flex; align-items: center; gap: 15px">
<h1 class="title is-5 m-0"><code>${p.file || p.arquivo}</code></h1>
${p.bpm ? `<span class="tag is-dark">🎵 ${p.bpm}</span>` : ""}
</div>
<div style="display: flex; align-items: center; gap: 15px">
<audio controls style="height: 25px;">
<source src="{{ '/src_mmpSearch/wav/' | relative_url }}${p.file}.ogg" type="audio/ogg">
</audio>
</div>
`;
// 2. Render Tags
let tagsHtml = "";
if (p.tags) {
tagsHtml = `<details class="card mb-4" open style="background-color: #f0f8ff; border: 1px solid #cfe8fc; border-radius: 8px;">
<summary class="card-header" style="cursor: pointer; background-color: #e1f0fa;"><p class="card-header-title">🏷️ Tags</p></summary>
let tagsHtml = '';
if (page.tags) {
tagsHtml = `
<details class="card mb-4" open style="background-color: #f0f8ff; border: 1px solid #cfe8fc; border-radius: 8px;">
<summary class="card-header" style="cursor: pointer; background-color: #e1f0fa; border-bottom: 1px solid #cfe8fc;">
<p class="card-header-title" style="color: #205081">🏷️ Tags do Projeto</p>
</summary>
<div class="card-content">
${Object.entries(p.tags)
.map(
([cat, values]) => `
${Object.entries(page.tags).map(([cat, values]) => {
if (!values || values.length === 0) return '';
return `
<div class="mb-3">
<strong class="is-size-7 is-uppercase" style="color: #5b7da3">${cat}:</strong>
<div class="tags mt-2">${values.map((v) => `<span class="tag is-link is-light">${v}</span>`).join("")}</div>
<div class="tags mt-2">
${values.map(v => `<span class="tag is-link is-light">${v}</span>`).join('')}
</div>
`,
)
.join("")}
</div>`;
}).join('')}
</div>
</details>`;
}
// 3. Render Tracks/Instruments
const tracksHtml = `
let tracksHtml = '';
if (page.tracks && page.tracks.length > 0) {
tracksHtml = `
<details class="card mb-4" open style="background-color: #f0f8ff; border: 1px solid #cfe8fc; border-radius: 8px;">
<summary class="card-header" style="cursor: pointer; background-color: #e1f0fa;"><p class="card-header-title">🎚️ Instrumentos</p></summary>
<div class="card-content">
${(p.tracks || [])
.map(
(track) => `
<div class="box mb-4" style="border-left: 5px solid #3298dc;">
<span class="tag is-link is-light mb-2">📂 ${track.bassline_name || "Track"}</span>
${(track.instruments || [track])
.map(
(inst) => `
<div class="instrument-wrapper p-2 mb-2" style="background: #fff; border-radius: 4px;">
<strong>${inst.instrument_name}</strong>
<div class="mt-2">${renderPatternGrid(inst.patterns)}</div>
<button class="button is-small is-primary is-outlined mt-2 js-play-synth" data-params='${JSON.stringify(inst)}'>Testar Plugin</button>
<summary class="card-header" style="cursor: pointer; background-color: #e1f0fa; border-bottom: 1px solid #cfe8fc;">
<p class="card-header-title" style="color: #205081">🎚️ Instrumentos & Padrões</p>
</summary>
<div class="card-content" style="padding: 1.5rem">
${page.tracks.map(track => {
const instruments = track.instruments || [track];
return `
<div class="box p-0 mb-4" style="background-color: #fff; border: 1px solid #cfe8fc; border-radius: 8px; overflow: hidden;">
<div class="px-4 py-3" style="background-color: #eef6fc; border-bottom: 1px solid #cfe8fc;">
<span class="tag is-link is-light" style="font-weight: 600">📂 ${track.bassline_name || 'Track Principal'}</span>
</div>
`,
)
.join("")}
<div class="p-4">
${instruments.map(inst => renderInstrumentBlock(inst)).join('')}
</div>
`,
)
.join("")}
</div>
</details>
`;
main.innerHTML = tagsHtml + tracksHtml;
// 4. Sidebar Editor
const creationUrl = `{{ '/mmpSearch/creation.html?project=' | relative_url }}${p.file}`;
sidebar.innerHTML = `
<div class="box has-background-info-light" style="position: sticky; top: 20px">
<h3 class="title is-6 has-text-info">🛠️ MMPCreator</h3>
<a href="${creationUrl}" target="_blank" class="button is-info is-fullwidth mb-4">Abrir no Editor</a>
<iframe src="${creationUrl}&embed=true" style="width: 100%; height: 300px; border-radius: 4px; background: #fff;"></iframe>
</div>
`;
document.getElementById("project-loader").classList.add("is-hidden");
document.getElementById("project-content").classList.remove("is-hidden");
// Event Listeners para Áudio
document.querySelectorAll(".js-play-synth").forEach((btn) => {
btn.addEventListener("click", () =>
factory.play(JSON.parse(btn.dataset.params)),
);
});
}
function renderPatternGrid(patterns) {
if (!patterns) return "";
return patterns
.map((p) => {
const steps = p.steps || [];
return `<div style="display: flex; gap: 2px; margin-bottom: 4px;">
${steps.map((s) => `<div style="width: 6px; height: 12px; background: ${s ? "#4caf50" : "#e0e0e0"}; border-radius: 1px;"></div>`).join("")}
</div>`;
})
.join("");
}).join('')}
</div>
</details>`;
}
root.innerHTML = `
<div class="publication">
<div class="container">
<br />
<div class="box has-background-white-ter mb-5" style="border-top: 4px solid #3273dc; padding: 8px 16px; display: flex; align-items: center; justify-content: space-between;">
<div style="display: flex; align-items: center; gap: 15px">
<h1 class="title is-5 m-0"><code>${page.file}</code></h1>
${page.bpm ? `<span class="tag is-dark is-small">🎵 ${page.bpm}</span>` : ''}
</div>
<div style="display: flex; align-items: center; gap: 15px">
<audio controls style="height: 25px; max-width: 220px;">
<source src="${audioFilePath}" type="audio/ogg">
</audio>
</div>
</div>
<div class="columns">
<div class="column is-8">${tagsHtml} ${tracksHtml}</div>
<div class="column is-4">
<div class="box has-background-info-light" style="position: sticky; top: 20px">
<h3 class="title is-6 has-text-info mb-3">🛠️ Editor Colaborativo</h3>
<a href="${creationUrl}" target="_blank" class="button is-info is-fullwidth is-medium mb-4 shadow-sm">Abrir no MMPCreator</a>
<div class="mt-4 is-hidden-mobile">
<p class="title is-7 has-text-info-dark mb-2">Prévia do Editor:</p>
<iframe src="${embedUrl}" style="width: 100%; height: 300px; border: 1px solid #ccc; border-radius: 4px; background: #fff;"></iframe>
</div>
</div>
</div>
</div>
</div>
</div>`;
// Ativa os botões de teste de synth
document.querySelectorAll('.js-play-synth-btn').forEach(btn => {
btn.addEventListener('click', () => {
const data = JSON.parse(btn.dataset.params);
factory.play(data);
});
});
}
function renderInstrumentBlock(inst) {
const isSample = (inst.instrument_name && inst.instrument_name.toLowerCase() === 'audiofileprocessor') || !!inst.audiofileprocessor;
let displayName = inst.instrument_name;
if (isSample && inst.patterns && inst.patterns.length > 0) {
displayName = inst.patterns[0].name ? inst.patterns[0].name.replace(/\.(ogg|mp3|flac|wav)$/i, '') : displayName;
}
return `
<div class="instrument-wrapper mb-4 p-2" style="border-left: 3px solid #3298dc; background: #fff">
<details open>
<summary style="cursor: pointer; margin-bottom: 0.5rem">
<span class="tag is-info is-light"><strong>${displayName}</strong> ${isSample ? '' : '<span class="ml-1" style="font-size: 0.7em">(Synth 🎹)</span>'}</span>
</summary>
${(inst.patterns || []).map(pat => renderPatternSteps(pat)).join('')}
</details>
<div class="playback-controls mt-2 pl-4">
${isSample ?
(inst.audiofileprocessor?.src ? `<audio controls style="height: 30px; width: 250px;"><source src="{{ '/src/samples/' | relative_url }}${inst.audiofileprocessor.src}" type="audio/ogg"></audio>` : '<p class="is-size-7 has-text-grey-light">Sample não disponível</p>')
: `<button class="button is-small is-primary is-outlined js-play-synth-btn" data-params='${JSON.stringify(inst)}'>Testar Plugin</button>`
}
</div>
</div>`;
}
function renderPatternSteps(pattern) {
if (!pattern.steps || pattern.steps.length === 0) return '';
return `
<div class="mt-2 ml-4" style="display: flex; flex-wrap: wrap; gap: 4px">
${pattern.steps.map(step => `
<div style="width: 5px; height: 10px; background-color: ${step ? '#4caf50' : '#e0e0e0'}; border-radius: 1px;"></div>
`).join('')}
</div>`;
}
</script>

View File

@ -4,6 +4,11 @@ title: MMPSearch - Projetos
permalink: /projetos/
---
{% assign list_plugins = "" %}{% assign list_instruments = "" %} {% for p in
site.data.all %}{% for item in p.tags.plugin %}{% assign list_plugins =
list_plugins | append: item | append: "|||" %}{% endfor %}{% endfor %} {% assign
unique_plugins = list_plugins | split: "|||" | uniq | sort %}
<div class="publication">
<div class="container is-fluid">
<br />
@ -11,14 +16,109 @@ permalink: /projetos/
{% include sidebar.html %}
</div>
<div class="columns is-vcentered mb-4">
<div class="column">
<h1 class="title is-3 has-text-grey-dark">📁 Projetos Disponíveis</h1>
</div>
<div class="column is-narrow">
<div class="field is-grouped">
<div class="control">
<div class="select is-small is-rounded">
<select id="sort-select">
<option value="default">Ordem Alfabética</option>
<option value="bpm_desc">BPM (Rápido)</option>
</select>
</div>
</div>
<div class="control">
<button
id="reset-all-filters"
class="button is-danger is-light is-small"
>
Limpar Tudo
</button>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="column is-3">
<div class="box p-3" id="sidebar-filters"></div>
<div class="box p-3" style="background: #fcfcfc">
<div class="field mb-5">
<label class="label is-size-7">BUSCA TEXTUAL</label>
<div class="control has-icons-left">
<input
class="input is-small"
type="text"
id="search-input"
placeholder="Nome, plugin..."
/><span class="icon is-small is-left"
><i class="fa-solid fa-magnifying-glass"></i
></span>
</div>
</div>
<hr />
<details open>
<summary
class="menu-label has-text-weight-bold mb-2"
style="cursor: pointer"
>
🥁 Pattern Rítmico
</summary>
<div
id="pattern-search-box"
style="
display: flex;
gap: 4px;
flex-wrap: wrap;
justify-content: center;
"
>
${Array.from({length: 16}).map((_, i) => `
<div
class="search-step"
data-index="${i}"
style="
width: 22px;
height: 35px;
background: #eee;
border: 1px solid #ccc;
border-radius: 3px;
cursor: pointer;
"
></div>
`).join('')}
</div>
</details>
<hr />
<label class="label is-size-7">BPM RANGE</label>
<div class="columns is-mobile is-variable is-1">
<div class="column">
<input
class="input is-small"
type="number"
id="bpm-min"
value="0"
/>
</div>
<div class="column">
<input
class="input is-small"
type="number"
id="bpm-max"
value="300"
/>
</div>
</div>
</div>
</div>
<div class="column is-9">
<div class="notification is-info is-light mb-4" id="results-count-bar">
Encontrados <span id="visible-count">0</span> projetos.
</div>
<div id="project-list" class="columns is-multiline"></div>
<div id="no-results" class="has-text-centered is-hidden mt-6">
<p class="subtitle has-text-grey">Nenhum projeto encontrado.</p>
</div>
@ -27,43 +127,187 @@ permalink: /projetos/
</div>
</div>
<style>
.search-step.is-active {
background-color: #3273dc !important;
border-color: #205081 !important;
}
.project-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(50, 115, 220, 0.15);
border-color: #3273dc !important;
transition: 0.3s;
}
.pattern-mini-grid {
display: inline-flex;
gap: 1px;
padding: 2px;
border: 1px solid #deeaf6;
border-radius: 2px;
background: #fff;
margin-right: 2px;
}
.mini-step {
width: 3px;
height: 6px;
border-radius: 1px;
}
.mini-step.active {
background-color: #3273dc;
}
.mini-step.inactive {
background-color: #eee;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
const projectList = document.getElementById("project-list");
const JSON_URL =
'{{ "/mmpSearch/src_mmpSearch/saida_analises/db_final_completo.json" | relative_url }}';
const searchInput = document.getElementById("search-input");
const bpmMin = document.getElementById("bpm-min");
const bpmMax = document.getElementById("bpm-max");
const countSpan = document.getElementById("visible-count");
const searchSteps = document.querySelectorAll(".search-step");
fetch(JSON_URL)
let allData = [];
let activePatternChunks = [];
// 1. Carregar Dados
fetch(
'{{ "/mmpSearch/src_mmpSearch/saida_analises/db_final_completo.json" | relative_url }}',
)
.then((r) => r.json())
.then((data) => {
renderAllCards(data);
// Aqui você chama as suas funções de filtro originais (applyGlobalFilters)
// que agora vão atuar sobre os elementos criados dinamicamente.
allData = data;
renderCards(allData);
applyFilters();
});
function renderAllCards(projects) {
projectList.innerHTML = projects
.map(
(p) => `
// 2. Função de Renderização de Cards
function renderCards(data) {
projectList.innerHTML = data
.map((p) => {
const pBpm = p.bpm || 0;
// Extração de chunks para o filtro de pattern (lógica idêntica ao original)
let chunks = [];
p.tracks?.forEach((t) => {
(t.instruments || [t]).forEach((inst) => {
inst.patterns?.forEach((pat) => {
for (let i = 0; i < (pat.steps?.length || 0); i += 4) {
const chunk = pat.steps
.slice(i, i + 4)
.map((s) => (s ? "1" : "0"))
.join("");
if (chunk !== "0000" && !chunks.includes(chunk))
chunks.push(chunk);
}
});
});
});
return `
<div class="column is-4 project-item"
data-name="${p.file.toLowerCase()}"
data-bpm="${p.bpm || 0}">
<div class="card project-card" style="height: 100%; border-radius: 12px; background-color: #f0f8ff;">
<a href="{{ '/projeto/?id=' | relative_url }}${p.file}" style="text-decoration: none;">
<div class="card-content has-text-centered">
<div class="icon is-large has-text-info"><i class="fa-solid fa-music fa-2x"></i></div>
<p class="title is-6 mt-3">${p.file.replace(".html", "")}</p>
<span class="tag is-dark is-rounded">🎵 ${p.bpm || "N/A"} BPM</span>
data-bpm="${pBpm}"
data-patterns="${chunks.join(",")}">
<div class="card project-card" style="height: 100%; border-radius: 12px; border: 1px solid #cfe8fc; display: flex; flex-direction: column;">
<a href="{{ '/projeto/?id=' | relative_url }}${p.file}" style="text-decoration: none; flex: 1;">
<div class="card-content has-text-centered p-4">
<div style="width: 50px; height: 50px; background: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 10px;">
<span class="icon" style="color: #3273dc;"><i class="fa-solid fa-music fa-lg"></i></span>
</div>
</a>
<footer class="card-footer">
<a href="{{ '/projeto/?id=' | relative_url }}${p.file}" class="card-footer-item">Ver Detalhes</a>
</footer>
<p class="title is-6 mb-2" style="color: #205081;">${p.file.replace(".html", "")}</p>
<div class="bpm-tag mb-3">
<span class="tag is-dark is-rounded is-light">🎵 ${pBpm} BPM</span>
</div>
<div class="patterns-preview" style="min-height: 20px;">
${chunks
.slice(0, 5)
.map(
(c) => `
<div class="pattern-mini-grid">
${c
.split("")
.map(
(b) =>
`<div class="mini-step ${b === "1" ? "active" : "inactive"}"></div>`,
)
.join("")}
</div>
`,
)
.join("")}
</div>
</div>
</a>
<footer class="card-footer" style="border-top: 1px solid #cfe8fc;">
<a href="{{ '/projeto/?id=' | relative_url }}${p.file}" class="card-footer-item" style="font-size: 0.8rem; font-weight: 600;">Ver Detalhes</a>
</footer>
</div>
</div>`;
})
.join("");
}
// 3. Lógica de Filtro
function applyFilters() {
const text = searchInput.value.toLowerCase();
const min = parseInt(bpmMin.value) || 0;
const max = parseInt(bpmMax.value) || 999;
let count = 0;
document.querySelectorAll(".project-item").forEach((item) => {
const name = item.dataset.name;
const bpm = parseInt(item.dataset.bpm);
const pPatterns = item.dataset.patterns.split(",");
const matchText = name.includes(text);
const matchBpm = bpm >= min && bpm <= max;
const matchPattern =
activePatternChunks.length === 0 ||
activePatternChunks.every((c) => pPatterns.includes(c));
if (matchText && matchBpm && matchPattern) {
item.style.display = "block";
count++;
} else {
item.style.display = "none";
}
});
countSpan.textContent = count;
}
// 4. Event Listeners
searchInput.addEventListener("input", applyFilters);
bpmMin.addEventListener("input", applyFilters);
bpmMax.addEventListener("input", applyFilters);
searchSteps.forEach((step) => {
step.addEventListener("click", function () {
this.classList.toggle("is-active");
updatePatternChunks();
applyFilters();
});
});
function updatePatternChunks() {
let full = Array.from(searchSteps)
.map((s) => (s.classList.contains("is-active") ? "1" : "0"))
.join("");
activePatternChunks = (full.match(/.{1,4}/g) || []).filter(
(c) => c !== "0000",
);
}
document
.getElementById("reset-all-filters")
.addEventListener("click", () => {
searchInput.value = "";
bpmMin.value = 0;
bpmMax.value = 300;
searchSteps.forEach((s) => s.classList.remove("is-active"));
activePatternChunks = [];
applyFilters();
});
});
</script>