xcss-core
Version:
New way of CSS.
323 lines (288 loc) • 11.8 kB
JavaScript
// --- Initial Load ---
const RootBody = document.getElementById('live-preview-body');
const RootMain = document.getElementById('live-preview-main');
window.addEventListener('load', () => {
setTimeout(() => {
RootBody.parentElement.removeAttribute("style");
console.log("Removed vscode css defaults.")
}, 100);
});
// --- Deploy Output Fragment ----
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
const StapleElement = document.getElementById('live-preview-output-staple');
const OutputElement = document.getElementById('live-preview-output-container');
const RootCssElement = document.getElementById('live-preview-root-css');
const CompCssElement = document.getElementById('live-preview-comp-css');
const SymClassElement = document.getElementById('live-preview-symclass');
const ComponentData = {
staple: "",
summon: "",
symclass: "",
attributes: {},
rootcss: "",
compcss: "",
}
const outputState = {
useProjectCss: false,
activateResize: false,
preserveScale: false,
activateDebug: false,
}
let OutputStyle = '';
function OutputUpdate(updateComponent = false) {
if (OutputElement.hasAttribute('style')) { OutputStyle = OutputElement.getAttribute('style') ?? OutputStyle; }
if (outputState.preserveScale) {
OutputElement.setAttribute('style', OutputStyle);
} else {
if (outputState.activateResize && !OutputElement.hasAttribute('style')) { OutputElement.setAttribute('style', OutputStyle); }
else if (!outputState.activateResize && OutputElement.hasAttribute('style')) { OutputElement.removeAttribute('style'); }
}
OutputElement.setAttribute("data-live-preview-output-container-debug", String(outputState.activateDebug))
OutputElement.setAttribute("data-live-preview-output-container-resize", String(outputState.activateResize))
OutputElement.setAttribute("data-live-preview-output-container-preserve", String(outputState.preserveScale))
OutputElement.setAttribute("data-live-preview-output-container-preserve", String(outputState.preserveScale))
RootCssElement.innerText = outputState.useProjectCss ? ComponentData.rootcss : "";
CompCssElement.innerText = ComponentData.compcss;
if (updateComponent) {
const staple = (typeof ComponentData.staple === "string") ? ComponentData.staple : '';
const structure = (typeof ComponentData.summon === "string" && ComponentData.summon.length) ? ComponentData.summon : "{Content}";
const selector = (typeof ComponentData.symclass === "string" && ComponentData.symclass.length) ? ComponentData.symclass : '[N/A]';
StapleElement.innerHTML = staple;
OutputElement.innerHTML = structure;
OutputElement.className = "_";
SymClassElement.innerHTML = selector;
const attributes = ComponentData.attributes
if (typeof attributes === "object") {
RootMain.setAttribute("style", typeof attributes["style"] === "string" ? attributes["style"].slice(1, -1) : "")
Object.entries(attributes).forEach(([attr, value]) => {
if (typeof value === "string") {
const fval = value.slice(1, -1);
if (attr === "class") {
OutputElement.classList.add(...fval.split(" "))
} else if (![
"id", "style",
"data-live-preview-output-container-debug",
"data-live-preview-output-container-resize",
].includes(attr)) {
OutputElement.setAttribute(attr, fval)
}
}
})
} else {
RootMain.removeAttribute("style", "");
OutputElement.getAttributeNames.forEach(attr => {
if (![
"id", "class", "style",
"data-live-preview-output-container-debug",
"data-live-preview-output-container-resize",
].includes(attr)) {
OutputElement.removeAttribute(attr)
}
})
}
}
}
// --- Drag handle Logic ---
let dragActive = false;
const dragStart = { x: 0, y: 0 };
const dragPosition = { top: 0, left: 0 };
const widgetElement = document.getElementById('live-preview-widget');
const dragElement = document.getElementById('live-preview-option-drag-handle');
if (dragElement && widgetElement) {
dragElement.addEventListener('mousedown', (e) => {
dragActive = true;
dragStart.x = e.clientX;
dragStart.y = e.clientY;
const computedStyle = window.getComputedStyle(widgetElement);
dragPosition.left = parseFloat(computedStyle.left || '0');
dragPosition.top = parseFloat(computedStyle.top || '0');
document.body.style.userSelect = 'none';
});
document.addEventListener('mousemove', (e) => {
if (dragActive) {
const dx = e.clientX - dragStart.x;
const dy = e.clientY - dragStart.y;
dragPosition.top = dragPosition.top + dy;
dragPosition.left = dragPosition.left + dx;
widgetElement.style.top = dragPosition.top + 'px';
widgetElement.style.left = dragPosition.left + 'px';
dragStart.x = e.clientX;
dragStart.y = e.clientY;
}
});
document.addEventListener('mouseup', () => {
if (dragActive) {
dragActive = false;
document.body.style.userSelect = '';
}
});
}
// --- WebSocket Setup ---
const ws = new WebSocket(`ws://${location.hostname}:${location.port}/ws`);
ws.onopen = function () {
console.log('WebSocket connected');
};
ws.onerror = function (e) {
console.error('WebSocket error:', e);
};
let awaitRefresh = false;
let currentComponentId = 0;
ws.onmessage = function (evt) {
const response = JSON.parse(evt.data);
if (response.method === 'sandbox-state-set' || response.method === 'sandbox-state-init') {
tweakIndex[response.result.key]?.apply(response.result.value);
OutputUpdate(false);
} else if (response.method === 'sandbox-view') {
if (awaitRefresh) { return }
awaitRefresh = true;
setTimeout(() => awaitRefresh = false, 250)
try {
if (response["id"] === currentComponentId) return;
const newData = response.result;
if (newData && typeof newData === "object") {
Object.assign(ComponentData, newData);
OutputUpdate(true);
} else {
OutputUpdate(false);
}
currentComponentId = newData["id"]
} catch (e) {
console.error("Unable to update component!");
}
}
};
// --- Tweak class modification ---
class Tweak {
constructor(key, fallback, applyFunction = () => { }, options = {}) {
this.key = key;
this.fallback = fallback;
this.apply = applyFunction.bind(this);
this.dom_element = this.element;
this.options = options;
}
get element() {
if (!this.dom_element) {
this.dom_element = document.getElementById(this.key);
if (this.dom_element) {
this.dom_element.addEventListener('change', () => { this.Update(); });
}
}
return this.dom_element;
}
Initialize() {
if (this.element) {
const message = JSON.stringify({
jsonrpc: "2.0",
method: 'sandbox-state-init',
id: Date.now(),
params: {
key: this.key,
value: this.fallback,
}
})
ws.send(message);
this.apply();
}
}
Update() {
if (ws.readyState === WebSocket.OPEN && this.element) {
const message = JSON.stringify({
jsonrpc: "2.0",
method: 'sandbox-state-set',
id: Date.now(),
params: {
key: this.key,
value: this.element.type === 'checkbox'
? this.element.checked
: this.element.value
}
})
console.log(message)
ws.send(message);
this.apply();
}
}
}
const tweaks = [
new Tweak('live-preview-option-color-picker', '#ffffff', function (value = this.element?.value) {
if (this.element && value !== undefined) {
this.element.value = value;
document.getElementById("live-preview-body").style.setProperty("--live-preview-extension-background", value);
}
}),
new Tweak('live-preview-option-live-cursor', false, function (value = this.element?.checked) {
if (this.element && value !== undefined) {
const valBool = typeof value === "boolean" ? value : (value === 'true');
this.element.checked = valBool;
if (valBool) this.element.setAttribute("checked", "")
else this.element.removeAttribute("checked")
OutputUpdate();
}
}),
new Tweak('live-preview-option-project-index', false, function (value = this.element?.checked) {
if (this.element && value !== undefined) {
const valBool = typeof value === "boolean" ? value : (value === 'true');
this.element.checked = valBool;
outputState.useProjectCss = valBool;
if (valBool) this.element.setAttribute("checked", "")
else this.element.removeAttribute("checked")
OutputUpdate();
}
}),
new Tweak('live-preview-option-container-resize', false, function (value = this.element?.checked) {
if (this.element && value !== undefined) {
const valBool = typeof value === "boolean" ? value : (value === 'true');
this.element.checked = valBool;
outputState.activateResize = valBool;
if (valBool) this.element.setAttribute("checked", "")
else this.element.removeAttribute("checked")
OutputUpdate();
}
}),
new Tweak('live-preview-option-preserve-scale', false, function (value = this.element?.checked) {
if (this.element && value !== undefined) {
const valBool = typeof value === "boolean" ? value : (value === 'true');
this.element.checked = valBool;
outputState.preserveScale = valBool;
if (valBool) this.element.setAttribute("checked", "")
else this.element.removeAttribute("checked")
OutputUpdate();
}
}),
new Tweak('live-preview-option-debug-mode', false, function (value = this.element?.checked) {
if (this.element && value !== undefined) {
const valBool = typeof value === "boolean" ? value : (value === 'true');
this.element.checked = valBool;
outputState.activateDebug = valBool;
if (valBool) this.element.setAttribute("checked", "")
else this.element.removeAttribute("checked")
OutputUpdate();
}
}),
];
function requstComponent() {
if (!awaitRefresh) {
ws.send(JSON.stringify({
jsonrpc: "2.0",
method: 'sandbox-view',
id: Date.now(),
params: {}
}));
}
}
const tweakIndex = {}
ws.onopen = () => {
tweaks.forEach((tweak) => {
tweakIndex[tweak.key] = tweak;
tweak.Initialize();
});
requstComponent;
setInterval(requstComponent, 1000)
};