Clean up desktop module files
This commit is contained in:
parent
017d4aecd0
commit
737f934d68
29 changed files with 0 additions and 3454 deletions
|
|
@ -1,66 +0,0 @@
|
||||||
/* Drive Styles */
|
|
||||||
.drive-layout {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 250px 1fr 300px;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drive-sidebar, .drive-details {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drive-main {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
margin: 0.25rem 0.5rem;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item:hover {
|
|
||||||
background: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item.active {
|
|
||||||
background: #3b82f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-list {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-item {
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
border-bottom: 1px solid #334155;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-item:hover {
|
|
||||||
background: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-item.selected {
|
|
||||||
background: #1e40af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-icon {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
/* Mail Styles */
|
|
||||||
.mail-layout {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 250px 350px 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mail-sidebar, .mail-list, .mail-content {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mail-item {
|
|
||||||
padding: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
border-bottom: 1px solid #334155;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mail-item:hover {
|
|
||||||
background: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mail-item.unread {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mail-item.selected {
|
|
||||||
background: #1e40af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mail-content-view {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mail-header {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-bottom: 1px solid #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mail-body {
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
/* Tasks Styles */
|
|
||||||
.tasks-container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-input {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-input input {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.75rem;
|
|
||||||
background: #1e293b;
|
|
||||||
border: 1px solid #334155;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
color: #e2e8f0;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-input input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #3b82f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-input button {
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
background: #3b82f6;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-input button:hover {
|
|
||||||
background: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-list {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item {
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
background: #1e293b;
|
|
||||||
border: 1px solid #334155;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item.completed span {
|
|
||||||
text-decoration: line-through;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item input[type="checkbox"] {
|
|
||||||
width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item span {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item button {
|
|
||||||
background: #ef4444;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item button:hover {
|
|
||||||
background: #dc2626;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-filters {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 2rem;
|
|
||||||
padding-top: 2rem;
|
|
||||||
border-top: 1px solid #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-filters button {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
background: #334155;
|
|
||||||
color: #e2e8f0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-filters button.active {
|
|
||||||
background: #3b82f6;
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
// DateRangePicker component
|
|
||||||
class DateRangePicker {
|
|
||||||
constructor() {
|
|
||||||
this.state = {
|
|
||||||
startDate: new Date(),
|
|
||||||
endDate: new Date()
|
|
||||||
};
|
|
||||||
this.element = document.createElement('div');
|
|
||||||
this.element.className = 'date-range-picker';
|
|
||||||
this.render();
|
|
||||||
this.bindEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
this.element.innerHTML = `
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<button class="start-date-btn">
|
|
||||||
Start: ${this.formatDate(this.state.startDate)}
|
|
||||||
</button>
|
|
||||||
<span>to</span>
|
|
||||||
<button class="end-date-btn">
|
|
||||||
End: ${this.formatDate(this.state.endDate)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
bindEvents() {
|
|
||||||
this.element.querySelector('.start-date-btn').addEventListener('click', () => {
|
|
||||||
this.setStartDate();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.element.querySelector('.end-date-btn').addEventListener('click', () => {
|
|
||||||
this.setEndDate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setStartDate() {
|
|
||||||
const input = prompt("Enter start date (YYYY-MM-DD)");
|
|
||||||
if (input) {
|
|
||||||
this.state.startDate = new Date(input);
|
|
||||||
this.render();
|
|
||||||
this.onDateChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setEndDate() {
|
|
||||||
const input = prompt("Enter end date (YYYY-MM-DD)");
|
|
||||||
if (input) {
|
|
||||||
this.state.endDate = new Date(input);
|
|
||||||
this.render();
|
|
||||||
this.onDateChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
formatDate(date) {
|
|
||||||
return date.toLocaleDateString('en-US', {
|
|
||||||
month: 'short',
|
|
||||||
day: '2-digit',
|
|
||||||
year: 'numeric'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onDateChange() {
|
|
||||||
// To be implemented by parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize and mount the component
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const picker = new DateRangePicker();
|
|
||||||
document.querySelector('.date-range-picker').replaceWith(picker.element);
|
|
||||||
});
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// Overview component
|
|
||||||
class Overview {
|
|
||||||
constructor() {
|
|
||||||
this.element = document.createElement('div');
|
|
||||||
this.element.className = 'overview-chart';
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
this.element.innerHTML = `
|
|
||||||
<div class="chart-container">
|
|
||||||
<div class="flex justify-between items-end h-40">
|
|
||||||
${[100, 80, 60, 40, 20].map((h, i) => `
|
|
||||||
<div class="chart-bar"
|
|
||||||
style="height:${h}px;background-color:hsl(var(--chart-${(i%5)+1}))">
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize and mount the component
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const overview = new Overview();
|
|
||||||
document.querySelector('.overview-chart').replaceWith(overview.element);
|
|
||||||
});
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
// RecentSales component
|
|
||||||
class RecentSales {
|
|
||||||
constructor() {
|
|
||||||
this.salesData = [
|
|
||||||
{ name: "Olivia Martin", email: "olivia.martin@email.com", amount: "+$1,999.00" },
|
|
||||||
{ name: "Jackson Lee", email: "jackson.lee@email.com", amount: "+$39.00" },
|
|
||||||
{ name: "Isabella Nguyen", email: "isabella.nguyen@email.com", amount: "+$299.00" },
|
|
||||||
{ name: "William Kim", email: "will@email.com", amount: "+$99.00" },
|
|
||||||
{ name: "Sofia Davis", email: "sofia.davis@email.com", amount: "+$39.00" }
|
|
||||||
];
|
|
||||||
this.element = document.createElement('div');
|
|
||||||
this.element.className = 'recent-sales-list';
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
this.element.innerHTML = `
|
|
||||||
<div class="sales-list">
|
|
||||||
${this.salesData.map(sale => `
|
|
||||||
<div class="sale-item">
|
|
||||||
<div class="sale-info">
|
|
||||||
<div class="avatar">${sale.name[0]}</div>
|
|
||||||
<div>
|
|
||||||
<div class="name">${sale.name}</div>
|
|
||||||
<div class="email">${sale.email}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="amount">${sale.amount}</div>
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize and mount the component
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const recentSales = new RecentSales();
|
|
||||||
document.querySelector('.recent-sales-list').replaceWith(recentSales.element);
|
|
||||||
});
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
/* Dashboard styles - updated to match visual identity */
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cards-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 1rem;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding: 1.5rem;
|
|
||||||
background: #1e293b;
|
|
||||||
border: 1px solid #334155;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card h3 {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: #94a3b8;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card .value {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card .subtext {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #94a3b8;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 1rem;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.overview-container,
|
|
||||||
.recent-sales-container {
|
|
||||||
padding: 1.5rem;
|
|
||||||
background: #1e293b;
|
|
||||||
border: 1px solid #334155;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overview-container h3,
|
|
||||||
.recent-sales-container h3 {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #e2e8f0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
background: #3b82f6;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn:hover {
|
|
||||||
background: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-section {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<div id="main-content">
|
|
||||||
<div class="content-section active">
|
|
||||||
<main class="container p-4 space-y-4">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h1 class="text-2xl font-bold text-foreground">1Dashboard</h1>
|
|
||||||
<div class="date-range-picker"></div>
|
|
||||||
<button class="download-btn">Download</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cards-grid">
|
|
||||||
<!-- Cards will be populated by JavaScript -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dashboard-grid">
|
|
||||||
<div class="overview-container">
|
|
||||||
<h3>Overview</h3>
|
|
||||||
<div class="overview-chart"></div>
|
|
||||||
</div>
|
|
||||||
<div class="recent-sales-container">
|
|
||||||
<h3>Recent Sales</h3>
|
|
||||||
<p>You made 265 sales this month.</p>
|
|
||||||
<div class="recent-sales-list"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="dashboard.js"></script>
|
|
||||||
<script src="components/date-range-picker.js"></script>
|
|
||||||
<script src="components/overview.js"></script>
|
|
||||||
<script src="components/recent-sales.js"></script>
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
// Dashboard module JavaScript
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
// Dashboard state
|
|
||||||
const state = {
|
|
||||||
dateRange: {
|
|
||||||
startDate: new Date(),
|
|
||||||
endDate: new Date()
|
|
||||||
},
|
|
||||||
salesData: [
|
|
||||||
{ name: "Olivia Martin", email: "olivia.martin@email.com", amount: "+$1,999.00" },
|
|
||||||
{ name: "Jackson Lee", email: "jackson.lee@email.com", amount: "+$39.00" },
|
|
||||||
{ name: "Isabella Nguyen", email: "isabella.nguyen@email.com", amount: "+$299.00" },
|
|
||||||
{ name: "William Kim", email: "will@email.com", amount: "+$99.00" },
|
|
||||||
{ name: "Sofia Davis", email: "sofia.davis@email.com", amount: "+$39.00" },
|
|
||||||
],
|
|
||||||
cards: [
|
|
||||||
{ title: "Total Revenue", value: "$45,231.89", subtext: "+20.1% from last month" },
|
|
||||||
{ title: "Subscriptions", value: "+2350", subtext: "+180.1% from last month" },
|
|
||||||
{ title: "Sales", value: "+12,234", subtext: "+19% from last month" },
|
|
||||||
{ title: "Active Now", value: "+573", subtext: "+201 since last hour" },
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize dashboard safely
|
|
||||||
function init() {
|
|
||||||
const ensure = setInterval(() => {
|
|
||||||
const main = document.querySelector('#main-content');
|
|
||||||
const section = main && main.querySelector('.cards-grid');
|
|
||||||
const btn = main && main.querySelector('.download-btn');
|
|
||||||
if (section && btn) {
|
|
||||||
clearInterval(ensure);
|
|
||||||
renderCards();
|
|
||||||
btn.addEventListener('click', handleDownload);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render dashboard cards
|
|
||||||
function renderCards() {
|
|
||||||
const container = document.querySelector('.cards-grid');
|
|
||||||
container.innerHTML = state.cards.map(card => `
|
|
||||||
<div class="card">
|
|
||||||
<h3>${card.title}</h3>
|
|
||||||
<p class="value">${card.value}</p>
|
|
||||||
<p class="subtext">${card.subtext}</p>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle download button click
|
|
||||||
function handleDownload() {
|
|
||||||
console.log('Downloading dashboard data...');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format date helper
|
|
||||||
function formatDate(date) {
|
|
||||||
return date.toLocaleDateString('en-US', {
|
|
||||||
month: 'short',
|
|
||||||
day: '2-digit',
|
|
||||||
year: 'numeric'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize dashboard
|
|
||||||
document.addEventListener('DOMContentLoaded',()=>{init();});
|
|
||||||
});
|
|
||||||
|
|
@ -1,423 +0,0 @@
|
||||||
:root {
|
|
||||||
/* 3DBevel Theme */
|
|
||||||
--background: 0 0% 80%;
|
|
||||||
--foreground: 0 0% 10%;
|
|
||||||
--card: 0 0% 75%;
|
|
||||||
--card-foreground: 0 0% 10%;
|
|
||||||
--popover: 0 0% 80%;
|
|
||||||
--popover-foreground: 0 0% 10%;
|
|
||||||
--primary: 210 80% 40%;
|
|
||||||
--primary-foreground: 0 0% 80%;
|
|
||||||
--secondary: 0 0% 70%;
|
|
||||||
--secondary-foreground: 0 0% 10%;
|
|
||||||
--muted: 0 0% 65%;
|
|
||||||
--muted-foreground: 0 0% 30%;
|
|
||||||
--accent: 30 80% 40%;
|
|
||||||
--accent-foreground: 0 0% 80%;
|
|
||||||
--destructive: 0 85% 60%;
|
|
||||||
--destructive-foreground: 0 0% 98%;
|
|
||||||
--border: 0 0% 70%;
|
|
||||||
--input: 0 0% 70%;
|
|
||||||
--ring: 210 80% 40%;
|
|
||||||
--radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.word-clone {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: hsl(var(--background));
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Title Bar */
|
|
||||||
.title-bar {
|
|
||||||
background: hsl(var(--primary));
|
|
||||||
color: hsl(var(--primary-foreground));
|
|
||||||
padding: 8px 16px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
border-bottom: 2px solid hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-bar h1 {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-input {
|
|
||||||
background: hsl(var(--input));
|
|
||||||
border: 1px solid hsl(var(--border));
|
|
||||||
border-radius: var(--radius);
|
|
||||||
padding: 4px 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Quick Access Toolbar */
|
|
||||||
.quick-access {
|
|
||||||
background: hsl(var(--card));
|
|
||||||
border-bottom: 1px solid hsl(var(--border));
|
|
||||||
padding: 4px 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-access-btn {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-access-btn:hover {
|
|
||||||
background: hsl(var(--muted));
|
|
||||||
border-color: hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ribbon */
|
|
||||||
.ribbon {
|
|
||||||
background: hsl(var(--card));
|
|
||||||
border-bottom: 2px solid hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-tabs {
|
|
||||||
display: flex;
|
|
||||||
background: hsl(var(--muted));
|
|
||||||
border-bottom: 1px solid hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-tab-button {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-tab-button:hover {
|
|
||||||
background: hsl(var(--secondary));
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-tab-button.active {
|
|
||||||
background: hsl(var(--card));
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
border-bottom-color: hsl(var(--primary));
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-content {
|
|
||||||
display: flex;
|
|
||||||
padding: 8px;
|
|
||||||
gap: 2px;
|
|
||||||
min-height: 80px;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-right: 1px solid hsl(var(--border));
|
|
||||||
padding-right: 8px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-group:last-child {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-group-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 2px;
|
|
||||||
flex: 1;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-group-title {
|
|
||||||
font-size: 10px;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 4px;
|
|
||||||
border-top: 1px solid hsl(var(--border));
|
|
||||||
padding-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-button {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
transition: all 0.2s;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-button:hover {
|
|
||||||
background: hsl(var(--muted));
|
|
||||||
border-color: hsl(var(--border));
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-button.active {
|
|
||||||
background: hsl(var(--primary));
|
|
||||||
color: hsl(var(--primary-foreground));
|
|
||||||
border-color: hsl(var(--primary));
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-button.medium {
|
|
||||||
padding: 6px;
|
|
||||||
min-width: 32px;
|
|
||||||
min-height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-button.large {
|
|
||||||
padding: 8px;
|
|
||||||
min-width: 48px;
|
|
||||||
min-height: 48px;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-button-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ribbon-button-label {
|
|
||||||
font-size: 10px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-arrow {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 2px;
|
|
||||||
right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Format Controls */
|
|
||||||
.format-select {
|
|
||||||
background: hsl(var(--input));
|
|
||||||
border: 1px solid hsl(var(--border));
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 4px 6px;
|
|
||||||
font-size: 11px;
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-picker-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-picker {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
opacity: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-indicator {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 2px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 16px;
|
|
||||||
height: 3px;
|
|
||||||
border-radius: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Editor Area */
|
|
||||||
.editor-container {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
background: hsl(var(--muted));
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-sidebar {
|
|
||||||
width: 200px;
|
|
||||||
background: hsl(var(--card));
|
|
||||||
border-right: 1px solid hsl(var(--border));
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-main {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: calc(100vh - 200px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pages-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
/* Example: Use a CSS variable for zoom, set --zoom: 1 for 100% */
|
|
||||||
transform: scale(var(--zoom, 1));
|
|
||||||
transform-origin: top center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
width: 210mm;
|
|
||||||
min-height: 297mm;
|
|
||||||
background: white;
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 1px hsl(var(--border)),
|
|
||||||
0 4px 8px rgba(0, 0, 0, 0.1),
|
|
||||||
0 8px 16px rgba(0, 0, 0, 0.05);
|
|
||||||
position: relative;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-number {
|
|
||||||
position: absolute;
|
|
||||||
top: -30px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
font-size: 12px;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
background: hsl(var(--background));
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-content {
|
|
||||||
padding: 25mm;
|
|
||||||
min-height: 247mm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ProseMirror {
|
|
||||||
outline: none;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ProseMirror img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ProseMirror a {
|
|
||||||
color: hsl(var(--primary));
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table styles */
|
|
||||||
.editor-table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 16px 0;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-table td,
|
|
||||||
.editor-table th {
|
|
||||||
border: 1px solid hsl(var(--border));
|
|
||||||
padding: 8px 12px;
|
|
||||||
min-width: 50px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-table th {
|
|
||||||
background: hsl(var(--muted));
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bubble Menu */
|
|
||||||
.bubble-menu {
|
|
||||||
background: hsl(var(--card));
|
|
||||||
border: 1px solid hsl(var(--border));
|
|
||||||
border-radius: var(--radius);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
||||||
display: flex;
|
|
||||||
padding: 4px;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bubble-menu .ribbon-button {
|
|
||||||
min-width: 28px;
|
|
||||||
min-height: 28px;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Status Bar */
|
|
||||||
.status-bar {
|
|
||||||
background: hsl(var(--card));
|
|
||||||
border-top: 1px solid hsl(var(--border));
|
|
||||||
padding: 4px 12px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 11px;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.zoom-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.zoom-slider {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
|
|
||||||
.title-bar,
|
|
||||||
.quick-access,
|
|
||||||
.ribbon,
|
|
||||||
.editor-sidebar,
|
|
||||||
.status-bar {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-main {
|
|
||||||
padding: 0;
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pages-container {
|
|
||||||
transform: none;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
box-shadow: none;
|
|
||||||
margin: 0;
|
|
||||||
break-after: page;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-number {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
<div id="main-content" x-data="editor">
|
|
||||||
<div class="content-section active">
|
|
||||||
<!-- Quick Access Toolbar -->
|
|
||||||
<div class="quick-access">
|
|
||||||
<button class="quick-access-btn" @click="document.execCommand('undo', false)">
|
|
||||||
<svg><!-- Undo icon --></svg>
|
|
||||||
</button>
|
|
||||||
<button class="quick-access-btn" @click="document.execCommand('redo', false)">
|
|
||||||
<svg><!-- Redo icon --></svg>
|
|
||||||
</button>
|
|
||||||
<div class="title-controls">
|
|
||||||
<input type="text" class="title-input" x-model="fileName" placeholder="Document name">
|
|
||||||
<button class="quick-access-btn" @click="saveDocument">
|
|
||||||
<svg><!-- Save icon --></svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Ribbon -->
|
|
||||||
<div class="ribbon">
|
|
||||||
<div class="ribbon-tabs">
|
|
||||||
<button class="ribbon-tab-button" :class="{ 'active': activeTab === 'home' }"
|
|
||||||
@click="setActiveTab('home')">Home</button>
|
|
||||||
<button class="ribbon-tab-button" :class="{ 'active': activeTab === 'insert' }"
|
|
||||||
@click="setActiveTab('insert')">Insert</button>
|
|
||||||
<button class="ribbon-tab-button" :class="{ 'active': activeTab === 'view' }"
|
|
||||||
@click="setActiveTab('view')">View</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ribbon-content" x-show="activeTab === 'home'">
|
|
||||||
<div class="ribbon-group">
|
|
||||||
<div class="ribbon-group-title">Format</div>
|
|
||||||
<div class="ribbon-group-content">
|
|
||||||
<button class="ribbon-button" @click="formatBold">B</button>
|
|
||||||
<button class="ribbon-button" @click="formatItalic">I</button>
|
|
||||||
<button class="ribbon-button" @click="formatUnderline">U</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ribbon-group">
|
|
||||||
<div class="ribbon-group-title">Alignment</div>
|
|
||||||
<div class="ribbon-group-content">
|
|
||||||
<button class="ribbon-button" @click="alignLeft">Left</button>
|
|
||||||
<button class="ribbon-button" @click="alignCenter">Center</button>
|
|
||||||
<button class="ribbon-button" @click="alignRight">Right</button>
|
|
||||||
<button class="ribbon-button" @click="alignJustify">Justify</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Editor Area -->
|
|
||||||
<div class="editor-container">
|
|
||||||
<div class="editor-main">
|
|
||||||
<div class="pages-container">
|
|
||||||
<div class="page">
|
|
||||||
<div class="page-number">Page 1</div>
|
|
||||||
<div class="page-content" id="editor-content" contenteditable="true" x-html="content"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status Bar -->
|
|
||||||
<div class="status-bar">
|
|
||||||
<div>Page <span x-text="pages.length"></span> of <span x-text="pages.length"></span></div>
|
|
||||||
<div class="zoom-controls">
|
|
||||||
<button @click="zoomOut">-</button>
|
|
||||||
<span x-text="zoom + '%'"></span>
|
|
||||||
<button @click="zoomIn">+</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
// Editor module JavaScript using Alpine.js
|
|
||||||
document.addEventListener('alpine:init', () => {
|
|
||||||
Alpine.data('editor', () => ({
|
|
||||||
fileName: 'Document 1',
|
|
||||||
fontSize: '12',
|
|
||||||
fontFamily: 'Calibri',
|
|
||||||
textColor: '#000000',
|
|
||||||
highlightColor: '#ffff00',
|
|
||||||
activeTab: 'home',
|
|
||||||
zoom: 100,
|
|
||||||
pages: [1],
|
|
||||||
content: '',
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// Initialize with default content
|
|
||||||
this.content = `
|
|
||||||
<h1 style="text-align: center; font-size: 24px; margin-bottom: 20px;">${this.fileName}</h1>
|
|
||||||
<p><br></p>
|
|
||||||
<p>Start typing your document here...</p>
|
|
||||||
<p><br></p>
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Ribbon tab switching
|
|
||||||
setActiveTab(tab) {
|
|
||||||
this.activeTab = tab;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Formatting methods
|
|
||||||
formatBold() {
|
|
||||||
document.execCommand('bold', false);
|
|
||||||
},
|
|
||||||
formatItalic() {
|
|
||||||
document.execCommand('italic', false);
|
|
||||||
},
|
|
||||||
formatUnderline() {
|
|
||||||
document.execCommand('underline', false);
|
|
||||||
},
|
|
||||||
alignLeft() {
|
|
||||||
document.execCommand('justifyLeft', false);
|
|
||||||
},
|
|
||||||
alignCenter() {
|
|
||||||
document.execCommand('justifyCenter', false);
|
|
||||||
},
|
|
||||||
alignRight() {
|
|
||||||
document.execCommand('justifyRight', false);
|
|
||||||
},
|
|
||||||
alignJustify() {
|
|
||||||
document.execCommand('justifyFull', false);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Zoom controls
|
|
||||||
zoomOut() {
|
|
||||||
this.zoom = Math.max(50, this.zoom - 10);
|
|
||||||
this.updateZoom();
|
|
||||||
},
|
|
||||||
zoomIn() {
|
|
||||||
this.zoom = Math.min(200, this.zoom + 10);
|
|
||||||
this.updateZoom();
|
|
||||||
},
|
|
||||||
updateZoom() {
|
|
||||||
document.querySelector('.pages-container').style.transform = `scale(${this.zoom / 100})`;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Save document
|
|
||||||
saveDocument() {
|
|
||||||
const content = document.getElementById('editor-content').innerHTML;
|
|
||||||
const blob = new Blob([content], { type: 'text/html' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `${this.fileName}.html`;
|
|
||||||
a.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
/* News module specific styles */
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<div id="main-content">
|
|
||||||
<div class="content-section active">
|
|
||||||
<div class="panel">
|
|
||||||
<!-- News content will go here -->
|
|
||||||
<h1>News</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
// News module JavaScript
|
|
||||||
// Will be added as needed
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
/* Paper specific styles */
|
|
||||||
.editor-content {
|
|
||||||
outline: none;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating-toolbar {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 2rem;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
z-index: 100;
|
|
||||||
/* Smooth transition */
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Paper shadow effect */
|
|
||||||
.bg-card {
|
|
||||||
background-color: hsl(var(--card));
|
|
||||||
color: hsl(var(--card-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-border {
|
|
||||||
border-color: hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toolbar button styles */
|
|
||||||
button {
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: hsl(var(--accent));
|
|
||||||
color: hsl(var(--accent-foreground));
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
<div id="main-content" x-data="paper">
|
|
||||||
<div class="content-section active">
|
|
||||||
<div class="max-w-4xl mx-auto">
|
|
||||||
<!-- Paper Shadow Effect -->
|
|
||||||
<div class="mx-4 my-8 bg-card rounded-lg shadow-2xl shadow-black/20 border border-border">
|
|
||||||
<div class="editor-content" x-ref="editor" contenteditable="true" x-init="initEditor()"
|
|
||||||
class="min-h-[calc(100vh-12rem)] p-8 prose max-w-none focus:outline-none">
|
|
||||||
Start writing your thoughts here...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Floating Toolbar -->
|
|
||||||
<div class="floating-toolbar" x-show="showToolbar" x-transition>
|
|
||||||
<div class="flex items-center bg-card border border-border rounded-lg shadow-lg p-1">
|
|
||||||
<!-- Text Formatting -->
|
|
||||||
<button @click="formatText('bold')" class="p-2 rounded hover:bg-accent transition-colors"
|
|
||||||
:class="{'bg-primary text-primary-foreground': isActive('bold')}">
|
|
||||||
B
|
|
||||||
</button>
|
|
||||||
<button @click="formatText('italic')" class="p-2 rounded hover:bg-accent transition-colors"
|
|
||||||
:class="{'bg-primary text-primary-foreground': isActive('italic')}">
|
|
||||||
I
|
|
||||||
</button>
|
|
||||||
<button @click="formatText('underline')" class="p-2 rounded hover:bg-accent transition-colors"
|
|
||||||
:class="{'bg-primary text-primary-foreground': isActive('underline')}">
|
|
||||||
U
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="w-px h-6 bg-border mx-1"></div>
|
|
||||||
|
|
||||||
<!-- Text Alignment -->
|
|
||||||
<button @click="alignText('left')" class="p-2 rounded hover:bg-accent transition-colors"
|
|
||||||
:class="{'bg-primary text-primary-foreground': isAligned('left')}">
|
|
||||||
Left
|
|
||||||
</button>
|
|
||||||
<button @click="alignText('center')" class="p-2 rounded hover:bg-accent transition-colors"
|
|
||||||
:class="{'bg-primary text-primary-foreground': isAligned('center')}">
|
|
||||||
Center
|
|
||||||
</button>
|
|
||||||
<button @click="alignText('right')" class="p-2 rounded hover:bg-accent transition-colors"
|
|
||||||
:class="{'bg-primary text-primary-foreground': isAligned('right')}">
|
|
||||||
Right
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="w-px h-6 bg-border mx-1"></div>
|
|
||||||
|
|
||||||
<!-- Link -->
|
|
||||||
<button @click="addLink" class="p-2 rounded hover:bg-accent transition-colors">
|
|
||||||
Link
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
// Paper module JavaScript
|
|
||||||
document.addEventListener('alpine:init', () => {
|
|
||||||
Alpine.data('paper', () => ({
|
|
||||||
showToolbar: false,
|
|
||||||
selection: null,
|
|
||||||
|
|
||||||
initEditor() {
|
|
||||||
const editor = this.$refs.editor;
|
|
||||||
|
|
||||||
// Track selection for floating toolbar
|
|
||||||
editor.addEventListener('mouseup', this.updateSelection.bind(this));
|
|
||||||
editor.addEventListener('keyup', this.updateSelection.bind(this));
|
|
||||||
|
|
||||||
// Show/hide toolbar based on selection
|
|
||||||
document.addEventListener('selectionchange', () => {
|
|
||||||
const selection = window.getSelection();
|
|
||||||
this.showToolbar = !selection.isCollapsed &&
|
|
||||||
editor.contains(selection.anchorNode);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSelection() {
|
|
||||||
this.selection = window.getSelection();
|
|
||||||
},
|
|
||||||
|
|
||||||
formatText(format) {
|
|
||||||
document.execCommand(format, false);
|
|
||||||
this.updateSelection();
|
|
||||||
},
|
|
||||||
|
|
||||||
alignText(align) {
|
|
||||||
document.execCommand('justify' + align, false);
|
|
||||||
this.updateSelection();
|
|
||||||
},
|
|
||||||
|
|
||||||
isActive(format) {
|
|
||||||
return document.queryCommandState(format);
|
|
||||||
},
|
|
||||||
|
|
||||||
isAligned(align) {
|
|
||||||
return document.queryCommandValue('justify' + align) === align;
|
|
||||||
},
|
|
||||||
|
|
||||||
addLink() {
|
|
||||||
const url = prompt('Enter URL:');
|
|
||||||
if (url) {
|
|
||||||
document.execCommand('createLink', false, url);
|
|
||||||
}
|
|
||||||
this.updateSelection();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
/* Player specific styles */
|
|
||||||
.player-container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-container {
|
|
||||||
background: #000;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-container video {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.play-btn {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
background: hsl(var(--primary));
|
|
||||||
color: hsl(var(--primary-foreground));
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-display {
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Slider styles */
|
|
||||||
.slider {
|
|
||||||
flex: 1;
|
|
||||||
height: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider::-webkit-slider-thumb {
|
|
||||||
appearance: none;
|
|
||||||
height: 12px;
|
|
||||||
width: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: hsl(var(--primary));
|
|
||||||
cursor: pointer;
|
|
||||||
border: 2px solid hsl(var(--primary-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider::-moz-range-thumb {
|
|
||||||
height: 12px;
|
|
||||||
width: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: hsl(var(--primary));
|
|
||||||
cursor: pointer;
|
|
||||||
border: 2px solid hsl(var(--primary-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider::-webkit-slider-track {
|
|
||||||
height: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: hsl(var(--muted));
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider::-moz-range-track {
|
|
||||||
height: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: hsl(var(--muted));
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
<div id="main-content">
|
|
||||||
<div class="content-section active">
|
|
||||||
<div class="player-container">
|
|
||||||
<div class="video-container">
|
|
||||||
<!-- Video element will be inserted here -->
|
|
||||||
</div>
|
|
||||||
<div class="player-controls">
|
|
||||||
<button class="play-btn">Play</button>
|
|
||||||
<input type="range" class="slider progress-slider" min="0" max="100" value="0">
|
|
||||||
<span class="time-display">0:00 / 0:00</span>
|
|
||||||
<input type="range" class="slider volume-slider" min="0" max="100" value="80">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
// Player module JavaScript
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const ready = setInterval(() => {
|
|
||||||
const container = document.querySelector('.player-container');
|
|
||||||
if (!container) return;
|
|
||||||
clearInterval(ready);
|
|
||||||
const videoContainer = document.querySelector('.video-container');
|
|
||||||
const playBtn = document.querySelector('.play-btn');
|
|
||||||
const progressSlider = document.querySelector('.progress-slider');
|
|
||||||
const volumeSlider = document.querySelector('.volume-slider');
|
|
||||||
const timeDisplay = document.querySelector('.time-display');
|
|
||||||
const ensure = setInterval(() => {
|
|
||||||
const container = document.querySelector('.video-container');
|
|
||||||
if (container) {
|
|
||||||
clearInterval(ensure);
|
|
||||||
const video = document.createElement('video');
|
|
||||||
video.src = '';
|
|
||||||
video.controls = false;
|
|
||||||
container.appendChild(video);
|
|
||||||
setupControls(video);
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
function setupControls(video) {
|
|
||||||
const playBtn = document.querySelector('.play-btn');
|
|
||||||
const progressSlider = document.querySelector('.progress-slider');
|
|
||||||
const volumeSlider = document.querySelector('.volume-slider');
|
|
||||||
const timeDisplay = document.querySelector('.time-display');
|
|
||||||
playBtn.addEventListener('click', () => {
|
|
||||||
if (video.paused) {
|
|
||||||
video.play();
|
|
||||||
playBtn.textContent = 'Pause';
|
|
||||||
} else {
|
|
||||||
video.pause();
|
|
||||||
playBtn.textContent = 'Play';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
video.addEventListener('timeupdate', () => {
|
|
||||||
const progress = (video.currentTime / video.duration) * 100;
|
|
||||||
progressSlider.value = progress || 0;
|
|
||||||
updateTimeDisplay(video, timeDisplay);
|
|
||||||
});
|
|
||||||
progressSlider.addEventListener('input', () => {
|
|
||||||
const seekTime = (progressSlider.value / 100) * video.duration;
|
|
||||||
video.currentTime = seekTime;
|
|
||||||
});
|
|
||||||
volumeSlider.addEventListener('input', () => {
|
|
||||||
video.volume = volumeSlider.value / 100;
|
|
||||||
});
|
|
||||||
window.loadMedia = (src) => {
|
|
||||||
video.src = src;
|
|
||||||
video.load();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function updateTimeDisplay(video, display) {
|
|
||||||
const formatTime = (seconds) => {
|
|
||||||
const mins = Math.floor(seconds / 60);
|
|
||||||
const secs = Math.floor(seconds % 60);
|
|
||||||
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
|
||||||
};
|
|
||||||
display.textContent = `${formatTime(video.currentTime)} / ${formatTime(video.duration)}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
/* Settings specific styles */
|
|
||||||
input, textarea {
|
|
||||||
border-color: hsl(var(--border));
|
|
||||||
background-color: hsl(var(--input));
|
|
||||||
color: hsl(var(--foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus, textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: hsl(var(--ring));
|
|
||||||
box-shadow: 0 0 0 2px hsl(var(--ring)/0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-500 {
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-gray-200 {
|
|
||||||
border-color: hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-blue-500 {
|
|
||||||
background-color: hsl(var(--primary));
|
|
||||||
color: hsl(var(--primary-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:bg-blue-600:hover {
|
|
||||||
background-color: hsl(var(--primary)/0.9);
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
<div id="main-content">
|
|
||||||
<div class="content-section active">
|
|
||||||
<div class="space-y-6 p-4">
|
|
||||||
<div>
|
|
||||||
<h2 class="text-lg font-medium">Profile</h2>
|
|
||||||
<p class="text-sm text-gray-500"></p>
|
|
||||||
</div>
|
|
||||||
<div class="border-t border-gray-200 my-4"></div>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<!-- Username Field -->
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="block text-sm font-medium mb-1">Username</label>
|
|
||||||
<input class="w-full p-2 border rounded" x-model="username" placeholder="Enter username" />
|
|
||||||
<template x-if="errors.username">
|
|
||||||
<p class="text-red-500 text-xs mt-1" x-text="errors.username"></p>
|
|
||||||
</template>
|
|
||||||
<p class="text-sm text-gray-500 mt-1">
|
|
||||||
This is your public display name. It can be your real name or a pseudonym.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Email Field -->
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="block text-sm font-medium mb-1">Email</label>
|
|
||||||
<input type="email" class="w-full p-2 border rounded" x-model="email" placeholder="Enter email" />
|
|
||||||
<template x-if="errors.email">
|
|
||||||
<p class="text-red-500 text-xs mt-1" x-text="errors.email"></p>
|
|
||||||
</template>
|
|
||||||
<p class="text-sm text-gray-500 mt-1">
|
|
||||||
You can manage verified email addresses in your email settings.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Bio Field -->
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="block text-sm font-medium mb-1">Bio</label>
|
|
||||||
<textarea class="w-full p-2 border rounded" x-model="bio" rows="4"
|
|
||||||
placeholder="Tell us a little bit about yourself"></textarea>
|
|
||||||
<template x-if="errors.bio">
|
|
||||||
<p class="text-red-500 text-xs mt-1" x-text="errors.bio"></p>
|
|
||||||
</template>
|
|
||||||
<p class="text-sm text-gray-500 mt-1">
|
|
||||||
You can @mention other users and organizations to link to them.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" @click="submitForm">
|
|
||||||
Update profile
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
// Settings module JavaScript
|
|
||||||
document.addEventListener('alpine:init', () => {
|
|
||||||
Alpine.data('settings', () => ({
|
|
||||||
username: '',
|
|
||||||
email: '',
|
|
||||||
bio: '',
|
|
||||||
errors: {},
|
|
||||||
|
|
||||||
submitForm() {
|
|
||||||
this.errors = {};
|
|
||||||
let isValid = true;
|
|
||||||
|
|
||||||
// Validate username
|
|
||||||
if (this.username.length < 2) {
|
|
||||||
this.errors.username = "Username must be at least 2 characters.";
|
|
||||||
isValid = false;
|
|
||||||
} else if (this.username.length > 30) {
|
|
||||||
this.errors.username = "Username must not be longer than 30 characters.";
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate email
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
if (!emailRegex.test(this.email)) {
|
|
||||||
this.errors.email = "Please enter a valid email address.";
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate bio
|
|
||||||
if (this.bio.length < 4) {
|
|
||||||
this.errors.bio = "Bio must be at least 4 characters.";
|
|
||||||
isValid = false;
|
|
||||||
} else if (this.bio.length > 160) {
|
|
||||||
this.errors.bio = "Bio must not be longer than 160 characters.";
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValid) {
|
|
||||||
this.saveSettings();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
saveSettings() {
|
|
||||||
const settings = {
|
|
||||||
username: this.username,
|
|
||||||
email: this.email,
|
|
||||||
bio: this.bio
|
|
||||||
};
|
|
||||||
console.log('Saving settings:', settings);
|
|
||||||
// Here you would typically send the data to a server
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Prompts come from: https://github.com/0xeb/TheBigPromptLibrary
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,108 +0,0 @@
|
||||||
/* Tables specific styles */
|
|
||||||
.app-container {
|
|
||||||
display: 100vh;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-container {
|
|
||||||
flex: 1;
|
|
||||||
overflow: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
color: hsl(var(--muted-foreground));
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resizable-container {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resizable-panel {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resizable-handle {
|
|
||||||
width: 4px;
|
|
||||||
background: hsl(var(--border));
|
|
||||||
cursor: col-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spreadsheet-content {
|
|
||||||
width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
border: 1px solid hsl(var(--border));
|
|
||||||
padding: 0.5rem;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
background: hsl(var(--muted));
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.selected {
|
|
||||||
background: hsl(var(--accent));
|
|
||||||
color: hsl(var(--accent-foreground));
|
|
||||||
}
|
|
||||||
|
|
||||||
.formula-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-top: 1px solid hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.formula-bar span {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formula-bar input {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-top: 1px solid hsl(var(--border));
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-top: 1px solid hsl(var(--border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar button {
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background: hsl(var(--primary));
|
|
||||||
color: hsl(var(--primary-foreground));
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar button:hover {
|
|
||||||
background: hsl(var(--primary)/0.9);
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
<div id="main-content">
|
|
||||||
<div class="content-section active">
|
|
||||||
<div class="app-container">
|
|
||||||
<div class="content-container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>📊 Tables</h1>
|
|
||||||
<div class="subtitle">Excel Clone - Celebrating Lotus 1-2-3 Legacy 🎉</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="resizable-container">
|
|
||||||
<div class="resizable-panel left" style="width: 30%">
|
|
||||||
<!-- Left panel content -->
|
|
||||||
</div>
|
|
||||||
<div class="resizable-handle"></div>
|
|
||||||
<div class="resizable-panel right" style="width: 70%">
|
|
||||||
<div class="spreadsheet-content">
|
|
||||||
<table>
|
|
||||||
<thead id="tableHead"></thead>
|
|
||||||
<tbody id="tableBody"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Formula bar -->
|
|
||||||
<div class="formula-bar">
|
|
||||||
<span id="cellRef">A1</span>
|
|
||||||
<input type="text"
|
|
||||||
id="formulaInput"
|
|
||||||
placeholder="Enter formula..."
|
|
||||||
@keypress.enter="updateCellValue($event.target.value)"
|
|
||||||
x-model="formulaInputValue">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status bar -->
|
|
||||||
<div class="status-bar">
|
|
||||||
<span>Rows: <span id="rowCount" x-text="rows"></span></span>
|
|
||||||
<span>Columns: <span id="colCount" x-text="cols"></span></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Toolbar -->
|
|
||||||
<div class="toolbar">
|
|
||||||
<button @click="addRow()">Add Row</button>
|
|
||||||
<button @click="addColumn()">Add Column</button>
|
|
||||||
<button @click="deleteRow()">Delete Row</button>
|
|
||||||
<button @click="deleteColumn()">Delete Column</button>
|
|
||||||
<button @click="sort()">Sort</button>
|
|
||||||
<button @click="sum()">Sum</button>
|
|
||||||
<button @click="average()">Average</button>
|
|
||||||
<button @click="exportData()">Export</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
// Tables module JavaScript
|
|
||||||
document.addEventListener('alpine:init', () => {
|
|
||||||
Alpine.data('tablesApp', () => ({
|
|
||||||
data: [],
|
|
||||||
selectedCell: null,
|
|
||||||
cols: 26,
|
|
||||||
rows: 100,
|
|
||||||
init() {
|
|
||||||
this.data = this.generateMockData(this.rows, this.cols);
|
|
||||||
this.renderTable();
|
|
||||||
},
|
|
||||||
|
|
||||||
generateMockData(rows, cols) {
|
|
||||||
const data = [];
|
|
||||||
const products = ['Laptop', 'Mouse', 'Keyboard', 'Monitor', 'Headphones', 'Webcam', 'Desk', 'Chair'];
|
|
||||||
const regions = ['North', 'South', 'East', 'West'];
|
|
||||||
|
|
||||||
for (let i = 0; i < rows; i++) {
|
|
||||||
const row = {};
|
|
||||||
for (let j = 0; j < cols; j++) {
|
|
||||||
const col = this.getColumnName(j);
|
|
||||||
if (i === 0) {
|
|
||||||
if (j === 0) row[col] = 'Product';
|
|
||||||
else if (j === 1) row[col] = 'Region';
|
|
||||||
else if (j === 2) row[col] = 'Q1';
|
|
||||||
else if (j === 3) row[col] = 'Q2';
|
|
||||||
else if (j === 4) row[col] = 'Q3';
|
|
||||||
else if (j === 5) row[col] = 'Q4';
|
|
||||||
else if (j === 6) row[col] = 'Total';
|
|
||||||
else row[col] = `Col ${col}`;
|
|
||||||
} else {
|
|
||||||
if (j === 0) row[col] = products[i % products.length];
|
|
||||||
else if (j === 1) row[col] = regions[i % regions.length];
|
|
||||||
else if (j >= 2 && j <= 5) row[col] = Math.floor(Math.random() * 10000) + 1000;
|
|
||||||
else if (j === 6) {
|
|
||||||
row[col] = `=C${i+1}+D${i+1}+E${i+1}+F${i+1}`;
|
|
||||||
}
|
|
||||||
else row[col] = Math.random() > 0.5 ? Math.floor(Math.random() * 1000) : '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.push(row);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
getColumnName(index) {
|
|
||||||
let name = '';
|
|
||||||
while (index >= 0) {
|
|
||||||
name = String.fromCharCode(65 + (index % 26)) + name;
|
|
||||||
index = Math.floor(index / 26) - 1;
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
},
|
|
||||||
|
|
||||||
selectCell(cell) {
|
|
||||||
if (this.selectedCell) {
|
|
||||||
this.selectedCell.classList.remove('selected');
|
|
||||||
}
|
|
||||||
this.selectedCell = cell;
|
|
||||||
cell.classList.add('selected');
|
|
||||||
|
|
||||||
const cellRef = cell.dataset.cell;
|
|
||||||
document.getElementById('cellRef').textContent = cellRef;
|
|
||||||
|
|
||||||
const row = parseInt(cell.dataset.row);
|
|
||||||
const col = this.getColumnName(parseInt(cell.dataset.col));
|
|
||||||
const value = this.data[row][col] || '';
|
|
||||||
document.getElementById('formulaInput').value = value;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateCellValue(value) {
|
|
||||||
if (!this.selectedCell) return;
|
|
||||||
|
|
||||||
const row = parseInt(this.selectedCell.dataset.row);
|
|
||||||
const col = this.getColumnName(parseInt(this.selectedCell.dataset.col));
|
|
||||||
|
|
||||||
this.data[row][col] = value;
|
|
||||||
this.renderTable();
|
|
||||||
|
|
||||||
const newCell = document.querySelector(`td[data-row="${row}"][data-col="${this.selectedCell.dataset.col}"]`);
|
|
||||||
if (newCell) this.selectCell(newCell);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderTable() {
|
|
||||||
const thead = document.getElementById('tableHead');
|
|
||||||
const tbody = document.getElementById('tableBody');
|
|
||||||
|
|
||||||
let headerHTML = '<tr><th></th>';
|
|
||||||
for (let i = 0; i < this.cols; i++) {
|
|
||||||
headerHTML += `<th>${this.getColumnName(i)}</th>`;
|
|
||||||
}
|
|
||||||
headerHTML += '</tr>';
|
|
||||||
thead.innerHTML = headerHTML;
|
|
||||||
|
|
||||||
let bodyHTML = '';
|
|
||||||
for (let i = 0; i < this.rows; i++) {
|
|
||||||
bodyHTML += `<tr><th>${i + 1}</th>`;
|
|
||||||
for (let j = 0; j < this.cols; j++) {
|
|
||||||
const col = this.getColumnName(j);
|
|
||||||
const value = this.data[i][col] || '';
|
|
||||||
const displayValue = this.calculateCell(value, i, j);
|
|
||||||
bodyHTML += `<td @click="selectCell($el)"
|
|
||||||
data-row="${i}"
|
|
||||||
data-col="${j}"
|
|
||||||
data-cell="${col}${i+1}"
|
|
||||||
:class="{ 'selected': selectedCell === $el }">
|
|
||||||
${displayValue}
|
|
||||||
</td>`;
|
|
||||||
}
|
|
||||||
bodyHTML += '</tr>';
|
|
||||||
}
|
|
||||||
tbody.innerHTML = bodyHTML;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Toolbar actions
|
|
||||||
addRow() {
|
|
||||||
const newRow = {};
|
|
||||||
for (let i = 0; i < this.cols; i++) {
|
|
||||||
newRow[this.getColumnName(i)] = '';
|
|
||||||
}
|
|
||||||
this.data.push(newRow);
|
|
||||||
this.rows++;
|
|
||||||
this.renderTable();
|
|
||||||
},
|
|
||||||
|
|
||||||
addColumn() {
|
|
||||||
const newCol = this.getColumnName(this.cols);
|
|
||||||
this.data.forEach(row => row[newCol] = '');
|
|
||||||
this.cols++;
|
|
||||||
this.renderTable();
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteRow() {
|
|
||||||
if (this.selectedCell && this.rows > 1) {
|
|
||||||
const row = parseInt(this.selectedCell.dataset.row);
|
|
||||||
this.data.splice(row, 1);
|
|
||||||
this.rows--;
|
|
||||||
this.renderTable();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteColumn() {
|
|
||||||
if (this.selectedCell && this.cols > 1) {
|
|
||||||
const col = this.getColumnName(parseInt(this.selectedCell.dataset.col));
|
|
||||||
this.data.forEach(row => delete row[col]);
|
|
||||||
this.cols--;
|
|
||||||
this.renderTable();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
exportData() {
|
|
||||||
const csv = this.data.map(row => {
|
|
||||||
return Object.values(row).join(',');
|
|
||||||
}).join('\n');
|
|
||||||
|
|
||||||
const blob = new Blob([csv], { type: 'text/csv' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = 'tables_export.csv';
|
|
||||||
a.click();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
Loading…
Add table
Reference in a new issue