# Studio Arayüzü

> Tecof Studio arayüzü — kanvas, paneller, Inspector sekmeleri, düzenleme/önizleme modları, otomatik kayıt, canlı katılım ve TecofEditor prop’larının tamamı.
>
> Kaynak: https://tecof-app-dev.vercel.app/editor-studio · Bölüm: Tema geliştirme · Son güncelleme: 2018-10-20

Tecof Studio, sayfayı gerçek tasarım genişliğinde bir iframe kanvasında gösterir; bileşenleri sürükle-bırak ile ekler, Inspector’dan düzenler ve taslağı backend’e kaydeder. Bu sayfa arayüzün parçalarını ve `TecofEditor` prop’larını anlatır.

## Arayüz

| Bölge | Ne yapar |
| :--- | :--- |
| Üst çubuk | Geri al / yinele, Düzenleme ↔ Önizleme modu, Masaüstü / Tablet / Mobil görünüm ve masaüstü genişliği, düzenlenen dil, kılavuz ızgarası (`G`), boyutlandırma (`R`) ve boşluk tutamaçları (`B`), yardım, sol/sağ paneli aç-kapa |
| Sol panel | Bileşen kütüphanesi (kategoriler, varsa önizleme görselleri), sayfa şablonları ve katman ağacı |
| Kanvas | Host CSS’inden yalıtılmış iframe; seçim, hover, sürükleme ve satır içi metin düzenleme |
| Sağ panel (Inspector) | Seçim yokken **Sayfa** ve **Tema** sekmeleri; bir düğüm seçiliyken **İçerik**, **Stil** ve **Etkileşimler** |
| Komut paleti (`⌘K`) | Eylemler, bileşen ekleme ve (yapılandırıldıysa) AI ile bölüm üretme |

### Kanvas

- **Gerçek genişlikte kanvas.** Masaüstü görünümü seçilen tasarım genişliğinde (1280 / 1440 / 1680 / 1920 px ya da özel; varsayılan 1440) çizilir ve alana sığması için ölçeklenir. Tablet 768, mobil 375 px’tir. Üst çubuktaki çip etkin genişliği ve ölçek yüzdesini gösterir.
- **Düzenleme modu.** Tıklama düğümü seçer, çift tıklama satır içi metin düzenlemeyi başlatır, sürükleme düğümü taşır. Bağlantı, buton ve formların tarayıcı davranışı (gezinme, gönderme) bilerek engellenir.
- **Önizleme modu.** Editör çerçeveleri gizlenir; sayfa ziyaretçinin gördüğü gibi çalışır.
- **Satır içi düzenleme.** Metne çift tıklayın; `Enter` kaydeder, `Esc` vazgeçer. Bileşenler düzenlenebilir metni `data-tecof-prop="propAdı"` (çok dilli için ek olarak `data-tecof-lang`) ile işaretleyebilir; işaret yoksa metin eşleştirmesine düşülür.
- **Kanvastan panele odak.** Bir öğeye tıklamak Inspector’da İçerik sekmesini açar ve ilgili alana kaydırıp kısa bir vurgu yapar.
- **Boşluk görünümü.** Hover’da padding (yeşil) ve margin (amber) devtools tarzında gösterilir.
- **Dayanıklılık.** Render hatası veren bir bileşen kanvası düşürmez; düğüm bazlı error boundary yalnız o düğümü izole eder ve prop düzelince kendiliğinden toparlar.
- **Dokunmatik sürükle-bırak.** Tablet ve telefonda bir bileşene, palet bloğuna ya da katman satırına ~300 ms basılı tutup sürükleyin; fare ise HTML5 sürükle-bırak yolunu kullanır.

### Array kartları (`data-tecof-item`)

Bir bileşen `type: "array"` prop’unu kendi render’ında map’liyorsa (slot kullanmadan) kartları editöre tanıtabilir:

```tsx
{services.map((s, i) => (
  <li key={i} data-tecof-item={`services:${i}`}>
    <h3 data-tecof-item-prop="title">{s.title}</h3>
    <p data-tecof-item-prop="description">{s.description}</p>
  </li>
))}
```

Kart hover çerçevesi alır, tıklama Inspector’da o satırı açar, çift tıklama satır alanını yerinde düzenler (tek geri al adımı). Satır kayıt anında silinmiş ya da taşınmışsa yazım sessizce iptal edilir; yanlış satıra yazılmaz.

### Etkileşimli kontroller (`registerOverlayPortal`)

Düzenleme modunda kanvas her tıklamayı editöre yönlendirir. Sekme başlığı, slider oku ya da akordeon gibi kontrollerin düzenlerken de çalışması gerekiyorsa elemanı `puck.registerOverlayPortal` ile işaretleyin (React ref callback’i olarak, `null`’a dayanıklı):

```tsx
render: ({ puck, tabs }) => (
  <div>
    {tabs.map((tab, i) => (
      <button key={i} ref={puck.registerOverlayPortal} onClick={() => setActive(i)}>
        {tab.title}
      </button>
    ))}
  </div>
)
```

İşaretli eleman ve alt elemanları seçim, çift tıklama ve sürükleme işleyicilerinden muaf tutulur. `router.push` gibi JS yönlendirmeleri tarayıcı davranışı olmadığı için engellenemez; bu tür bileşenler `editMode` / `puck.isEditing` değerine bakmalıdır.

| `puck` alanı | Tip | Açıklama |
| :--- | :--- | :--- |
| `registerOverlayPortal` | `(el: HTMLElement \| null) => () => void` | Elemanı düzenleme modunda canlı bırakır |
| `isEditing` | `boolean` | Düzenleme modunda mı (`editMode` prop’u ile aynı) |
| `dragRef` | `(el: HTMLElement \| null) => void` | Yalnız `inline` bileşenlerde sürükleme tutamacı |
| `renderDropZone` | `(opts) => ReactNode` | Slot’u elle çizmek için (bkz. [Slotlar](/editor-slots)) |

### Inspector

- **İçerik.** Bileşenin alanları. Kapsayıcı bir düğüm seçildiğinde bölümün kendi alanlarının yanında tüm alt elementlerin alanları da girintili, katlanabilir gruplar hâlinde listelenir. Bileşen `fieldsGroups` tanımlarsa alanlar akordeon gruplarına ayrılır. `variants` tanımlıysa varyant çipleri görünür.
- **Stil.** Görsel stil editörü: kırılım ve durum bazlı token’lar, sayfalar arası stil uygulama. Bkz. [Stil editörü](/editor-styles).
- **Etkileşimler.** Tetikleyici → eylem kuralları (`click` / `hover` → `scrollTo`, `toggleClass`, `show`, `hide`, `toggle`). Kurallar düğümün `_interactions` prop’unda saklanır ve yayında `TecofRender` tarafından çalıştırılır.
- **Sayfa / Tema.** Seçim yokken sayfa geneli (`config.root.fields`) ve canlı tema düzenleyici. Bkz. [Canlı tema](/editor-theme).

### Katmanlar

Katman ağacında her satırda kilit ve gizle düğmeleri vardır (`_locked` / `_hidden`). Kilitli düğüm seçilebilir ama taşınamaz, silinemez ve alanları salt okunur olur. Gizli düğüm editörde soluk görünür; önizlemede ve yayında hiç render edilmez.

### Ortak bileşenler (symbol)

Bir bölüm ortak bileşene dönüştürülünce aynı `sharedComponentId` değerini taşıyan tüm örnekler tek kaynaktan beslenir: bir örnekte yapılan değişiklik diğerlerine canlı yansır. Örneğe özel alanlar `_symbolOverrides` ile ayrılır. Kayıtta backend örneği referansa indirger ve okurken asıl kayıttan çözer; ortak bileşen silinirken referanslar önce sayfalara gerçek içerik olarak yazılır, içerik kaybolmaz. Ortak bileşenler kanvasta mor rozetle işaretlenir.

### Canlı katılım

Aynı sayfayı aynı anda açan başka bir ekip üyesi üst çubukta görünür. Amaç eşzamanlı düzenleme değil **çakışma uyarısıdır**: iki kişi aynı taslağı kaydederse son kaydeden diğerinin işini ezer. Bağlantı yalnız editörde ve `accessToken` varken açılır; kimlik bilgisi sunucuda token’dan çözülür.

### AI ile bölüm üretme

`config.ai` tanımlıysa komut paletinde **AI ile bölüm üret** görünür. Kütüphane bileşen kataloğundan istemi kurar ve modelin yanıtını savunmacı biçimde doğrular (uydurma tip reddi, id temizliği, derinlik sınırı); LLM çağrısını host yapar:

```ts
export const editorConfig: StudioConfig = {
  components,
  ai: {
    complete: async ({ system, prompt }) => {
      const res = await fetch("/api/ai/section", { method: "POST", body: JSON.stringify({ system, prompt }) });
      return res.text(); // modelin ham metni: JSON bölüm yükünü içermeli
    },
  },
};
```

`config.ai` yoksa AI arayüzü hiç görünmez.

### Revizyon önizlemesi

Editör adresine `?revision=<id>` eklenirse taslak yerine o revizyonun anlık görüntüsü açılır ve editör **salt okunur** olur (kaydetme ve içe aktarma kilitlenir; iframe’de kayıt isteği `puck:saveError` döner).

## TecofEditor prop’ları

| Prop | Tip | Varsayılan | Açıklama |
| :--- | :--- | :--- | :--- |
| `pageId` | `string` | zorunlu | Düzenlenecek sayfanın backend kimliği |
| `config` | `StudioConfig` | zorunlu | Yayınla aynı bileşen kaydı |
| `accessToken` | `string?` | — | Kayıt ve yazma isteklerinde `Authorization` başlığı (panelin verdiği JWT) |
| `hostOrigin` | `string?` | `'*'` | iframe’e gömülü kullanımda izin verilen üst pencere origin’i; gelen ve giden `postMessage` bu origin’e kısıtlanır |
| `autoSave` | `boolean?` | `false` | Düzenlemeler durulunca taslağı otomatik kaydet |
| `autoSaveDelay` | `number?` | `2000` | Otomatik kayıt bekleme süresi (ms) |
| `warnOnUnsavedChanges` | `boolean?` | `true` | Kaydedilmemiş değişiklik varken tarayıcının yerel `beforeunload` uyarısı |
| `onChange` | `(data) => void` | — | ~300 ms debounce’lu değişiklik bildirimi |
| `onSave` | `(data) => void` | — | Başarılı taslak kaydından sonra |
| `onLanguageChange` | `(code: string) => void` | — | Aktif düzenleme dili ilk çözüldüğünde ve her değiştiğinde |
| `panelUrl` | `string?` | `https://app.tecof.com` | Alanlardaki “Panelde yönet / Panelde ekle” bağlantılarının kökü (yeni sekmede açılır) |
| `className` | `string?` | — | Studio kök elemanına ek sınıf |
| `overrides`, `plugins` | — | — | Ayrılmış; şu an kullanılmıyor |

`onLanguageChange` sözleşmesi: merchant bilgisi gelmeden boş dil (`""`) gönderilmez, aynı kod art arda iki kez gönderilmez ve her render’da yeni fonksiyon verseniz de ek çağrı olmaz. Tema bunu kendi i18n sağlayıcısına bağlayarak kanvas içeriğini o dilde gösterebilir; editör arayüzü Türkçe kalır. Kanvastaki bileşenler aynı dili `useActiveLanguage()` ile okuyabilir (Studio dışında `null` döner).

```tsx title="components/EditorClient.tsx"
"use client";

import { useState } from "react";
import { NextIntlClientProvider, useMessages } from "next-intl";
import { TecofEditor } from "@tecof/theme-editor";
import { editorConfig } from "@/app/editor-config";

export default function EditorClient({ pageId, accessToken }: { pageId: string; accessToken?: string }) {
  const messages = useMessages();
  const [canvasLocale, setCanvasLocale] = useState("tr");
  return (
    <NextIntlClientProvider locale={canvasLocale} messages={messages}>
      <TecofEditor pageId={pageId} config={editorConfig} accessToken={accessToken} onLanguageChange={setCanvasLocale} />
    </NextIntlClientProvider>
  );
}
```

## Pencereler ve onaylar

Editördeki her pencere (ayarlar, yardım, bölüm ekleme, seçiciler, onaylar) aynı kabuğu kullanır: `StudioDrawer` (Radix Dialog, `modal={false}`; içinden açılan renk/ikon/CMS popover’ları tıklanabilir kalır). Host eklentileri aynı kabuğu ve onay kuyruğunu kullanabilir:

```tsx
import { StudioDrawer, studioDialog } from "@tecof/theme-editor";

const ok = await studioDialog.confirm({
  title: "Ortak bileşeni sil",
  description: "Bu bileşeni kullanan tüm sayfalar etkilenir.",
  confirmLabel: "Sil",
  danger: true,
});
await studioDialog.alert({ title: "İçe aktarma tamamlandı" });
```

| `StudioDrawer` prop’u | Açıklama |
| :--- | :--- |
| `open` / `onOpenChange` | Açıklık durumu |
| `title` | Başlık (zorunlu; erişilebilirlik için de kullanılır) |
| `description`, `icon` | Alt başlık ve rozet ikonu |
| `tone` | `default` · `primary` · `danger` |
| `size` | `sm` 400 · `md` 560 · `lg` 820 · `xl` 1180 px (varsayılan `md`) |
| `busy` | İş sürerken kapatma yollarını kilitler |
| `footer`, `headerActions`, `hideClose`, `elevated`, `inert` | Alt eylem satırı, baş satırı kontrolleri, X’i gizle, başka drawer’ın üstünde aç, etkileşimi kapat |

Bir drawer açıkken Studio’nun genel kısayolları (`Delete`, `⌘Z`, `G` / `R` / `B` …) çalışmaz; denetim `isStudioDrawerOpen()` ile yapılır.

## Performans

Monaco (kod), TipTap (zengin metin) ve FilePond/Doka (medya) gibi ağır alanlar ayrı chunk’lara bölünüp ihtiyaç anında yüklenir. Bileşen kütüphanesindeki önizleme görselleri tembel yüklenir.
