draply-dev 1.3.9 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +218 -293
- package/package.json +1 -1
- package/src/overlay.js +116 -37
- package/src/draply-features.js +0 -625
package/package.json
CHANGED
package/src/overlay.js
CHANGED
|
@@ -629,7 +629,8 @@
|
|
|
629
629
|
<input type="file" id="__ast_file__" accept="image/*" style="display:none">
|
|
630
630
|
<div id="__ast_list__" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px"></div>
|
|
631
631
|
<div id="__ast_hint__" style="font-size:10px;color:#44446a;line-height:1.5;display:none">
|
|
632
|
-
Click
|
|
632
|
+
✦ Click to <b>place</b><br>
|
|
633
|
+
✦ Shift+Click to <b>replace</b> src/bg
|
|
633
634
|
</div>
|
|
634
635
|
<!-- Z-INDEX CONTROLS -->
|
|
635
636
|
<div id="__ast_zctrl__" style="display:none;margin-top:6px;">
|
|
@@ -642,7 +643,6 @@
|
|
|
642
643
|
<span class="ps-lbl">z-index</span>
|
|
643
644
|
<input class="ps-inp" id="__z_val__" type="number" value="1" min="-999" max="9999" style="width:70px;flex:none">
|
|
644
645
|
<button class="ps-apply" id="__z_set__" style="margin:0;padding:4px 8px;flex:none">SET</button>
|
|
645
|
-
<button class="ps-apply" id="__ast_del__" style="width:100%;margin-top:6px;background:#ff4444;color:#fff;border:none;">✕ Delete image</button>
|
|
646
646
|
</div>
|
|
647
647
|
</div>
|
|
648
648
|
</div>
|
|
@@ -1162,6 +1162,23 @@
|
|
|
1162
1162
|
};
|
|
1163
1163
|
|
|
1164
1164
|
function populateTypo(el) {
|
|
1165
|
+
// Make text directly editable
|
|
1166
|
+
if (!el.isContentEditable) {
|
|
1167
|
+
el.contentEditable = 'true';
|
|
1168
|
+
el.dataset.origText = el.innerText || '';
|
|
1169
|
+
el.focus();
|
|
1170
|
+
|
|
1171
|
+
const finishEdit = () => {
|
|
1172
|
+
el.contentEditable = 'false';
|
|
1173
|
+
el.removeEventListener('blur', finishEdit);
|
|
1174
|
+
if (el.innerText !== el.dataset.origText) {
|
|
1175
|
+
rec(el, { innerText: el.innerText }, { innerText: el.dataset.origText });
|
|
1176
|
+
toast('Text updated!');
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
el.addEventListener('blur', finishEdit);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1165
1182
|
const cs = getComputedStyle(el);
|
|
1166
1183
|
typElName.textContent = el.tagName.toLowerCase() + (el.id ? '#' + el.id : el.className ? '.' + [...el.classList].filter(c => !c.startsWith('__'))[0] : '');
|
|
1167
1184
|
typSz.value = Math.round(parseFloat(cs.fontSize)) || 16;
|
|
@@ -1209,7 +1226,10 @@
|
|
|
1209
1226
|
'line-height': typLh.value,
|
|
1210
1227
|
'letter-spacing': typLs.value + 'px',
|
|
1211
1228
|
};
|
|
1212
|
-
if (typFont.value)
|
|
1229
|
+
if (typFont.value) {
|
|
1230
|
+
props['font-family'] = `'${typFont.value}', sans-serif`;
|
|
1231
|
+
props['googleFont'] = typFont.value;
|
|
1232
|
+
}
|
|
1213
1233
|
// Snapshot BEFORE previewTypo applies styles
|
|
1214
1234
|
const prevProps = {};
|
|
1215
1235
|
Object.keys(props).forEach(p => {
|
|
@@ -1248,12 +1268,25 @@
|
|
|
1248
1268
|
[...files].forEach(file => {
|
|
1249
1269
|
if (!file.type.startsWith('image/')) return;
|
|
1250
1270
|
const reader = new FileReader();
|
|
1251
|
-
reader.onload = ev => {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1271
|
+
reader.onload = async ev => {
|
|
1272
|
+
try {
|
|
1273
|
+
const res = await fetch('/draply-upload', {
|
|
1274
|
+
method: 'POST',
|
|
1275
|
+
body: JSON.stringify({ name: file.name, base64: ev.target.result })
|
|
1276
|
+
});
|
|
1277
|
+
const data = await res.json();
|
|
1278
|
+
if (data.ok) {
|
|
1279
|
+
const asset = { id: Date.now() + Math.random(), name: file.name, src: data.url };
|
|
1280
|
+
astStore.push(asset);
|
|
1281
|
+
addThumb(asset);
|
|
1282
|
+
astHint.style.display = 'block';
|
|
1283
|
+
toast('🖼️ ' + file.name + ' loaded');
|
|
1284
|
+
} else {
|
|
1285
|
+
toast('⚠ Upload failed');
|
|
1286
|
+
}
|
|
1287
|
+
} catch(e) {
|
|
1288
|
+
toast('⚠ Upload failed');
|
|
1289
|
+
}
|
|
1257
1290
|
};
|
|
1258
1291
|
reader.readAsDataURL(file);
|
|
1259
1292
|
});
|
|
@@ -1303,10 +1336,28 @@
|
|
|
1303
1336
|
e.preventDefault(); e.stopPropagation();
|
|
1304
1337
|
if (!pendingAsset || !placingEl) return;
|
|
1305
1338
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1339
|
+
if (e.shiftKey) {
|
|
1340
|
+
// REPLACE mode
|
|
1341
|
+
if (e.target.tagName.toLowerCase() === 'img') {
|
|
1342
|
+
const prevSrc = e.target.getAttribute('src');
|
|
1343
|
+
e.target.src = pendingAsset.src;
|
|
1344
|
+
rec(e.target, { src: pendingAsset.src }, { src: prevSrc || '' });
|
|
1345
|
+
toast('🖼️ Image source replaced!');
|
|
1346
|
+
} else {
|
|
1347
|
+
const cs = getComputedStyle(e.target);
|
|
1348
|
+
const prevBg = cs.backgroundImage;
|
|
1349
|
+
e.target.style.backgroundImage = `url('${pendingAsset.src}')`;
|
|
1350
|
+
e.target.style.backgroundSize = 'cover';
|
|
1351
|
+
e.target.style.backgroundPosition = 'center';
|
|
1352
|
+
rec(e.target, { backgroundImage: `url('${pendingAsset.src}')`, backgroundSize: 'cover', backgroundPosition: 'center' }, { backgroundImage: prevBg });
|
|
1353
|
+
toast('🖼️ Background image set!');
|
|
1354
|
+
}
|
|
1355
|
+
} else {
|
|
1356
|
+
// PLACE mode
|
|
1357
|
+
const x = e.clientX - 60;
|
|
1358
|
+
const y = e.clientY - 60;
|
|
1359
|
+
placeAsset(pendingAsset, x, y, 120, 120);
|
|
1360
|
+
}
|
|
1310
1361
|
cancelPlacing();
|
|
1311
1362
|
}
|
|
1312
1363
|
|
|
@@ -1353,7 +1404,7 @@
|
|
|
1353
1404
|
});
|
|
1354
1405
|
|
|
1355
1406
|
rec(wrap, {
|
|
1356
|
-
src: asset.
|
|
1407
|
+
src: asset.src,
|
|
1357
1408
|
left: Math.round(x) + 'px',
|
|
1358
1409
|
top: Math.round(y) + 'px',
|
|
1359
1410
|
width: w + 'px',
|
|
@@ -1414,20 +1465,9 @@
|
|
|
1414
1465
|
|
|
1415
1466
|
zVal.onkeydown = e => { if (e.key === 'Enter') zSet.click(); };
|
|
1416
1467
|
|
|
1417
|
-
const astDel = document.getElementById('__ast_del__');
|
|
1418
|
-
astDel.onclick = () => {
|
|
1419
|
-
if (!selectedPlaced) return;
|
|
1420
|
-
selectedPlaced.remove();
|
|
1421
|
-
selectedPlaced = null;
|
|
1422
|
-
zCtrl.style.display = 'none';
|
|
1423
|
-
toast('🗑 Image deleted');
|
|
1424
|
-
};
|
|
1425
1468
|
// Drag placed assets
|
|
1426
1469
|
document.addEventListener('mousemove', e => {
|
|
1427
1470
|
if (!astDragEl || !state.dragging) return;
|
|
1428
|
-
if (state.resizing || hdl.classList.contains('v') && document.activeElement !== hdl) {
|
|
1429
|
-
// only handle if we initiated from an asset
|
|
1430
|
-
}
|
|
1431
1471
|
const dx = e.clientX - astDragSX, dy = e.clientY - astDragSY;
|
|
1432
1472
|
astDragEl.style.left = (astDragOL + dx) + 'px';
|
|
1433
1473
|
astDragEl.style.top = (astDragOT + dy) + 'px';
|
|
@@ -1446,7 +1486,6 @@
|
|
|
1446
1486
|
});
|
|
1447
1487
|
astDragEl.style.cursor = 'grab';
|
|
1448
1488
|
astDragEl = null;
|
|
1449
|
-
// Don't clear state.dragging here — the move tool mouseup handler does it
|
|
1450
1489
|
});
|
|
1451
1490
|
|
|
1452
1491
|
// ══════════════════════════════════════════
|
|
@@ -1469,11 +1508,26 @@
|
|
|
1469
1508
|
});
|
|
1470
1509
|
}
|
|
1471
1510
|
|
|
1472
|
-
//
|
|
1511
|
+
// Extract exact file from React Fiber if available
|
|
1512
|
+
function getReactSource(element) {
|
|
1513
|
+
for (const key in element) {
|
|
1514
|
+
if (key.startsWith('__reactFiber$')) {
|
|
1515
|
+
let fiber = element[key];
|
|
1516
|
+
while (fiber) {
|
|
1517
|
+
if (fiber._debugSource && fiber._debugSource.fileName) return fiber._debugSource.fileName;
|
|
1518
|
+
fiber = fiber.return;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
return null;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// Merge into state.changes (for save/apply)
|
|
1473
1526
|
const ch = {
|
|
1474
1527
|
type: el.dataset.pixelshiftId ? 'inline' : 'css',
|
|
1475
1528
|
pixelshiftId: el.dataset.pixelshiftId || null,
|
|
1476
1529
|
selector,
|
|
1530
|
+
exactFile: getReactSource(el),
|
|
1477
1531
|
file: el.dataset.pixelshiftFile || null,
|
|
1478
1532
|
props
|
|
1479
1533
|
};
|
|
@@ -1530,17 +1584,42 @@
|
|
|
1530
1584
|
toast('↩ Reverted');
|
|
1531
1585
|
}
|
|
1532
1586
|
|
|
1533
|
-
sv.addEventListener('click', () => {
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1587
|
+
sv.addEventListener('click', async () => {
|
|
1588
|
+
// Check key config status
|
|
1589
|
+
let hasKey = false;
|
|
1590
|
+
try {
|
|
1591
|
+
const cfgRes = await fetch('/draply-config');
|
|
1592
|
+
const cfg = await cfgRes.json();
|
|
1593
|
+
hasKey = cfg.hasKey;
|
|
1594
|
+
} catch (e) {}
|
|
1595
|
+
|
|
1596
|
+
if (!hasKey) {
|
|
1597
|
+
const key = prompt('Draply AI Save: Enter your free Groq API key (from console.groq.com) to enable saving directly to React files:');
|
|
1598
|
+
if (key) {
|
|
1599
|
+
await fetch('/draply-config', { method: 'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ apiKey: key.trim(), provider: 'groq' }) });
|
|
1600
|
+
} else {
|
|
1601
|
+
toast('Save aborted: AI Key required');
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
sv.disabled = true;
|
|
1607
|
+
sv.textContent = 'Applying...';
|
|
1608
|
+
try {
|
|
1609
|
+
const r = await fetch('/draply-ai-apply', {
|
|
1610
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1611
|
+
body: JSON.stringify({ changes: state.changes })
|
|
1612
|
+
});
|
|
1613
|
+
const d = await r.json();
|
|
1614
|
+
if (d.ok) toast(d.fallback ? 'Saved to draply.css (No AI Key)' : '✅ AI Applied to Source Code!');
|
|
1541
1615
|
else toast('⚠ Error: ' + (d.error || 'unknown'));
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1616
|
+
} catch {
|
|
1617
|
+
toast('⚠ Server unreachable');
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
state.changes = []; history.length = 0;
|
|
1621
|
+
sv.textContent = 'Save';
|
|
1622
|
+
updateUnsUI();
|
|
1544
1623
|
});
|
|
1545
1624
|
|
|
1546
1625
|
document.getElementById('__uns_clear__').onclick = () => {
|