view postdog/main.c @ 115:96db6c3f38d6

[Postdog] Got history working.
author June Park <parkjune1995@gmail.com>
date Tue, 06 Jan 2026 08:19:07 -0800
parents e2a73e64e8e6
children 7bd795bac997
line wrap: on
line source

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include "dowa/dowa.h"

#include <curl/curl.h>
#include "third_party/raylib/include/raylib.h"
#define RAYGUI_IMPLEMENTATION
#include "third_party/raylib/include/raygui.h"

#ifndef POSTDOG_PATHS
  #define POSTDOG_PATHS "/Users/mrjunejune/zenbu/postdog/history"
#endif

#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 780
#define TEXT_SIZE 10

#define JSON_INPUT_BUFFER_LEN 8192
#define HEADER_INPUT_BUFFER_LEN 4096
#define PARAM_INPUT_BUFFER_LEN 4096
#define MAX_HISTORY_ITEMS 100

#define HEADER_BUFFER_LENGTH 1024 * 4
#define DEFAULT_TEXT_BUFFER_LENGTH 1024 * 4
#define URL_TEXT_BUFFER 1024 * 10
#define BODY_BUFFER_LENGTH 1024 * 1025 * 5
#define RESULT_BUFFER_LENGTH 1024 * 1025 * 5
#define AREANA_BUFFER_LENGTH 1024 * 1025 * 15


#ifdef _WIN32
    #include <direct.h>
    #include <io.h>
    #define mkdir(path, mode) _mkdir(path)
    #define access _access
    #define F_OK 0
#else
    #include <sys/stat.h>
    #include <dirent.h>
    #include <unistd.h>
#endif

typedef Dowa_KV(char*, char*) INPUT_HASHMAP;

typedef struct {
  char *data;
  size_t size;
} ResponseBuffer;

typedef struct {
  char *filename;
  Rectangle rect;
  long time_modified;
} HistoryItem;

typedef struct {
  Rectangle rectangle;
  char *label;
  bool active;
} TabItem;

typedef enum {
  TAB_HEADER = 0,
  TAB_BODY,
  TAB_GET_PARAMS,
  TAB_BAR,
  TAB_LENGTH
} PostDog_Tab_Enum;

static uint32 counter = 0;
HistoryItem *history_items = NULL;
HistoryItem *new_history_items = NULL;

int CompareHistoryItemsByDate(const void *a, const void *b) {
  HistoryItem *itemA = (HistoryItem *)a;
  HistoryItem *itemB = (HistoryItem *)b;
  return (itemB->time_modified - itemA->time_modified); 
}

// TODO: Make this into generic fucntion so I can use it across different thing.
void PostDog_List_Directory(const char *path, HistoryItem **p_file_arr)
{
  HistoryItem *file_arr = *p_file_arr;
#ifdef _WIN32
  struct _finddata_t fileinfo;
  intptr_t handle;
  char search_path[256];
  sprintf(search_path, "%s\\*", path);

  if ((handle = _findfirst(search_path, &fileinfo)) == -1L) {
    printf("Directory is empty or cannot be read.\n");
  } else {
    do {
      HistoryItem item = {0};
      item.filename = strdup(fileinfo.name);
      item.rect = (Rectangle){0};
      item.time_modified = fileinfo.time_write; 
      Dowa_Array_Push(file_arr, item);
    } while (_findnext(handle, &fileinfo) == 0);
    _findclose(handle);
  }
#else
  struct dirent *entry;
  struct stat file_stat;
  DIR *dp = opendir(path);
  if (dp == NULL) return;

  char full_path[256];
  while ((entry = readdir(dp)))
  {
    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
      continue;
    snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
    if (stat(full_path, &file_stat) == 0)
    {
      HistoryItem item = {0};
      item.filename = strdup(entry->d_name);
      item.time_modified = file_stat.st_mtime;
      Dowa_Array_Push(file_arr, item);
    }
  }
  closedir(dp);
#endif
  int count = Dowa_Array_Length(file_arr);
  if (count > 1) {
    qsort(file_arr, count, sizeof(HistoryItem), CompareHistoryItemsByDate);
  }
}

int PostDog_History_Load(HistoryItem **p_history_files)
{
  if (access(POSTDOG_PATHS, F_OK) == -1)
  {
    printf("Directory '%s' not found. Creating it...\n", POSTDOG_PATHS);
    if (mkdir(POSTDOG_PATHS, 0777) != 0)
      return -1;
    return 0;
  }
  printf("Directory '%s' already exists.\n", POSTDOG_PATHS);
  PostDog_List_Directory(POSTDOG_PATHS, p_history_files);
  return 0;
}

bool InArea(Vector2 mouse_position, Rectangle area)
{
  return (
    mouse_position.x >= area.x &&
    mouse_position.x < area.x + area.width &&
    mouse_position.y >= area.y &&
    mouse_position.y < area.y + area.height
  );
}

bool Clicked(Vector2 mouse_position, Rectangle area)
{
  return (InArea(mouse_position, area) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
}

char *PostDog_Enum_To_String(int active_enum)
{
  switch(active_enum)
  {
    case 0: return "GET";
    case 1: return "POST";
    case 2: return "PUT";
    case 3: return "DELETE";
  }
  return 0;
}

void PostDog_History_CreateFile(char *filename, char* values)
{
  char full_file_path[512] = {0};
  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
  FILE *file = fopen(full_file_path, "w");
  if (!file)
  {
    printf("Failed to create a file: %s\n", full_file_path);
    return;
  }
  fwrite(values, 1, strlen(values), file);
  fclose(file);
}

void PostDog_Request_SaveFile(
    const char *url,
    const char *method,
    const char *headers,
    const char *body, 
    const char *response)
{
  size_t new_file_size = 1024 * 1024;
  Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1024 * 2);
  char *new_file = Dowa_Arena_Allocate(arena, 1024 * 1024);
  snprintf(
      new_file,
      new_file_size,
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n"
      "---\n"
      "%s\n",
      url,
      method,
      headers,
      body,
      response
  );
  char *filename = Dowa_Arena_Allocate(arena, 1024);
  if (!filename)
  {
    perror("Error opening file");
    exit(EXIT_FAILURE); 
  }
  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, 37);
  if (!uuid4)
  {
    perror("Error uuid");
    exit(EXIT_FAILURE); 
  }
  
  int32 seed = (uint32)time(NULL) ^ counter++;
  Dowa_String_UUID(seed, uuid4);
  snprintf(filename, 1024, "%s.txt", uuid4);
  PostDog_History_CreateFile(filename, new_file);

  HistoryItem item = (HistoryItem){ .filename = malloc(sizeof(char) * strlen(filename) + 1), .rect = (Rectangle){0} };
  memcpy(item.filename, filename, strlen(filename) + 1);
  Dowa_Array_Push(new_history_items, item);
  
  Dowa_Arena_Free(arena);
}

static size_t Postdog_Curl_Callback(void *contents, size_t size, size_t nmemb, void *userp)
{
  size_t real_size = size * nmemb;
  ResponseBuffer *buf = (ResponseBuffer *)userp;

  char *ptr = realloc(buf->data, buf->size + real_size + 1);
  if (ptr == NULL)
  {
    printf("Not enough memory for response\n");
    return 0;
  }

  buf->data = ptr;
  memcpy(&(buf->data[buf->size]), contents, real_size);
  buf->size += real_size;
  buf->data[buf->size] = 0;

  return real_size;
}

int PostDog_Http_Request(
    const char *url,
    const char *method,
    const char *headers,
    const char *body, 
    char *response, size_t responseSize)
{
  CURL *curl;
  CURLcode res;
  ResponseBuffer buffer = { .data = malloc(1), .size = 0 };

  response[0] = '\n';

  if (buffer.data == NULL)
  {
    snprintf(response, responseSize, "Error: Failed to allocate memory");
    return -1;
  }
  buffer.data[0] = '\0';

  curl_global_init(CURL_GLOBAL_DEFAULT);
  curl = curl_easy_init();

  if (curl)
  {
    struct curl_slist *headerList = NULL;

    // Set URL
    curl_easy_setopt(curl, CURLOPT_URL, url);

    // Set HTTP method
    if (strcmp(method, "POST") == 0)
    {
      curl_easy_setopt(curl, CURLOPT_POST, 1L);
      if (body && strlen(body) > 0)
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    } 
    else if (strcmp(method, "PUT") == 0)
    {
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
      if (body && strlen(body) > 0)
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    }
    else if (strcmp(method, "DELETE") == 0)
    {
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    }
    // Default is GET

    // Parse and add headers
    if (headers && strlen(headers) > 0)
    {
      char *headersCopy = strdup(headers);
      char *line = strtok(headersCopy, "\n");
      while (line != NULL) {
        // Trim whitespace
        while (*line == ' ' || *line == '\t') line++;
        if (strlen(line) > 0)
          headerList = curl_slist_append(headerList, line);
        line = strtok(NULL, "\n");
      }
      curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList);
      free(headersCopy);
    }

    // Set write callback
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Postdog_Curl_Callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer);

    // Follow redirects
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    // Set timeout
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
    // Perform request
    res = curl_easy_perform(curl);

    if (res != CURLE_OK)
      snprintf(response, responseSize, "Error: %s\n", curl_easy_strerror(res));
    else
    {
      long response_code;
      curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);

      snprintf(response, responseSize, "HTTP Status: %ld\n\n%s",
               response_code, buffer.data ? buffer.data : "");
    }

    // Cleanup
    if (headerList) curl_slist_free_all(headerList);
    curl_easy_cleanup(curl);
  } else {
    snprintf(response, responseSize, "Error: Failed to initialize curl");
  }

  free(buffer.data);
  curl_global_cleanup();

  PostDog_Request_SaveFile(
    url,
    method,
    headers,
    body,
    response);
  return 0;
}

void PostDog_Update_URL(char **p_url_input_text, char* get_params)
{
  char *url_input_text = *p_url_input_text;

  // Reset 
  char *question_mark = strchr(url_input_text, '?');
  if (question_mark)
    *question_mark = '\0';

  int get_params_length = (int)strlen(get_params) ;
  if (get_params_length == 0)
    return;

  char *separator = "?";

  Dowa_Arena *arena = Dowa_Arena_Create(1024*1024);
  char **lines = Dowa_String_Split(get_params, "\n", get_params_length, 1, arena);
  for (int i = 0; i < Dowa_Array_Length(lines); i++)
  {
    char *line = lines[i];
    char **key_value = Dowa_String_Split(line, " ", (int)strlen(line), 1, arena);

    if (Dowa_Array_Length(key_value) < 2)
      break;

    strcat(url_input_text, separator);
    strcat(url_input_text, key_value[0]);
    strcat(url_input_text, "=");
    for (int i = 1; i < Dowa_Array_Length(key_value); i++)
    {
      if (!key_value[i] || key_value[i][0] == '\0')
        break;
      if (i > 1) strcat(url_input_text, "%20");
      strcat(url_input_text, key_value[i]);
    }
    separator = "&";
  }

  Dowa_Arena_Free(arena);
}

int PostDog_String_To_MethodEnum(char *value)
{
  if (strstr(value, "GET"))
    return 0;
  if (strstr(value, "POST"))
    return 1;
  if (strstr(value, "PUT"))
    return 2;
  if (strstr(value, "DELETE"))
    return 3;
  return 0;
}

void PostDog_Params_Reset(
    char **p_url_input_text,
    int  *p_active_method_dropdown,
    char **url_body_map,
    char **p_url_result_text
)
{
  char *url_input_text = *p_url_input_text;
  char *url_result_text = *p_url_result_text;
  int active_method_dropdown = *p_active_method_dropdown;

  url_input_text = "";
  url_result_text = "";
  active_method_dropdown = 0;
  for (int i = 0; i < Dowa_Array_Length(url_body_map); i++)
    url_body_map[i] = "";
}

void PostDog_Load_File(
    const char *filename,
    char **p_url_input_text,
    int  *p_active_method_dropdown,
    char **url_body_map,
    char **p_url_result_text
) { 
  char *url_input_text = *p_url_input_text;
  char *url_result_text = *p_url_result_text;
  int active_method_dropdown = *p_active_method_dropdown;

  char full_file_path[512] = {0};
  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
  FILE *file = fopen(full_file_path, "r");
  if (!file)
    return;

  fseek(file, 0, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0, SEEK_SET);

  Dowa_Arena *init_arena = Dowa_Arena_Create(file_size + 2);
  Dowa_Arena *split_arena = Dowa_Arena_Create(file_size * 2);
  char *file_buffer = Dowa_Arena_Allocate(init_arena, file_size+1);
  fread(file_buffer, 1, file_size, file);
  char **values = Dowa_String_Split(file_buffer, "---\n", file_size, 4, split_arena);
  Dowa_Arena_Free(init_arena);
  for (int i = 0; i < Dowa_Array_Length(values); i++)
  {
    if (i == 0)
    {
      snprintf(url_input_text, strlen(values[i]) + 1, "%s", values[i]);
      url_input_text[strcspn(url_input_text, "\n")] = '\0';
    }
    else if (i == 1)
      active_method_dropdown = PostDog_String_To_MethodEnum(values[i]);
    else if (i <= 3)
    {
      snprintf(url_body_map[i-2], strlen(values[i]) + 1, "%s", values[i]);
      for (int j = strlen(values[i]); j > 0; j--)
      {
        if (url_body_map[i-2][j] == '\n')
        {
          url_body_map[i-2][j] = '\0';
          break;
        }
      }
    }
    else
      snprintf(url_result_text, strlen(values[i]) + 1, "%s", values[i]);
  }
}

Rectangle AddPadding(Rectangle rect, float padding)
{
  return (Rectangle){
    rect.x + padding,
    rect.y + padding,
    rect.width - (2 * padding),
    rect.height - (2 * padding)
  };
}

// Layout helper functions
Rectangle RightOf(Rectangle ref, float padding)
{
  return (Rectangle){
    .x = ref.x + ref.width + padding,
    .y = ref.y,
    .width = 0,
    .height = ref.height
  };
}

Rectangle Below(Rectangle ref, float padding)
{
  return (Rectangle){
    .x = ref.x,
    .y = ref.y + ref.height + padding,
    .width = ref.width,
    .height = 0
  };
}

Rectangle LeftColumn(Rectangle container, float ratio, float padding)
{
  return (Rectangle){
    .x = container.x + padding,
    .y = container.y + padding,
    .width = (container.width * ratio) - padding,
    .height = container.height - (2 * padding)
  };
}

Rectangle RightColumn(Rectangle container, Rectangle leftCol, float padding)
{
  return (Rectangle){
    .x = leftCol.x + leftCol.width + padding,
    .y = container.y + padding,
    .width = container.width - leftCol.width - (3 * padding),
    .height = container.height - (2 * padding)
  };
}

Rectangle HorizontalSplit(Rectangle container, float ratio)
{
  return (Rectangle){
    .x = container.x,
    .y = container.y,
    .width = container.width * ratio,
    .height = container.height
  };
}

int main()
{
  // -- initizlied --//
  InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "PostDog");
  SetWindowState(FLAG_WINDOW_RESIZABLE);
  SetTargetFPS(60);

  Dowa_Arena *arena = Dowa_Arena_Create(AREANA_BUFFER_LENGTH);

  Font customFont = LoadFontEx("postdog/Roboto-Regular.ttf", 20, 0, 0);
  GuiSetFont(customFont);
  GuiSetStyle(DEFAULT, TEXT_SIZE, 20);

  // -- Starting pos ---//
  // Everyhting is relative to sidebar at this point lol
  float side_bar_x = 10;
  float side_bar_y = 10;
  Rectangle history_sidebar = { .x = side_bar_x, .y = side_bar_y, .width = 0, .height = 0 };
  Dowa_Array_Reserve(history_items, 10);
  Dowa_Array_Reserve(new_history_items, 10);
  PostDog_History_Load(&history_items);

  Rectangle url_area = { 0 };
  Rectangle textBounds = { 0 };
  Rectangle url_input_bounds = { 0 };
  bool url_input_edit = false;
  Rectangle url_text_bounds = { 0 };
  Rectangle url_enter_button = { 0 };
  Rectangle method_dropdown = { 0 };


  char *url_input_text = (char *)Dowa_Arena_Allocate(arena, URL_TEXT_BUFFER);
  snprintf(url_input_text, URL_TEXT_BUFFER, "https://httpbin.org/get");

  char **url_body_map = NULL;
  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, HEADER_BUFFER_LENGTH), arena);
  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, BODY_BUFFER_LENGTH), arena);
  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);

  snprintf(url_body_map[TAB_HEADER], HEADER_BUFFER_LENGTH, "Content-Type: application/json");
  snprintf(url_body_map[TAB_BODY], HEADER_BUFFER_LENGTH, "");

  char *url_result_text = (char *)Dowa_Arena_Allocate(arena, RESULT_BUFFER_LENGTH);

  int active_method_dropdown = 0;
  bool method_edit = false;

  int sendRequest;

  // -- input --//
  Rectangle input_area = { 0 };
  Rectangle input_tab = { 0 };
  Rectangle input_tab_item = { 0 };
  Rectangle input_body = { 0 };
  bool input_body_bool = false;

  // -- result --//
  Rectangle result_area = { 0 };
  Rectangle result_body = { 0 };

  // General styling.
  float padding = 10; // TODO make it % based?
  int active_input_tab = 0;

  while (!WindowShouldClose())
  {
    int screen_width = GetScreenWidth();
    int screen_height = GetScreenHeight();

    // Define main screen container
    Rectangle screen = { 0, 0, screen_width, screen_height };

    // Layout: Sidebar (left 15%) and Content Area (right 85%)
    history_sidebar = LeftColumn(screen, 0.15, padding);
    Rectangle content_area = RightColumn(screen, history_sidebar, padding);

    // History items inside sidebar
    int32 new_history_items_length = Dowa_Array_Length(new_history_items);
    int32 history_item_length = Dowa_Array_Length(history_items);
    int32 total = new_history_items_length + history_item_length;
    float item_height = history_sidebar.height * 0.05;

    for (int i = 0; i < total; i++)
    {
      HistoryItem *curr_history_items = i < new_history_items_length ?
        &new_history_items[i] : &history_items[i - new_history_items_length];

      curr_history_items->rect = (Rectangle){
        .x = history_sidebar.x,
        .y = history_sidebar.y + (padding + item_height) * i,
        .width = history_sidebar.width,
        .height = item_height
      };
    }

    // Content area: split into URL bar (top 10%) and body (bottom 90%)
    url_area = (Rectangle){
      .x = content_area.x,
      .y = content_area.y,
      .width = content_area.width,
      .height = content_area.height * 0.1
    };

    // URL bar elements laid out horizontally
    float url_control_y = url_area.y + (url_area.height - TEXT_SIZE * 2) / 2;

    url_text_bounds = (Rectangle){
      .x = url_area.x + padding,
      .y = url_control_y,
      .width = 7 * (TEXT_SIZE / 2),
      .height = TEXT_SIZE * 2
    };

    url_input_bounds = RightOf(url_text_bounds, padding);
    url_input_bounds.width = url_area.width * 0.7;
    url_input_bounds.height = TEXT_SIZE * 2;

    url_enter_button = RightOf(url_input_bounds, padding);
    url_enter_button.width = url_area.width * 0.1;
    url_enter_button.height = TEXT_SIZE * 2;

    method_dropdown = RightOf(url_enter_button, padding);
    method_dropdown.width = url_area.width * 0.1;
    method_dropdown.height = TEXT_SIZE * 2;

    // Body area: split into input (left 50%) and result (right 50%)
    Rectangle body_area = Below(url_area, 0);
    body_area.height = content_area.height - url_area.height;

    input_area = HorizontalSplit(body_area, 0.5);
    result_area = RightOf(input_area, 0);
    result_area.width = body_area.width - input_area.width;

    // Input area: tabs at top (10%) and text box below (90%)
    input_tab = (Rectangle){
      .x = input_area.x + padding,
      .y = input_area.y + padding,
      .width = input_area.width - (2 * padding),
      .height = input_area.height * 0.1
    };

    input_tab_item = input_tab;
    input_tab_item.width = input_tab.width / 4;

    input_body = Below(input_tab, 0);
    input_body.width = input_tab.width;
    input_body.height = input_area.height - input_tab.height - (2 * padding);

    // Result area: aligned with input tabs
    result_body = (Rectangle){
      .x = result_area.x + padding,
      .y = input_body.y,
      .width = result_area.width - (2 * padding),
      .height = input_body.height
    };

    Vector2 mouse_position = GetMousePosition();

    BeginDrawing();
      ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));

      DrawRectangleRec(history_sidebar, Fade(GRAY, 0.1f));
      for (int i = 0; i < total; i++)
      {
        HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length];
        DrawRectangleRec(curr_history_items->rect, Fade(RED, 0.1f)); 
        GuiDrawText(curr_history_items->filename, AddPadding(curr_history_items->rect, padding), TEXT_ALIGN_CENTER, RED);
      }

      // URL area Rect
      GuiDrawText("URL: ", url_text_bounds, TEXT_ALIGN_CENTER, RED); 
      DrawRectangleRec(url_area, Fade(RED, 0.1f));
      if (GuiTextBox(url_input_bounds, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_edit))
        url_input_edit = !url_input_edit;

      sendRequest = GuiButton(url_enter_button, "ENTER");
      if (sendRequest)
        PostDog_Http_Request(
            url_input_text,
            PostDog_Enum_To_String(active_method_dropdown),
            url_body_map[TAB_HEADER],
            url_body_map[TAB_BODY],
            url_result_text,
            RESULT_BUFFER_LENGTH
        );
      if (GuiDropdownBox(method_dropdown, "GET;POST;PUT;DELETE", &active_method_dropdown, method_edit))
        method_edit = !method_edit;

      // Input Tabs Rect
      DrawRectangleRec(input_area, Fade(BLUE, 0.1f));
      DrawRectangleRec(input_tab,  Fade(DARKBLUE, 0.1f));
      GuiSetStyle(TOGGLE, GROUP_PADDING, 0);
      if (JUNE_GuiTextBox(input_body, url_body_map[active_input_tab], DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool))
        input_body_bool = !input_body_bool;
      GuiToggleGroup(input_tab_item, "Header;Body;Get Param;Bar", &active_input_tab);

      PostDog_Update_URL(&url_input_text, url_body_map[TAB_GET_PARAMS]);

      // Result Rect
      DrawRectangleRec(result_area, Fade(GREEN, 0.1f));
      DrawRectangleRec(result_body, Fade(DARKGREEN, 0.1f));
      GuiTextBoxMulti(result_body, url_result_text, RESULT_BUFFER_LENGTH, false);

      if (url_input_edit && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
      {
        SetClipboardText(url_input_text);
      }
      else if (input_body_bool && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
      {
        SetClipboardText(url_body_map[active_input_tab]);
      }
      else if (InArea(mouse_position, result_body))
      {
        DrawRectangleRec(result_body, Fade(GREEN, 0.3f));
        if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
          SetClipboardText(url_result_text);
      }

      for (int i = 0; i < Dowa_Array_Length(history_items); i++)
      {
        if (Clicked(mouse_position, history_items[i].rect))
          PostDog_Load_File(
              history_items[i].filename,
              &url_input_text,
              &active_method_dropdown,
              url_body_map,
              &url_result_text
          );
      }
    EndDrawing();
  }
  CloseWindow();
  return 0;
}