Skip to content
This repository was archived by the owner on Nov 7, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .github/workflows/core-tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: VisuStructCore tests
name: CI

on:
push:
Expand All @@ -17,3 +17,17 @@ jobs:
run: swift --version
- name: Build and test
run: swift test

app-build:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Xcode version
run: xcodebuild -version
- name: Build app for iOS Simulator
run: |
xcodebuild build \
-project VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct.xcodeproj \
-scheme VisuStruct \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO
104 changes: 86 additions & 18 deletions VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,14 @@ struct ContentView: View {
}
// iPhone UX: Einfügemarke + Inspector einklappen beim Einfügen.
@State private var insertTarget: DiagramInsertTarget? = nil
@State private var hideInspectorOnPhone: Bool = false
/// iPhone: Inspector startet eingeklappt, damit das Diagramm Platz hat.
@State private var hideInspectorOnPhone: Bool = true
// iPhone UX: Palette automatisch einklappen beim Text-Editieren.
@State private var showPaletteOnPhone: Bool = true
/// iPhone: Palette als Sheet über dem Editor (statt fest über der Navigationsleiste).
@State private var showPaletteOnPhone: Bool = false
/// iPhone: Diagrammtitel wird in einem kleinen Dialog bearbeitet statt in einem eigenen Feld über dem Diagramm.
@State private var showPhoneTitleEditor: Bool = false
@State private var phoneTitleDraft: String = ""
// In-App Zwischenablage für Diagramm-Elemente (Copy/Cut/Paste).
@State private var elementClipboard: VisuStructElement? = nil
#if canImport(UIKit)
Expand Down Expand Up @@ -177,7 +182,7 @@ struct ContentView: View {
.navigationSplitViewStyle(.balanced)
} else {
VStack(spacing: 0) {
if !isPhone || showPaletteOnPhone {
if !isPhone {
paletteListPanel
.frame(maxHeight: 300, alignment: .top)
.background(Color(.secondarySystemGroupedBackground))
Expand All @@ -187,6 +192,14 @@ struct ContentView: View {
mainEditorNavigationStack
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.sheet(isPresented: phonePaletteSheetBinding) {
paletteListPanel
.environment(\.appLanguage, lang)
.presentationDetents([.medium, .large])
// Diagramm bleibt bedienbar, z. B. um eine Einfügestelle zu wählen.
.presentationBackgroundInteraction(.enabled(upThrough: .medium))
.presentationDragIndicator(.visible)
}
}
}
#if canImport(UIKit)
Expand Down Expand Up @@ -346,6 +359,11 @@ struct ContentView: View {
refreshLineEditor()
DiagramCommandCenter.shared.update(file: diagramFileActions, edit: diagramEditActions)
}
.onChange(of: insertTarget) { _, newTarget in
// iPhone: Einfügestelle gewählt → Palette öffnen, damit gleich ein Element gewählt werden kann.
guard isPhone, newTarget != nil else { return }
showPaletteOnPhone = true
}
.onChange(of: focusedField) { _, newVal in
// iPhone: Sobald Textbearbeitung startet, Palette einklappen für mehr Platz.
guard isPhone else { return }
Expand Down Expand Up @@ -881,21 +899,25 @@ struct ContentView: View {
let blockCorner: CGFloat = horizontalSizeClass == .regular ? 14 : 12

return VStack(alignment: .leading, spacing: 0) {
VStack(alignment: .leading, spacing: 8) {
Text(lang.tr("diagram_title_label"))
.font(horizontalSizeClass == .regular ? .caption : .subheadline)
.foregroundStyle(.secondary)
TextField(lang.tr("diagram_title_placeholder"), text: captionBinding)
.font(horizontalSizeClass == .regular ? .subheadline : .body)
.textFieldStyle(.roundedBorder)
.textInputAutocapitalization(.never)
.autocorrectionDisabled(true)
.focused($focusedField, equals: .caption)
if isPhone {
phoneCaptionRow
} else {
VStack(alignment: .leading, spacing: 8) {
Text(lang.tr("diagram_title_label"))
.font(horizontalSizeClass == .regular ? .caption : .subheadline)
.foregroundStyle(.secondary)
TextField(lang.tr("diagram_title_placeholder"), text: captionBinding)
.font(horizontalSizeClass == .regular ? .subheadline : .body)
.textFieldStyle(.roundedBorder)
.textInputAutocapitalization(.never)
.autocorrectionDisabled(true)
.focused($focusedField, equals: .caption)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 14)
.padding(.top, 8)
.padding(.bottom, 6)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 14)
.padding(.top, 8)
.padding(.bottom, 6)
// Innerer Trennstrich — Titel zählt zum Diagramm-„Blatt“, nicht als losgelöster Bereich oberhalb.
Rectangle()
.fill(Color.primary.opacity(0.1))
Expand Down Expand Up @@ -935,6 +957,47 @@ struct ContentView: View {
}
}

/// iPhone: Titel als eine kompakte Zeile; Tippen öffnet einen Dialog zum Bearbeiten.
private var phoneCaptionRow: some View {
Button {
phoneTitleDraft = document.caption
showPhoneTitleEditor = true
} label: {
HStack(spacing: 6) {
Text(document.caption.isEmpty ? lang.tr("diagram_title_placeholder") : document.caption)
.font(.subheadline.weight(.semibold))
.foregroundStyle(document.caption.isEmpty ? .secondary : .primary)
.lineLimit(1)
Image(systemName: "pencil")
.font(.caption)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityLabel(lang.tr("diagram_title_label"))
.padding(.horizontal, 14)
.padding(.vertical, 8)
.alert(lang.tr("diagram_title_label"), isPresented: $showPhoneTitleEditor) {
TextField(lang.tr("diagram_title_placeholder"), text: $phoneTitleDraft)
.textInputAutocapitalization(.never)
.autocorrectionDisabled(true)
Button(lang.tr("phone_title_done")) {
if phoneTitleDraft != document.caption {
captionBinding.wrappedValue = phoneTitleDraft
}
}
}
}

private var phonePaletteSheetBinding: Binding<Bool> {
Binding(
get: { isPhone && showPaletteOnPhone },
set: { showPaletteOnPhone = $0 }
)
}

// MARK: - Bindings (Dokument, Caption)

private var documentBinding: Binding<VisuStructDocument> {
Expand Down Expand Up @@ -1494,7 +1557,11 @@ struct ContentView: View {
insertTargetActive: insertTarget != nil,
hideInspectorOnPhone: hideInspectorOnPhone,
onCancelInsert: { insertTarget = nil },
onShowInspector: { hideInspectorOnPhone = false }
onShowInspector: { hideInspectorOnPhone = false },
onHideInspector: {
focusedField = nil
hideInspectorOnPhone = true
}
) {
inspectorPanel
}
Expand Down Expand Up @@ -1845,6 +1912,7 @@ struct ContentView: View {
private func insertPaletteItem(_ item: ElementPalette.PaletteItem) {
if isPhone {
hideInspectorOnPhone = true
showPaletteOnPhone = false
}
let el = ElementPalette.element(for: item, lang: lang)
let doc = document
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ struct InspectorPhoneChrome<Inspector: View>: View {
let hideInspectorOnPhone: Bool
let onCancelInsert: () -> Void
let onShowInspector: () -> Void
let onHideInspector: () -> Void
@ViewBuilder let inspector: () -> Inspector

var body: some View {
Expand All @@ -211,7 +212,16 @@ struct InspectorPhoneChrome<Inspector: View>: View {
}
.buttonStyle(.bordered)
} else {
inspector()
VStack(alignment: .trailing, spacing: 4) {
Button {
onHideInspector()
} label: {
Label(lang.tr("phone_hide_text_editor"), systemImage: "chevron.down")
.font(.subheadline)
}
.buttonStyle(.borderless)
inspector()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "إلغاء الإدراج";
"phone_show_text_editor" = "تحرير النص";
"phone_hide_text_editor" = "طيّ";
"phone_title_done" = "تم";

/* Alerts */
"err_save_failed_fmt" = "تعذر الحفظ: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,5 @@
"sim_notice_division_by_zero" = "Division med nul.";
"sim_notice_whole_number_expected" = "Indtast et heltal.";
"sim_notice_number_expected" = "Indtast et tal.";
"phone_hide_text_editor" = "Skjul";
"phone_title_done" = "Færdig";
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@
/* iPhone: Einfügen per Tap */
"phone_insert_cancel" = "Einfügen abbrechen";
"phone_show_text_editor" = "Text bearbeiten";
"phone_hide_text_editor" = "Einklappen";
"phone_title_done" = "Fertig";

/* Meldungen */
"err_save_failed_fmt" = "Speichern fehlgeschlagen: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "Ακύρωση εισαγωγής";
"phone_show_text_editor" = "Επεξεργασία κειμένου";
"phone_hide_text_editor" = "Σύμπτυξη";
"phone_title_done" = "Τέλος";

/* Alerts */
"err_save_failed_fmt" = "Δεν ήταν δυνατή η αποθήκευση: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "Cancel insert";
"phone_show_text_editor" = "Edit text";
"phone_hide_text_editor" = "Collapse";
"phone_title_done" = "Done";

/* Alerts */
"err_save_failed_fmt" = "Could not save: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "Cancelar inserción";
"phone_show_text_editor" = "Editar texto";
"phone_hide_text_editor" = "Contraer";
"phone_title_done" = "Listo";

/* Alerts */
"err_save_failed_fmt" = "No se pudo guardar: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "Annuler l’insertion";
"phone_show_text_editor" = "Modifier le texte";
"phone_hide_text_editor" = "Réduire";
"phone_title_done" = "OK";

/* Alerts */
"err_save_failed_fmt" = "Impossible d’enregistrer : %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "בטל הוספה";
"phone_show_text_editor" = "עריכת טקסט";
"phone_hide_text_editor" = "כיווץ";
"phone_title_done" = "סיום";

/* Alerts */
"err_save_failed_fmt" = "לא ניתן לשמור: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "Annulla inserimento";
"phone_show_text_editor" = "Modifica testo";
"phone_hide_text_editor" = "Comprimi";
"phone_title_done" = "Fine";

/* Alerts */
"err_save_failed_fmt" = "Impossibile salvare: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "挿入をキャンセル";
"phone_show_text_editor" = "テキストを編集";
"phone_hide_text_editor" = "折りたたむ";
"phone_title_done" = "完了";

/* Alerts */
"err_save_failed_fmt" = "保存できませんでした:%@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "삽입 취소";
"phone_show_text_editor" = "텍스트 편집";
"phone_hide_text_editor" = "접기";
"phone_title_done" = "완료";

/* Alerts */
"err_save_failed_fmt" = "저장할 수 없음: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,5 @@
"sim_notice_division_by_zero" = "Divisjon med null.";
"sim_notice_whole_number_expected" = "Skriv inn et heltall.";
"sim_notice_number_expected" = "Skriv inn et tall.";
"phone_hide_text_editor" = "Skjul";
"phone_title_done" = "Ferdig";
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,5 @@
"sim_notice_division_by_zero" = "Deling door nul.";
"sim_notice_whole_number_expected" = "Voer een geheel getal in.";
"sim_notice_number_expected" = "Voer een getal in.";
"phone_hide_text_editor" = "Inklappen";
"phone_title_done" = "Gereed";
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "Anuluj wstawianie";
"phone_show_text_editor" = "Edytuj tekst";
"phone_hide_text_editor" = "Zwiń";
"phone_title_done" = "Gotowe";

/* Alerts */
"err_save_failed_fmt" = "Nie można zapisać: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@
/* iPhone: inserir por toque */
"phone_insert_cancel" = "Cancelar inserção";
"phone_show_text_editor" = "Editar texto";
"phone_hide_text_editor" = "Recolher";
"phone_title_done" = "OK";

/* Alertas */
"err_save_failed_fmt" = "Não foi possível guardar: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "Отменить вставку";
"phone_show_text_editor" = "Редактировать текст";
"phone_hide_text_editor" = "Свернуть";
"phone_title_done" = "Готово";

/* Alerts */
"err_save_failed_fmt" = "Не удалось сохранить: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,5 @@
"sim_notice_division_by_zero" = "Division med noll.";
"sim_notice_whole_number_expected" = "Ange ett heltal.";
"sim_notice_number_expected" = "Ange ett tal.";
"phone_hide_text_editor" = "Fäll ihop";
"phone_title_done" = "Klar";
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "Ekleme iptal";
"phone_show_text_editor" = "Metni düzenle";
"phone_hide_text_editor" = "Daralt";
"phone_title_done" = "Bitti";

/* Alerts */
"err_save_failed_fmt" = "Kaydedilemedi: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "Скасувати вставку";
"phone_show_text_editor" = "Редагувати текст";
"phone_hide_text_editor" = "Згорнути";
"phone_title_done" = "Готово";

/* Alerts */
"err_save_failed_fmt" = "Не вдалося зберегти: %@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "取消插入";
"phone_show_text_editor" = "编辑文本";
"phone_hide_text_editor" = "收起";
"phone_title_done" = "完成";

/* Alerts */
"err_save_failed_fmt" = "无法保存:%@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
/* iPhone: tap-to-insert */
"phone_insert_cancel" = "取消插入";
"phone_show_text_editor" = "編輯文字";
"phone_hide_text_editor" = "收合";
"phone_title_done" = "完成";

/* Alerts */
"err_save_failed_fmt" = "無法儲存:%@";
Expand Down
Loading