add cloning logic
This commit is contained in:
parent
365b297397
commit
835c5fdd66
8 changed files with 367 additions and 49 deletions
213
index.js
213
index.js
|
|
@ -28,7 +28,16 @@ const db = betterSqlite3("./dbs/0.sqlite");
|
|||
db.pragma("journal_mode = WAL");
|
||||
|
||||
function getAllRoutes(db) {
|
||||
return db.prepare("SELECT * from routes ORDER BY id ASC;").all();
|
||||
return db
|
||||
.prepare(
|
||||
`
|
||||
SELECT routes.*, structures.route_prefix
|
||||
FROM routes
|
||||
JOIN structures ON routes.structure_id = structures.id
|
||||
ORDER BY routes.id ASC;
|
||||
`
|
||||
)
|
||||
.all();
|
||||
}
|
||||
|
||||
app.use(
|
||||
|
|
@ -71,8 +80,11 @@ function applyMigrations() {
|
|||
|
||||
function buildRoutes() {
|
||||
for (let route of getAllRoutes(db)) {
|
||||
const prefix = route.route_prefix;
|
||||
console.log(route);
|
||||
const p = prefix ? path.join(route.route_prefix, route.path) : route.path;
|
||||
routes[route.verb].push({
|
||||
matcher: match(route.path, { decode: decodeURIComponent }),
|
||||
matcher: match(p, { decode: decodeURIComponent }),
|
||||
id: route.id,
|
||||
path: route.path,
|
||||
});
|
||||
|
|
@ -211,6 +223,17 @@ function updateDb(db, appDb) {
|
|||
db.prepare(sql).run(...values);
|
||||
}
|
||||
|
||||
function updateStruct(db, struct) {
|
||||
const fields = ["name", "route_prefix"];
|
||||
console.log(struct, fields);
|
||||
const values = fields.map((field) => struct[field]);
|
||||
const placeholders = fields.map((field) => `${field} = ?`).join(", ");
|
||||
|
||||
const sql = `UPDATE structures SET ${placeholders} WHERE id = ?`;
|
||||
values.push(struct.id); // Add routeId to the end for the WHERE clause
|
||||
db.prepare(sql).run(...values);
|
||||
}
|
||||
|
||||
function updateTemplate(db, template) {
|
||||
const fields = ["content", "name", "test_object"];
|
||||
const values = fields.map((field) => template[field]);
|
||||
|
|
@ -371,7 +394,92 @@ function getDbInstance(dbId) {
|
|||
return dbInstance;
|
||||
}
|
||||
|
||||
function bootstrapTemplateWithHTMXetc(htmlString, blissRoute, wrapHTML) {
|
||||
function cloneStructure(
|
||||
structId,
|
||||
newStructureName,
|
||||
userId,
|
||||
routePrefix = "",
|
||||
cloneDb = false
|
||||
) {
|
||||
const transaction = db.transaction(() => {
|
||||
const cloneStructure = db.prepare(`
|
||||
INSERT INTO structures (name, user_id, route_prefix, cloned_from)
|
||||
VALUES (?, ?, ?, ?);
|
||||
`);
|
||||
|
||||
const newStructId = cloneStructure.run(
|
||||
newStructureName,
|
||||
userId,
|
||||
routePrefix,
|
||||
structId
|
||||
).lastInsertRowid;
|
||||
|
||||
if (cloneDb) {
|
||||
const dbIds = db
|
||||
.prepare(`select db_id from structure_dbs where structure_id = ?;`)
|
||||
.all(structId);
|
||||
|
||||
for (let { db_id } of dbIds) {
|
||||
const newDb = db
|
||||
.prepare(
|
||||
`
|
||||
INSERT INTO dbs (name, structure_id, library)
|
||||
SELECT name, ?, library FROM dbs WHERE id = ?;
|
||||
`
|
||||
)
|
||||
.run(newStructId, db_id).lastInsertRowid;
|
||||
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO structure_dbs (db_id, structure_id, alias)
|
||||
SELECT ?, ?, alias FROM structure_dbs WHERE db_id = ? AND structure_id = ?;
|
||||
`
|
||||
).run(newDb, newStructId, db_id, structId);
|
||||
|
||||
db.prepare(`select id from dbs where structure_id = ?`)
|
||||
.all(newStructId)
|
||||
.map((new_db) => {
|
||||
console.log(db_id, new_db.id);
|
||||
const srcPath = path.join(__dirname, "dbs", `${db_id}.sqlite`);
|
||||
const destPath = path.join(__dirname, "dbs", `${new_db.id}.sqlite`);
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
fs.copyFileSync(srcPath + "-shm", destPath + "-shm");
|
||||
fs.copyFileSync(srcPath + "-wal", destPath + "-wal");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO structure_dbs (db_id, structure_id, alias)
|
||||
SELECT db_id, ?, alias FROM structure_dbs WHERE structure_id = ?;
|
||||
`
|
||||
).run(newStructId, structId);
|
||||
}
|
||||
const cloneTemplates = db.prepare(`
|
||||
INSERT INTO templates (name, content, structure_id, test_object, engine)
|
||||
SELECT name, content, ?, test_object, engine FROM templates WHERE structure_id = ?;
|
||||
`);
|
||||
cloneTemplates.run(newStructId, structId);
|
||||
|
||||
const cloneRoutes = db.prepare(`
|
||||
INSERT INTO routes (verb, path, structure_id, handler)
|
||||
SELECT verb, path, ?, handler FROM routes WHERE structure_id = ?;
|
||||
`);
|
||||
cloneRoutes.run(newStructId, structId);
|
||||
|
||||
return newStructId;
|
||||
});
|
||||
|
||||
return transaction();
|
||||
}
|
||||
|
||||
function bootstrapTemplateWithHTMXetc(
|
||||
htmlString,
|
||||
blissRoute,
|
||||
blissClone,
|
||||
blissCopy,
|
||||
wrapHTML
|
||||
) {
|
||||
if (htmlString.startsWith("<html>") || wrapHTML) {
|
||||
const $ = cheerio.load(htmlString);
|
||||
let head = $("head");
|
||||
|
|
@ -390,13 +498,18 @@ function bootstrapTemplateWithHTMXetc(htmlString, blissRoute, wrapHTML) {
|
|||
`);
|
||||
|
||||
if (blissRoute) {
|
||||
$.root().children().attr("data-bliss-route", blissRoute);
|
||||
$("body").attr("data-bliss-route", blissRoute);
|
||||
$("body").attr("data-bliss-clone", blissClone);
|
||||
if (blissCopy) $("body").attr("data-bliss-copy", blissCopy);
|
||||
}
|
||||
|
||||
htmlString = $.html();
|
||||
} else if (blissRoute) {
|
||||
const $ = cheerio.load(htmlString, null, false);
|
||||
$.root().children().attr("data-bliss-route", blissRoute);
|
||||
$.root().children().attr("data-bliss-clone", blissClone);
|
||||
if (blissCopy) $.root().children().attr("data-bliss-copy", blissCopy);
|
||||
console.log(blissCopy, "fleem")
|
||||
htmlString = $.html();
|
||||
}
|
||||
|
||||
|
|
@ -444,6 +557,27 @@ app.get("/workshop/:structure_id", (req, res) => {
|
|||
);
|
||||
});
|
||||
|
||||
app.post("/workshop/:structure_id/clone", (req, res) => {
|
||||
let routePrefix = req.body.route_prefix;
|
||||
routePrefix = routePrefix[0] == "/" ? routePrefix : "/" + routePrefix;
|
||||
const cloneDb = req.body.clone_db == "true";
|
||||
let newStructureId;
|
||||
try {
|
||||
newStructureId = cloneStructure(
|
||||
req.params.structure_id,
|
||||
req.body.name,
|
||||
1,
|
||||
// req.session.userId,
|
||||
routePrefix,
|
||||
cloneDb
|
||||
);
|
||||
buildRoutes();
|
||||
} catch (e) {
|
||||
return res.send(e.stack);
|
||||
}
|
||||
return smartRedirect(req, res, `/workshop/${newStructureId}`);
|
||||
});
|
||||
|
||||
app.post("/workshop/:structure_id/db", (req, res) => {
|
||||
const dbId = createDb(db, req.params.structure_id, req.body.name);
|
||||
const redirectUrl = `/workshop/${req.params.structure_id}/db/${dbId}`;
|
||||
|
|
@ -485,6 +619,7 @@ app.post("/workshop/:structure_id/route", (req, res) => {
|
|||
req.params.structure_id,
|
||||
"// put your handler code here\n\nfunction handler(req, res) {\n res.send('henlo world') \n}"
|
||||
);
|
||||
// todo optimize by only adding new, don't just rebuild all
|
||||
buildRoutes();
|
||||
return smartRedirect(
|
||||
req,
|
||||
|
|
@ -591,15 +726,22 @@ app.get("/workshop/:structure_id/template/:template_id", (req, res) => {
|
|||
|
||||
app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
|
||||
const template = getTemplate(db, req.params.template_id);
|
||||
const struct = getStructure(db, req.params.structure_id);
|
||||
const eta = getTemplater(req.params.structure_id);
|
||||
|
||||
const context = vm.createContext({ it: null });
|
||||
vm.runInContext(template.test_object, context);
|
||||
|
||||
context.it.route = function (url) {
|
||||
return struct.route_prefix ? path.join(struct.route_prefix, url) : url;
|
||||
};
|
||||
|
||||
return res.send(
|
||||
bootstrapTemplateWithHTMXetc(
|
||||
eta.render(template.name, context.it),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true
|
||||
)
|
||||
);
|
||||
|
|
@ -607,11 +749,15 @@ app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
|
|||
|
||||
app.get("/workshop/:structure_id/route/:id", (req, res) => {
|
||||
const route = getRoute(db, req.params.id);
|
||||
const routePrefix = getStructure(db, req.params.structure_id).route_prefix;
|
||||
return res.send(
|
||||
bootstrapTemplateWithHTMXetc(
|
||||
eta.render("workshop/route", {
|
||||
route: route,
|
||||
previewUrl: route["verb"] == "GET" ? route.path : "/workshop/tip",
|
||||
previewUrl:
|
||||
route["verb"] == "GET"
|
||||
? path.join(routePrefix, route.path)
|
||||
: "/workshop/tip",
|
||||
...sidebarStuff(db, req.params.structure_id),
|
||||
})
|
||||
)
|
||||
|
|
@ -682,6 +828,25 @@ app.get("/workshop/:structure_id/files", (req, res) => {
|
|||
);
|
||||
});
|
||||
|
||||
app.get("/workshop/:structure_id/settings", (req, res) => {
|
||||
return res.send(
|
||||
bootstrapTemplateWithHTMXetc(
|
||||
eta.render("workshop/settings", {
|
||||
...sidebarStuff(db, req.params.structure_id),
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
app.put("/workshop/:structure_id/settings", (req, res) => {
|
||||
let structId = req.params.structure_id;
|
||||
let struct = getStructure(db, structId);
|
||||
updateStruct(db, { ...struct, route_prefix: req.body.route_prefix });
|
||||
struct = getStructure(db, structId);
|
||||
|
||||
res.send("success!");
|
||||
});
|
||||
|
||||
app.get("/workshop/:structure_id/new_template_modal", (req, res) => {
|
||||
return res.send(
|
||||
bootstrapTemplateWithHTMXetc(
|
||||
|
|
@ -712,15 +877,31 @@ app.get("/workshop/:structure_id/new_db_modal", (req, res) => {
|
|||
);
|
||||
});
|
||||
|
||||
app.get("/workshop/:structure_id/clone_modal", (req, res) => {
|
||||
const structure = getStructure(db, req.params.structure_id);
|
||||
return res.send(
|
||||
bootstrapTemplateWithHTMXetc(
|
||||
eta.render("workshop/clone_structure_modal", {
|
||||
structure,
|
||||
defaultRoutePrefix: path.join(structure.route_prefix || "", "clone"),
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
function embedHTML(url) {
|
||||
return `<div hx-get="${url}" hx-trigger="load"></div>`;
|
||||
}
|
||||
|
||||
app.all("*", (req, res) => {
|
||||
const path = req.path;
|
||||
const req_path = req.path;
|
||||
const verb = req.method;
|
||||
|
||||
try {
|
||||
let routeMatch = null;
|
||||
|
||||
for (let { matcher, id } of routes[verb]) {
|
||||
let matchFromRoute = matcher(path);
|
||||
let matchFromRoute = matcher(req_path);
|
||||
if (matchFromRoute) {
|
||||
routeMatch = { params: matchFromRoute.params, id: id };
|
||||
}
|
||||
|
|
@ -733,6 +914,7 @@ app.all("*", (req, res) => {
|
|||
.get(routeMatch.id);
|
||||
|
||||
let dbs = getDbsForStructure(db, route.structure_id);
|
||||
let __urlPrefix = getStructure(db, route.structure_id).route_prefix;
|
||||
|
||||
const allDbInstances = {};
|
||||
|
||||
|
|
@ -762,13 +944,26 @@ app.all("*", (req, res) => {
|
|||
res.rawSend(
|
||||
bootstrapTemplateWithHTMXetc(
|
||||
args[0],
|
||||
`/workshop/${route.structure_id}/route/${route.id}`
|
||||
`/workshop/${route.structure_id}/route/${route.id}`,
|
||||
`/workshop/${route.structure_id}/clone/`,
|
||||
verb == "GET" ? embedHTML(req.originalUrl) : null
|
||||
)
|
||||
);
|
||||
};
|
||||
res.render = (template, context) => {
|
||||
context.route = function (url) {
|
||||
return __urlPrefix ? path.join(__urlPrefix, url) : url;
|
||||
};
|
||||
const eta = getTemplater(route.structure_id);
|
||||
res.send(bootstrapTemplateWithHTMXetc(eta.render(template, context)));
|
||||
console.log(route.method, "\n\n\n");
|
||||
res.rawSend(
|
||||
bootstrapTemplateWithHTMXetc(
|
||||
eta.render(template, context),
|
||||
`/workshop/${route.structure_id}/route/${route.id}`,
|
||||
`/workshop/${route.structure_id}/clone_modal/`,
|
||||
verb == "GET" ? embedHTML(req.originalUrl) : null
|
||||
)
|
||||
);
|
||||
};
|
||||
return vm.runInContext(
|
||||
(route.handler += `\n\nhandler(req, res)`),
|
||||
|
|
|
|||
|
|
@ -11,9 +11,12 @@ CREATE TABLE structures (
|
|||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
user_id INTEGER,
|
||||
route_prefix TEXT,
|
||||
cloned_from INTEGER,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id),
|
||||
FOREIGN KEY(cloned_from) REFERENCES structures(id),
|
||||
UNIQUE(name, user_id)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,65 +1,121 @@
|
|||
document.addEventListener("DOMContentLoaded", function () {
|
||||
console.log("supp", document.location)
|
||||
// Create the magnifying glass emoji button
|
||||
const magnifyingGlass = document.createElement('div');
|
||||
magnifyingGlass.innerHTML = '🔍';
|
||||
magnifyingGlass.className = 'fixed top-2 right-2 cursor-pointer text-2xl z-50';
|
||||
const magnifyingGlass = document.createElement("div");
|
||||
magnifyingGlass.innerHTML = "🔍";
|
||||
magnifyingGlass.className =
|
||||
"fixed bottom-2 right-2 cursor-pointer text-2xl z-50";
|
||||
document.body.appendChild(magnifyingGlass);
|
||||
|
||||
let highlighted = false;
|
||||
|
||||
// Function to highlight elements
|
||||
function highlightElements() {
|
||||
const elements = document.querySelectorAll('[data-bliss-route]');
|
||||
elements.forEach(el => {
|
||||
if (!el.classList.contains('highlighted')) {
|
||||
el.classList.add('border-2', 'border-yellow-500', 'relative', 'highlighted');
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "inspector-modal";
|
||||
document.body.appendChild(modal);
|
||||
|
||||
const elements = document.querySelectorAll("[data-bliss-route]");
|
||||
elements.forEach((el) => {
|
||||
if (!el.classList.contains("highlighted")) {
|
||||
el.classList.add(
|
||||
"border-2",
|
||||
"border-yellow-500",
|
||||
"relative",
|
||||
"highlighted",
|
||||
"p-2"
|
||||
);
|
||||
let controls = document.createElement("div");
|
||||
controls.className =
|
||||
"flex absolute gap-1 -bottom-3 right-0 inspector-controls";
|
||||
|
||||
// Create the edit icon
|
||||
const editIcon = document.createElement('a');
|
||||
editIcon.href = el.getAttribute('data-bliss-route');
|
||||
editIcon.innerHTML = '✏️';
|
||||
editIcon.className = 'absolute text-lg bg-white rounded-full p-1 shadow -bottom-3 -right-3';
|
||||
el.appendChild(editIcon);
|
||||
const editIcon = document.createElement("a");
|
||||
editIcon.href = el.getAttribute("data-bliss-route");
|
||||
editIcon.innerHTML = "✏️";
|
||||
editIcon.className =
|
||||
"text-lg bg-white rounded-full p-1 shadow edit-icon";
|
||||
controls.appendChild(editIcon);
|
||||
|
||||
// Create the copy icon
|
||||
if (el.getAttribute("data-bliss-copy")) {
|
||||
const copyIcon = document.createElement("button");
|
||||
copyIcon.innerHTML = "📋";
|
||||
copyIcon.className =
|
||||
"text-lg bg-white rounded-full p-1 shadow copy-icon";
|
||||
controls.appendChild(copyIcon);
|
||||
copyIcon.addEventListener("click", () => {
|
||||
navigator.clipboard
|
||||
.writeText(el.getAttribute("data-bliss-copy"))
|
||||
.then(() => {
|
||||
copyIcon.innerHTML = "✅";
|
||||
})
|
||||
.catch((err) => {
|
||||
copyIcon.innerHTML = "❌";
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Create the clone icon
|
||||
const cloneIcon = document.createElement("div");
|
||||
cloneIcon.innerHTML = "👯♀️";
|
||||
cloneIcon.className =
|
||||
"text-lg bg-white rounded-full cursor-pointer p-1 shadow clone-icon";
|
||||
cloneIcon.setAttribute("hx-get", el.getAttribute("data-bliss-clone"));
|
||||
cloneIcon.setAttribute("hx-target", "#inspector-modal");
|
||||
|
||||
controls.setAttribute(
|
||||
"_",
|
||||
`on mouseover set my.style.zIndex to 10000
|
||||
on mouseout set my.style.zIndex to "initial"
|
||||
`
|
||||
);
|
||||
controls.appendChild(cloneIcon);
|
||||
|
||||
el.appendChild(controls);
|
||||
htmx.process(controls);
|
||||
_hyperscript.processNode(controls);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to remove highlights
|
||||
function removeHighlights() {
|
||||
const elements = document.querySelectorAll('[data-bliss-route].highlighted');
|
||||
elements.forEach(el => {
|
||||
el.classList.remove('border-2', 'border-yellow-500', 'relative', 'highlighted');
|
||||
const editIcon = el.querySelector('a');
|
||||
if (editIcon) {
|
||||
el.removeChild(editIcon);
|
||||
}
|
||||
const elements = document.querySelectorAll(
|
||||
"[data-bliss-route].highlighted"
|
||||
);
|
||||
elements.forEach((el) => {
|
||||
el.classList.remove(
|
||||
"border-2",
|
||||
"border-yellow-500",
|
||||
"relative",
|
||||
"highlighted",
|
||||
"p-2"
|
||||
);
|
||||
el.querySelectorAll(".inspector-controls").forEach((el) => el.remove());
|
||||
});
|
||||
}
|
||||
|
||||
magnifyingGlass.addEventListener('click', function() {
|
||||
magnifyingGlass.addEventListener("click", function () {
|
||||
if (!highlighted) {
|
||||
highlightElements();
|
||||
magnifyingGlass.innerHTML = '❌';
|
||||
magnifyingGlass.innerHTML = "❌";
|
||||
} else {
|
||||
removeHighlights();
|
||||
magnifyingGlass.innerHTML = '🔍';
|
||||
magnifyingGlass.innerHTML = "🔍";
|
||||
}
|
||||
highlighted = !highlighted;
|
||||
});
|
||||
|
||||
// Observe the document for changes
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (highlighted) {
|
||||
highlightElements();
|
||||
}
|
||||
});
|
||||
});
|
||||
// const observer = new MutationObserver(function (mutations) {
|
||||
// if (highlighted) {
|
||||
// highlightElements();
|
||||
// }
|
||||
// });
|
||||
|
||||
// Configure the observer
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
// // Configure the observer
|
||||
// observer.observe(document.body, {
|
||||
// childList: true,
|
||||
// subtree: true,
|
||||
// });
|
||||
});
|
||||
24
views/workshop/clone_structure_modal.eta
Normal file
24
views/workshop/clone_structure_modal.eta
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<div class="fixed inset-0 flex items-center justify-center z-10 " _="on closeModal remove me">
|
||||
<div class="bg-white rounded-md border border-black p-2">
|
||||
<div class="text-lg">Clone Structure</div>
|
||||
<form hx-post="/workshop/<%= it.structure.id %>/clone" hx-target="#response" class="flex flex-col gap-2">
|
||||
<div class="flex flex-col">
|
||||
<label class="text-xs">Name</label>
|
||||
<input name="name" id="name" type="text" value="<%= it.structure.name %> clone" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<label class="text-xs">Route Prefix</label>
|
||||
<input name="route_prefix" id="route_prefix" type="text" value="<%= it.defaultRoutePrefix %>" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<input id="default-checkbox" type="checkbox" name="cloneDBs" value="true">
|
||||
<label class="text-xs">Clone DBs</label>
|
||||
</div>
|
||||
<section class="flex flex-end gap-2" style="justify-content: flex-end">
|
||||
<button class="bg-green-500 rounded p-2 hover:bg-green-700 text-white" type="submit">Clone</button>
|
||||
<button class="bg-gray-400 rounded p-2 hover:bg-gray-700 text-white" type="button" _="on click trigger closeModal">Cancel</button>
|
||||
</section>
|
||||
</form>
|
||||
<div id="response"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -39,3 +39,4 @@
|
|||
<div id="response"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -20,3 +20,4 @@
|
|||
<div id="response"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
34
views/workshop/settings.eta
Normal file
34
views/workshop/settings.eta
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/css/xp.css">
|
||||
<script src="/js/ace/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="flex flex-col w-full h-full window">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">Workshop - <%~ include('/workshop/structure_name', {structure: it.structure}) %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row flex-grow window-body">
|
||||
<div class="w-1/5 h-full">
|
||||
<%~ include('/workshop/sidebar', it) %>
|
||||
</div>
|
||||
<div id="main-editor" class="flex flex-grow">
|
||||
<div id="left-pane" class="flex-grow h-full ">
|
||||
<form hx-put="/workshop/<%= it.structure.id %>/settings" hx-target="#result" class="p-2 flex flex-col gap-2 max-w-48">
|
||||
<div class="field-row-stacked">
|
||||
<label for="name">Route Prefix</label>
|
||||
<input name="route_prefix" value="<%= it.structure.route_prefix %>">
|
||||
</div>
|
||||
<button id="save-btn" type="submit">Save</button>
|
||||
<div id="result"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
<% it.routes.map(route => { %>
|
||||
<li>
|
||||
<a href="/workshop/<%= it.structure.id %>/route/<%= route.id %>">
|
||||
<%= route.verb %> - <%= route.path %>
|
||||
<%= route.verb %> -
|
||||
<%= it.structure.route_prefix %><%= route.path %>
|
||||
</a>
|
||||
</li>
|
||||
<% }) %>
|
||||
|
|
@ -59,5 +60,8 @@
|
|||
<li>
|
||||
<a href="/workshop/<%= it.structure.id %>/files">Files</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/workshop/<%= it.structure.id %>/settings">Settings</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="modal-zone" style="z-index: 1000000;"></div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue