Skip to content

Commit adf2a25

Browse files
committed
refactor: split ConfigDetail and ConfigEditor into sub-components
ConfigDetail.svelte: 1588 → 1269 lines ConfigEditor.svelte: 1569 → 1491 lines Extracted: - ShareModal.svelte (208 lines) — share dialog with copy-link and X share - MacOSPreferencesDisplay.svelte (143 lines) — read-only prefs accordion for ConfigDetail - MacOSPreferencesEditor.svelte (572 lines) — interactive prefs editor with catalog for ConfigEditor 247 tests pass, 0 type errors.
1 parent de40e2b commit adf2a25

File tree

5 files changed

+928
-402
lines changed

5 files changed

+928
-402
lines changed

src/lib/components/ConfigDetail.svelte

Lines changed: 4 additions & 323 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script lang="ts">
22
import { goto } from '$app/navigation';
33
import { getCatalogItem, CATALOG_CATEGORIES } from '$lib/macos-prefs-catalog';
4+
import ShareModal from '$lib/components/ShareModal.svelte';
5+
import MacOSPreferencesDisplay from '$lib/components/MacOSPreferencesDisplay.svelte';
46
57
let {
68
configUser,
@@ -14,7 +16,6 @@
1416
1517
let copied = $state(false);
1618
let showShareModal = $state(false);
17-
let shareCopied = $state(false);
1819
let forking = $state(false);
1920
let forkError = $state('');
2021
let showAllApps = $state(false);
@@ -56,27 +57,9 @@
5657
}
5758
5859
function openShareModal() {
59-
shareCopied = false;
6060
showShareModal = true;
6161
}
6262
63-
function closeShareModal() {
64-
showShareModal = false;
65-
}
66-
67-
function shareCopyLink() {
68-
navigator.clipboard.writeText(getShareUrl());
69-
shareCopied = true;
70-
setTimeout(() => shareCopied = false, 2000);
71-
}
72-
73-
function shareOnTwitter() {
74-
const text = `My dev stack: ${config.name} — set up in minutes with @openbootdotdev`;
75-
const hashtags = 'OpenBoot,macOS,DevTools';
76-
const tweetUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(getShareUrl())}&hashtags=${encodeURIComponent(hashtags)}`;
77-
window.open(tweetUrl, '_blank', 'width=550,height=420');
78-
}
79-
8063
async function forkConfig() {
8164
forking = true;
8265
forkError = '';
@@ -469,37 +452,7 @@
469452
{#if macosPrefs.length > 0}
470453
<section class="section">
471454
<h2 class="section-title">🍎 macOS Preferences</h2>
472-
<div class="prefs-accordion">
473-
{#each prefCategoryNames as cat}
474-
{@const items = prefsByCategory[cat]}
475-
<div class="prefs-acc-group">
476-
<div class="prefs-acc-header">
477-
<span class="prefs-acc-name">{cat}</span>
478-
<span class="prefs-acc-count">{items.length}</span>
479-
</div>
480-
<div class="prefs-acc-body">
481-
{#each items as { pref, catalogItem }}
482-
<div class="prefs-kv-row">
483-
<span class="prefs-kv-label">{catalogItem?.label ?? pref.key}</span>
484-
<span class="prefs-kv-value">
485-
{#if catalogItem?.options}
486-
{@const opt = catalogItem.options.find((o: { value: string; label: string }) => o.value === pref.value)}
487-
<span class="pref-option-val">{opt?.label ?? pref.value}</span>
488-
{:else if (catalogItem?.type ?? pref.type) === 'bool'}
489-
{@const boolOn = pref.value === 'true' || pref.value === '1'}
490-
<span class="pref-bool {boolOn ? 'pref-bool-on' : 'pref-bool-off'}">
491-
{boolOn ? 'ON' : 'OFF'}
492-
</span>
493-
{:else}
494-
<span class="pref-raw-val">{pref.value}</span>
495-
{/if}
496-
</span>
497-
</div>
498-
{/each}
499-
</div>
500-
</div>
501-
{/each}
502-
</div>
455+
<MacOSPreferencesDisplay {prefsByCategory} {prefCategoryNames} />
503456
</section>
504457
{/if}
505458

@@ -536,41 +489,7 @@
536489
</main>
537490
</div>
538491

539-
{#if showShareModal}
540-
<div class="share-overlay" onclick={closeShareModal} onkeydown={(e) => e.key === 'Escape' && closeShareModal()} role="dialog" tabindex="0">
541-
<div class="share-modal" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()} role="presentation">
542-
<div class="share-modal-header">
543-
<h3 class="share-modal-title">Share Configuration</h3>
544-
<button class="share-close-btn" onclick={closeShareModal} aria-label="Close share modal">
545-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
546-
</button>
547-
</div>
548-
<div class="share-modal-body">
549-
<div class="share-url-display">
550-
<code>{getShareUrl()}</code>
551-
</div>
552-
553-
<div class="share-options">
554-
<button class="share-option" onclick={shareCopyLink}>
555-
<span class="share-option-icon">
556-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
557-
</span>
558-
<span class="share-option-label">{shareCopied ? 'Copied!' : 'Copy Link'}</span>
559-
</button>
560-
561-
<button class="share-option" onclick={shareOnTwitter}>
562-
<span class="share-option-icon">
563-
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
564-
</span>
565-
<span class="share-option-label">Share on X</span>
566-
</button>
567-
</div>
568-
</div>
569-
</div>
570-
</div>
571-
{/if}
572-
573-
<svelte:window onkeydown={(e) => { if (e.key === 'Escape' && showShareModal) closeShareModal(); }} />
492+
<ShareModal bind:show={showShareModal} shareUrl={getShareUrl()} configName={config.name} />
574493

575494
<style>
576495
* {
@@ -1179,104 +1098,6 @@
11791098
color: var(--text-primary);
11801099
}
11811100
1182-
.prefs-accordion {
1183-
border: 1px solid var(--border);
1184-
border-radius: 12px;
1185-
overflow: hidden;
1186-
}
1187-
1188-
.prefs-acc-group {
1189-
border-bottom: 1px solid var(--border);
1190-
}
1191-
1192-
.prefs-acc-group:last-child {
1193-
border-bottom: none;
1194-
}
1195-
1196-
.prefs-acc-header {
1197-
display: flex;
1198-
align-items: center;
1199-
gap: 8px;
1200-
padding: 10px 16px;
1201-
background: var(--bg-tertiary);
1202-
}
1203-
1204-
.prefs-acc-name {
1205-
font-size: 0.75rem;
1206-
font-weight: 700;
1207-
text-transform: uppercase;
1208-
letter-spacing: 0.08em;
1209-
color: var(--text-muted);
1210-
flex: 1;
1211-
}
1212-
1213-
.prefs-acc-count {
1214-
font-size: 0.7rem;
1215-
color: var(--text-muted);
1216-
}
1217-
1218-
.prefs-acc-body {
1219-
display: grid;
1220-
grid-template-columns: 1fr;
1221-
}
1222-
1223-
@media (min-width: 640px) {
1224-
.prefs-acc-body {
1225-
grid-template-columns: 1fr 1fr;
1226-
}
1227-
}
1228-
1229-
.prefs-kv-row {
1230-
display: flex;
1231-
align-items: center;
1232-
justify-content: space-between;
1233-
gap: 12px;
1234-
padding: 8px 16px;
1235-
border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
1236-
}
1237-
1238-
.prefs-kv-label {
1239-
font-size: 0.88rem;
1240-
color: var(--text-primary);
1241-
font-weight: 500;
1242-
}
1243-
1244-
.prefs-kv-value {
1245-
flex-shrink: 0;
1246-
}
1247-
1248-
.pref-bool {
1249-
display: inline-block;
1250-
padding: 3px 8px;
1251-
border-radius: 5px;
1252-
font-size: 0.72rem;
1253-
font-weight: 700;
1254-
letter-spacing: 0.05em;
1255-
}
1256-
1257-
.pref-bool-on {
1258-
background: rgba(34, 197, 94, 0.15);
1259-
color: var(--accent);
1260-
border: 1px solid rgba(34, 197, 94, 0.3);
1261-
}
1262-
1263-
.pref-bool-off {
1264-
background: var(--bg-secondary);
1265-
color: var(--text-muted);
1266-
border: 1px solid var(--border);
1267-
}
1268-
1269-
.pref-option-val,
1270-
.pref-raw-val {
1271-
font-family: 'JetBrains Mono', monospace;
1272-
font-size: 0.8rem;
1273-
color: var(--accent);
1274-
background: var(--bg-secondary);
1275-
padding: 3px 8px;
1276-
border-radius: 5px;
1277-
border: 1px solid var(--border);
1278-
}
1279-
12801101
.cta {
12811102
display: flex;
12821103
align-items: center;
@@ -1337,146 +1158,6 @@
13371158
transform: translateY(-2px);
13381159
}
13391160
1340-
.share-overlay {
1341-
position: fixed;
1342-
inset: 0;
1343-
background: rgba(0, 0, 0, 0.8);
1344-
backdrop-filter: blur(4px);
1345-
display: flex;
1346-
justify-content: center;
1347-
align-items: center;
1348-
z-index: 1000;
1349-
padding: 20px;
1350-
animation: fadeIn 0.2s;
1351-
}
1352-
1353-
@keyframes fadeIn {
1354-
from { opacity: 0; }
1355-
to { opacity: 1; }
1356-
}
1357-
1358-
.share-modal {
1359-
background: var(--bg-secondary);
1360-
border: 1px solid var(--border);
1361-
border-radius: 16px;
1362-
width: 100%;
1363-
max-width: 480px;
1364-
overflow: hidden;
1365-
animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1366-
}
1367-
1368-
@keyframes slideUp {
1369-
from {
1370-
opacity: 0;
1371-
transform: translateY(20px);
1372-
}
1373-
to {
1374-
opacity: 1;
1375-
transform: translateY(0);
1376-
}
1377-
}
1378-
1379-
.share-modal-header {
1380-
padding: 24px;
1381-
border-bottom: 1px solid var(--border);
1382-
display: flex;
1383-
justify-content: space-between;
1384-
align-items: center;
1385-
}
1386-
1387-
.share-modal-title {
1388-
font-size: 1.3rem;
1389-
font-weight: 700;
1390-
margin: 0;
1391-
color: var(--text-primary);
1392-
}
1393-
1394-
.share-close-btn {
1395-
background: none;
1396-
border: none;
1397-
cursor: pointer;
1398-
color: var(--text-muted);
1399-
padding: 4px;
1400-
border-radius: 6px;
1401-
transition: all 0.2s;
1402-
display: flex;
1403-
align-items: center;
1404-
justify-content: center;
1405-
}
1406-
1407-
.share-close-btn:hover {
1408-
color: var(--text-primary);
1409-
background: var(--bg-tertiary);
1410-
}
1411-
1412-
.share-modal-body {
1413-
padding: 24px;
1414-
display: flex;
1415-
flex-direction: column;
1416-
gap: 20px;
1417-
}
1418-
1419-
.share-url-display {
1420-
background: var(--bg-tertiary);
1421-
border: 1px solid var(--border);
1422-
border-radius: 10px;
1423-
padding: 14px 18px;
1424-
}
1425-
1426-
.share-url-display code {
1427-
font-family: 'JetBrains Mono', monospace;
1428-
font-size: 0.85rem;
1429-
color: var(--accent);
1430-
word-break: break-all;
1431-
}
1432-
1433-
.share-options {
1434-
display: flex;
1435-
flex-direction: column;
1436-
gap: 10px;
1437-
}
1438-
1439-
.share-option {
1440-
display: flex;
1441-
align-items: center;
1442-
gap: 14px;
1443-
padding: 16px 18px;
1444-
background: var(--bg-tertiary);
1445-
border: 1px solid var(--border);
1446-
border-radius: 10px;
1447-
color: var(--text-primary);
1448-
font-size: 0.95rem;
1449-
font-weight: 500;
1450-
cursor: pointer;
1451-
transition: all 0.2s;
1452-
}
1453-
1454-
.share-option:hover {
1455-
border-color: var(--accent);
1456-
background: var(--bg-secondary);
1457-
transform: translateX(4px);
1458-
}
1459-
1460-
.share-option-icon {
1461-
display: flex;
1462-
align-items: center;
1463-
justify-content: center;
1464-
width: 40px;
1465-
height: 40px;
1466-
background: var(--bg-secondary);
1467-
border-radius: 8px;
1468-
color: var(--text-secondary);
1469-
flex-shrink: 0;
1470-
}
1471-
1472-
.share-option:hover .share-option-icon {
1473-
color: var(--accent);
1474-
}
1475-
1476-
.share-option-label {
1477-
flex: 1;
1478-
}
1479-
14801161
@media (max-width: 768px) {
14811162
.hero {
14821163
padding: 100px 20px 60px;

0 commit comments

Comments
 (0)