Upload apps/cong-thuc/src/App.svelte with huggingface_hub
Browse files- apps/cong-thuc/src/App.svelte +118 -0
apps/cong-thuc/src/App.svelte
CHANGED
|
@@ -32,6 +32,18 @@
|
|
| 32 |
let startPanX = 0;
|
| 33 |
let startPanY = 0;
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
const latexTemplates = [
|
| 36 |
{ cat: 'Cơ bản', name: 'Phân số', code: '$$ \\frac{a}{b} $$', icon: 'a/b' },
|
| 37 |
{ cat: 'Cơ bản', name: 'Căn bậc n', code: '$$ \\sqrt[n]{x} $$', icon: 'ⁿ√x' },
|
|
@@ -151,8 +163,38 @@ $ A = mat(1, 2; 3, 4) $
|
|
| 151 |
onMount(() => {
|
| 152 |
codeInput = defaultLatex;
|
| 153 |
renderContent();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
});
|
| 155 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
function setMode(newMode: 'latex' | 'typst') {
|
| 157 |
if (mode === newMode) return;
|
| 158 |
mode = newMode;
|
|
@@ -418,6 +460,8 @@ $ A = mat(1, 2; 3, 4) $
|
|
| 418 |
<div class="w-px h-4 bg-zinc-600 mx-1"></div>
|
| 419 |
<button class="w-8 h-8 flex items-center justify-center rounded text-zinc-400 hover:text-white hover:bg-zinc-700 transition-colors font-sans font-bold" on:click={() => insertText('\n## Tiêu đề\n')} title="Tiêu đề (Heading)">H</button>
|
| 420 |
<button class="w-8 h-8 flex items-center justify-center rounded text-zinc-400 hover:text-white hover:bg-zinc-700 transition-colors" on:click={() => insertText('\n- Danh sách\n')} title="Danh sách (List)">•</button>
|
|
|
|
|
|
|
| 421 |
</div>
|
| 422 |
|
| 423 |
<div class="flex gap-3">
|
|
@@ -515,6 +559,80 @@ $ A = mat(1, 2; 3, 4) $
|
|
| 515 |
|
| 516 |
</div>
|
| 517 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
<style>
|
| 519 |
.custom-scrollbar::-webkit-scrollbar {
|
| 520 |
width: 8px;
|
|
|
|
| 32 |
let startPanX = 0;
|
| 33 |
let startPanY = 0;
|
| 34 |
|
| 35 |
+
// Saved Formulas State
|
| 36 |
+
interface SavedFormula {
|
| 37 |
+
id: string;
|
| 38 |
+
name: string;
|
| 39 |
+
code: string;
|
| 40 |
+
mode: 'latex' | 'typst';
|
| 41 |
+
timestamp: number;
|
| 42 |
+
}
|
| 43 |
+
let savedFormulas: SavedFormula[] = [];
|
| 44 |
+
let isManagerOpen = false;
|
| 45 |
+
let newFormulaName = '';
|
| 46 |
+
|
| 47 |
const latexTemplates = [
|
| 48 |
{ cat: 'Cơ bản', name: 'Phân số', code: '$$ \\frac{a}{b} $$', icon: 'a/b' },
|
| 49 |
{ cat: 'Cơ bản', name: 'Căn bậc n', code: '$$ \\sqrt[n]{x} $$', icon: 'ⁿ√x' },
|
|
|
|
| 163 |
onMount(() => {
|
| 164 |
codeInput = defaultLatex;
|
| 165 |
renderContent();
|
| 166 |
+
const saved = localStorage.getItem('linhhuong-saved-formulas');
|
| 167 |
+
if (saved) {
|
| 168 |
+
try { savedFormulas = JSON.parse(saved); } catch (e) {}
|
| 169 |
+
}
|
| 170 |
});
|
| 171 |
|
| 172 |
+
function saveCurrentFormula() {
|
| 173 |
+
if (!newFormulaName.trim() || !codeInput.trim()) return;
|
| 174 |
+
const newFormula: SavedFormula = {
|
| 175 |
+
id: crypto.randomUUID(),
|
| 176 |
+
name: newFormulaName.trim(),
|
| 177 |
+
code: codeInput,
|
| 178 |
+
mode: mode,
|
| 179 |
+
timestamp: Date.now()
|
| 180 |
+
};
|
| 181 |
+
savedFormulas = [newFormula, ...savedFormulas];
|
| 182 |
+
localStorage.setItem('linhhuong-saved-formulas', JSON.stringify(savedFormulas));
|
| 183 |
+
newFormulaName = '';
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
function deleteFormula(id: string) {
|
| 187 |
+
savedFormulas = savedFormulas.filter(f => f.id !== id);
|
| 188 |
+
localStorage.setItem('linhhuong-saved-formulas', JSON.stringify(savedFormulas));
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
function loadFormula(formula: SavedFormula) {
|
| 192 |
+
setMode(formula.mode);
|
| 193 |
+
codeInput = formula.code;
|
| 194 |
+
renderContent();
|
| 195 |
+
isManagerOpen = false;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
function setMode(newMode: 'latex' | 'typst') {
|
| 199 |
if (mode === newMode) return;
|
| 200 |
mode = newMode;
|
|
|
|
| 460 |
<div class="w-px h-4 bg-zinc-600 mx-1"></div>
|
| 461 |
<button class="w-8 h-8 flex items-center justify-center rounded text-zinc-400 hover:text-white hover:bg-zinc-700 transition-colors font-sans font-bold" on:click={() => insertText('\n## Tiêu đề\n')} title="Tiêu đề (Heading)">H</button>
|
| 462 |
<button class="w-8 h-8 flex items-center justify-center rounded text-zinc-400 hover:text-white hover:bg-zinc-700 transition-colors" on:click={() => insertText('\n- Danh sách\n')} title="Danh sách (List)">•</button>
|
| 463 |
+
<div class="w-px h-4 bg-zinc-600 mx-1"></div>
|
| 464 |
+
<button class="w-8 h-8 flex items-center justify-center rounded text-amber-400 hover:text-amber-300 hover:bg-zinc-700 transition-colors" on:click={() => isManagerOpen = true} title="Quản lý Thư viện Công thức">📚</button>
|
| 465 |
</div>
|
| 466 |
|
| 467 |
<div class="flex gap-3">
|
|
|
|
| 559 |
|
| 560 |
</div>
|
| 561 |
|
| 562 |
+
<!-- Manager Popup -->
|
| 563 |
+
{#if isManagerOpen}
|
| 564 |
+
<div class="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm p-8">
|
| 565 |
+
<div class="bg-[#1e1e1e] w-full max-w-4xl h-[80vh] rounded-2xl shadow-2xl flex flex-col overflow-hidden border border-zinc-700">
|
| 566 |
+
|
| 567 |
+
<div class="h-16 border-b border-zinc-700 flex items-center px-6 justify-between bg-zinc-800/50 shrink-0">
|
| 568 |
+
<h2 class="text-lg font-bold text-white flex items-center gap-2"><span>📚</span> Thư viện Công thức</h2>
|
| 569 |
+
<button class="w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/10 text-zinc-400 hover:text-white transition-colors" on:click={() => isManagerOpen = false}>✕</button>
|
| 570 |
+
</div>
|
| 571 |
+
|
| 572 |
+
<div class="flex-1 flex overflow-hidden">
|
| 573 |
+
<div class="w-1/3 border-r border-zinc-700 p-6 flex flex-col bg-zinc-900/30">
|
| 574 |
+
<h3 class="text-sm font-bold text-zinc-300 mb-4 uppercase tracking-wider">Lưu công thức hiện tại</h3>
|
| 575 |
+
<input
|
| 576 |
+
type="text"
|
| 577 |
+
bind:value={newFormulaName}
|
| 578 |
+
placeholder="Nhập tên công thức..."
|
| 579 |
+
class="w-full bg-zinc-800 border border-zinc-700 rounded-lg px-4 py-2.5 text-sm text-white placeholder:text-zinc-500 focus:outline-none focus:border-indigo-500 transition-colors mb-4"
|
| 580 |
+
on:keydown={(e) => e.key === 'Enter' && saveCurrentFormula()}
|
| 581 |
+
/>
|
| 582 |
+
<button
|
| 583 |
+
class="w-full py-2.5 bg-indigo-600 hover:bg-indigo-500 text-white font-bold rounded-lg shadow-lg transition-colors flex justify-center items-center gap-2 disabled:opacity-50"
|
| 584 |
+
on:click={saveCurrentFormula}
|
| 585 |
+
disabled={!newFormulaName.trim() || !codeInput.trim()}
|
| 586 |
+
>
|
| 587 |
+
<span>💾</span> Lưu vào Thư viện
|
| 588 |
+
</button>
|
| 589 |
+
|
| 590 |
+
<div class="mt-8">
|
| 591 |
+
<h3 class="text-sm font-bold text-zinc-300 mb-2 uppercase tracking-wider">Thống kê</h3>
|
| 592 |
+
<div class="flex gap-4 text-xs text-zinc-500">
|
| 593 |
+
<div class="bg-zinc-800 px-3 py-2 rounded border border-zinc-700">LaTeX: {savedFormulas.filter(f => f.mode === 'latex').length}</div>
|
| 594 |
+
<div class="bg-zinc-800 px-3 py-2 rounded border border-zinc-700">Typst: {savedFormulas.filter(f => f.mode === 'typst').length}</div>
|
| 595 |
+
</div>
|
| 596 |
+
</div>
|
| 597 |
+
</div>
|
| 598 |
+
|
| 599 |
+
<div class="w-2/3 p-6 overflow-y-auto custom-scrollbar bg-[#1e1e1e]">
|
| 600 |
+
<h3 class="text-sm font-bold text-zinc-300 mb-4 uppercase tracking-wider">Thư viện của tôi ({savedFormulas.length})</h3>
|
| 601 |
+
{#if savedFormulas.length === 0}
|
| 602 |
+
<div class="flex flex-col items-center justify-center h-48 text-zinc-500 border-2 border-dashed border-zinc-700 rounded-xl">
|
| 603 |
+
<span class="text-3xl mb-2">📭</span>
|
| 604 |
+
<p>Chưa có công thức nào được lưu.</p>
|
| 605 |
+
</div>
|
| 606 |
+
{:else}
|
| 607 |
+
<div class="grid grid-cols-2 gap-4">
|
| 608 |
+
{#each savedFormulas as formula}
|
| 609 |
+
<div class="bg-zinc-800/80 border border-zinc-700 hover:border-indigo-500/50 rounded-xl p-4 flex flex-col group transition-all">
|
| 610 |
+
<div class="flex items-center justify-between mb-2">
|
| 611 |
+
<div class="flex items-center gap-2 overflow-hidden">
|
| 612 |
+
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold uppercase {formula.mode === 'latex' ? 'bg-blue-500/20 text-blue-400' : 'bg-emerald-500/20 text-emerald-400'}">
|
| 613 |
+
{formula.mode}
|
| 614 |
+
</span>
|
| 615 |
+
<h4 class="font-bold text-sm text-zinc-200 truncate" title={formula.name}>{formula.name}</h4>
|
| 616 |
+
</div>
|
| 617 |
+
<button class="text-zinc-500 hover:text-rose-400 opacity-0 group-hover:opacity-100 transition-opacity shrink-0" title="Xóa" on:click={() => deleteFormula(formula.id)}>🗑️</button>
|
| 618 |
+
</div>
|
| 619 |
+
<div class="flex-1 bg-zinc-900 rounded p-2 mb-3 overflow-hidden relative h-20">
|
| 620 |
+
<pre class="text-[10px] text-zinc-400 font-mono whitespace-pre-wrap">{formula.code}</pre>
|
| 621 |
+
<div class="absolute bottom-0 left-0 w-full h-8 bg-gradient-to-t from-zinc-900 to-transparent"></div>
|
| 622 |
+
</div>
|
| 623 |
+
<button class="w-full py-1.5 bg-zinc-700 hover:bg-indigo-600 text-xs font-bold text-white rounded transition-colors" on:click={() => loadFormula(formula)}>
|
| 624 |
+
Mở trong Editor
|
| 625 |
+
</button>
|
| 626 |
+
</div>
|
| 627 |
+
{/each}
|
| 628 |
+
</div>
|
| 629 |
+
{/if}
|
| 630 |
+
</div>
|
| 631 |
+
</div>
|
| 632 |
+
</div>
|
| 633 |
+
</div>
|
| 634 |
+
{/if}
|
| 635 |
+
|
| 636 |
<style>
|
| 637 |
.custom-scrollbar::-webkit-scrollbar {
|
| 638 |
width: 8px;
|