comparison postdog/main.c @ 111:48f260576059

[PostDog] Rewriting it from scratch as it is unreadable for me.
author June Park <parkjune1995@gmail.com>
date Sun, 04 Jan 2026 06:39:16 -0800
parents fff1b048dda6
children d6d578b49a19
comparison
equal deleted inserted replaced
110:99c4530e4629 111:48f260576059
1 /**
2 * Entirely written by Claude AI.
3 */
4 #include <stdio.h> 1 #include <stdio.h>
5 #include <stdlib.h> 2 #include <stdlib.h>
6 #include <string.h> 3 #include <string.h>
7 #include <time.h> 4 #include <time.h>
8 #include <sys/stat.h> 5 #include <sys/stat.h>
9 #include <dirent.h> 6 #include <dirent.h>
10 7 #include "dowa/dowa.h"
11 8
12 // third party
13 #include <curl/curl.h> 9 #include <curl/curl.h>
14 #include "third_party/raylib/include/raylib.h" 10 #include "third_party/raylib/include/raylib.h"
15 #define RAYGUI_IMPLEMENTATION 11 #define RAYGUI_IMPLEMENTATION
16 #include "third_party/raylib/include/raygui.h" 12 #include "third_party/raylib/include/raygui.h"
17 13
60 ActiveTab_JSON = 0, 56 ActiveTab_JSON = 0,
61 ActiveTab_Headers, 57 ActiveTab_Headers,
62 ActiveTab_Params, 58 ActiveTab_Params,
63 } ActiveTab; 59 } ActiveTab;
64 60
65 // Callback function for curl to write response data
66 static size_t Postdog_Curl_Callback(void *contents, size_t size, size_t nmemb, void *userp) 61 static size_t Postdog_Curl_Callback(void *contents, size_t size, size_t nmemb, void *userp)
67 { 62 {
68 size_t real_size = size * nmemb; 63 size_t real_size = size * nmemb;
69 ResponseBuffer *buf = (ResponseBuffer *)userp; 64 ResponseBuffer *buf = (ResponseBuffer *)userp;
70 65
81 buf->data[buf->size] = 0; 76 buf->data[buf->size] = 0;
82 77
83 return real_size; 78 return real_size;
84 } 79 }
85 80
86 // Function to make HTTP request using curl
87 int PostDog_Make_HttpRequest(const char *url, const char *method, const char *headers, 81 int PostDog_Make_HttpRequest(const char *url, const char *method, const char *headers,
88 const char *body, char *response, size_t responseSize) 82 const char *body, char *response, size_t responseSize)
89 { 83 {
90 CURL *curl; 84 CURL *curl;
91 CURLcode res; 85 CURLcode res;
157 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); 151 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
158 152
159 // Perform request 153 // Perform request
160 res = curl_easy_perform(curl); 154 res = curl_easy_perform(curl);
161 155
162 if (res != CURLE_OK) { 156 if (res != CURLE_OK)
163 snprintf(response, responseSize, "Error: %s\n", curl_easy_strerror(res)); 157 snprintf(response, responseSize, "Error: %s\n", curl_easy_strerror(res));
164 } else { 158 else
159 {
165 long response_code; 160 long response_code;
166 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); 161 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
167 162
168 snprintf(response, responseSize, "HTTP Status: %ld\n\n%s", 163 snprintf(response, responseSize, "HTTP Status: %ld\n\n%s",
169 response_code, buffer.data ? buffer.data : ""); 164 response_code, buffer.data ? buffer.data : "");
180 curl_global_cleanup(); 175 curl_global_cleanup();
181 176
182 return 0; 177 return 0;
183 } 178 }
184 179
185 void PostDog_HistoryDirectory_Exists() 180 typedef struct {
186 { 181 Rectangle rectangle;
187 struct stat st = {0}; 182 char *label;
188 if (stat("history", &st) == -1) 183 } TabItem;
189 {
190 mkdir("history", 0700);
191 }
192 }
193
194 int PostDog_HistoryDistory_ItemsLoad(HistoryItem *items, int maxItems)
195 {
196 DIR *dir = opendir("history");
197 if (!dir) return 0;
198
199 struct dirent *entry;
200 int count = 0;
201
202 while ((entry = readdir(dir)) != NULL && count < maxItems)
203 {
204 if (entry->d_name[0] == '.') continue;
205 if (strstr(entry->d_name, ".txt") == NULL) continue;
206
207 // Parse filename: YYYYMMDD_HHMMSS_METHOD.txt
208 strncpy(items[count].filename, entry->d_name, sizeof(items[count].filename) - 1);
209
210 // Extract method from filename
211 char *methodStart = strrchr(entry->d_name, '_');
212 if (methodStart)
213 {
214 methodStart++; // Skip underscore
215 char *dotPos = strchr(methodStart, '.');
216 if (dotPos) {
217 int len = dotPos - methodStart;
218 if (len < 16) {
219 strncpy(items[count].method, methodStart, len);
220 items[count].method[len] = '\0';
221 }
222 }
223 }
224
225 // Create display name: METHOD - YYYYMMDD HHMMSS
226 char dateTime[32] = "";
227 if (strlen(entry->d_name) >= 15)
228 {
229 snprintf(dateTime, sizeof(dateTime), "%.8s %.6s",
230 entry->d_name, entry->d_name + 9);
231 }
232 snprintf(items[count].displayName, sizeof(items[count].displayName),
233 "%s - %s", items[count].method, dateTime);
234 count++;
235 }
236
237 closedir(dir);
238 return count;
239 }
240
241 int PostDog_HistoryDirectory_LoadRequest(const char *filename, char *url, char *method, char *headers, char *body)
242 {
243 char filepath[512];
244 snprintf(filepath, sizeof(filepath), "history/%s", filename);
245
246 FILE *f = fopen(filepath, "r");
247 if (!f) return -1;
248
249 char line[2048];
250 int section = 0; // 0=url, 1=method, 2=headers, 3=body
251
252 url[0] = '\0';
253 method[0] = '\0';
254 headers[0] = '\0';
255 body[0] = '\0';
256
257 while (fgets(line, sizeof(line), f))
258 {
259 // Remove newline
260 line[strcspn(line, "\n")] = 0;
261
262 if (section == 0)
263 {
264 // First line is URL
265 strncpy(url, line, 1024 - 1);
266 section = 1;
267 }
268 else if (strncmp(line, "Method: ", 8) == 0)
269 {
270 strncpy(method, line + 8, 15);
271 section = 2;
272 }
273 else if (strncmp(line, "Headers:", 8) == 0)
274 {
275 section = 2;
276 }
277 else if (strcmp(line, "---") == 0)
278 {
279 section = 3;
280 }
281 else if (strncmp(line, "Body:", 5) == 0)
282 {
283 section = 3;
284 }
285 else if (section == 2 && strcmp(line, "None") != 0)
286 {
287 // Add header line
288 if (strlen(headers) > 0) strcat(headers, "\n");
289 strncat(headers, line, HEADER_INPUT_BUFFER_LEN - strlen(headers) - 1);
290 }
291 else if (section == 3 && strcmp(line, "None") != 0)
292 {
293 // Add body line
294 if (strlen(body) > 0) strcat(body, "\n");
295 strncat(body, line, JSON_INPUT_BUFFER_LEN - strlen(body) - 1);
296 }
297 }
298
299 fclose(f);
300 return 0;
301 }
302
303 void Postdog_UpdateUrlWithParams(char *url, size_t urlSize, const char *baseUrl, const char *params)
304 {
305 // Find if there's already a ? in the URL
306 char *questionMark = strchr(baseUrl, '?');
307
308 if (questionMark != NULL) {
309 // URL already has params, just copy the base
310 strncpy(url, baseUrl, urlSize - 1);
311 } else {
312 // No params yet, add them
313 snprintf(url, urlSize, "%s", baseUrl);
314 }
315
316 // Parse and append params
317 if (params && strlen(params) > 0) {
318 char *paramsCopy = strdup(params);
319 char *line = strtok(paramsCopy, "\n");
320 bool firstParam = (questionMark == NULL);
321
322 while (line != NULL) {
323 // Trim whitespace
324 while (*line == ' ' || *line == '\t') line++;
325
326 if (strlen(line) > 0 && strchr(line, '=')) {
327 size_t currentLen = strlen(url);
328 if (currentLen + 2 < urlSize) {
329 strcat(url, firstParam ? "?" : "&");
330 firstParam = false;
331 strncat(url, line, urlSize - strlen(url) - 1);
332 }
333 }
334 line = strtok(NULL, "\n");
335 }
336 free(paramsCopy);
337 }
338 }
339
340 // Save request to history file
341 void Postdog_SaveRequestToHistory(const char *url, const char *method, const char *headers, const char *body)
342 {
343 PostDog_HistoryDirectory_Exists();
344
345 // Generate filename with timestamp
346 time_t now = time(NULL);
347 struct tm *t = localtime(&now);
348 char filename[256];
349 snprintf(filename, sizeof(filename), "history/%04d%02d%02d_%02d%02d%02d_%s.txt",
350 t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
351 t->tm_hour, t->tm_min, t->tm_sec, method);
352
353 FILE *f = fopen(filename, "w");
354 if (f == NULL) {
355 printf("Failed to create history file\n");
356 return;
357 }
358
359 // Write URL
360 fprintf(f, "%s\n", url);
361
362 // Write method
363 fprintf(f, "Method: %s\n", method);
364
365 // Write headers
366 fprintf(f, "Headers:\n");
367 if (headers && strlen(headers) > 0)
368 {
369 fprintf(f, "%s\n", headers);
370 }
371 else
372 {
373 fprintf(f, "None\n");
374 }
375
376 fprintf(f, "---\n");
377
378 // Write body
379 fprintf(f, "Body:\n");
380 if (body && strlen(body) > 0)
381 {
382 fprintf(f, "%s\n", body);
383 }
384 else
385 {
386 fprintf(f, "None\n");
387 }
388
389 fclose(f);
390 printf("Request saved to %s\n", filename);
391 }
392
393 void PostDog_Render_TextWithScroll(Rectangle textArea, Vector2 scroll, char *input)
394 {
395 BeginScissorMode(textArea.x, textArea.y, textArea.width, textArea.height);
396
397 int yPos = textArea.y + 5 - (int)scroll.y;
398 int charactersPerLine = (int)(textArea.width / (TEXT_SIZE/1.5)); // Account for padding
399 int totalPos = 0;
400 int inputLen = strlen(input);
401
402 while (totalPos < inputLen)
403 {
404 int lineEnd = totalPos;
405 int lineLength = 0;
406
407 while (lineEnd < inputLen && lineLength < charactersPerLine && input[lineEnd] != '\n')
408 {
409 lineEnd++;
410 lineLength++;
411 }
412
413 if (yPos + LINE_HEIGHT > textArea.y && yPos < textArea.y + textArea.height)
414 {
415 char savedChar = input[lineEnd];
416 input[lineEnd] = '\0';
417 DrawText(&input[totalPos], textArea.x + 5, yPos, TEXT_SIZE, DARKGRAY);
418 input[lineEnd] = savedChar;
419 }
420
421 yPos += LINE_HEIGHT;
422
423 totalPos = lineEnd;
424 if (totalPos < inputLen && input[totalPos] == '\n')
425 {
426 totalPos++;
427 }
428 }
429
430 EndScissorMode();
431 }
432 184
433 int main() 185 int main()
434 { 186 {
435 InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "PostDog - HTTP Client"); 187 // -- initizlied --//
188 InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "PostDog");
436 SetWindowState(FLAG_WINDOW_RESIZABLE); 189 SetWindowState(FLAG_WINDOW_RESIZABLE);
437 SetTargetFPS(60); 190 SetTargetFPS(60);
438 191
439 PostDog_HistoryDirectory_Exists(); 192 Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1025 * 10);
440 193
441 // UI State 194 // -- Starting pos ---//
442 char urlInput[1024] = "https://httpbin.org/get"; 195 float side_bar_x = 10;
443 bool urlEditMode = false; 196 float side_bar_y = 10;
444 197
445 char jsonInput[JSON_INPUT_BUFFER_LEN] = "{\"key\":\"value\"}"; 198 Rectangle history_sidebar = { .x = side_bar_x, .y = side_bar_y, .width = 0, .height = 0 };
446 bool jsonEditMode = false; 199
447 200 Rectangle url_area = { 0 };
448 char headersInput[HEADER_INPUT_BUFFER_LEN] = "Content-Type: application/json"; 201 Rectangle textBounds = { 0 };
449 bool headersEditMode = false; 202 Rectangle url_input_bounds = { 0 };
450 203 Rectangle url_text_bounds = { 0 };
451 char responseText[16384] = "Response will appear here...\n\nTry the default URL or enter your own!"; 204 Rectangle url_enter_button = { 0 };
452 205
453 char paramsInput[PARAM_INPUT_BUFFER_LEN] = "key1=value1\nkey2=value2"; 206 char *input_text = (char *)Dowa_Arena_Allocate(arena, 1024 * 4);
454 bool paramsEditMode = false; 207 int sendRequest;
455 208
456 ActiveTab activeTab = ActiveTab_JSON; // 0 = JSON, 1 = Headers, 2 = Params 209 Rectangle input_area = { 0 };
457 210 Rectangle input_tab = { 0 };
458 // HTTP method selection 211 TabItem input_tab_items[4] = {
459 int methodActive = 0; 212 {
460 bool methodDropdown = false; 213 .rectangle={0}, .label="body"
461 const char *methods[] = { "GET", "POST", "PUT", "DELETE" }; 214 },
462 215 {
463 // Scroll support 216 .rectangle={0}, .label="header"
464 Vector2 jsonScroll = { 0, 0 }; 217 },
465 Vector2 headersScroll = { 0, 0 }; 218 {
466 Vector2 paramsScroll = { 0, 0 }; 219 .rectangle={0}, .label="foo"
467 Vector2 responseScroll = { 0, 0 }; 220 },
468 Vector2 historyScroll = { 0, 0 }; 221 {
469 222 .rectangle={0}, .label="bar"
470 // History 223 },
471 HistoryItem historyItems[MAX_HISTORY_ITEMS]; 224 };
472 int historyCount = 0; 225 Rectangle input_body = { 0 };
473 int selectedHistoryIndex = -1; 226
474 227 Rectangle result_area = { 0 };
475 // Load initial history 228 Rectangle result_body = { 0 };
476 historyCount = PostDog_HistoryDistory_ItemsLoad(historyItems, MAX_HISTORY_ITEMS); 229
230 // General styling.
231 int padding = 10; // TODO make it % based?
232
477 233
478 while (!WindowShouldClose()) 234 while (!WindowShouldClose())
479 { 235 {
480 // Get current window dimensions for responsive layout 236 int screen_width = GetScreenWidth();
481 int screenWidth = GetScreenWidth(); 237 int screen_height = GetScreenHeight();
482 int screenHeight = GetScreenHeight(); 238
483 239 history_sidebar.width = screen_width * 0.15;
484 // Layout calculations 240 history_sidebar.height = screen_width - 10;
485 Rectangle sidebar = { 0, 10, SIDEBAR_WIDTH, screenHeight }; 241
486 242 // -- URL Area --//
487 float mainX = SIDEBAR_WIDTH + GENERIC_PADDING; 243 url_area.x = (side_bar_x + history_sidebar.width);
488 float mainWidth = screenWidth - SIDEBAR_WIDTH - GENERIC_PADDING * 2; 244 url_area.y = (side_bar_y);
489 245 url_area.width = (screen_width - history_sidebar.width) * 0.9;
490 // URL input box - leave space for SEND button on the right 246 url_area.height = (screen_height) * 0.1;
491 Rectangle urlBox = { 247
492 mainX, 248 url_text_bounds.x = url_area.x + padding;
493 GENERIC_PADDING, 249 url_text_bounds.y = (url_area.height) / 2;
494 mainWidth - SEND_BUTTON_WIDTH - GENERIC_PADDING, 250 url_text_bounds.width = 7 * (TEXT_SIZE / 2);
495 URL_INPUT_HEIGHT 251 url_text_bounds.height = TEXT_SIZE * 2;
496 }; 252
497 253 url_input_bounds.x = url_text_bounds.x + url_text_bounds.width + padding;
498 // SEND button positioned beside URL input 254 url_input_bounds.y = (url_area.height) / 2;
499 Rectangle sendButton = { 255 url_input_bounds.width = (url_area.width - padding) * 0.7;
500 urlBox.x + urlBox.width + GENERIC_PADDING, 256 url_input_bounds.height = TEXT_SIZE * 2;
501 GENERIC_PADDING, 257
502 SEND_BUTTON_WIDTH, 258 url_enter_button.x = url_input_bounds.x + url_input_bounds.width + padding;
503 SEND_BUTTON_HEIGHT 259 url_enter_button.y = (url_area.height) / 2;
504 }; 260 url_enter_button.width = (url_area.width - padding) * 0.1;
505 261 url_enter_button.height = TEXT_SIZE * 2;
506 // Method dropdown below URL 262
507 Rectangle methodButton = { 263 // -- Input Area --//
508 mainX, 264 input_area.x = (side_bar_x + history_sidebar.width);
509 urlBox.y + urlBox.height + GENERIC_PADDING, 265 input_area.y = (side_bar_y + url_area.height);
510 METHOD_BUTTON_WIDTH, 266 input_area.width = url_area.width * 0.5;
511 METHOD_BUTTON_HEIGHT 267 input_area.height = screen_height - url_area.height;
512 }; 268
513 269 input_tab.x = (side_bar_x + history_sidebar.width) + padding;
514 float tabHeight = 30; 270 input_tab.y = (side_bar_y + url_area.height) + padding;
515 float contentY = methodButton.y + methodButton.height + GENERIC_PADDING + tabHeight; 271 input_tab.width = url_area.width * 0.49 - padding;
516 float contentHeight = screenHeight - contentY - GENERIC_PADDING; 272 input_tab.height = input_area.height * 0.1;
517 273 for (int pos = 0; pos < 4; pos++)
518 float panelWidth = (mainWidth - GENERIC_PADDING) / 2; 274 {
519 275 input_tab_items[pos].rectangle = input_tab;
520 Rectangle tabBar = { 276 input_tab_items[pos].rectangle.x = input_tab.x + (pos * input_tab.width / 4);
521 mainX, 277 input_tab_items[pos].rectangle.width = input_tab.width / 4;
522 methodButton.y + methodButton.height + GENERIC_PADDING, 278 }
523 panelWidth, 279
524 tabHeight 280 input_body.x = input_tab.x;
525 }; 281 input_body.y = input_tab.y + input_tab.height;
526 282 input_body.width = url_area.width * 0.49 - padding;
527 Rectangle requestPanel = { 283 input_body.height = input_area.height - input_tab.height - padding;
528 mainX, 284
529 contentY, 285 // -- Result Area --//
530 panelWidth, 286 result_area.x = (input_area.x + input_area.width);
531 contentHeight 287 result_area.y = (side_bar_y + url_area.height);
532 }; 288 result_area.width = url_area.width * 0.49;
533 289 result_area.height = screen_height - url_area.height;
534 Rectangle responsePanel = { 290
535 mainX + panelWidth + GENERIC_PADDING, 291 result_body.x = result_area.x + padding;
536 contentY, 292 result_body.y = result_area.y + input_tab.height + padding;
537 panelWidth, 293 result_body.width = url_area.width * 0.49 - padding;
538 contentHeight 294 result_body.height = result_area.height - input_tab.height - padding;
539 };
540 295
541 BeginDrawing(); 296 BeginDrawing();
542 ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); 297 ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));
543 298
544 // --- Sidebar Component--- 299 // Sidebar Rect
545 DrawRectangleRec(sidebar, Fade(GRAY, 0.1f)); 300 DrawRectangleRec(history_sidebar, Fade(GRAY, 0.1f));
546 GuiGroupBox(sidebar, "HISTORY"); 301
547 302 // URL area Rect
548 Rectangle refreshBtn = { 303 GuiDrawText("URL: ", url_text_bounds, TEXT_ALIGN_CENTER, RED);
549 sidebar.x + SIDEBAR_PADDING_GENERAL, 304 DrawRectangleRec(url_area, Fade(RED, 0.1f));
550 sidebar.y + SIDEBAR_PADDING_GENERAL, 305 GuiTextBox(url_input_bounds, input_text, 1024 * 4, true);
551 SIDEBAR_REFERSH_BUTTON_WIDTH, 306 sendRequest = GuiButton(url_enter_button, "ENTER");
552 SIDEBAR_REFERSH_BUTTON_HEIGHT 307
553 }; 308 // Input Tabs Rect
554 if (GuiButton(refreshBtn, "Refresh")) 309 DrawRectangleRec(input_area, Fade(BLUE, 0.1f));
310 DrawRectangleRec(input_tab, Fade(DARKBLUE, 0.1f));
311 for (int pos = 0; pos < 4; pos++)
555 { 312 {
556 historyCount = PostDog_HistoryDistory_ItemsLoad(historyItems, MAX_HISTORY_ITEMS); 313 JUNE_GuiButton(input_tab_items[pos].rectangle, input_tab_items[pos].label, 0, Fade(DARKBLUE, 0.1f));
557 } 314 }
558 315 JUNE_GuiTextBox(input_body, input_text, 1024 * 4, true);
559 Rectangle historyArea = { 316
560 sidebar.x + SIDEBAR_PADDING_GENERAL, 317 // Result Rect
561 sidebar.y + SIDEBAR_AREA_PADDING_Y, 318 DrawRectangleRec(result_area, Fade(GREEN, 0.1f));
562 sidebar.width - SIDEBAR_AREA_PADDING_X, 319 DrawRectangleRec(result_body, Fade(DARKGREEN, 0.1f));
563 sidebar.height - SIDEBAR_AREA_PADDING_Y
564 };
565 if (CheckCollisionPointRec(GetMousePosition(), historyArea))
566 {
567 float wheel = GetMouseWheelMove();
568 historyScroll.y += wheel * 20;
569 if (historyScroll.y < 0) historyScroll.y = 0;
570 }
571
572 BeginScissorMode(historyArea.x, historyArea.y, historyArea.width, historyArea.height);
573
574 if (historyCount == 0)
575 {
576 DrawText("No requests yet", historyArea.x + 5, historyArea.y + 25, 10, DARKGRAY);
577 }
578 else
579 {
580 int item_y_position = historyArea.y + SIDEBAR_AREA_PADDING_Y + 5 - (int)historyScroll.y;
581 for (
582 int current_history_item_number = 0;
583 current_history_item_number < historyCount;
584 current_history_item_number++
585 )
586 {
587 if (item_y_position > historyArea.y - SIDEBAR_HISTORY_ITEM_HEIGHT && item_y_position < historyArea.y + historyArea.height)
588 {
589 Rectangle itemRect = { historyArea.x, item_y_position, historyArea.width, SIDEBAR_HISTORY_ITEM_HEIGHT - 2 };
590
591 // Draw button for history item
592 Color bgColor = (selectedHistoryIndex == current_history_item_number) ? Fade(BLUE, 0.3f) : Fade(LIGHTGRAY, 0.5f);
593 if (CheckCollisionPointRec(GetMousePosition(), itemRect) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
594 {
595 // TODO: This is cringe as fuck probably should just have a strucut that we assign and then zero out lol
596 char tempUrl[1024], tempMethod[16], tempHeaders[HEADER_INPUT_BUFFER_LEN], tempBody[JSON_INPUT_BUFFER_LEN];
597
598 if (PostDog_HistoryDirectory_LoadRequest(
599 historyItems[current_history_item_number].filename, tempUrl, tempMethod, tempHeaders, tempBody) == 0)
600 {
601 strncpy(urlInput, tempUrl, sizeof(urlInput) - 1);
602 strncpy(headersInput, tempHeaders, sizeof(headersInput) - 1);
603 strncpy(jsonInput, tempBody, sizeof(jsonInput) - 1);
604
605 // Set method
606 for (int m = 0; m < 4; m++)
607 {
608 if (strcmp(methods[m], tempMethod) == 0)
609 {
610 methodActive = m;
611 break;
612 }
613 }
614
615 selectedHistoryIndex = current_history_item_number;
616 strcpy(responseText, "Request loaded from history. Click SEND to execute.");
617 }
618 }
619
620 DrawRectangleRec(itemRect, bgColor);
621 DrawRectangleLinesEx(itemRect, 1, GRAY);
622
623 // Draw method badge
624 DrawText(historyItems[current_history_item_number].method, itemRect.x + 5, item_y_position + 5, 10, BLACK);
625
626 // Draw timestamp (date only)
627 char dateStr[16] = "";
628 if (strlen(historyItems[current_history_item_number].filename) >= 8)
629 {
630 snprintf(dateStr, sizeof(dateStr), "%.4s-%.2s-%.2s",
631 historyItems[current_history_item_number].filename, historyItems[current_history_item_number].filename + 4, historyItems[current_history_item_number].filename + 6);
632 }
633 DrawText(dateStr, itemRect.x + 5, item_y_position + 18, 8, DARKGRAY);
634
635 // Draw time
636 char timeStr[16] = "";
637 if (strlen(historyItems[current_history_item_number].filename) >= 15) {
638 snprintf(timeStr, sizeof(timeStr), "%.2s:%.2s:%.2s",
639 historyItems[current_history_item_number].filename + 9, historyItems[current_history_item_number].filename + 11, historyItems[current_history_item_number].filename + 13);
640 }
641 DrawText(timeStr, itemRect.x + 5, item_y_position + 28, 8, DARKGRAY);
642 }
643
644 item_y_position += SIDEBAR_HISTORY_ITEM_HEIGHT;
645 }
646 }
647
648 EndScissorMode();
649
650 // --- URL Input Component ---
651 GuiLabel((Rectangle){ mainX, GENERIC_PADDING - 15, 100, 20 }, "URL:");
652 if (GuiTextBox(urlBox, urlInput, 1024, urlEditMode))
653 {
654 urlEditMode = !urlEditMode;
655 }
656 if (urlEditMode && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
657 {
658 SetClipboardText(urlInput);
659 }
660
661 // Send button (beside URL)
662 if (GuiButton(sendButton, "SEND") || (urlEditMode && IsKeyDown(KEY_ENTER)))
663 {
664 strcpy(responseText, "Sending request...\n");
665
666 // Make the actual HTTP request
667 char tempResponse[16384];
668 const char *selectedMethod = methods[methodActive];
669
670 // Use JSON body for POST/PUT, otherwise use empty body
671 const char *requestBody = (methodActive == 1 || methodActive == 2) ? jsonInput : NULL;
672 const char *requestHeaders = headersInput;
673
674 // Save request to history
675 Postdog_SaveRequestToHistory(urlInput, selectedMethod, requestHeaders, requestBody);
676
677 // Reload history to show the new request
678 historyCount = PostDog_HistoryDistory_ItemsLoad(historyItems, MAX_HISTORY_ITEMS);
679
680 // Making the requests.
681 PostDog_Make_HttpRequest(urlInput, selectedMethod, requestHeaders,
682 requestBody, tempResponse, sizeof(tempResponse));
683 strncpy(responseText, tempResponse, sizeof(responseText) - 1);
684 responseText[sizeof(responseText) - 1] = '\0';
685 }
686
687 // Tab toggle (3 tabs now)
688 float tabWidth = tabBar.width / 3;
689 Rectangle jsonTab = { tabBar.x, tabBar.y - 10, tabWidth, tabBar.height };
690 Rectangle headersTab = { tabBar.x + tabWidth, tabBar.y - 10, tabWidth, tabBar.height };
691 Rectangle paramsTab = { tabBar.x + tabWidth * 2, tabBar.y - 10, tabWidth, tabBar.height };
692
693 if (GuiButton(jsonTab, activeTab == ActiveTab_JSON ? "#191#Body" : "Body"))
694 {
695 activeTab = ActiveTab_JSON;
696 }
697
698 if (GuiButton(headersTab, activeTab == ActiveTab_Headers ? "#191#Headers" : "Headers"))
699 {
700 activeTab = ActiveTab_Headers;
701 }
702
703 if (GuiButton(paramsTab, activeTab == ActiveTab_Params ? "#191#Params" : "Params"))
704 {
705 activeTab = ActiveTab_Params;
706 }
707
708 const char *panelTitle;
709 switch(activeTab) {
710 case ActiveTab_JSON:
711 {
712 panelTitle = "Request Body (JSON)";
713 break;
714 }
715 case ActiveTab_Headers:
716 {
717 panelTitle = "Request Headers";
718 break;
719 }
720 case ActiveTab_Params:
721 {
722 panelTitle = "Query Parameters";
723 break;
724 }
725 }
726
727 // Panel title
728 GuiGroupBox(requestPanel, panelTitle);
729 Rectangle textArea = {
730 requestPanel.x + 10,
731 requestPanel.y + 30,
732 requestPanel.width - 20,
733 requestPanel.height - 40
734 };
735
736 // Handle click outside to disable edit mode
737 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
738 {
739 if (!CheckCollisionPointRec(GetMousePosition(), textArea))
740 {
741 jsonEditMode = false;
742 headersEditMode = false;
743 paramsEditMode = false;
744 }
745 }
746
747 // Draw border for text area
748 DrawRectangleLinesEx(textArea, 1, GRAY);
749
750 // Manual scroll handling with mouse wheel
751 if (CheckCollisionPointRec(GetMousePosition(), textArea))
752 {
753 float wheel = GetMouseWheelMove();
754 switch(activeTab)
755 {
756 case ActiveTab_JSON:
757 {
758 jsonScroll.y += wheel * 20;
759 if (jsonScroll.y < 0) jsonScroll.y = 0;
760 }
761 case ActiveTab_Headers:
762 {
763 headersScroll.y += wheel * 20;
764 if (headersScroll.y < 0) headersScroll.y = 0;
765 }
766 case ActiveTab_Params:
767 {
768 paramsScroll.y += wheel * 20;
769 if (paramsScroll.y < 0) paramsScroll.y = 0;
770 }
771 }
772 }
773
774 char *copyFromInput;
775 bool *currentMode;
776 switch(activeTab)
777 {
778 case ActiveTab_JSON:
779 {
780 PostDog_Render_TextWithScroll(textArea, jsonScroll, jsonInput);
781 copyFromInput = jsonInput;
782 currentMode = &jsonEditMode;
783 break;
784 }
785 case ActiveTab_Headers:
786 {
787 PostDog_Render_TextWithScroll(textArea, headersScroll, headersInput);
788 copyFromInput = headersInput;
789 currentMode = &headersEditMode;
790 break;
791 }
792 case ActiveTab_Params:
793 {
794 PostDog_Render_TextWithScroll(textArea, paramsScroll, paramsInput);
795 copyFromInput = paramsInput;
796 currentMode = &paramsEditMode;
797
798 Rectangle updateUrlBtn = { textArea.x + 30, textArea.y + textArea.height - 10, 120, 20 };
799 // TODO: Automatic update
800 if (GuiButton(updateUrlBtn, "Update URL"))
801 {
802 char tempUrl[1024];
803 strncpy(tempUrl, urlInput, sizeof(tempUrl) - 1);
804
805 // Remove existing params if any
806 char *questionMark = strchr(tempUrl, '?');
807 if (questionMark) *questionMark = '\0';
808
809 Postdog_UpdateUrlWithParams(urlInput, sizeof(urlInput), tempUrl, paramsInput);
810 }
811 break;
812 }
813 }
814
815 if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
816 {
817 SetClipboardText(copyFromInput);
818 }
819
820 Rectangle editBtn = { textArea.x + textArea.width - 60, textArea.y + textArea.height - 10, 50, 20 };
821 if (GuiButton(editBtn, "Edit"))
822 {
823 *currentMode = !(*currentMode);
824 }
825
826 // Response Panel with scroll
827 GuiGroupBox(responsePanel, "Response");
828
829 Rectangle responseArea = {
830 responsePanel.x + 10,
831 responsePanel.y + 30,
832 responsePanel.width - 20,
833 responsePanel.height - 40
834 };
835
836 // Manual scroll for response
837 if (CheckCollisionPointRec(GetMousePosition(), responseArea))
838 {
839 float wheel = GetMouseWheelMove();
840 responseScroll.y += wheel * 20;
841 if (responseScroll.y < 0) responseScroll.y = 0;
842 }
843
844 // Draw border
845 DrawRectangleLinesEx(responseArea, 1, GRAY);
846
847 // Draw response text with scroll
848 PostDog_Render_TextWithScroll(responseArea, responseScroll, responseText);
849
850 if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
851 {
852 if (CheckCollisionPointRec(GetMousePosition(), responseArea)) {
853 SetClipboardText(responseText);
854 }
855 }
856
857 // --- Edit modal ----
858 if (jsonEditMode)
859 {
860 GuiTextBox(
861 (Rectangle){ screenWidth/2 - 300, screenHeight/2 - 200, 600, 400 },
862 jsonInput, JSON_INPUT_BUFFER_LEN, true);
863 }
864
865 if (headersEditMode)
866 {
867 GuiTextBox(
868 (Rectangle){ screenWidth/2 - 300, screenHeight/2 - 200, 600, 400 },
869 headersInput, HEADER_INPUT_BUFFER_LEN, true);
870 }
871
872 if (paramsEditMode)
873 {
874 GuiTextBox(
875 (Rectangle){ screenWidth/2 - 300, screenHeight/2 - 200, 600, 400 },
876 paramsInput, PARAM_INPUT_BUFFER_LEN, true);
877 }
878
879 if (GuiDropdownBox(methodButton, "GET;POST;PUT;DELETE", &methodActive, methodDropdown))
880 {
881 methodDropdown = !methodDropdown;
882 }
883
884
885 EndDrawing(); 320 EndDrawing();
886 } 321 }
887
888 CloseWindow(); 322 CloseWindow();
889 return 0; 323 return 0;
890 } 324 }