Skip to content

Commit c74792e

Browse files
authored
Add files via upload
1 parent f1b283b commit c74792e

4 files changed

Lines changed: 96 additions & 14 deletions

File tree

app.js

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,9 @@ const els = {
11201120
writtenPattern: document.querySelector("#writtenPattern"),
11211121
previousRoundBtn: document.querySelector("#previousRoundBtn"),
11221122
nextRoundBtn: document.querySelector("#nextRoundBtn"),
1123+
trackPartModal: document.querySelector("#trackPartModal"),
1124+
closeTrackPartModal: document.querySelector("#closeTrackPartModal"),
1125+
trackPartOptions: document.querySelector("#trackPartOptions"),
11231126
previousStitchBtn: document.querySelector("#previousStitchBtn"),
11241127
nextStitchBtn: document.querySelector("#nextStitchBtn"),
11251128
restartRoundBtn: document.querySelector("#restartRoundBtn"),
@@ -2371,6 +2374,15 @@ function uniquePatternName(baseName, currentId = "") {
23712374
return `${name} ${index}`;
23722375
}
23732376

2377+
function uniqueProjectName(baseName, currentId = "") {
2378+
const existing = new Set(state.projects.filter((project) => project.id !== currentId).map((project) => project.name.trim()));
2379+
let name = baseName.trim() || "未命名作品";
2380+
if (!existing.has(name)) return name;
2381+
let index = 2;
2382+
while (existing.has(`${name} ${index}`)) index += 1;
2383+
return `${name} ${index}`;
2384+
}
2385+
23742386
function hasSameName(name, list, getName = (item) => item, currentId = "") {
23752387
const normalized = String(name || "").trim().toLowerCase();
23762388
if (!normalized) return false;
@@ -3282,6 +3294,12 @@ function checkSharedStashFromUrl() {
32823294
function attachPatternCopy(project, templateId) {
32833295
const template = state.patterns.find((pattern) => pattern.id === templateId) || templatePatterns()[0];
32843296
if (!template) return "";
3297+
const currentPattern = state.patterns.find((pattern) => pattern.id === project.activePatternId);
3298+
const sourcePattern = currentPattern?.sourcePatternId
3299+
? state.patterns.find((pattern) => pattern.id === currentPattern.sourcePatternId)
3300+
: currentPattern;
3301+
const shouldAdoptName = !project.name || /^\s*\d+$/.test(project.name) || (sourcePattern?.name && project.name === sourcePattern.name);
3302+
const shouldAdoptType = !project.type || project.type === (state.projectTypes[0] || "作品") || (sourcePattern?.type && project.type === sourcePattern.type);
32853303
if (project.activePatternId) {
32863304
state.patterns = state.patterns.filter((pattern) => pattern.id !== project.activePatternId || !pattern.isProjectCopy);
32873305
}
@@ -3292,6 +3310,8 @@ function attachPatternCopy(project, templateId) {
32923310
state.patterns.push(copy);
32933311
project.patternIds = [copy.id];
32943312
project.activePatternId = copy.id;
3313+
if (shouldAdoptName) project.name = uniqueProjectName(template.name, project.id);
3314+
if (shouldAdoptType) project.type = template.type || project.type || state.projectTypes[0] || "作品";
32953315
project.yarnIds = Array.from(new Set([...(project.yarnIds || []), ...(copy.yarnIds || [])]));
32963316
project.supplyIds = Array.from(new Set([...(project.supplyIds || []), ...(copy.supplyIds || [])]));
32973317
project.toolIds = Array.from(new Set([...(project.toolIds || []), ...(copy.toolIds || [])]));
@@ -3514,9 +3534,6 @@ function renderTracking() {
35143534
const rows = expandedRows(pattern);
35153535
progress.completedRounds = Math.min(rows.length, Math.max(0, Number(progress.completedRounds || 0)));
35163536
progress.currentRound = clamp(progress.currentRound, 1, rows.length);
3517-
if (progress.completedRounds < rows.length && progress.currentRound <= progress.completedRounds) {
3518-
progress.currentRound = progress.completedRounds + 1;
3519-
}
35203537
const row = rows[progress.currentRound - 1];
35213538
const stitches = rowStitches(row);
35223539
progress.currentStitch = clamp(progress.currentStitch, 1, stitches.length);
@@ -4575,16 +4592,28 @@ function completeCurrentRound() {
45754592
}
45764593
}
45774594

4578-
function switchToNextPart() {
4595+
function switchToPart(partId) {
45794596
const pattern = currentPattern();
45804597
const rows = expandedRows(pattern);
4581-
const progress = currentProgress();
4582-
const currentRow = rows[progress.currentRound - 1];
4583-
const partIds = rows.map((row) => row.partId).filter((partId, index, list) => partId && list.indexOf(partId) === index);
4584-
if (!partIds.length) return;
4585-
const nextPartId = partIds[(partIds.indexOf(currentRow?.partId) + 1) % partIds.length];
4586-
const rowIndex = rows.findIndex((row) => row.partId === nextPartId);
4587-
if (rowIndex >= 0) updateProgress({ currentRound: rowIndex + 1, currentStitch: 1 });
4598+
const rowIndex = rows.findIndex((row) => row.partId === partId);
4599+
if (rowIndex < 0) return;
4600+
updateProgress({ currentRound: rowIndex + 1, currentStitch: 1 });
4601+
}
4602+
4603+
function openTrackPartPicker() {
4604+
const pattern = currentPattern();
4605+
const project = currentProject();
4606+
if (!pattern || !els.trackPartModal || !els.trackPartOptions) return;
4607+
const rows = expandedRows(pattern);
4608+
const currentRow = rows[currentProgress().currentRound - 1];
4609+
const progressList = partProgressList(project, pattern);
4610+
els.trackPartOptions.innerHTML = progressList.map(({ part, done, total, percent }) => `
4611+
<button class="part-switch-option ${part.id === currentRow?.partId ? "current" : ""}" data-switch-track-part="${part.id}">
4612+
<strong>${escapeHtml(part.name)}</strong>
4613+
<span>${done}/${total} 段 · ${percent}%</span>
4614+
</button>
4615+
`).join("");
4616+
els.trackPartModal.classList.remove("hidden");
45884617
}
45894618

45904619
function clamp(value, min, max) {
@@ -5260,7 +5289,14 @@ els.completeRoundBtn.addEventListener("click", () => {
52605289
render();
52615290
}
52625291
});
5263-
els.roundTitle.addEventListener("click", switchToNextPart);
5292+
els.roundTitle.addEventListener("click", openTrackPartPicker);
5293+
els.closeTrackPartModal?.addEventListener("click", () => els.trackPartModal.classList.add("hidden"));
5294+
els.trackPartOptions?.addEventListener("click", (event) => {
5295+
const partId = event.target.closest("[data-switch-track-part]")?.dataset.switchTrackPart;
5296+
if (!partId) return;
5297+
els.trackPartModal.classList.add("hidden");
5298+
switchToPart(partId);
5299+
});
52645300

52655301
els.newPatternBtn.addEventListener("click", () => {
52665302
const baseName = "新織圖";

index.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,16 @@ <h2>雲端同步</h2>
451451
</div>
452452
</div>
453453

454+
<div class="modal hidden" id="trackPartModal">
455+
<div class="modal-sheet">
456+
<div class="section-header">
457+
<h2>切換部分</h2>
458+
<button class="header-icon" id="closeTrackPartModal">×</button>
459+
</div>
460+
<div class="part-switch-list" id="trackPartOptions"></div>
461+
</div>
462+
</div>
463+
454464
<div class="modal hidden" id="attachModal">
455465
<div class="modal-sheet">
456466
<div class="section-header">
@@ -639,7 +649,7 @@ <h2>從色卡新增線材</h2>
639649
品牌分類
640650
<select id="colorCardStockBrand"></select>
641651
</label>
642-
<label>
652+
<label class="color-card-stock-url-field">
643653
購買連結
644654
<input id="colorCardStockUrl" type="url" placeholder="https://..." />
645655
</label>

service-worker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const VERSION = "137";
1+
const VERSION = "141";
22
const CACHE_NAME = `free-knit-workbench-v${VERSION}`;
33
const ASSETS = [
44
"./",

styles.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,39 @@ textarea {
449449
color: var(--muted);
450450
font-size: var(--text-sm);
451451
}
452+
.part-switch-list {
453+
display: grid;
454+
gap: 10px;
455+
}
456+
.part-switch-option {
457+
width: 100%;
458+
min-height: 56px;
459+
display: flex;
460+
align-items: center;
461+
justify-content: space-between;
462+
gap: 12px;
463+
border: 1px solid var(--line);
464+
border-radius: 16px;
465+
background: var(--paper);
466+
color: var(--ink);
467+
padding: 10px 14px;
468+
text-align: left;
469+
font-size: var(--text-base);
470+
font-weight: var(--weight-bold);
471+
}
472+
.part-switch-option.current {
473+
border-color: var(--accent);
474+
background: var(--accent-panel);
475+
color: var(--accent-strong);
476+
}
477+
.part-switch-option span {
478+
color: var(--muted);
479+
font-size: var(--text-sm);
480+
font-weight: var(--weight-medium);
481+
}
482+
#roundTitle {
483+
cursor: pointer;
484+
}
452485

453486
.settings-card { margin-top: 16px; padding: 14px; }
454487
.settings-card summary {
@@ -1228,6 +1261,9 @@ textarea {
12281261
max-height: 42vh;
12291262
overflow: auto;
12301263
}
1264+
.color-card-stock-url-field {
1265+
margin-bottom: 12px;
1266+
}
12311267
.brand-card-color-row {
12321268
display: grid;
12331269
grid-template-columns: 58px minmax(56px, .5fr) minmax(84px, .65fr) minmax(140px, 1fr) auto;

0 commit comments

Comments
 (0)