Add pattern templates for projects
This commit is contained in:
parent
7865d8e772
commit
a1ee84967d
@ -1,5 +1,7 @@
|
||||
// --- Data Init & Colors ---
|
||||
let projects = JSON.parse(localStorage.getItem('crochetCounters')) || [];
|
||||
let patterns = JSON.parse(localStorage.getItem('crochetPatterns')) || [];
|
||||
if (!Array.isArray(patterns)) patterns = [];
|
||||
// New Earthy/Woodland Palette extracted from image vibes
|
||||
const colors = [
|
||||
'#a17d63', // Soft oak
|
||||
@ -41,6 +43,9 @@ const colorGrid = document.getElementById('colorGrid');
|
||||
const customColorInput = document.getElementById('customColorInput');
|
||||
const saveOverlay = document.getElementById('saveOverlay');
|
||||
const saveList = document.getElementById('saveList');
|
||||
const patternPicker = document.getElementById('patternPicker');
|
||||
const patternSelect = document.getElementById('patternSelect');
|
||||
if (patternPicker && patternSelect) populatePatternSelect();
|
||||
let pendingSaveSelection = [];
|
||||
let lastCountPulse = null;
|
||||
let lastFinishedId = null;
|
||||
@ -230,11 +235,23 @@ function closeColorPicker() {
|
||||
colorOverlay.dataset.partId = '';
|
||||
}
|
||||
|
||||
function savePatterns() {
|
||||
localStorage.setItem('crochetPatterns', JSON.stringify(patterns));
|
||||
}
|
||||
|
||||
function populatePatternSelect() {
|
||||
if (!patternPicker || !patternSelect) return;
|
||||
const hasPatterns = patterns.length > 0;
|
||||
patternPicker.style.display = hasPatterns ? 'block' : 'none';
|
||||
patternSelect.innerHTML = '<option value=\"\">No pattern</option>' + patterns.map(p => `<option value="${p.id}">${p.name}</option>`).join('');
|
||||
}
|
||||
|
||||
function exportData(selectedProjects = projects) {
|
||||
const payload = {
|
||||
projects: selectedProjects,
|
||||
isDarkMode,
|
||||
animationsEnabled
|
||||
animationsEnabled,
|
||||
patterns
|
||||
};
|
||||
const names = selectedProjects.map(p => p.name || 'Project').join('_').replace(/\s+/g, '-').slice(0, 50) || 'projects';
|
||||
const filename = `toadstool_${names}.json`;
|
||||
@ -271,6 +288,10 @@ async function handleImport(event) {
|
||||
animationsEnabled = data.animationsEnabled;
|
||||
localStorage.setItem('crochetAnimations', animationsEnabled);
|
||||
}
|
||||
if (Array.isArray(data.patterns)) {
|
||||
patterns = data.patterns;
|
||||
localStorage.setItem('crochetPatterns', JSON.stringify(patterns));
|
||||
}
|
||||
localStorage.setItem('crochetCounters', JSON.stringify(projects));
|
||||
applyTheme();
|
||||
render();
|
||||
@ -629,6 +650,12 @@ document.addEventListener('visibilitychange', async () => {
|
||||
}
|
||||
}
|
||||
|
||||
function saveProjectAsPattern(pId) {
|
||||
const project = projects.find(p => p.id === pId);
|
||||
if (!project) return;
|
||||
openModal('savePattern', pId);
|
||||
}
|
||||
|
||||
// --- Modal Logic ---
|
||||
function openModal(type, pId = null, partId = null) {
|
||||
modalState = { type, pId, partId };
|
||||
@ -637,6 +664,10 @@ function openModal(type, pId = null, partId = null) {
|
||||
if (type === 'addProject') {
|
||||
modalTitle.innerText = "New Project Name";
|
||||
modalInput.type = "text"; modalInput.placeholder = "e.g., Amigurumi Bear";
|
||||
if (patternPicker && patternSelect) {
|
||||
populatePatternSelect();
|
||||
patternSelect.value = '';
|
||||
}
|
||||
} else if (type === 'addPart') {
|
||||
modalTitle.innerText = "New Part Name";
|
||||
modalInput.type = "text"; modalInput.placeholder = "e.g., Head";
|
||||
@ -651,6 +682,11 @@ function openModal(type, pId = null, partId = null) {
|
||||
if(part.locked || part.finished) return;
|
||||
modalTitle.innerText = "Rename Part";
|
||||
modalInput.value = part.name; modalInput.type = "text";
|
||||
} else if (type === 'savePattern') {
|
||||
modalTitle.innerText = "Save as Pattern";
|
||||
const project = projects.find(p => p.id === pId);
|
||||
modalInput.value = project ? `${project.name} pattern` : '';
|
||||
modalInput.type = "text"; modalInput.placeholder = "Pattern name";
|
||||
} else if (type === 'manualCount') {
|
||||
const part = projects.find(p => p.id === pId).parts.find(pt => pt.id === partId);
|
||||
if(part.locked || part.finished) return;
|
||||
@ -661,6 +697,9 @@ function openModal(type, pId = null, partId = null) {
|
||||
const part = projects.find(p => p.id === pId).parts.find(pt => pt.id === partId);
|
||||
if (part.locked) return;
|
||||
}
|
||||
if (type !== 'addProject' && patternPicker) {
|
||||
patternPicker.style.display = 'none';
|
||||
}
|
||||
modal.classList.add('active');
|
||||
setTimeout(() => modalInput.focus(), 100);
|
||||
}
|
||||
@ -674,12 +713,31 @@ function closeModal() {
|
||||
|
||||
if (modalState.type === 'addProject') {
|
||||
const nextColor = colors[projects.length % colors.length];
|
||||
projects.push({ id: Date.now(), name: val, color: nextColor, collapsed: false, parts: [] });
|
||||
projects[projects.length-1].parts.push({ id: Date.now() + 1, name: 'Part 1', count: 0, locked: false, finished: false, minimized: false, max: null, color: nextColor });
|
||||
const newProject = { id: Date.now(), name: val, color: nextColor, collapsed: false, note: '', parts: [] };
|
||||
const selectedPatternId = patternSelect ? patternSelect.value : '';
|
||||
const chosenPattern = selectedPatternId ? patterns.find(p => String(p.id) === selectedPatternId) : null;
|
||||
if (chosenPattern && Array.isArray(chosenPattern.parts) && chosenPattern.parts.length) {
|
||||
chosenPattern.parts.forEach((pt, idx) => {
|
||||
newProject.parts.push({
|
||||
id: Date.now() + idx + 1,
|
||||
name: pt.name || `Part ${idx + 1}`,
|
||||
count: 0,
|
||||
locked: false,
|
||||
finished: false,
|
||||
minimized: false,
|
||||
max: pt.max ?? null,
|
||||
color: pt.color || newProject.color,
|
||||
note: ''
|
||||
});
|
||||
});
|
||||
} else {
|
||||
newProject.parts.push({ id: Date.now() + 1, name: 'Part 1', count: 0, locked: false, finished: false, minimized: false, max: null, color: nextColor, note: '' });
|
||||
}
|
||||
projects.push(newProject);
|
||||
}
|
||||
else if (modalState.type === 'addPart') {
|
||||
const project = projects.find(p => p.id === modalState.pId);
|
||||
project.parts.push({ id: Date.now(), name: val, count: 0, locked: false, finished: false, minimized: false, max: null, color: project.color });
|
||||
project.parts.push({ id: Date.now(), name: val, count: 0, locked: false, finished: false, minimized: false, max: null, color: project.color, note: '' });
|
||||
project.collapsed = false;
|
||||
}
|
||||
else if (modalState.type === 'renamePart') {
|
||||
@ -710,6 +768,15 @@ function closeModal() {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (modalState.type === 'savePattern') {
|
||||
const project = projects.find(p => p.id === modalState.pId);
|
||||
if (project) {
|
||||
const template = project.parts.map(pt => ({ name: pt.name, color: pt.color, max: pt.max }));
|
||||
patterns.push({ id: Date.now(), name: val || project.name, color: project.color, parts: template });
|
||||
savePatterns();
|
||||
populatePatternSelect();
|
||||
}
|
||||
}
|
||||
save();
|
||||
closeModal();
|
||||
}
|
||||
@ -824,6 +891,7 @@ function render() {
|
||||
</div>
|
||||
<div class="project-actions">
|
||||
<button class="btn-add-part" onclick="openModal('addPart', ${project.id})">+ Part</button>
|
||||
<button class="btn-save-pattern" onclick="saveProjectAsPattern(${project.id})" title="Save as pattern"><i class="fa-solid fa-swatchbook"></i></button>
|
||||
<button class="btn-delete-project" onclick="deleteProject(${project.id})">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
1
assets/app.min.js
vendored
Normal file
1
assets/app.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -297,6 +297,11 @@ h1 {
|
||||
cursor: pointer; box-shadow: 0 3px 8px rgba(0,0,0,0.1); transition: transform 0.1s;
|
||||
}
|
||||
.btn-add-part:active { transform: scale(0.95); }
|
||||
.btn-save-pattern {
|
||||
background: none; border: 1px dashed var(--border); color: var(--text-muted);
|
||||
border-radius: 14px; padding: 6px 10px; font-size: 0.9rem; cursor: pointer;
|
||||
}
|
||||
.btn-save-pattern:hover { color: var(--project-color); border-color: var(--project-color); }
|
||||
|
||||
.btn-delete-project {
|
||||
background: none; border: none; color: var(--text-muted); font-size: 1.2rem; cursor: pointer; padding: 5px;
|
||||
@ -520,6 +525,22 @@ button:active { transform: scale(0.97); box-shadow: none; }
|
||||
.modal-btn { padding: 12px 24px; border: none; border-radius: 10px; font-size: 1rem; font-weight: 600; cursor: pointer; }
|
||||
.btn-cancel { background: var(--lock-btn-bg); color: var(--text-muted); }
|
||||
.btn-save { background: var(--text); color: var(--bg); }
|
||||
.pattern-picker { display: none; margin: 12px 0 6px; }
|
||||
.pattern-picker label {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.pattern-picker select {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--input-bg);
|
||||
color: var(--text);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.empty-state { text-align: center; color: var(--text-muted); margin-top: 80px; font-size: 1.2rem; font-style: italic;}
|
||||
|
||||
|
||||
1
assets/style.min.css
vendored
Normal file
1
assets/style.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -40,6 +40,12 @@
|
||||
<div class="modal-content">
|
||||
<h3 class="modal-title" id="modalTitle">Title</h3>
|
||||
<input type="text" class="modal-input" id="modalInput" autocomplete="off">
|
||||
<div class="pattern-picker" id="patternPicker">
|
||||
<label for="patternSelect">Pattern (optional)</label>
|
||||
<select id="patternSelect">
|
||||
<option value="">No pattern</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="modal-btn btn-cancel" onclick="closeModal()">Cancel</button>
|
||||
<button class="modal-btn btn-save" onclick="saveModal()">Save</button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user