feat: added blog selection for existing blog
This commit is contained in:
35
lib/bds/desktop/folder_picker.ex
Normal file
35
lib/bds/desktop/folder_picker.ex
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
defmodule BDS.Desktop.FolderPicker do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
def choose_directory(prompt) when is_binary(prompt) do
|
||||||
|
case :os.type() do
|
||||||
|
{:unix, :darwin} -> choose_directory_macos(prompt)
|
||||||
|
_other -> {:error, %{message: "Folder selection is only supported on macOS desktop"}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp choose_directory_macos(prompt) do
|
||||||
|
script = "POSIX path of (choose folder with prompt \"#{escape_applescript(prompt)}\")"
|
||||||
|
|
||||||
|
case System.cmd("osascript", ["-e", script], stderr_to_stdout: true) do
|
||||||
|
{output, 0} -> {:ok, String.trim(output)}
|
||||||
|
{output, _status} -> normalize_picker_failure(output)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_picker_failure(output) do
|
||||||
|
message = String.trim(output)
|
||||||
|
|
||||||
|
if message == "" or String.contains?(String.downcase(message), "canceled") do
|
||||||
|
:cancel
|
||||||
|
else
|
||||||
|
{:error, %{message: message}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp escape_applescript(value) do
|
||||||
|
value
|
||||||
|
|> String.replace("\\", "\\\\")
|
||||||
|
|> String.replace("\"", "\\\"")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -51,6 +51,15 @@ defmodule BDS.Desktop.Router do
|
|||||||
|> Plug.Conn.send_resp(200, BDS.Desktop.ShellController.upsert_project_json(payload))
|
|> Plug.Conn.send_resp(200, BDS.Desktop.ShellController.upsert_project_json(payload))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post "/api/project-folder" do
|
||||||
|
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
||||||
|
payload = if body == "", do: %{}, else: Jason.decode!(body)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> Plug.Conn.put_resp_content_type("application/json")
|
||||||
|
|> Plug.Conn.send_resp(200, BDS.Desktop.ShellController.choose_project_folder_json(payload))
|
||||||
|
end
|
||||||
|
|
||||||
post "/api/commands" do
|
post "/api/commands" do
|
||||||
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
||||||
payload = if body == "", do: %{}, else: Jason.decode!(body)
|
payload = if body == "", do: %{}, else: Jason.decode!(body)
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ defmodule BDS.Desktop.ShellController do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def choose_project_folder_json(payload \\ %{}) when is_map(payload) do
|
||||||
|
prompt = Map.get(payload, "prompt") || Map.get(payload, :prompt) || "Select existing blog folder"
|
||||||
|
|
||||||
|
case folder_picker().choose_directory(prompt) do
|
||||||
|
{:ok, path} -> Jason.encode!(project_folder_payload(path))
|
||||||
|
:cancel -> Jason.encode!(%{status: "cancel"})
|
||||||
|
{:error, error} -> Jason.encode!(%{status: "error", error: normalize_error(error)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def command_json(payload) when is_map(payload) do
|
def command_json(payload) when is_map(payload) do
|
||||||
action = Map.get(payload, "action") || Map.get(payload, :action)
|
action = Map.get(payload, "action") || Map.get(payload, :action)
|
||||||
params = Map.get(payload, "params") || Map.get(payload, :params) || %{}
|
params = Map.get(payload, "params") || Map.get(payload, :params) || %{}
|
||||||
@@ -47,6 +57,7 @@ defmodule BDS.Desktop.ShellController do
|
|||||||
{:create,
|
{:create,
|
||||||
%{
|
%{
|
||||||
name: String.trim(Map.get(payload, "name") || Map.get(payload, :name)),
|
name: String.trim(Map.get(payload, "name") || Map.get(payload, :name)),
|
||||||
|
description: blank_to_nil(Map.get(payload, "description") || Map.get(payload, :description)),
|
||||||
data_path: blank_to_nil(Map.get(payload, "data_path") || Map.get(payload, :data_path))
|
data_path: blank_to_nil(Map.get(payload, "data_path") || Map.get(payload, :data_path))
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -84,6 +95,45 @@ defmodule BDS.Desktop.ShellController do
|
|||||||
%{id: project.id, name: project.name, slug: project.slug, data_path: project.data_path, is_active: project.is_active}
|
%{id: project.id, name: project.name, slug: project.slug, data_path: project.data_path, is_active: project.is_active}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp project_folder_payload(path) do
|
||||||
|
normalized_path = Path.expand(path)
|
||||||
|
project_metadata = read_project_metadata(normalized_path)
|
||||||
|
existing_project = find_project_by_data_path(normalized_path)
|
||||||
|
|
||||||
|
%{
|
||||||
|
status: "ok",
|
||||||
|
path: normalized_path,
|
||||||
|
name: Map.get(project_metadata, "name") || Path.basename(normalized_path),
|
||||||
|
description: Map.get(project_metadata, "description"),
|
||||||
|
existing_project_id: existing_project && existing_project.id
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp read_project_metadata(path) do
|
||||||
|
project_json_path = Path.join([path, "meta", "project.json"])
|
||||||
|
|
||||||
|
case File.read(project_json_path) do
|
||||||
|
{:ok, contents} -> Jason.decode!(contents)
|
||||||
|
{:error, :enoent} -> %{}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp find_project_by_data_path(path) do
|
||||||
|
normalized_path = Path.expand(path)
|
||||||
|
|
||||||
|
BDS.Projects.list_projects()
|
||||||
|
|> Enum.find(fn project ->
|
||||||
|
case project.data_path do
|
||||||
|
value when is_binary(value) -> Path.expand(value) == normalized_path
|
||||||
|
_other -> false
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp folder_picker do
|
||||||
|
Application.get_env(:bds, :desktop, [])[:folder_picker] || BDS.Desktop.FolderPicker
|
||||||
|
end
|
||||||
|
|
||||||
defp default_project_snapshot do
|
defp default_project_snapshot do
|
||||||
%{
|
%{
|
||||||
active_project_id: "default",
|
active_project_id: "default",
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"Offline Gate": "Offline-Sperre",
|
"Offline Gate": "Offline-Sperre",
|
||||||
"Open": "Öffnen",
|
"Open": "Öffnen",
|
||||||
"Open Data Folder": "Datenordner öffnen",
|
"Open Data Folder": "Datenordner öffnen",
|
||||||
|
"Open Existing Blog": "Bestehenden Blog öffnen",
|
||||||
"Open in Browser": "Im Browser öffnen",
|
"Open in Browser": "Im Browser öffnen",
|
||||||
"Opened URL": "URL geöffnet",
|
"Opened URL": "URL geöffnet",
|
||||||
"Orphan Files": "Verwaiste Dateien",
|
"Orphan Files": "Verwaiste Dateien",
|
||||||
@@ -133,6 +134,7 @@
|
|||||||
"Script": "Skript",
|
"Script": "Skript",
|
||||||
"Scripts": "Skripte",
|
"Scripts": "Skripte",
|
||||||
"Select Project": "Projekt auswählen",
|
"Select Project": "Projekt auswählen",
|
||||||
|
"Select existing blog folder": "Bestehenden Blog-Ordner auswählen",
|
||||||
"Settings": "Einstellungen",
|
"Settings": "Einstellungen",
|
||||||
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Seitenleiste, Tabs, Panel und Assistentenbereiche sind als DOM-Regionen inspizierbar",
|
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Seitenleiste, Tabs, Panel und Assistentenbereiche sind als DOM-Regionen inspizierbar",
|
||||||
"Site Validation": "Website-Validierung",
|
"Site Validation": "Website-Validierung",
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"Offline Gate": "Offline Gate",
|
"Offline Gate": "Offline Gate",
|
||||||
"Open": "Open",
|
"Open": "Open",
|
||||||
"Open Data Folder": "Open Data Folder",
|
"Open Data Folder": "Open Data Folder",
|
||||||
|
"Open Existing Blog": "Open Existing Blog",
|
||||||
"Open in Browser": "Open in Browser",
|
"Open in Browser": "Open in Browser",
|
||||||
"Opened URL": "Opened URL",
|
"Opened URL": "Opened URL",
|
||||||
"Orphan Files": "Orphan Files",
|
"Orphan Files": "Orphan Files",
|
||||||
@@ -133,6 +134,7 @@
|
|||||||
"Script": "Script",
|
"Script": "Script",
|
||||||
"Scripts": "Scripts",
|
"Scripts": "Scripts",
|
||||||
"Select Project": "Select Project",
|
"Select Project": "Select Project",
|
||||||
|
"Select existing blog folder": "Select existing blog folder",
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Sidebar, tabs, panel, and assistant panes are inspectable DOM regions",
|
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Sidebar, tabs, panel, and assistant panes are inspectable DOM regions",
|
||||||
"Site Validation": "Site Validation",
|
"Site Validation": "Site Validation",
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"Offline Gate": "Bloqueo sin conexión",
|
"Offline Gate": "Bloqueo sin conexión",
|
||||||
"Open": "Abrir",
|
"Open": "Abrir",
|
||||||
"Open Data Folder": "Abrir carpeta de datos",
|
"Open Data Folder": "Abrir carpeta de datos",
|
||||||
|
"Open Existing Blog": "Abrir blog existente",
|
||||||
"Open in Browser": "Abrir en el navegador",
|
"Open in Browser": "Abrir en el navegador",
|
||||||
"Opened URL": "URL abierta",
|
"Opened URL": "URL abierta",
|
||||||
"Orphan Files": "Archivos huérfanos",
|
"Orphan Files": "Archivos huérfanos",
|
||||||
@@ -133,6 +134,7 @@
|
|||||||
"Script": "Script",
|
"Script": "Script",
|
||||||
"Scripts": "Scripts",
|
"Scripts": "Scripts",
|
||||||
"Select Project": "Seleccionar proyecto",
|
"Select Project": "Seleccionar proyecto",
|
||||||
|
"Select existing blog folder": "Seleccionar carpeta de blog existente",
|
||||||
"Settings": "Configuración",
|
"Settings": "Configuración",
|
||||||
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "La barra lateral, las pestañas, el panel y el asistente son regiones DOM inspeccionables",
|
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "La barra lateral, las pestañas, el panel y el asistente son regiones DOM inspeccionables",
|
||||||
"Site Validation": "Validación del sitio",
|
"Site Validation": "Validación del sitio",
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"Offline Gate": "Verrou hors ligne",
|
"Offline Gate": "Verrou hors ligne",
|
||||||
"Open": "Ouvrir",
|
"Open": "Ouvrir",
|
||||||
"Open Data Folder": "Ouvrir le dossier de données",
|
"Open Data Folder": "Ouvrir le dossier de données",
|
||||||
|
"Open Existing Blog": "Ouvrir un blog existant",
|
||||||
"Open in Browser": "Ouvrir dans le navigateur",
|
"Open in Browser": "Ouvrir dans le navigateur",
|
||||||
"Opened URL": "URL ouverte",
|
"Opened URL": "URL ouverte",
|
||||||
"Orphan Files": "Fichiers orphelins",
|
"Orphan Files": "Fichiers orphelins",
|
||||||
@@ -133,6 +134,7 @@
|
|||||||
"Script": "Script",
|
"Script": "Script",
|
||||||
"Scripts": "Scripts",
|
"Scripts": "Scripts",
|
||||||
"Select Project": "Sélectionner un projet",
|
"Select Project": "Sélectionner un projet",
|
||||||
|
"Select existing blog folder": "Sélectionner le dossier d’un blog existant",
|
||||||
"Settings": "Paramètres",
|
"Settings": "Paramètres",
|
||||||
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "La barre latérale, les onglets, le panneau et l’assistant sont des régions DOM inspectables",
|
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "La barre latérale, les onglets, le panneau et l’assistant sont des régions DOM inspectables",
|
||||||
"Site Validation": "Validation du site",
|
"Site Validation": "Validation du site",
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"Offline Gate": "Blocco offline",
|
"Offline Gate": "Blocco offline",
|
||||||
"Open": "Apri",
|
"Open": "Apri",
|
||||||
"Open Data Folder": "Apri cartella dati",
|
"Open Data Folder": "Apri cartella dati",
|
||||||
|
"Open Existing Blog": "Apri blog esistente",
|
||||||
"Open in Browser": "Apri nel browser",
|
"Open in Browser": "Apri nel browser",
|
||||||
"Opened URL": "URL aperto",
|
"Opened URL": "URL aperto",
|
||||||
"Orphan Files": "File orfani",
|
"Orphan Files": "File orfani",
|
||||||
@@ -133,6 +134,7 @@
|
|||||||
"Script": "Script",
|
"Script": "Script",
|
||||||
"Scripts": "Script",
|
"Scripts": "Script",
|
||||||
"Select Project": "Seleziona progetto",
|
"Select Project": "Seleziona progetto",
|
||||||
|
"Select existing blog folder": "Seleziona la cartella di un blog esistente",
|
||||||
"Settings": "Impostazioni",
|
"Settings": "Impostazioni",
|
||||||
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Barra laterale, schede, pannello e assistente sono regioni DOM ispezionabili",
|
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Barra laterale, schede, pannello e assistente sono regioni DOM ispezionabili",
|
||||||
"Site Validation": "Validazione sito",
|
"Site Validation": "Validazione sito",
|
||||||
|
|||||||
@@ -919,9 +919,12 @@ button {
|
|||||||
.project-dropdown-footer {
|
.project-dropdown-footer {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-project-btn {
|
.create-project-btn,
|
||||||
|
.existing-project-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -936,7 +939,8 @@ button {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-project-btn:hover {
|
.create-project-btn:hover,
|
||||||
|
.existing-project-btn:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.18);
|
background-color: rgba(255, 255, 255, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -483,6 +483,12 @@ function bindEvents() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
root.querySelectorAll("[data-project-import]").forEach((button) => {
|
||||||
|
button.onclick = () => {
|
||||||
|
void importExistingProject();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
root.querySelectorAll("[data-command='set-ui-language']").forEach((select) => {
|
root.querySelectorAll("[data-command='set-ui-language']").forEach((select) => {
|
||||||
select.onchange = (event) => {
|
select.onchange = (event) => {
|
||||||
setUiLanguage(event.target.value);
|
setUiLanguage(event.target.value);
|
||||||
@@ -677,8 +683,42 @@ async function fetchProjects() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createProject() {
|
async function chooseProjectFolder() {
|
||||||
const name = window.prompt(t("New project name"), t("New Project"));
|
try {
|
||||||
|
const response = await fetch("/api/project-folder", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ prompt: t("Select existing blog folder") }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok || payload.status === "error") {
|
||||||
|
appendOutputEntry(t("Open Existing Blog"), payload.error?.message || t("Command failed with HTTP %{status}", { status: response.status }));
|
||||||
|
setPanelTab("output");
|
||||||
|
render();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.status === "cancel") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
} catch (error) {
|
||||||
|
appendOutputEntry(t("Open Existing Blog"), error?.message || String(error));
|
||||||
|
setPanelTab("output");
|
||||||
|
render();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createProject(options = {}) {
|
||||||
|
const suggestedName = options.name ? String(options.name).trim() : "";
|
||||||
|
const name = suggestedName || window.prompt(t("New project name"), t("New Project"));
|
||||||
if (!name || !name.trim()) {
|
if (!name || !name.trim()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -692,7 +732,11 @@ async function createProject() {
|
|||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ name: name.trim() }),
|
body: JSON.stringify({
|
||||||
|
name: name.trim(),
|
||||||
|
description: options.description,
|
||||||
|
data_path: options.dataPath,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const payload = await response.json();
|
const payload = await response.json();
|
||||||
@@ -714,6 +758,27 @@ async function createProject() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function importExistingProject() {
|
||||||
|
closeProjectMenu();
|
||||||
|
|
||||||
|
const selection = await chooseProjectFolder();
|
||||||
|
|
||||||
|
if (!selection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.existing_project_id) {
|
||||||
|
await selectProject(selection.existing_project_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await createProject({
|
||||||
|
name: selection.name,
|
||||||
|
description: selection.description,
|
||||||
|
dataPath: selection.path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function selectProject(projectId) {
|
async function selectProject(projectId) {
|
||||||
if (!projectId || projectId === state.projects.active_project_id) {
|
if (!projectId || projectId === state.projects.active_project_id) {
|
||||||
closeProjectMenu();
|
closeProjectMenu();
|
||||||
@@ -1408,6 +1473,12 @@ function renderProjectDropdown() {
|
|||||||
${state.projects.projects.map((project) => renderProjectItem(project)).join("")}
|
${state.projects.projects.map((project) => renderProjectItem(project)).join("")}
|
||||||
</div>
|
</div>
|
||||||
<div class="project-dropdown-footer">
|
<div class="project-dropdown-footer">
|
||||||
|
<button class="existing-project-btn" data-project-import type="button">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path d="M1.75 3A1.75 1.75 0 013.5 1.25h3.92c.46 0 .9.18 1.22.5l1.1 1.1c.09.1.22.15.35.15h2.41A1.75 1.75 0 0114.25 4.75v6.5A1.75 1.75 0 0112.5 13h-9A1.75 1.75 0 011.75 11.25v-8.5zm1.75-.25a.75.75 0 00-.75.75v8.5c0 .41.34.75.75.75h9c.41 0 .75-.34.75-.75v-6.5a.75.75 0 00-.75-.75h-2.41a1.7 1.7 0 01-1.06-.44l-1.1-1.1a.74.74 0 00-.52-.21H3.5z"></path>
|
||||||
|
</svg>
|
||||||
|
${escapeHtml(t("Open Existing Blog"))}
|
||||||
|
</button>
|
||||||
<button class="create-project-btn" data-project-create type="button">
|
<button class="create-project-btn" data-project-create type="button">
|
||||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
|
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<path d="M14 7v1H8v6H7V8H1V7h6V1h1v6h6z"></path>
|
<path d="M14 7v1H8v6H7V8H1V7h6V1h1v6h6z"></path>
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ defmodule BDS.DesktopTest do
|
|||||||
|
|
||||||
import Plug.Test
|
import Plug.Test
|
||||||
|
|
||||||
|
defmodule TestFolderPicker do
|
||||||
|
def choose_directory(_prompt), do: Process.get(:test_folder_picker_response, :cancel)
|
||||||
|
end
|
||||||
|
|
||||||
test "desktop configuration no longer uses a pending adapter" do
|
test "desktop configuration no longer uses a pending adapter" do
|
||||||
assert Application.get_env(:bds, BDS.Application)[:desktop_adapter] == :desktop
|
assert Application.get_env(:bds, BDS.Application)[:desktop_adapter] == :desktop
|
||||||
end
|
end
|
||||||
@@ -172,34 +176,72 @@ defmodule BDS.DesktopTest do
|
|||||||
assert after_internal_dirs == before_internal_dirs
|
assert after_internal_dirs == before_internal_dirs
|
||||||
end
|
end
|
||||||
|
|
||||||
test "desktop router executes shell commands through the JSON api" do
|
test "desktop router lets the shell choose an existing project folder and reuses matching projects" do
|
||||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||||
:ok = Ecto.Adapters.SQL.Sandbox.allow(BDS.Repo, self(), Process.whereis(BDS.Preview))
|
|
||||||
|
|
||||||
temp_dir = Path.join(System.tmp_dir!(), "bds-desktop-router-#{System.unique_integer([:positive])}")
|
temp_dir = Path.join(System.tmp_dir!(), "bds-desktop-existing-project-#{System.unique_integer([:positive])}")
|
||||||
File.mkdir_p!(temp_dir)
|
meta_dir = Path.join(temp_dir, "meta")
|
||||||
|
File.mkdir_p!(meta_dir)
|
||||||
|
|
||||||
on_exit(fn ->
|
File.write!(
|
||||||
File.rm_rf(temp_dir)
|
Path.join(meta_dir, "project.json"),
|
||||||
_ = BDS.Preview.stop_preview("default")
|
Jason.encode!(%{"name" => "Existing Blog", "description" => "Imported from disk"})
|
||||||
end)
|
)
|
||||||
|
|
||||||
{:ok, project} = BDS.Projects.create_project(%{name: "Desktop Router", data_path: temp_dir})
|
{:ok, project} = BDS.Projects.create_project(%{name: "Existing Blog", data_path: temp_dir})
|
||||||
{:ok, _project} = BDS.Projects.set_active_project(project.id)
|
|
||||||
|
|
||||||
conn =
|
previous_desktop = Application.get_env(:bds, :desktop, [])
|
||||||
conn(:post, "/api/commands?k=#{Desktop.Auth.login_key()}", Jason.encode!(%{"action" => "open_in_browser"}))
|
Application.put_env(:bds, :desktop, Keyword.put(previous_desktop, :folder_picker, TestFolderPicker))
|
||||||
|> Plug.Conn.put_req_header("content-type", "application/json")
|
Process.put(:test_folder_picker_response, {:ok, temp_dir})
|
||||||
|
|
||||||
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
on_exit(fn ->
|
||||||
|
Application.put_env(:bds, :desktop, previous_desktop)
|
||||||
|
Process.delete(:test_folder_picker_response)
|
||||||
|
File.rm_rf(temp_dir)
|
||||||
|
end)
|
||||||
|
|
||||||
assert conn.status == 200
|
conn = conn(:post, "/api/project-folder?k=#{Desktop.Auth.login_key()}")
|
||||||
assert Plug.Conn.get_resp_header(conn, "content-type") == ["application/json; charset=utf-8"]
|
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
||||||
|
|
||||||
payload = Jason.decode!(conn.resp_body)
|
assert conn.status == 200
|
||||||
|
|
||||||
assert payload["result"]["kind"] == "open_url"
|
payload = Jason.decode!(conn.resp_body)
|
||||||
assert payload["result"]["project_id"] == project.id
|
|
||||||
assert payload["result"]["url"] == "http://127.0.0.1:4123/"
|
assert payload["status"] == "ok"
|
||||||
end
|
assert payload["path"] == temp_dir
|
||||||
|
assert payload["name"] == "Existing Blog"
|
||||||
|
assert payload["description"] == "Imported from disk"
|
||||||
|
assert payload["existing_project_id"] == project.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "desktop router executes shell commands through the JSON api" do
|
||||||
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||||
|
:ok = Ecto.Adapters.SQL.Sandbox.allow(BDS.Repo, self(), Process.whereis(BDS.Preview))
|
||||||
|
|
||||||
|
temp_dir = Path.join(System.tmp_dir!(), "bds-desktop-router-#{System.unique_integer([:positive])}")
|
||||||
|
File.mkdir_p!(temp_dir)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
File.rm_rf(temp_dir)
|
||||||
|
_ = BDS.Preview.stop_preview("default")
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, project} = BDS.Projects.create_project(%{name: "Desktop Router", data_path: temp_dir})
|
||||||
|
{:ok, _project} = BDS.Projects.set_active_project(project.id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:post, "/api/commands?k=#{Desktop.Auth.login_key()}", Jason.encode!(%{"action" => "open_in_browser"}))
|
||||||
|
|> Plug.Conn.put_req_header("content-type", "application/json")
|
||||||
|
|
||||||
|
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
||||||
|
|
||||||
|
assert conn.status == 200
|
||||||
|
assert Plug.Conn.get_resp_header(conn, "content-type") == ["application/json; charset=utf-8"]
|
||||||
|
|
||||||
|
payload = Jason.decode!(conn.resp_body)
|
||||||
|
|
||||||
|
assert payload["result"]["kind"] == "open_url"
|
||||||
|
assert payload["result"]["project_id"] == project.id
|
||||||
|
assert payload["result"]["url"] == "http://127.0.0.1:4123/"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -212,15 +212,18 @@ defmodule BDS.UI.ShellTest do
|
|||||||
assert js =~ "return [\"tasks\", \"output\", \"git_log\", state.session.panel.active_tab].filter(uniqueValue);"
|
assert js =~ "return [\"tasks\", \"output\", \"git_log\", state.session.panel.active_tab].filter(uniqueValue);"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "static shell bundle renders a left-side project field with selection and create affordances" do
|
test "static shell bundle renders a left-side project field with selection, existing-folder import, and create affordances" do
|
||||||
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
||||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||||
|
|
||||||
assert js =~ "project-selector-trigger"
|
assert js =~ "project-selector-trigger"
|
||||||
assert js =~ "project-dropdown"
|
assert js =~ "project-dropdown"
|
||||||
assert js =~ "create-project-btn"
|
assert js =~ "create-project-btn"
|
||||||
|
assert js =~ "existing-project-btn"
|
||||||
|
assert js =~ "/api/project-folder"
|
||||||
assert js =~ "fetchProjects"
|
assert js =~ "fetchProjects"
|
||||||
assert js =~ "createProject"
|
assert js =~ "createProject"
|
||||||
|
assert js =~ "importExistingProject"
|
||||||
assert js =~ "selectProject"
|
assert js =~ "selectProject"
|
||||||
assert js =~ "toggleProjectMenu"
|
assert js =~ "toggleProjectMenu"
|
||||||
assert js =~ "closeProjectMenu"
|
assert js =~ "closeProjectMenu"
|
||||||
@@ -228,6 +231,7 @@ defmodule BDS.UI.ShellTest do
|
|||||||
assert css =~ ".project-selector-trigger"
|
assert css =~ ".project-selector-trigger"
|
||||||
assert css =~ ".project-dropdown"
|
assert css =~ ".project-dropdown"
|
||||||
assert css =~ ".create-project-btn"
|
assert css =~ ".create-project-btn"
|
||||||
|
assert css =~ ".existing-project-btn"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "static shell bundle uses translation catalogs for visible shell chrome" do
|
test "static shell bundle uses translation catalogs for visible shell chrome" do
|
||||||
|
|||||||
Reference in New Issue
Block a user