Refactor: Notion 동기화를 웹사이트 API 방식으로 전환
- Notion 관련 코드 제거: - NotionBackgroundSync.cs 삭제 - NotionSyncSettings.cs → BackgroundSyncSettings.cs 이름 변경 - 웹사이트 API 연동 기능 추가: - WebsiteBackgroundExporter.cs: HTTP POST로 배경 데이터 업로드 - BackgroundSyncSettings.cs: API 엔드포인트 및 Git URL 설정 - BackgroundThumbnailCapture.cs 수정: - 새 설정 클래스 참조로 변경 - Notion 자동 동기화 코드 제거 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2eb039e7ae
commit
83c188f1bd
BIN
Assets/Resources/Settings/BackgroundSyncSettings.asset
(Stored with Git LFS)
Normal file
BIN
Assets/Resources/Settings/BackgroundSyncSettings.asset
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 738b59decd79ba245a5ddd6b03d7292c
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Resources/Settings/NotionSyncSettings.asset
(Stored with Git LFS)
BIN
Assets/Resources/Settings/NotionSyncSettings.asset
(Stored with Git LFS)
Binary file not shown.
@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a1b2c3d4e5f6789012345678abcdef01
|
|
||||||
NativeFormatImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
mainObjectFileID: 11400000
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
BIN
Assets/ResourcesData/Background/[아이시아]영재학교 배경/scene/[아이시아]영재학교 배경.png
(Stored with Git LFS)
BIN
Assets/ResourcesData/Background/[아이시아]영재학교 배경/scene/[아이시아]영재학교 배경.png
(Stored with Git LFS)
Binary file not shown.
@ -4,19 +4,19 @@ using UnityEngine;
|
|||||||
namespace Streamingle.Background.Editor
|
namespace Streamingle.Background.Editor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notion 동기화 설정을 저장하는 ScriptableObject
|
/// 배경 동기화 설정을 저장하는 ScriptableObject
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CreateAssetMenu(fileName = "NotionSyncSettings", menuName = "Streamingle/Notion Sync Settings")]
|
[CreateAssetMenu(fileName = "BackgroundSyncSettings", menuName = "Streamingle/Background Sync Settings")]
|
||||||
public class NotionSyncSettings : ScriptableObject
|
public class BackgroundSyncSettings : ScriptableObject
|
||||||
{
|
{
|
||||||
[Header("Notion API 설정")]
|
[Header("웹사이트 API 설정")]
|
||||||
[Tooltip("Notion Integration Token (ntn_xxx... 또는 secret_xxx...)")]
|
[Tooltip("배경 API 엔드포인트 URL (예: https://minglestudio.co.kr/api/backgrounds)")]
|
||||||
public string notionApiToken = "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";
|
public string apiEndpoint = "https://minglestudio.co.kr/api/backgrounds";
|
||||||
|
|
||||||
[Tooltip("Notion Database ID (32자리 hex) - 배경 일람")]
|
[Tooltip("API 인증 키 (선택사항)")]
|
||||||
public string notionDatabaseId = "25ea7d46b958805f88fcf127979934bf";
|
public string apiKey = "";
|
||||||
|
|
||||||
[Header("Git 설정")]
|
[Header("Git 설정 (썸네일 URL용)")]
|
||||||
[Tooltip("Git 서버 URL (예: https://kindnick-git.duckdns.org)")]
|
[Tooltip("Git 서버 URL (예: https://kindnick-git.duckdns.org)")]
|
||||||
public string gitServerUrl = "https://kindnick-git.duckdns.org";
|
public string gitServerUrl = "https://kindnick-git.duckdns.org";
|
||||||
|
|
||||||
@ -26,13 +26,6 @@ namespace Streamingle.Background.Editor
|
|||||||
[Tooltip("Git 브랜치 (예: main)")]
|
[Tooltip("Git 브랜치 (예: main)")]
|
||||||
public string gitBranch = "main";
|
public string gitBranch = "main";
|
||||||
|
|
||||||
[Header("동기화 설정")]
|
|
||||||
[Tooltip("썸네일 캡처 시 자동 동기화")]
|
|
||||||
public bool autoSyncOnCapture = false;
|
|
||||||
|
|
||||||
[Tooltip("배경 씬 로드 시 사용 이력 기록")]
|
|
||||||
public bool trackUsageHistory = true;
|
|
||||||
|
|
||||||
[Header("썸네일 설정")]
|
[Header("썸네일 설정")]
|
||||||
[Tooltip("썸네일 너비")]
|
[Tooltip("썸네일 너비")]
|
||||||
public int thumbnailWidth = 1920;
|
public int thumbnailWidth = 1920;
|
||||||
@ -40,13 +33,21 @@ namespace Streamingle.Background.Editor
|
|||||||
[Tooltip("썸네일 높이 (16:9 비율)")]
|
[Tooltip("썸네일 높이 (16:9 비율)")]
|
||||||
public int thumbnailHeight = 1080;
|
public int thumbnailHeight = 1080;
|
||||||
|
|
||||||
|
[Header("동기화 설정")]
|
||||||
|
[Tooltip("썸네일 캡처 시 자동 동기화")]
|
||||||
|
public bool autoSyncOnCapture = false;
|
||||||
|
|
||||||
|
[Header("웹사이트 설정")]
|
||||||
|
[Tooltip("배경 페이지 URL (브라우저에서 열기용)")]
|
||||||
|
public string websiteUrl = "https://minglestudio.co.kr/backgrounds";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Git Raw 파일 URL 생성 (Gitea/Gogs 형식)
|
/// Git Media 파일 URL 생성 (Gitea 형식)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string GetGitRawUrl(string assetPath)
|
public string GetGitRawUrl(string assetPath)
|
||||||
{
|
{
|
||||||
// Assets/ResourcesData/Background/... 형식의 경로를 Git Raw URL로 변환
|
// Assets/ResourcesData/Background/... 형식의 경로를 Git Media URL로 변환
|
||||||
// Gitea Raw URL 형식: {serverUrl}/{repoPath}/raw/branch/{branch}/{filePath}
|
// Gitea Media URL 형식: {serverUrl}/{repoPath}/media/branch/{branch}/{filePath}
|
||||||
string relativePath = assetPath.Replace("\\", "/");
|
string relativePath = assetPath.Replace("\\", "/");
|
||||||
|
|
||||||
// 경로의 각 세그먼트를 URL 인코딩 (슬래시는 유지)
|
// 경로의 각 세그먼트를 URL 인코딩 (슬래시는 유지)
|
||||||
@ -57,29 +58,17 @@ namespace Streamingle.Background.Editor
|
|||||||
}
|
}
|
||||||
string encodedPath = string.Join("/", segments);
|
string encodedPath = string.Join("/", segments);
|
||||||
|
|
||||||
return $"{gitServerUrl}/{gitRepoPath}/raw/branch/{gitBranch}/{encodedPath}";
|
return $"{gitServerUrl}/{gitRepoPath}/media/branch/{gitBranch}/{encodedPath}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 설정이 유효한지 확인
|
/// API 설정이 유효한지 확인
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsValid()
|
public bool IsValid()
|
||||||
{
|
{
|
||||||
return !string.IsNullOrEmpty(notionApiToken) &&
|
return !string.IsNullOrEmpty(apiEndpoint) &&
|
||||||
!string.IsNullOrEmpty(notionDatabaseId) &&
|
|
||||||
!string.IsNullOrEmpty(gitServerUrl) &&
|
!string.IsNullOrEmpty(gitServerUrl) &&
|
||||||
!string.IsNullOrEmpty(gitRepoPath);
|
!string.IsNullOrEmpty(gitRepoPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notion API Token이 설정되었는지 확인
|
|
||||||
/// </summary>
|
|
||||||
public bool HasNotionToken => !string.IsNullOrEmpty(notionApiToken) &&
|
|
||||||
(notionApiToken.StartsWith("secret_") || notionApiToken.StartsWith("ntn_"));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Database ID가 유효한 형식인지 확인
|
|
||||||
/// </summary>
|
|
||||||
public bool HasValidDatabaseId => !string.IsNullOrEmpty(notionDatabaseId) && notionDatabaseId.Length >= 32;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10,7 +10,7 @@ namespace Streamingle.Background.Editor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class BackgroundThumbnailCapture : EditorWindow
|
public class BackgroundThumbnailCapture : EditorWindow
|
||||||
{
|
{
|
||||||
// 16:9 기본값 (Notion용)
|
// 16:9 기본값
|
||||||
private const int DEFAULT_WIDTH = 1920;
|
private const int DEFAULT_WIDTH = 1920;
|
||||||
private const int DEFAULT_HEIGHT = 1080;
|
private const int DEFAULT_HEIGHT = 1080;
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ namespace Streamingle.Background.Editor
|
|||||||
private int _width = DEFAULT_WIDTH;
|
private int _width = DEFAULT_WIDTH;
|
||||||
private int _height = DEFAULT_HEIGHT;
|
private int _height = DEFAULT_HEIGHT;
|
||||||
|
|
||||||
private NotionSyncSettings _notionSettings;
|
private BackgroundSyncSettings _syncSettings;
|
||||||
private string _savePath;
|
private string _savePath;
|
||||||
private string _fileName;
|
private string _fileName;
|
||||||
private bool _useSceneCamera = true;
|
private bool _useSceneCamera = true;
|
||||||
@ -42,15 +42,15 @@ namespace Streamingle.Background.Editor
|
|||||||
_database = AssetDatabase.LoadAssetAtPath<BackgroundSceneDatabase>(
|
_database = AssetDatabase.LoadAssetAtPath<BackgroundSceneDatabase>(
|
||||||
"Assets/Resources/Settings/BackgroundSceneDatabase.asset");
|
"Assets/Resources/Settings/BackgroundSceneDatabase.asset");
|
||||||
|
|
||||||
// Notion 설정 로드
|
// 동기화 설정 로드
|
||||||
_notionSettings = AssetDatabase.LoadAssetAtPath<NotionSyncSettings>(
|
_syncSettings = AssetDatabase.LoadAssetAtPath<BackgroundSyncSettings>(
|
||||||
"Assets/Resources/Settings/NotionSyncSettings.asset");
|
"Assets/Resources/Settings/BackgroundSyncSettings.asset");
|
||||||
|
|
||||||
// 설정에서 해상도 가져오기
|
// 설정에서 해상도 가져오기
|
||||||
if (_notionSettings != null)
|
if (_syncSettings != null)
|
||||||
{
|
{
|
||||||
_width = _notionSettings.thumbnailWidth;
|
_width = _syncSettings.thumbnailWidth;
|
||||||
_height = _notionSettings.thumbnailHeight;
|
_height = _syncSettings.thumbnailHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 현재 로드된 배경 씬 찾기
|
// 현재 로드된 배경 씬 찾기
|
||||||
@ -338,7 +338,7 @@ namespace Streamingle.Background.Editor
|
|||||||
if (GUILayout.Button("960x540")) { _width = 960; _height = 540; }
|
if (GUILayout.Button("960x540")) { _width = 960; _height = 540; }
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
EditorGUILayout.HelpBox("16:9 비율 썸네일이 권장됩니다.\n에디터에서는 1:1로 크롭되어 표시되고, Notion에는 16:9 원본이 업로드됩니다.", MessageType.Info);
|
EditorGUILayout.HelpBox("16:9 비율 썸네일이 권장됩니다.\n에디터에서는 1:1로 크롭되어 표시되고, 웹사이트에는 16:9 원본이 사용됩니다.", MessageType.Info);
|
||||||
|
|
||||||
EditorGUILayout.Space(10);
|
EditorGUILayout.Space(10);
|
||||||
|
|
||||||
@ -580,33 +580,9 @@ namespace Streamingle.Background.Editor
|
|||||||
loaderWindow.RefreshThumbnail(assetPath);
|
loaderWindow.RefreshThumbnail(assetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notion 자동 동기화
|
|
||||||
if (_notionSettings != null && _notionSettings.autoSyncOnCapture && _currentSceneInfo != null)
|
|
||||||
{
|
|
||||||
SyncToNotionAsync(_currentSceneInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorUtility.DisplayDialog("완료", $"썸네일이 저장되었습니다.\n{fullPath}", "확인");
|
EditorUtility.DisplayDialog("완료", $"썸네일이 저장되었습니다.\n{fullPath}", "확인");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void SyncToNotionAsync(BackgroundSceneInfo sceneInfo)
|
|
||||||
{
|
|
||||||
if (_notionSettings == null || !_notionSettings.IsValid())
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogWarning("[Notion] 설정이 올바르지 않아 동기화를 건너뜁니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sync = new NotionBackgroundSync(_notionSettings, _database);
|
|
||||||
|
|
||||||
sync.OnProgress += (msg) => UnityEngine.Debug.Log($"[Notion] {msg}");
|
|
||||||
sync.OnError += (msg) => UnityEngine.Debug.LogError($"[Notion] {msg}");
|
|
||||||
sync.OnCompleted += () => UnityEngine.Debug.Log("[Notion] 동기화 완료");
|
|
||||||
|
|
||||||
await sync.SyncSingleToNotion(sceneInfo);
|
|
||||||
sync.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CaptureAllBackgroundScenes()
|
private void CaptureAllBackgroundScenes()
|
||||||
{
|
{
|
||||||
if (_database == null)
|
if (_database == null)
|
||||||
|
|||||||
@ -1,848 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace Streamingle.Background.Editor
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Notion 배경 씬 동기화 매니저
|
|
||||||
/// </summary>
|
|
||||||
public class NotionBackgroundSync
|
|
||||||
{
|
|
||||||
private const string NOTION_API_VERSION = "2022-06-28";
|
|
||||||
private const string NOTION_API_URL = "https://api.notion.com/v1";
|
|
||||||
|
|
||||||
private readonly NotionSyncSettings _settings;
|
|
||||||
private readonly BackgroundSceneDatabase _database;
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public event Action<string> OnProgress;
|
|
||||||
public event Action<string> OnError;
|
|
||||||
public event Action OnCompleted;
|
|
||||||
|
|
||||||
public NotionBackgroundSync(NotionSyncSettings settings, BackgroundSceneDatabase database)
|
|
||||||
{
|
|
||||||
_settings = settings;
|
|
||||||
_database = database;
|
|
||||||
_httpClient = new HttpClient();
|
|
||||||
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_settings.notionApiToken}");
|
|
||||||
_httpClient.DefaultRequestHeaders.Add("Notion-Version", NOTION_API_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 모든 배경 씬을 Notion에 동기화
|
|
||||||
/// </summary>
|
|
||||||
public async Task SyncAllToNotion()
|
|
||||||
{
|
|
||||||
if (!_settings.IsValid())
|
|
||||||
{
|
|
||||||
OnError?.Invoke("Notion 설정이 올바르지 않습니다. 설정을 확인하세요.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
OnProgress?.Invoke("데이터베이스 속성 확인 중...");
|
|
||||||
|
|
||||||
// 0. 데이터베이스 속성 확인 및 생성
|
|
||||||
await EnsureDatabaseProperties();
|
|
||||||
|
|
||||||
OnProgress?.Invoke("기존 Notion 항목 조회 중...");
|
|
||||||
|
|
||||||
// 1. 기존 Notion 데이터베이스 항목 조회
|
|
||||||
var existingPages = await GetExistingPages();
|
|
||||||
|
|
||||||
int total = _database.scenes.Count;
|
|
||||||
int current = 0;
|
|
||||||
|
|
||||||
foreach (var sceneInfo in _database.scenes)
|
|
||||||
{
|
|
||||||
current++;
|
|
||||||
OnProgress?.Invoke($"동기화 중: {sceneInfo.sceneName} ({current}/{total})");
|
|
||||||
|
|
||||||
// 기존 페이지가 있는지 확인
|
|
||||||
string existingPageId = FindExistingPage(existingPages, sceneInfo.scenePath);
|
|
||||||
|
|
||||||
if (existingPageId != null)
|
|
||||||
{
|
|
||||||
// 업데이트
|
|
||||||
await UpdatePage(existingPageId, sceneInfo);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 새로 생성
|
|
||||||
await CreatePage(sceneInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// API Rate Limit 방지
|
|
||||||
await Task.Delay(350);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnProgress?.Invoke("동기화 완료!");
|
|
||||||
OnCompleted?.Invoke();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
OnError?.Invoke($"동기화 실패: {ex.Message}");
|
|
||||||
UnityEngine.Debug.LogException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 단일 배경 씬을 Notion에 동기화
|
|
||||||
/// </summary>
|
|
||||||
public async Task SyncSingleToNotion(BackgroundSceneInfo sceneInfo)
|
|
||||||
{
|
|
||||||
if (!_settings.IsValid())
|
|
||||||
{
|
|
||||||
OnError?.Invoke("Notion 설정이 올바르지 않습니다.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
OnProgress?.Invoke("데이터베이스 속성 확인 중...");
|
|
||||||
await EnsureDatabaseProperties();
|
|
||||||
|
|
||||||
OnProgress?.Invoke($"동기화 중: {sceneInfo.sceneName}");
|
|
||||||
|
|
||||||
var existingPages = await GetExistingPages();
|
|
||||||
string existingPageId = FindExistingPage(existingPages, sceneInfo.scenePath);
|
|
||||||
|
|
||||||
if (existingPageId != null)
|
|
||||||
{
|
|
||||||
await UpdatePage(existingPageId, sceneInfo);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await CreatePage(sceneInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnProgress?.Invoke("동기화 완료!");
|
|
||||||
OnCompleted?.Invoke();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
OnError?.Invoke($"동기화 실패: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 데이터베이스 속성 확인 및 누락된 속성 생성
|
|
||||||
/// </summary>
|
|
||||||
private async Task EnsureDatabaseProperties()
|
|
||||||
{
|
|
||||||
// 데이터베이스 정보 조회
|
|
||||||
var response = await _httpClient.GetAsync($"{NOTION_API_URL}/databases/{_settings.notionDatabaseId}");
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
throw new Exception($"데이터베이스 조회 실패: {responseText}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var dbInfo = JObject.Parse(responseText);
|
|
||||||
var existingProperties = dbInfo["properties"] as JObject;
|
|
||||||
|
|
||||||
// 필요한 속성들 정의
|
|
||||||
var requiredProperties = new Dictionary<string, JObject>
|
|
||||||
{
|
|
||||||
// Title 속성은 이미 존재하므로 이름만 확인
|
|
||||||
["카테고리"] = new JObject { ["select"] = new JObject { ["options"] = new JArray() } },
|
|
||||||
["태그"] = new JObject { ["multi_select"] = new JObject { ["options"] = new JArray() } },
|
|
||||||
["씬 경로"] = new JObject { ["rich_text"] = new JObject() },
|
|
||||||
["폴더 이름"] = new JObject { ["rich_text"] = new JObject() },
|
|
||||||
["썸네일"] = new JObject { ["url"] = new JObject() },
|
|
||||||
["마지막 동기화"] = new JObject { ["date"] = new JObject() },
|
|
||||||
["마지막 사용"] = new JObject { ["date"] = new JObject() }
|
|
||||||
};
|
|
||||||
|
|
||||||
// 누락된 속성 찾기
|
|
||||||
var propertiesToAdd = new JObject();
|
|
||||||
foreach (var prop in requiredProperties)
|
|
||||||
{
|
|
||||||
bool found = false;
|
|
||||||
foreach (var existing in existingProperties)
|
|
||||||
{
|
|
||||||
if (existing.Key == prop.Key)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
propertiesToAdd[prop.Key] = prop.Value;
|
|
||||||
UnityEngine.Debug.Log($"[Notion] 속성 추가 예정: {prop.Key}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 누락된 속성이 있으면 추가
|
|
||||||
if (propertiesToAdd.Count > 0)
|
|
||||||
{
|
|
||||||
OnProgress?.Invoke($"누락된 속성 {propertiesToAdd.Count}개 생성 중...");
|
|
||||||
|
|
||||||
var updateBody = new JObject
|
|
||||||
{
|
|
||||||
["properties"] = propertiesToAdd
|
|
||||||
};
|
|
||||||
|
|
||||||
var content = new StringContent(updateBody.ToString(), Encoding.UTF8, "application/json");
|
|
||||||
var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"{NOTION_API_URL}/databases/{_settings.notionDatabaseId}")
|
|
||||||
{
|
|
||||||
Content = content
|
|
||||||
};
|
|
||||||
|
|
||||||
var updateResponse = await _httpClient.SendAsync(request);
|
|
||||||
var updateResponseText = await updateResponse.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
if (!updateResponse.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogWarning($"[Notion] 속성 추가 실패 (무시하고 계속): {updateResponseText}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.Log($"[Notion] 데이터베이스 속성 {propertiesToAdd.Count}개 추가됨");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title 속성 이름 확인 (기본 "이름" 또는 "Name")
|
|
||||||
_titlePropertyName = FindTitlePropertyName(existingProperties);
|
|
||||||
UnityEngine.Debug.Log($"[Notion] Title 속성 이름: {_titlePropertyName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _titlePropertyName = "이름";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Title 타입 속성의 이름 찾기
|
|
||||||
/// </summary>
|
|
||||||
private string FindTitlePropertyName(JObject properties)
|
|
||||||
{
|
|
||||||
foreach (var prop in properties)
|
|
||||||
{
|
|
||||||
var propType = prop.Value["type"]?.Value<string>();
|
|
||||||
if (propType == "title")
|
|
||||||
{
|
|
||||||
return prop.Key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "이름"; // 기본값
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 기존 Notion 페이지 목록 조회
|
|
||||||
/// </summary>
|
|
||||||
private async Task<List<JObject>> GetExistingPages()
|
|
||||||
{
|
|
||||||
var pages = new List<JObject>();
|
|
||||||
string cursor = null;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
var requestBody = new JObject
|
|
||||||
{
|
|
||||||
["page_size"] = 100
|
|
||||||
};
|
|
||||||
|
|
||||||
if (cursor != null)
|
|
||||||
{
|
|
||||||
requestBody["start_cursor"] = cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json");
|
|
||||||
var response = await _httpClient.PostAsync(
|
|
||||||
$"{NOTION_API_URL}/databases/{_settings.notionDatabaseId}/query",
|
|
||||||
content);
|
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
throw new Exception($"Notion API 오류: {responseText}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = JObject.Parse(responseText);
|
|
||||||
var results = result["results"] as JArray;
|
|
||||||
|
|
||||||
if (results != null)
|
|
||||||
{
|
|
||||||
foreach (var page in results)
|
|
||||||
{
|
|
||||||
pages.Add(page as JObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = result["has_more"]?.Value<bool>() == true
|
|
||||||
? result["next_cursor"]?.Value<string>()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
} while (cursor != null);
|
|
||||||
|
|
||||||
return pages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 씬 경로로 기존 페이지 찾기
|
|
||||||
/// </summary>
|
|
||||||
private string FindExistingPage(List<JObject> pages, string scenePath)
|
|
||||||
{
|
|
||||||
foreach (var page in pages)
|
|
||||||
{
|
|
||||||
var properties = page["properties"] as JObject;
|
|
||||||
if (properties == null) continue;
|
|
||||||
|
|
||||||
// "씬 경로" 또는 "ScenePath" 속성에서 검색
|
|
||||||
var pathProperty = properties["씬 경로"] ?? properties["ScenePath"];
|
|
||||||
if (pathProperty == null) continue;
|
|
||||||
|
|
||||||
var richText = pathProperty["rich_text"] as JArray;
|
|
||||||
if (richText != null && richText.Count > 0)
|
|
||||||
{
|
|
||||||
var text = richText[0]["plain_text"]?.Value<string>();
|
|
||||||
if (text == scenePath)
|
|
||||||
{
|
|
||||||
return page["id"]?.Value<string>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 새 페이지 생성
|
|
||||||
/// </summary>
|
|
||||||
private async Task CreatePage(BackgroundSceneInfo sceneInfo)
|
|
||||||
{
|
|
||||||
var properties = BuildProperties(sceneInfo);
|
|
||||||
|
|
||||||
var requestBody = new JObject
|
|
||||||
{
|
|
||||||
["parent"] = new JObject { ["database_id"] = _settings.notionDatabaseId },
|
|
||||||
["properties"] = properties
|
|
||||||
};
|
|
||||||
|
|
||||||
// 썸네일이 있으면 커버 이미지로 추가
|
|
||||||
if (!string.IsNullOrEmpty(sceneInfo.thumbnailPath))
|
|
||||||
{
|
|
||||||
// Git 커밋 상태 확인
|
|
||||||
bool isCommitted = CheckIfFileIsCommitted(sceneInfo.thumbnailPath);
|
|
||||||
if (!isCommitted)
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogWarning($"[Notion] 썸네일이 Git에 커밋되지 않음: {sceneInfo.thumbnailPath}\n" +
|
|
||||||
"Notion에서 이미지가 표시되지 않을 수 있습니다. Git commit & push 후 다시 동기화하세요.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string imageUrl = _settings.GetGitRawUrl(sceneInfo.thumbnailPath);
|
|
||||||
requestBody["cover"] = new JObject
|
|
||||||
{
|
|
||||||
["type"] = "external",
|
|
||||||
["external"] = new JObject { ["url"] = imageUrl }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json");
|
|
||||||
var response = await _httpClient.PostAsync($"{NOTION_API_URL}/pages", content);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
throw new Exception($"페이지 생성 실패: {responseText}");
|
|
||||||
}
|
|
||||||
|
|
||||||
UnityEngine.Debug.Log($"[Notion] 페이지 생성됨: {sceneInfo.sceneName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 기존 페이지 업데이트
|
|
||||||
/// </summary>
|
|
||||||
private async Task UpdatePage(string pageId, BackgroundSceneInfo sceneInfo)
|
|
||||||
{
|
|
||||||
var properties = BuildProperties(sceneInfo);
|
|
||||||
|
|
||||||
var requestBody = new JObject
|
|
||||||
{
|
|
||||||
["properties"] = properties
|
|
||||||
};
|
|
||||||
|
|
||||||
// 썸네일이 있으면 커버 이미지도 업데이트
|
|
||||||
if (!string.IsNullOrEmpty(sceneInfo.thumbnailPath))
|
|
||||||
{
|
|
||||||
// Git 커밋 상태 확인
|
|
||||||
bool isCommitted = CheckIfFileIsCommitted(sceneInfo.thumbnailPath);
|
|
||||||
if (!isCommitted)
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogWarning($"[Notion] 썸네일이 Git에 커밋되지 않음: {sceneInfo.thumbnailPath}\n" +
|
|
||||||
"Notion에서 이미지가 표시되지 않을 수 있습니다. Git commit & push 후 다시 동기화하세요.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string imageUrl = _settings.GetGitRawUrl(sceneInfo.thumbnailPath);
|
|
||||||
requestBody["cover"] = new JObject
|
|
||||||
{
|
|
||||||
["type"] = "external",
|
|
||||||
["external"] = new JObject { ["url"] = imageUrl }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json");
|
|
||||||
var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"{NOTION_API_URL}/pages/{pageId}")
|
|
||||||
{
|
|
||||||
Content = content
|
|
||||||
};
|
|
||||||
|
|
||||||
var response = await _httpClient.SendAsync(request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
throw new Exception($"페이지 업데이트 실패: {responseText}");
|
|
||||||
}
|
|
||||||
|
|
||||||
UnityEngine.Debug.Log($"[Notion] 페이지 업데이트됨: {sceneInfo.sceneName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notion 속성 빌드
|
|
||||||
/// </summary>
|
|
||||||
private JObject BuildProperties(BackgroundSceneInfo sceneInfo)
|
|
||||||
{
|
|
||||||
var properties = new JObject();
|
|
||||||
|
|
||||||
// 이름 (Title) - 동적으로 찾은 속성 이름 사용
|
|
||||||
properties[_titlePropertyName] = new JObject
|
|
||||||
{
|
|
||||||
["title"] = new JArray
|
|
||||||
{
|
|
||||||
new JObject
|
|
||||||
{
|
|
||||||
["text"] = new JObject { ["content"] = sceneInfo.sceneName }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 카테고리 (Select) - 첫 번째 태그 사용
|
|
||||||
string category = ExtractCategory(sceneInfo.categoryName);
|
|
||||||
properties["카테고리"] = new JObject
|
|
||||||
{
|
|
||||||
["select"] = new JObject { ["name"] = category }
|
|
||||||
};
|
|
||||||
|
|
||||||
// 태그 (Multi-Select) - 대괄호 안의 모든 태그 추출
|
|
||||||
var tags = ExtractAllTags(sceneInfo.categoryName);
|
|
||||||
if (tags.Count > 0)
|
|
||||||
{
|
|
||||||
var tagArray = new JArray();
|
|
||||||
foreach (var tag in tags)
|
|
||||||
{
|
|
||||||
tagArray.Add(new JObject { ["name"] = tag });
|
|
||||||
}
|
|
||||||
properties["태그"] = new JObject
|
|
||||||
{
|
|
||||||
["multi_select"] = tagArray
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 씬 경로 (Rich Text)
|
|
||||||
properties["씬 경로"] = new JObject
|
|
||||||
{
|
|
||||||
["rich_text"] = new JArray
|
|
||||||
{
|
|
||||||
new JObject
|
|
||||||
{
|
|
||||||
["text"] = new JObject { ["content"] = sceneInfo.scenePath }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 썸네일 URL (URL)
|
|
||||||
if (!string.IsNullOrEmpty(sceneInfo.thumbnailPath))
|
|
||||||
{
|
|
||||||
string imageUrl = _settings.GetGitRawUrl(sceneInfo.thumbnailPath);
|
|
||||||
properties["썸네일"] = new JObject
|
|
||||||
{
|
|
||||||
["url"] = imageUrl
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 폴더 이름 (Rich Text)
|
|
||||||
properties["폴더 이름"] = new JObject
|
|
||||||
{
|
|
||||||
["rich_text"] = new JArray
|
|
||||||
{
|
|
||||||
new JObject
|
|
||||||
{
|
|
||||||
["text"] = new JObject { ["content"] = sceneInfo.categoryName }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 마지막 동기화 (Date)
|
|
||||||
properties["마지막 동기화"] = new JObject
|
|
||||||
{
|
|
||||||
["date"] = new JObject
|
|
||||||
{
|
|
||||||
["start"] = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 카테고리 이름에서 첫 번째 태그 부분 추출 (예: "[공용]농구장" → "공용")
|
|
||||||
/// </summary>
|
|
||||||
private string ExtractCategory(string folderName)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(folderName)) return "기타";
|
|
||||||
|
|
||||||
int startBracket = folderName.IndexOf('[');
|
|
||||||
int endBracket = folderName.IndexOf(']');
|
|
||||||
|
|
||||||
if (startBracket >= 0 && endBracket > startBracket)
|
|
||||||
{
|
|
||||||
return folderName.Substring(startBracket + 1, endBracket - startBracket - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return folderName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 카테고리 이름에서 모든 태그 추출 (예: "[공용][야외]농구장" → ["공용", "야외"])
|
|
||||||
/// </summary>
|
|
||||||
private List<string> ExtractAllTags(string folderName)
|
|
||||||
{
|
|
||||||
var tags = new List<string>();
|
|
||||||
if (string.IsNullOrEmpty(folderName)) return tags;
|
|
||||||
|
|
||||||
int searchStart = 0;
|
|
||||||
while (searchStart < folderName.Length)
|
|
||||||
{
|
|
||||||
int startBracket = folderName.IndexOf('[', searchStart);
|
|
||||||
if (startBracket < 0) break;
|
|
||||||
|
|
||||||
int endBracket = folderName.IndexOf(']', startBracket);
|
|
||||||
if (endBracket < 0) break;
|
|
||||||
|
|
||||||
string tag = folderName.Substring(startBracket + 1, endBracket - startBracket - 1);
|
|
||||||
if (!string.IsNullOrEmpty(tag) && !tags.Contains(tag))
|
|
||||||
{
|
|
||||||
tags.Add(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
searchStart = endBracket + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 파일이 Git에 커밋되어 있는지 확인
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assetPath">Unity Asset 경로 (예: Assets/ResourcesData/...)</param>
|
|
||||||
/// <returns>커밋 여부 (true: 커밋됨, false: 커밋 안됨 또는 오류)</returns>
|
|
||||||
private bool CheckIfFileIsCommitted(string assetPath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Unity 프로젝트 루트 경로
|
|
||||||
string projectPath = Path.GetDirectoryName(Application.dataPath);
|
|
||||||
string filePath = Path.Combine(projectPath, assetPath).Replace("\\", "/");
|
|
||||||
|
|
||||||
// 파일이 존재하는지 먼저 확인
|
|
||||||
if (!File.Exists(filePath))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// git ls-files로 파일이 추적되고 있는지 확인
|
|
||||||
var lsFilesProcess = new Process
|
|
||||||
{
|
|
||||||
StartInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "git",
|
|
||||||
Arguments = $"ls-files \"{assetPath}\"",
|
|
||||||
WorkingDirectory = projectPath,
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
StandardOutputEncoding = Encoding.UTF8
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
lsFilesProcess.Start();
|
|
||||||
string lsFilesOutput = lsFilesProcess.StandardOutput.ReadToEnd().Trim();
|
|
||||||
lsFilesProcess.WaitForExit();
|
|
||||||
|
|
||||||
// 파일이 Git에 추적되고 있지 않으면
|
|
||||||
if (string.IsNullOrEmpty(lsFilesOutput))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// git status로 변경 사항 확인 (커밋되지 않은 변경이 있는지)
|
|
||||||
var statusProcess = new Process
|
|
||||||
{
|
|
||||||
StartInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "git",
|
|
||||||
Arguments = $"status --porcelain \"{assetPath}\"",
|
|
||||||
WorkingDirectory = projectPath,
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
StandardOutputEncoding = Encoding.UTF8
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
statusProcess.Start();
|
|
||||||
string statusOutput = statusProcess.StandardOutput.ReadToEnd().Trim();
|
|
||||||
statusProcess.WaitForExit();
|
|
||||||
|
|
||||||
// status 출력이 비어있으면 커밋된 상태 (변경 없음)
|
|
||||||
// 출력이 있으면 modified/added/untracked 등의 상태
|
|
||||||
return string.IsNullOrEmpty(statusOutput);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogWarning($"[Notion] Git 상태 확인 실패: {ex.Message}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 사용 이력 기록 (Notion에)
|
|
||||||
/// </summary>
|
|
||||||
public async Task RecordUsage(BackgroundSceneInfo sceneInfo)
|
|
||||||
{
|
|
||||||
if (!_settings.trackUsageHistory || !_settings.IsValid()) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var existingPages = await GetExistingPages();
|
|
||||||
string pageId = FindExistingPage(existingPages, sceneInfo.scenePath);
|
|
||||||
|
|
||||||
if (pageId != null)
|
|
||||||
{
|
|
||||||
// "마지막 사용" 날짜 업데이트
|
|
||||||
var properties = new JObject
|
|
||||||
{
|
|
||||||
["마지막 사용"] = new JObject
|
|
||||||
{
|
|
||||||
["date"] = new JObject
|
|
||||||
{
|
|
||||||
["start"] = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var requestBody = new JObject { ["properties"] = properties };
|
|
||||||
var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json");
|
|
||||||
var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"{NOTION_API_URL}/pages/{pageId}")
|
|
||||||
{
|
|
||||||
Content = content
|
|
||||||
};
|
|
||||||
|
|
||||||
await _httpClient.SendAsync(request);
|
|
||||||
UnityEngine.Debug.Log($"[Notion] 사용 이력 기록됨: {sceneInfo.sceneName}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogWarning($"[Notion] 사용 이력 기록 실패: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_httpClient?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notion 동기화 에디터 윈도우
|
|
||||||
/// </summary>
|
|
||||||
public class NotionSyncWindow : EditorWindow
|
|
||||||
{
|
|
||||||
private NotionSyncSettings _settings;
|
|
||||||
private BackgroundSceneDatabase _database;
|
|
||||||
private string _statusMessage = "";
|
|
||||||
private bool _isSyncing;
|
|
||||||
private float _progress;
|
|
||||||
|
|
||||||
[MenuItem("Streamingle/Notion Background Sync")]
|
|
||||||
public static void ShowWindow()
|
|
||||||
{
|
|
||||||
var window = GetWindow<NotionSyncWindow>("Notion 동기화");
|
|
||||||
window.minSize = new Vector2(400, 350);
|
|
||||||
window.Show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEnable()
|
|
||||||
{
|
|
||||||
LoadSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadSettings()
|
|
||||||
{
|
|
||||||
_settings = AssetDatabase.LoadAssetAtPath<NotionSyncSettings>(
|
|
||||||
"Assets/Resources/Settings/NotionSyncSettings.asset");
|
|
||||||
|
|
||||||
_database = AssetDatabase.LoadAssetAtPath<BackgroundSceneDatabase>(
|
|
||||||
"Assets/Resources/Settings/BackgroundSceneDatabase.asset");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGUI()
|
|
||||||
{
|
|
||||||
EditorGUILayout.Space(10);
|
|
||||||
EditorGUILayout.LabelField("Notion 배경 씬 동기화", EditorStyles.boldLabel);
|
|
||||||
EditorGUILayout.Space(10);
|
|
||||||
|
|
||||||
// 설정 확인
|
|
||||||
if (_settings == null)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("NotionSyncSettings을 찾을 수 없습니다.\n" +
|
|
||||||
"Assets/Resources/Settings/NotionSyncSettings.asset을 생성하세요.", MessageType.Warning);
|
|
||||||
|
|
||||||
if (GUILayout.Button("설정 파일 생성"))
|
|
||||||
{
|
|
||||||
CreateSettingsFile();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 설정 표시
|
|
||||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
||||||
EditorGUILayout.LabelField("설정", EditorStyles.boldLabel);
|
|
||||||
|
|
||||||
EditorGUI.BeginChangeCheck();
|
|
||||||
|
|
||||||
_settings.notionApiToken = EditorGUILayout.PasswordField("Notion API Token", _settings.notionApiToken);
|
|
||||||
_settings.notionDatabaseId = EditorGUILayout.TextField("Database ID", _settings.notionDatabaseId);
|
|
||||||
|
|
||||||
EditorGUILayout.Space(5);
|
|
||||||
|
|
||||||
_settings.gitServerUrl = EditorGUILayout.TextField("Git Server URL", _settings.gitServerUrl);
|
|
||||||
_settings.gitRepoPath = EditorGUILayout.TextField("Repo Path", _settings.gitRepoPath);
|
|
||||||
_settings.gitBranch = EditorGUILayout.TextField("Branch", _settings.gitBranch);
|
|
||||||
|
|
||||||
EditorGUILayout.Space(5);
|
|
||||||
|
|
||||||
_settings.autoSyncOnCapture = EditorGUILayout.Toggle("썸네일 캡처 시 자동 동기화", _settings.autoSyncOnCapture);
|
|
||||||
_settings.trackUsageHistory = EditorGUILayout.Toggle("사용 이력 추적", _settings.trackUsageHistory);
|
|
||||||
|
|
||||||
if (EditorGUI.EndChangeCheck())
|
|
||||||
{
|
|
||||||
EditorUtility.SetDirty(_settings);
|
|
||||||
AssetDatabase.SaveAssets();
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.EndVertical();
|
|
||||||
|
|
||||||
// 유효성 검사
|
|
||||||
EditorGUILayout.Space(10);
|
|
||||||
|
|
||||||
if (!_settings.HasNotionToken)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("Notion API Token이 필요합니다.\n" +
|
|
||||||
"https://www.notion.so/my-integrations 에서 발급하세요.", MessageType.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_settings.HasValidDatabaseId)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("Notion Database ID가 필요합니다.\n" +
|
|
||||||
"데이터베이스 URL에서 32자리 ID를 복사하세요.", MessageType.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Git URL 미리보기
|
|
||||||
EditorGUILayout.Space(10);
|
|
||||||
EditorGUILayout.LabelField("이미지 URL 예시:", EditorStyles.boldLabel);
|
|
||||||
string exampleUrl = _settings.GetGitRawUrl("Assets/ResourcesData/Background/Example/Scene/Example.png");
|
|
||||||
EditorGUILayout.SelectableLabel(exampleUrl, EditorStyles.miniLabel, GUILayout.Height(20));
|
|
||||||
|
|
||||||
// 동기화 버튼
|
|
||||||
EditorGUILayout.Space(20);
|
|
||||||
|
|
||||||
GUI.enabled = _settings.IsValid() && !_isSyncing && _database != null;
|
|
||||||
|
|
||||||
if (GUILayout.Button("전체 동기화", GUILayout.Height(35)))
|
|
||||||
{
|
|
||||||
SyncAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
GUI.enabled = true;
|
|
||||||
|
|
||||||
// 상태 메시지
|
|
||||||
if (!string.IsNullOrEmpty(_statusMessage))
|
|
||||||
{
|
|
||||||
EditorGUILayout.Space(10);
|
|
||||||
EditorGUILayout.HelpBox(_statusMessage, MessageType.Info);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 데이터베이스 정보
|
|
||||||
if (_database != null)
|
|
||||||
{
|
|
||||||
EditorGUILayout.Space(10);
|
|
||||||
EditorGUILayout.LabelField($"배경 씬 수: {_database.scenes.Count}개", EditorStyles.miniLabel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateSettingsFile()
|
|
||||||
{
|
|
||||||
string dirPath = "Assets/Resources/Settings";
|
|
||||||
if (!Directory.Exists(dirPath))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(dirPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
_settings = ScriptableObject.CreateInstance<NotionSyncSettings>();
|
|
||||||
AssetDatabase.CreateAsset(_settings, $"{dirPath}/NotionSyncSettings.asset");
|
|
||||||
AssetDatabase.SaveAssets();
|
|
||||||
|
|
||||||
UnityEngine.Debug.Log("NotionSyncSettings 파일이 생성되었습니다.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void SyncAll()
|
|
||||||
{
|
|
||||||
_isSyncing = true;
|
|
||||||
_statusMessage = "동기화 시작...";
|
|
||||||
Repaint();
|
|
||||||
|
|
||||||
var sync = new NotionBackgroundSync(_settings, _database);
|
|
||||||
|
|
||||||
sync.OnProgress += (msg) =>
|
|
||||||
{
|
|
||||||
_statusMessage = msg;
|
|
||||||
Repaint();
|
|
||||||
};
|
|
||||||
|
|
||||||
sync.OnError += (msg) =>
|
|
||||||
{
|
|
||||||
_statusMessage = $"오류: {msg}";
|
|
||||||
_isSyncing = false;
|
|
||||||
Repaint();
|
|
||||||
};
|
|
||||||
|
|
||||||
sync.OnCompleted += () =>
|
|
||||||
{
|
|
||||||
_statusMessage = "동기화 완료!";
|
|
||||||
_isSyncing = false;
|
|
||||||
Repaint();
|
|
||||||
};
|
|
||||||
|
|
||||||
await sync.SyncAllToNotion();
|
|
||||||
|
|
||||||
sync.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: e384f532f7d5f744083ecba75388fde0
|
|
||||||
@ -0,0 +1,406 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Streamingle.Background.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 배경 씬 데이터를 웹사이트 API로 업로드
|
||||||
|
/// 썸네일은 Git URL을 사용
|
||||||
|
/// </summary>
|
||||||
|
public class WebsiteBackgroundExporter : EditorWindow
|
||||||
|
{
|
||||||
|
private BackgroundSyncSettings _settings;
|
||||||
|
private BackgroundSceneDatabase _database;
|
||||||
|
private string _statusMessage = "";
|
||||||
|
private MessageType _statusType = MessageType.Info;
|
||||||
|
private bool _isExporting;
|
||||||
|
private UnityWebRequestAsyncOperation _currentRequest;
|
||||||
|
|
||||||
|
[MenuItem("Streamingle/Upload Backgrounds to Website")]
|
||||||
|
public static void ShowWindow()
|
||||||
|
{
|
||||||
|
var window = GetWindow<WebsiteBackgroundExporter>("배경 업로드");
|
||||||
|
window.minSize = new Vector2(450, 450);
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
LoadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadSettings()
|
||||||
|
{
|
||||||
|
// 새 설정 파일 경로
|
||||||
|
_settings = AssetDatabase.LoadAssetAtPath<BackgroundSyncSettings>(
|
||||||
|
"Assets/Resources/Settings/BackgroundSyncSettings.asset");
|
||||||
|
|
||||||
|
_database = AssetDatabase.LoadAssetAtPath<BackgroundSceneDatabase>(
|
||||||
|
"Assets/Resources/Settings/BackgroundSceneDatabase.asset");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
EditorGUILayout.Space(10);
|
||||||
|
EditorGUILayout.LabelField("배경 씬 웹사이트 업로드", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.Space(10);
|
||||||
|
|
||||||
|
// 설정 확인
|
||||||
|
if (_settings == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("BackgroundSyncSettings을 찾을 수 없습니다.\nAssets/Resources/Settings/BackgroundSyncSettings.asset 을 생성해주세요.", MessageType.Warning);
|
||||||
|
|
||||||
|
if (GUILayout.Button("설정 파일 생성"))
|
||||||
|
{
|
||||||
|
CreateSettingsAsset();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_database == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("BackgroundSceneDatabase를 찾을 수 없습니다.", MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API 설정
|
||||||
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||||
|
EditorGUILayout.LabelField("API 설정", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
|
||||||
|
_settings.apiEndpoint = EditorGUILayout.TextField("API 엔드포인트", _settings.apiEndpoint);
|
||||||
|
_settings.apiKey = EditorGUILayout.TextField("API 키 (선택)", _settings.apiKey);
|
||||||
|
|
||||||
|
EditorGUILayout.Space(5);
|
||||||
|
EditorGUILayout.LabelField("Git 설정 (썸네일 URL용)", EditorStyles.boldLabel);
|
||||||
|
_settings.gitServerUrl = EditorGUILayout.TextField("Git Server URL", _settings.gitServerUrl);
|
||||||
|
_settings.gitRepoPath = EditorGUILayout.TextField("Repo Path", _settings.gitRepoPath);
|
||||||
|
_settings.gitBranch = EditorGUILayout.TextField("Branch", _settings.gitBranch);
|
||||||
|
|
||||||
|
EditorGUILayout.Space(5);
|
||||||
|
_settings.websiteUrl = EditorGUILayout.TextField("웹사이트 URL", _settings.websiteUrl);
|
||||||
|
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(_settings);
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
|
||||||
|
// 설정 유효성 검사
|
||||||
|
EditorGUILayout.Space(10);
|
||||||
|
|
||||||
|
if (!_settings.IsValid())
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("API 엔드포인트와 Git 설정을 입력해주세요.", MessageType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 썸네일 URL 예시
|
||||||
|
EditorGUILayout.Space(5);
|
||||||
|
EditorGUILayout.LabelField("썸네일 URL 예시:", EditorStyles.boldLabel);
|
||||||
|
string exampleUrl = _settings.GetGitRawUrl("Assets/ResourcesData/Background/[공용]예시/Scene/Example.png");
|
||||||
|
EditorGUILayout.SelectableLabel(exampleUrl, EditorStyles.miniLabel, GUILayout.Height(20));
|
||||||
|
|
||||||
|
// 데이터베이스 정보
|
||||||
|
EditorGUILayout.Space(10);
|
||||||
|
EditorGUILayout.LabelField($"배경 씬 수: {_database.scenes.Count}개", EditorStyles.miniLabel);
|
||||||
|
|
||||||
|
// 업로드 버튼
|
||||||
|
EditorGUILayout.Space(20);
|
||||||
|
|
||||||
|
GUI.enabled = _settings.IsValid() && !_isExporting;
|
||||||
|
|
||||||
|
if (GUILayout.Button("웹사이트에 업로드", GUILayout.Height(35)))
|
||||||
|
{
|
||||||
|
UploadToWebsite();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space(5);
|
||||||
|
|
||||||
|
if (GUILayout.Button("업로드 후 브라우저에서 열기", GUILayout.Height(30)))
|
||||||
|
{
|
||||||
|
UploadToWebsite(() =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_settings.websiteUrl))
|
||||||
|
{
|
||||||
|
Application.OpenURL(_settings.websiteUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space(5);
|
||||||
|
|
||||||
|
if (GUILayout.Button("API 연결 테스트", GUILayout.Height(25)))
|
||||||
|
{
|
||||||
|
TestApiConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.enabled = true;
|
||||||
|
|
||||||
|
// 상태 메시지
|
||||||
|
if (!string.IsNullOrEmpty(_statusMessage))
|
||||||
|
{
|
||||||
|
EditorGUILayout.Space(10);
|
||||||
|
EditorGUILayout.HelpBox(_statusMessage, _statusType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateSettingsAsset()
|
||||||
|
{
|
||||||
|
// Settings 폴더 확인
|
||||||
|
if (!AssetDatabase.IsValidFolder("Assets/Resources"))
|
||||||
|
{
|
||||||
|
AssetDatabase.CreateFolder("Assets", "Resources");
|
||||||
|
}
|
||||||
|
if (!AssetDatabase.IsValidFolder("Assets/Resources/Settings"))
|
||||||
|
{
|
||||||
|
AssetDatabase.CreateFolder("Assets/Resources", "Settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 새 설정 파일 생성
|
||||||
|
var settings = CreateInstance<BackgroundSyncSettings>();
|
||||||
|
AssetDatabase.CreateAsset(settings, "Assets/Resources/Settings/BackgroundSyncSettings.asset");
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
|
||||||
|
_settings = settings;
|
||||||
|
UnityEngine.Debug.Log("[WebsiteExporter] BackgroundSyncSettings 생성됨");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UploadToWebsite(Action onSuccess = null)
|
||||||
|
{
|
||||||
|
_isExporting = true;
|
||||||
|
_statusMessage = "업로드 준비 중...";
|
||||||
|
_statusType = MessageType.Info;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// JSON 데이터 생성
|
||||||
|
var exportData = new WebsiteBackgroundData
|
||||||
|
{
|
||||||
|
lastUpdated = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss"),
|
||||||
|
backgrounds = new List<WebsiteBackgroundItem>()
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var sceneInfo in _database.scenes)
|
||||||
|
{
|
||||||
|
var item = new WebsiteBackgroundItem
|
||||||
|
{
|
||||||
|
sceneName = sceneInfo.sceneName,
|
||||||
|
scenePath = sceneInfo.scenePath,
|
||||||
|
categoryName = sceneInfo.categoryName,
|
||||||
|
category = ExtractCategory(sceneInfo.categoryName),
|
||||||
|
tags = ExtractAllTags(sceneInfo.categoryName),
|
||||||
|
thumbnailUrl = !string.IsNullOrEmpty(sceneInfo.thumbnailPath)
|
||||||
|
? _settings.GetGitRawUrl(sceneInfo.thumbnailPath)
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
|
||||||
|
exportData.backgrounds.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON 문자열 생성
|
||||||
|
string json = JsonConvert.SerializeObject(exportData, Formatting.Indented);
|
||||||
|
|
||||||
|
// HTTP POST 요청
|
||||||
|
SendPostRequest(json, onSuccess);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_statusMessage = $"데이터 준비 실패: {ex.Message}";
|
||||||
|
_statusType = MessageType.Error;
|
||||||
|
_isExporting = false;
|
||||||
|
UnityEngine.Debug.LogError($"[WebsiteExporter] 데이터 준비 실패: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendPostRequest(string jsonData, Action onSuccess)
|
||||||
|
{
|
||||||
|
_statusMessage = "서버에 업로드 중...";
|
||||||
|
|
||||||
|
var request = new UnityWebRequest(_settings.apiEndpoint, "POST");
|
||||||
|
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
|
||||||
|
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
|
||||||
|
request.downloadHandler = new DownloadHandlerBuffer();
|
||||||
|
request.SetRequestHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_settings.apiKey))
|
||||||
|
{
|
||||||
|
request.SetRequestHeader("X-API-Key", _settings.apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentRequest = request.SendWebRequest();
|
||||||
|
_currentRequest.completed += operation =>
|
||||||
|
{
|
||||||
|
HandleUploadResponse(request, onSuccess);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 진행 상황 업데이트를 위한 에디터 갱신
|
||||||
|
EditorApplication.update += UpdateProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateProgress()
|
||||||
|
{
|
||||||
|
if (_currentRequest != null && !_currentRequest.isDone)
|
||||||
|
{
|
||||||
|
_statusMessage = $"업로드 중... {(_currentRequest.progress * 100):F0}%";
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorApplication.update -= UpdateProgress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleUploadResponse(UnityWebRequest request, Action onSuccess)
|
||||||
|
{
|
||||||
|
_isExporting = false;
|
||||||
|
|
||||||
|
if (request.result == UnityWebRequest.Result.Success)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = JsonConvert.DeserializeObject<ApiResponse>(request.downloadHandler.text);
|
||||||
|
if (response.success)
|
||||||
|
{
|
||||||
|
_statusMessage = $"업로드 완료!\n{response.message}\n업데이트: {response.lastUpdated}";
|
||||||
|
_statusType = MessageType.Info;
|
||||||
|
UnityEngine.Debug.Log($"[WebsiteExporter] 업로드 성공: {response.message}");
|
||||||
|
onSuccess?.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_statusMessage = $"서버 오류: {response.error}";
|
||||||
|
_statusType = MessageType.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_statusMessage = $"응답 파싱 실패: {ex.Message}\n응답: {request.downloadHandler.text}";
|
||||||
|
_statusType = MessageType.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_statusMessage = $"업로드 실패!\n{request.error}\n상태코드: {request.responseCode}";
|
||||||
|
_statusType = MessageType.Error;
|
||||||
|
UnityEngine.Debug.LogError($"[WebsiteExporter] 업로드 실패: {request.error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Dispose();
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TestApiConnection()
|
||||||
|
{
|
||||||
|
_statusMessage = "API 연결 테스트 중...";
|
||||||
|
_statusType = MessageType.Info;
|
||||||
|
|
||||||
|
var request = UnityWebRequest.Get(_settings.apiEndpoint);
|
||||||
|
var operation = request.SendWebRequest();
|
||||||
|
|
||||||
|
operation.completed += op =>
|
||||||
|
{
|
||||||
|
if (request.result == UnityWebRequest.Result.Success)
|
||||||
|
{
|
||||||
|
_statusMessage = $"API 연결 성공!\n응답: {request.downloadHandler.text.Substring(0, Math.Min(200, request.downloadHandler.text.Length))}...";
|
||||||
|
_statusType = MessageType.Info;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_statusMessage = $"API 연결 실패!\n{request.error}\n상태코드: {request.responseCode}";
|
||||||
|
_statusType = MessageType.Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Dispose();
|
||||||
|
Repaint();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 카테고리 추출 (첫 번째 대괄호)
|
||||||
|
/// </summary>
|
||||||
|
private string ExtractCategory(string folderName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(folderName)) return "기타";
|
||||||
|
|
||||||
|
int startBracket = folderName.IndexOf('[');
|
||||||
|
int endBracket = folderName.IndexOf(']');
|
||||||
|
|
||||||
|
if (startBracket >= 0 && endBracket > startBracket)
|
||||||
|
{
|
||||||
|
return folderName.Substring(startBracket + 1, endBracket - startBracket - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 태그 추출 (대괄호 안의 모든 텍스트)
|
||||||
|
/// </summary>
|
||||||
|
private List<string> ExtractAllTags(string folderName)
|
||||||
|
{
|
||||||
|
var tags = new List<string>();
|
||||||
|
if (string.IsNullOrEmpty(folderName)) return tags;
|
||||||
|
|
||||||
|
var matches = Regex.Matches(folderName, @"\[([^\]]+)\]");
|
||||||
|
foreach (Match match in matches)
|
||||||
|
{
|
||||||
|
if (match.Groups.Count > 1)
|
||||||
|
{
|
||||||
|
string tag = match.Groups[1].Value;
|
||||||
|
if (!tags.Contains(tag))
|
||||||
|
{
|
||||||
|
tags.Add(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API 응답 구조
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class ApiResponse
|
||||||
|
{
|
||||||
|
public bool success;
|
||||||
|
public string message;
|
||||||
|
public string error;
|
||||||
|
public string lastUpdated;
|
||||||
|
public int count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 웹사이트용 배경 데이터 구조
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class WebsiteBackgroundData
|
||||||
|
{
|
||||||
|
public string lastUpdated;
|
||||||
|
public List<WebsiteBackgroundItem> backgrounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 웹사이트용 개별 배경 항목
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class WebsiteBackgroundItem
|
||||||
|
{
|
||||||
|
public string sceneName;
|
||||||
|
public string scenePath;
|
||||||
|
public string categoryName;
|
||||||
|
public string category;
|
||||||
|
public List<string> tags;
|
||||||
|
public string thumbnailUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7584b72e68020dc499d99b00273dc567
|
||||||
Loading…
x
Reference in New Issue
Block a user