feat(cpp,bash): app_about + Settings submenu, ensure_repo_synced pipeline
cpp/core: nuevo modulo app_about — ventana About con project/version/desc, componible via about_window_set_info() en el init de la app y rendererizada automaticamente por fn::run_app al final de cada frame. app_menubar: el item top-level "Settings..." pasa a ser un BeginMenu "Settings" con dos subitems: "Settings..." (existente) y "About..." (nuevo). bash/infra: nueva pipeline ensure_repo_synced que compone gitea_create_repo y gitea_push_directory para garantizar repo Gitea existente + sync de un directorio local en una sola llamada idempotente. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
name: ensure_repo_synced
|
||||||
|
kind: pipeline
|
||||||
|
lang: bash
|
||||||
|
domain: infra
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "ensure_repo_synced(directory: string, owner?: string, repo_name?: string, branch?: string, commit_msg?: string) -> void"
|
||||||
|
description: "Garantiza que un directorio tenga repo Gitea y este sincronizado. Crea el repo remoto si no existe, inicializa .git si falta, commitea cambios pendientes y pushea a origin. Idempotente."
|
||||||
|
tags: [gitea, git, sync, repo, pipeline, infra]
|
||||||
|
uses_functions:
|
||||||
|
- gitea_create_repo_bash_infra
|
||||||
|
- gitea_push_directory_bash_infra
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: directory
|
||||||
|
desc: "ruta del directorio local que se quiere sincronizar"
|
||||||
|
- name: owner
|
||||||
|
desc: "owner Gitea (org o user); default 'dataforge'"
|
||||||
|
- name: repo_name
|
||||||
|
desc: "nombre del repo Gitea; default basename del directorio"
|
||||||
|
- name: branch
|
||||||
|
desc: "rama destino del push; default 'master'"
|
||||||
|
- name: commit_msg
|
||||||
|
desc: "mensaje de commit usado si hay cambios pendientes; default 'chore: sync from fn_registry'"
|
||||||
|
output: "vacio — efectos en Gitea: repo creado (si no existia) y rama push-eada con los contenidos del directorio"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "bash/functions/infra/ensure_repo_synced.sh"
|
||||||
|
---
|
||||||
|
|
||||||
|
# ensure_repo_synced
|
||||||
|
|
||||||
|
Pipeline bash que compone `gitea_create_repo` + `gitea_push_directory` para garantizar que un directorio local este sincronizado con un repo Gitea, creando el repo si no existe.
|
||||||
|
|
||||||
|
## Cuando usarlo
|
||||||
|
|
||||||
|
Cuando se quiere sincronizar a Gitea un directorio que **podria** no tener todavia repo remoto creado, sin tener que comprobarlo manualmente. Idempotente: se puede llamar repetidamente sin romper nada.
|
||||||
|
|
||||||
|
## Variables de entorno requeridas
|
||||||
|
|
||||||
|
- `GITEA_URL` — base URL del Gitea (ej. `https://gitea.example.com`)
|
||||||
|
- `GITEA_TOKEN` — token de API con permisos de creacion de repos y push
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source bash/functions/infra/ensure_repo_synced.sh
|
||||||
|
|
||||||
|
# Sincronizar app local a dataforge/<basename>
|
||||||
|
ensure_repo_synced apps/mi_app
|
||||||
|
|
||||||
|
# Sincronizar con owner/repo custom y rama main
|
||||||
|
ensure_repo_synced apps/mi_app dataforge mi_app main "feat: initial sync"
|
||||||
|
```
|
||||||
Executable
+82
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ensure_repo_synced — Garantiza que un directorio tenga repo Gitea y este
|
||||||
|
# sincronizado: crea el repo remoto si no existe, inicializa .git si falta,
|
||||||
|
# commitea cambios pendientes y pushea a origin.
|
||||||
|
#
|
||||||
|
# Pipeline que compone gitea_create_repo + gitea_push_directory.
|
||||||
|
#
|
||||||
|
# Uso:
|
||||||
|
# ensure_repo_synced <directory> [owner] [repo_name] [branch] [commit_msg]
|
||||||
|
#
|
||||||
|
# Defaults:
|
||||||
|
# owner = dataforge
|
||||||
|
# repo_name = basename(directory)
|
||||||
|
# branch = master
|
||||||
|
# commit_msg = "chore: sync from fn_registry"
|
||||||
|
|
||||||
|
ensure_repo_synced() {
|
||||||
|
local directory="$1"
|
||||||
|
local owner="${2:-dataforge}"
|
||||||
|
local repo_name="$3"
|
||||||
|
local branch="${4:-master}"
|
||||||
|
local commit_msg="${5:-chore: sync from fn_registry}"
|
||||||
|
|
||||||
|
if [[ -z "$directory" ]]; then
|
||||||
|
echo "ensure_repo_synced: se requiere directory" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ ! -d "$directory" ]]; then
|
||||||
|
echo "ensure_repo_synced: directorio '$directory' no existe" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ -z "$repo_name" ]]; then
|
||||||
|
repo_name=$(basename "$(realpath "$directory")")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${GITEA_URL:-}" || -z "${GITEA_TOKEN:-}" ]]; then
|
||||||
|
echo "ensure_repo_synced: GITEA_URL y GITEA_TOKEN deben estar seteados" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local script_dir
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./gitea_create_repo.sh
|
||||||
|
source "$script_dir/gitea_create_repo.sh"
|
||||||
|
# shellcheck source=./gitea_push_directory.sh
|
||||||
|
source "$script_dir/gitea_push_directory.sh"
|
||||||
|
|
||||||
|
echo "ensure_repo_synced: $directory → $owner/$repo_name [$branch]" >&2
|
||||||
|
|
||||||
|
# 1. Asegurar repo remoto en Gitea (idempotente — 409 si existe)
|
||||||
|
if ! gitea_create_repo "$owner" "$repo_name" "false" "Synced from fn_registry" >/dev/null; then
|
||||||
|
echo "ensure_repo_synced: fallo creando/comprobando repo remoto" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Inicializar .git, commitear y pushear
|
||||||
|
# gitea_push_directory ya hace commit con su mensaje por defecto. Para
|
||||||
|
# respetar commit_msg custom, hacemos commit aqui antes si hay cambios.
|
||||||
|
if [[ -d "$directory/.git" ]]; then
|
||||||
|
local status
|
||||||
|
status=$(git -C "$directory" status --porcelain)
|
||||||
|
if [[ -n "$status" ]]; then
|
||||||
|
echo "ensure_repo_synced: commiteando cambios con mensaje: $commit_msg" >&2
|
||||||
|
git -C "$directory" add -A
|
||||||
|
git -C "$directory" \
|
||||||
|
-c user.email="agent@fn-registry" -c user.name="fn-registry agent" \
|
||||||
|
commit -m "$commit_msg"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! gitea_push_directory "$directory" "$owner" "$repo_name" "$branch"; then
|
||||||
|
echo "ensure_repo_synced: fallo en gitea_push_directory" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ensure_repo_synced: ok '$owner/$repo_name'" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Si se invoca como script (no source), ejecutar la funcion.
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
ensure_repo_synced "$@"
|
||||||
|
fi
|
||||||
@@ -100,6 +100,7 @@ add_library(fn_framework STATIC
|
|||||||
functions/core/tokens.cpp
|
functions/core/tokens.cpp
|
||||||
functions/core/icon_font.cpp
|
functions/core/icon_font.cpp
|
||||||
functions/core/app_settings.cpp
|
functions/core/app_settings.cpp
|
||||||
|
functions/core/app_about.cpp
|
||||||
functions/core/fps_overlay.cpp
|
functions/core/fps_overlay.cpp
|
||||||
functions/core/panel_menu.cpp
|
functions/core/panel_menu.cpp
|
||||||
functions/core/layouts_menu.cpp
|
functions/core/layouts_menu.cpp
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "core/tokens.h"
|
#include "core/tokens.h"
|
||||||
#include "core/icon_font.h"
|
#include "core/icon_font.h"
|
||||||
#include "core/app_settings.h"
|
#include "core/app_settings.h"
|
||||||
|
#include "core/app_about.h"
|
||||||
#include "core/fps_overlay.h"
|
#include "core/fps_overlay.h"
|
||||||
|
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
@@ -131,6 +132,9 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
|||||||
// Ventana de Settings (no-op si esta cerrada).
|
// Ventana de Settings (no-op si esta cerrada).
|
||||||
fn_ui::settings_window_render();
|
fn_ui::settings_window_render();
|
||||||
|
|
||||||
|
// Ventana About (no-op si esta cerrada).
|
||||||
|
fn_ui::about_window_render();
|
||||||
|
|
||||||
// FPS overlay si esta activado en Settings.
|
// FPS overlay si esta activado en Settings.
|
||||||
if (fn_ui::settings().show_fps) {
|
if (fn_ui::settings().show_fps) {
|
||||||
fps_overlay();
|
fps_overlay();
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#include "core/app_about.h"
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace fn_ui {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string g_project = "fn_registry app";
|
||||||
|
std::string g_version = "";
|
||||||
|
std::string g_description = "";
|
||||||
|
bool g_open = false;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void about_window_set_info(const char* project,
|
||||||
|
const char* version,
|
||||||
|
const char* description) {
|
||||||
|
if (project) g_project = project;
|
||||||
|
if (version) g_version = version;
|
||||||
|
if (description) g_description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool about_window_is_open() { return g_open; }
|
||||||
|
void about_window_set_open(bool v) { g_open = v; }
|
||||||
|
void about_window_toggle() { g_open = !g_open; }
|
||||||
|
|
||||||
|
bool about_window_menu_item(const char* label) {
|
||||||
|
if (ImGui::MenuItem(label)) {
|
||||||
|
g_open = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void about_window_render() {
|
||||||
|
if (!g_open) return;
|
||||||
|
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(420, 220), ImGuiCond_FirstUseEver);
|
||||||
|
if (!ImGui::Begin("About", &g_open, ImGuiWindowFlags_NoCollapse)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextUnformatted(g_project.c_str());
|
||||||
|
|
||||||
|
if (!g_version.empty()) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextDisabled("v%s", g_version.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_description.empty()) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextWrapped("%s", g_description.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextDisabled("fn_registry");
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace fn_ui
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Ventana flotante "About" para apps del registry. Muestra nombre del proyecto,
|
||||||
|
// version y descripcion opcional. Se abre desde el MenuItem "About..." en el
|
||||||
|
// submenu "Settings" de la MainMenuBar (ver app_menubar).
|
||||||
|
//
|
||||||
|
// Lifecycle:
|
||||||
|
// - app llama about_window_set_info(name, version, [description]) en su init
|
||||||
|
// - app_menubar() incluye el MenuItem "About..." en el submenu "Settings"
|
||||||
|
// - end of frame: about_window_render() — invocado por fn::run_app
|
||||||
|
//
|
||||||
|
// Apps que NO usan fn::run_app deben llamar about_window_render() manualmente
|
||||||
|
// despues del render_fn.
|
||||||
|
|
||||||
|
namespace fn_ui {
|
||||||
|
|
||||||
|
// Setea la informacion mostrada en la ventana About. Llamar una vez en el init
|
||||||
|
// de la app, antes de fn::run_app. Si no se llama, la ventana muestra valores
|
||||||
|
// por defecto ("fn_registry app", sin version).
|
||||||
|
void about_window_set_info(const char* project,
|
||||||
|
const char* version,
|
||||||
|
const char* description = "");
|
||||||
|
|
||||||
|
// Estado abierto/cerrado de la ventana.
|
||||||
|
bool about_window_is_open();
|
||||||
|
void about_window_set_open(bool v);
|
||||||
|
void about_window_toggle();
|
||||||
|
|
||||||
|
// MenuItem componible. Llamar dentro de un BeginMenu/BeginMainMenuBar exitoso.
|
||||||
|
// Click → abre la ventana. Returns true si el usuario clico.
|
||||||
|
bool about_window_menu_item(const char* label = "About...");
|
||||||
|
|
||||||
|
// Render de la ventana. No-op si is_open == false. Llamada por fn::run_app al
|
||||||
|
// final del frame, despues del render_fn de la app.
|
||||||
|
void about_window_render();
|
||||||
|
|
||||||
|
} // namespace fn_ui
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
name: app_about
|
||||||
|
kind: function
|
||||||
|
lang: cpp
|
||||||
|
domain: core
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "void fn_ui::about_window_set_info(const char* project, const char* version, const char* description = \"\"); bool fn_ui::about_window_is_open(); void fn_ui::about_window_set_open(bool); void fn_ui::about_window_toggle(); bool fn_ui::about_window_menu_item(const char* label = \"About...\"); void fn_ui::about_window_render()"
|
||||||
|
description: "Ventana flotante About para apps del registry con nombre del proyecto, version y descripcion. Se abre desde el submenu Settings -> About... de la MainMenuBar. Render automatico via fn::run_app."
|
||||||
|
tags: [imgui, about, window, ui, version]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: [imgui, string]
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "cpp/functions/core/app_about.cpp"
|
||||||
|
framework: imgui
|
||||||
|
params:
|
||||||
|
- name: project
|
||||||
|
desc: "Nombre del proyecto/app mostrado como titulo en la ventana About"
|
||||||
|
- name: version
|
||||||
|
desc: "Cadena de version (ej. '0.2.0') mostrada al lado del nombre del proyecto"
|
||||||
|
- name: description
|
||||||
|
desc: "Descripcion opcional mostrada debajo del titulo. Vacio = no se muestra"
|
||||||
|
- name: label
|
||||||
|
desc: "Texto del MenuItem en la menubar (default 'About...')"
|
||||||
|
output: "about_window_set_info muta el estado global del modulo. about_window_render es no-op si la ventana esta cerrada. about_window_menu_item retorna true si el usuario clico"
|
||||||
|
---
|
||||||
|
|
||||||
|
# app_about
|
||||||
|
|
||||||
|
Ventana flotante About para apps C++ del registry. Funciona en pareja con `app_settings`: ambas se exponen bajo el menu `Settings` de la MainMenuBar via `app_menubar`.
|
||||||
|
|
||||||
|
## Uso
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "core/app_about.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
fn_ui::about_window_set_info(
|
||||||
|
"Mi App",
|
||||||
|
"1.2.3",
|
||||||
|
"Descripcion corta de la app."
|
||||||
|
);
|
||||||
|
return fn::run_app({.title = "Mi App"}, render);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`fn::run_app` se encarga de invocar `about_window_render()` al final de cada frame.
|
||||||
|
|
||||||
|
## Integracion con app_menubar
|
||||||
|
|
||||||
|
`app_menubar` añade un menu `Settings` con dos items:
|
||||||
|
|
||||||
|
- `Settings...` — abre la ventana de `app_settings`
|
||||||
|
- `About...` — abre la ventana de `app_about`
|
||||||
@@ -19,9 +19,14 @@ bool app_menubar(const PanelToggle* panels, std::size_t count,
|
|||||||
changed |= layouts_menu_items("Layouts", *layouts_cb);
|
changed |= layouts_menu_items("Layouts", *layouts_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MenuItem "Settings..." — siempre. Abre la ventana flotante; el render
|
// Menu "Settings" — siempre. Submenus: Settings... y About...
|
||||||
// ocurre al final del frame en fn::run_app.
|
// Las ventanas se renderizan al final del frame en fn::run_app.
|
||||||
changed |= settings_window_menu_item("Settings...");
|
if (ImGui::BeginMenu("Settings")) {
|
||||||
|
changed |= settings_window_menu_item("Settings...");
|
||||||
|
ImGui::Separator();
|
||||||
|
changed |= about_window_menu_item("About...");
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::EndMainMenuBar();
|
ImGui::EndMainMenuBar();
|
||||||
return changed;
|
return changed;
|
||||||
|
|||||||
@@ -3,22 +3,25 @@
|
|||||||
#include "core/panel_menu.h"
|
#include "core/panel_menu.h"
|
||||||
#include "core/layouts_menu.h"
|
#include "core/layouts_menu.h"
|
||||||
#include "core/app_settings.h"
|
#include "core/app_settings.h"
|
||||||
|
#include "core/app_about.h"
|
||||||
|
|
||||||
namespace fn_ui {
|
namespace fn_ui {
|
||||||
|
|
||||||
// Renderiza una MainMenuBar completa con:
|
// Renderiza una MainMenuBar completa con:
|
||||||
// * Menu "View" (panel_menu_items con los toggles dados) [si panels]
|
// * Menu "View" (panel_menu_items con los toggles dados) [si panels]
|
||||||
// * Menu "Layouts" (layouts_menu_items con las callbacks dadas) [si layouts_cb]
|
// * Menu "Layouts" (layouts_menu_items con las callbacks dadas) [si layouts_cb]
|
||||||
// * MenuItem "Settings..." (abre la ventana de settings) [siempre]
|
// * Menu "Settings" con submenus: [siempre]
|
||||||
|
// - "Settings..." → abre la ventana de settings
|
||||||
|
// - "About..." → abre la ventana About
|
||||||
//
|
//
|
||||||
// Llamar despues de NewFrame() y antes del DockSpaceOverViewport.
|
// Llamar despues de NewFrame() y antes del DockSpaceOverViewport.
|
||||||
// Si layouts_cb es nullptr, omite Layouts.
|
// Si layouts_cb es nullptr, omite Layouts.
|
||||||
// Si panels es nullptr o count == 0, omite View.
|
// Si panels es nullptr o count == 0, omite View.
|
||||||
// El item Settings siempre aparece — la ventana se renderiza al final del
|
// Las ventanas Settings y About se renderizan al final del frame en
|
||||||
// frame en fn::run_app via settings_window_render().
|
// fn::run_app via settings_window_render() y about_window_render().
|
||||||
//
|
//
|
||||||
// Returns: true si el usuario togglo paneles, disparo accion de layouts,
|
// Returns: true si el usuario togglo paneles, disparo accion de layouts,
|
||||||
// o abrio la ventana de settings este frame.
|
// o abrio una ventana este frame.
|
||||||
bool app_menubar(const PanelToggle* panels, std::size_t count,
|
bool app_menubar(const PanelToggle* panels, std::size_t count,
|
||||||
LayoutCallbacks* layouts_cb);
|
LayoutCallbacks* layouts_cb);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user