Skip to content

Commit 520a486

Browse files
committed
Got retrieve (list/detail) and create working in js.
Still working through edit (project) and update (new release).
1 parent 8d5e091 commit 520a486

9 files changed

Lines changed: 617 additions & 46 deletions

File tree

project_manager/static/project_manager/css/custom.css

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,3 +467,41 @@ footer .container {
467467
right: inherit;
468468
}
469469
}
470+
471+
#project-list {
472+
list-style: none;
473+
padding: 0;
474+
margin: 0;
475+
}
476+
477+
.project-item {
478+
display: flex;
479+
align-items: flex-start;
480+
gap: 12px;
481+
padding: 12px 0;
482+
border-bottom: 1px solid #A44E25;
483+
}
484+
485+
.project-media {
486+
width: 400px;
487+
height: 225px;
488+
flex-shrink: 0;
489+
position: relative;
490+
}
491+
492+
.help-text {
493+
font-size: 1rem;
494+
font-style: italic;
495+
color: #888;
496+
display: block;
497+
}
498+
499+
.required-asterisk::after {
500+
content: " *";
501+
color: red;
502+
font-weight: bold;
503+
}
504+
505+
.form-field {
506+
margin-bottom: 12px;
507+
}
-1.82 KB
Loading
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
function isLongText(meta) {
2+
return !!(meta.max_length && meta.max_length > 200);
3+
}
4+
5+
function showNotAuthenticatedMessage(projectType) {
6+
const singProjectType = projectType.slice(0, -1);
7+
const formView = document.getElementById("form-view");
8+
formView.style.display = "block";
9+
formView.innerHTML = `
10+
<div class="auth-warning">
11+
<h2>You must be logged in to create a new ${singProjectType}.</h2>
12+
<p>Please log in and try again.</p>
13+
</div>
14+
`;
15+
}
16+
17+
function getCookie(name) {
18+
const value = `; ${document.cookie}`;
19+
const parts = value.split(`; ${name}=`);
20+
if (parts.length === 2) return parts.pop().split(';').shift();
21+
}
22+
23+
async function handleCreateError(form, response) {
24+
// Try to parse error details (DRF usually returns JSON)
25+
let errorText = "There was a problem creating your project.";
26+
27+
try {
28+
const err = await response.json();
29+
errorText = Object.values(err).flat().join(" ");
30+
} catch (e) {
31+
// If JSON parsing fails, keep default message
32+
}
33+
34+
// Clear the form
35+
form.reset();
36+
37+
// Insert an error message at the top
38+
const msg = document.createElement("div");
39+
msg.classList.add("form-error-message");
40+
msg.textContent = errorText;
41+
42+
form.prepend(msg);
43+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
window.onload = function(){
2+
console.log('create')
3+
const urlSearchParams = new URLSearchParams(window.location.search);
4+
const params = Object.fromEntries(urlSearchParams.entries());
5+
let originalPath = window.location.pathname;
6+
if (originalPath.startsWith("/")) {
7+
originalPath = originalPath.slice(1);
8+
}
9+
if (originalPath.endsWith("/")) {
10+
originalPath = originalPath.slice(0, -1);
11+
}
12+
const pathStrSplit = originalPath.split("/");
13+
const projectType = pathStrSplit[0];
14+
if (projectType === 'plugins' || projectType === 'packages') {
15+
if (pathStrSplit[1]) {
16+
const projectSlug = pathStrSplit[1];
17+
if (projectSlug === "create") {
18+
buildCreateForm(projectType);
19+
}
20+
} else {
21+
showListView(projectType);
22+
}
23+
}
24+
};
25+
26+
async function buildCreateForm(projectType) {
27+
const urlPath = '/api/' + projectType + '/projects/';
28+
29+
const res = await fetch(urlPath, { method: "OPTIONS" })
30+
const data = await res.json();
31+
if (!data.actions || !data.actions.POST) {
32+
showNotAuthenticatedMessage(projectType);
33+
return;
34+
}
35+
36+
document.getElementById("create-form-view").style.display = "block";
37+
const singProjectType = projectType.slice(0, -1);
38+
document.getElementById("create-form-title").textContent = "Create " + singProjectType;
39+
40+
const form = document.getElementById("dynamic-create-form");
41+
const fields = Object.entries(data.actions.POST).filter(([name, meta]) => !meta.read_only);
42+
fields.forEach(([fieldName, meta]) => {
43+
const fieldElement = buildField(fieldName, meta);
44+
form.appendChild(fieldElement);
45+
});
46+
47+
const submit = document.createElement("button");
48+
submit.type = "submit";
49+
submit.textContent = "Submit";
50+
form.appendChild(submit);
51+
52+
form.addEventListener("submit", async (e) => {
53+
e.preventDefault();
54+
55+
const csrftoken = getCookie("csrftoken");
56+
const formData = buildFormData(form, fields);
57+
const postRes = await fetch(urlPath, {
58+
method: "POST",
59+
headers: {
60+
"X-CSRFToken": csrftoken
61+
},
62+
body: formData
63+
});
64+
65+
if (postRes.status === 201) {
66+
const data = await postRes.json();
67+
const slug = data.slug;
68+
console.log(`/${projectType}/${slug}`);
69+
window.location.href = `/${projectType}/${slug}`;
70+
return;
71+
}
72+
handleCreateError(form, postRes);
73+
});
74+
}
75+
76+
function buildField(fieldName, meta, parentName = null) {
77+
const fullName = parentName ? `${parentName}.${fieldName}` : fieldName;
78+
79+
// Handle nested objects
80+
if (meta.children) {
81+
const fieldset = document.createElement("fieldset");
82+
83+
const legend = document.createElement("legend");
84+
legend.textContent = meta.label || fieldName;
85+
fieldset.appendChild(legend);
86+
87+
Object.entries(meta.children).forEach(([childName, childMeta]) => {
88+
const childField = buildField(childName, childMeta, fullName);
89+
fieldset.appendChild(childField);
90+
});
91+
92+
return fieldset;
93+
}
94+
95+
// Handle primitive fields
96+
const wrapper = document.createElement("div");
97+
wrapper.classList.add("form-field");
98+
99+
const label = document.createElement("label");
100+
label.textContent = meta.label || fieldName;
101+
label.setAttribute("for", fullName);
102+
103+
if (meta.type === "string" && isLongText(meta)) {
104+
input = document.createElement("textarea");
105+
input.rows = 6;
106+
input.cols = 50;
107+
} else {
108+
input = document.createElement("input");
109+
input.type = "text";
110+
}
111+
input.name = fullName;
112+
input.id = fullName;
113+
114+
switch (meta.type) {
115+
case "integer":
116+
input.type = "number";
117+
break;
118+
case "boolean":
119+
input.type = "checkbox";
120+
break;
121+
case "image upload":
122+
input.type = "file";
123+
input.accept = "image/*"
124+
break;
125+
case "file upload":
126+
input.type = "file";
127+
input.accept = ".zip"
128+
break;
129+
default:
130+
input.type = "text";
131+
}
132+
133+
if (meta.required) {
134+
input.required = true;
135+
label.classList.add("required-asterisk");
136+
}
137+
138+
wrapper.appendChild(label);
139+
if (meta.help_text) {
140+
const help = document.createElement("small");
141+
help.textContent = meta.help_text;
142+
help.classList.add("help-text");
143+
wrapper.appendChild(help);
144+
}
145+
wrapper.appendChild(input);
146+
147+
return wrapper;
148+
}
149+
150+
151+
function buildFormData(form, fields, formData = new FormData(), prefix = "") {
152+
fields.forEach(([fieldName, meta]) => {
153+
const fullName = prefix ? `${prefix}.${fieldName}` : fieldName;
154+
155+
if (meta.children) {
156+
buildFormData(form, Object.entries(meta.children), formData, fullName);
157+
return;
158+
}
159+
160+
const input = form.querySelector(`[name="${fullName}"]`);
161+
if (!input) return;
162+
163+
if (meta.type === "boolean") {
164+
formData.append(fullName, input.checked);
165+
} else if (meta.type === "file upload" || meta.type === "image upload") {
166+
if (input.files.length > 0) {
167+
formData.append(fullName, input.files[0] || null);
168+
}
169+
} else {
170+
formData.append(fullName, input.value);
171+
}
172+
});
173+
174+
return formData;
175+
}
176+
177+
178+
async function handleCreateError(form, response) {
179+
// Try to parse error details (DRF usually returns JSON)
180+
let errorText = "There was a problem creating your project.";
181+
182+
try {
183+
const err = await response.json();
184+
errorText = Object.values(err).flat().join(" ");
185+
} catch (e) {
186+
// If JSON parsing fails, keep default message
187+
}
188+
189+
// Clear the form
190+
form.reset();
191+
192+
// Insert an error message at the top
193+
const msg = document.createElement("div");
194+
msg.classList.add("form-error-message");
195+
msg.textContent = errorText;
196+
197+
form.prepend(msg);
198+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
window.onload = function(){
2+
console.log('delete')
3+
const urlSearchParams = new URLSearchParams(window.location.search);
4+
const params = Object.fromEntries(urlSearchParams.entries());
5+
let originalPath = window.location.pathname;
6+
if (originalPath.startsWith("/")) {
7+
originalPath = originalPath.slice(1);
8+
}
9+
if (originalPath.endsWith("/")) {
10+
originalPath = originalPath.slice(0, -1);
11+
}
12+
const pathStrSplit = originalPath.split("/");
13+
const projectType = pathStrSplit[0];
14+
if (projectType === 'plugins' || projectType === 'packages') {
15+
if (pathStrSplit[1]) {
16+
const projectSlug = pathStrSplit[1];
17+
if (projectSlug === "create") {
18+
buildCreateForm(projectType);
19+
} else {
20+
const projectAction = pathStrSplit[2];
21+
if (!projectAction) {
22+
showDetailView(projectType, projectSlug);
23+
} else if (projectAction === "edit") {
24+
buildEditForm(projectType, projectSlug);
25+
// } else if (projectAction === "update") {
26+
// buildUpdateForm(projectAction, projectSlug);
27+
}
28+
}
29+
} else {
30+
showListView(projectType);
31+
}
32+
}
33+
};
34+

0 commit comments

Comments
 (0)