Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src-tauri/gen/schemas/acl-manifests.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src-tauri/permissions/dmnote-allow-all.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
"css_tab_clear",
"css_tab_set",
"css_tab_toggle",
"note_tab_get_all",
"note_tab_get",
"note_tab_set",
"note_tab_clear",
"font_load",
"image_load",
"sound_load",
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod image;
pub mod js;
pub mod key_sound;
pub mod keys;
pub mod note_tab;
pub mod overlay;
pub mod plugin_storage;
pub mod preset;
Expand Down
106 changes: 106 additions & 0 deletions src-tauri/src/commands/note_tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use serde::Serialize;
use tauri::{AppHandle, Emitter, State};

use crate::{app_state::AppState, models::{TabNoteSettings, TabNoteOverrides}};

#[derive(Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TabNoteResponse {
pub tab_id: String,
pub settings: Option<TabNoteSettings>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TabNoteClearResponse {
pub success: bool,
pub tab_id: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TabNoteSetResponse {
pub success: bool,
pub tab_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub settings: Option<TabNoteSettings>,
}

/// 모든 탭의 노트 설정 오버라이드 조회
#[tauri::command(permission = "dmnote-allow-all")]
pub fn note_tab_get_all(state: State<'_, AppState>) -> Result<TabNoteOverrides, String> {
Ok(state.store.snapshot().tab_note_overrides)
}

/// 특정 탭의 노트 설정 조회
#[tauri::command(permission = "dmnote-allow-all")]
pub fn note_tab_get(state: State<'_, AppState>, tab_id: String) -> Result<TabNoteResponse, String> {
let overrides = state.store.snapshot().tab_note_overrides;
let settings = overrides.get(&tab_id).cloned();
Ok(TabNoteResponse { tab_id, settings })
}

/// 특정 탭의 노트 설정 저장
#[tauri::command(permission = "dmnote-allow-all")]
pub fn note_tab_set(
state: State<'_, AppState>,
app: AppHandle,
tab_id: String,
settings: Option<TabNoteSettings>,
) -> Result<TabNoteSetResponse, String> {
if let Some(ref note_settings) = settings {
state
.store
.update(|store| {
store.tab_note_overrides.insert(tab_id.clone(), note_settings.clone());
})
.map_err(|err| err.to_string())?;
} else {
state
.store
.update(|store| {
store.tab_note_overrides.remove(&tab_id);
})
.map_err(|err| err.to_string())?;
}

let response = TabNoteResponse {
tab_id: tab_id.clone(),
settings: settings.clone(),
};
app.emit("tabNote:changed", &response)
.map_err(|err| err.to_string())?;

Ok(TabNoteSetResponse {
success: true,
tab_id,
settings,
})
}

/// 특정 탭의 노트 설정 제거 (전역 설정으로 폴백)
#[tauri::command(permission = "dmnote-allow-all")]
pub fn note_tab_clear(
state: State<'_, AppState>,
app: AppHandle,
tab_id: String,
) -> Result<TabNoteClearResponse, String> {
state
.store
.update(|store| {
store.tab_note_overrides.remove(&tab_id);
})
.map_err(|err| err.to_string())?;

let response = TabNoteResponse {
tab_id: tab_id.clone(),
settings: None,
};
app.emit("tabNote:changed", &response)
.map_err(|err| err.to_string())?;

Ok(TabNoteClearResponse {
success: true,
tab_id,
})
}
15 changes: 14 additions & 1 deletion src-tauri/src/commands/preset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
models::{
CustomCss, CustomCssPatch, CustomJs, CustomJsPatch, CustomTab, KeyMappings, KeyPositions,
FontSettings, FontType, GraphPositions, NoteSettings, NoteSettingsPatch, SettingsPatchInput,
StatPositions,
StatPositions, TabNoteOverrides,
},
};

Expand Down Expand Up @@ -49,6 +49,9 @@ struct PresetFile {
#[serde(rename = "customJS")]
custom_js: Option<CustomJs>,
font_settings: Option<FontSettings>,
/// 탭별 노트 트랙 설정 오버라이드
#[serde(default, skip_serializing_if = "Option::is_none")]
tab_note_overrides: Option<TabNoteOverrides>,
embedded_local_fonts: Option<Vec<EmbeddedLocalFont>>,
embedded_local_images: Option<Vec<EmbeddedLocalImage>>,
embedded_local_sounds: Option<Vec<EmbeddedLocalSound>>,
Expand Down Expand Up @@ -131,6 +134,10 @@ pub fn preset_save(state: State<'_, AppState>) -> Result<PresetOperationResult,
use_custom_js: Some(snapshot.use_custom_js),
custom_js: Some(snapshot.custom_js),
font_settings: Some(font_settings),
tab_note_overrides: {
let overrides = snapshot.tab_note_overrides;
if overrides.is_empty() { None } else { Some(overrides) }
},
embedded_local_fonts: (!embedded_local_fonts.is_empty()).then_some(embedded_local_fonts),
embedded_local_images: (!embedded_local_images.is_empty()).then_some(embedded_local_images),
embedded_local_sounds: (!embedded_local_sounds.is_empty()).then_some(embedded_local_sounds),
Expand Down Expand Up @@ -215,6 +222,9 @@ pub fn preset_load(
preset.embedded_local_sounds.as_deref(),
)?;

// 탭별 노트 설정 복원 (없으면 빈 맵으로 초기화 → 전역 폴백)
let tab_note_overrides = preset.tab_note_overrides.unwrap_or_default();

state
.store
.update(|store| {
Expand All @@ -224,6 +234,7 @@ pub fn preset_load(
store.graph_positions = graph_positions.clone();
store.custom_tabs = custom_tabs.clone();
store.selected_key_type = selected_key_type.clone();
store.tab_note_overrides = tab_note_overrides.clone();
})
.map_err(|err| err.to_string())?;

Expand Down Expand Up @@ -290,6 +301,8 @@ pub fn preset_load(
.map_err(|err| err.to_string())?;
app.emit("js:content", &custom_js)
.map_err(|err| err.to_string())?;
app.emit("tabNote:changed_all", &tab_note_overrides)
.map_err(|err| err.to_string())?;

Ok(PresetOperationResult {
success: true,
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ fn main() {
commands::css::css_tab_clear,
commands::css::css_tab_set,
commands::css::css_tab_toggle,
commands::note_tab::note_tab_get_all,
commands::note_tab::note_tab_get,
commands::note_tab::note_tab_set,
commands::note_tab::note_tab_clear,
commands::font::font_load,
commands::image::image_load,
commands::sound::sound_load,
Expand Down
48 changes: 48 additions & 0 deletions src-tauri/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ pub enum FadePosition {
Top,
Bottom,
None,
Both,
}

/// 이미지 맞춤 설정 (CSS object-fit과 동일)
Expand Down Expand Up @@ -591,6 +592,49 @@ impl Default for TabCss {
/// 탭별 CSS 오버라이드 맵 (키: 탭 ID, 값: TabCss)
pub type TabCssOverrides = HashMap<String, TabCss>;

/// 탭별 노트 트랙 설정 (전역 NoteSettings를 탭별로 오버라이드)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TabNoteSettings {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub frame_limit: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub speed: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub track_height: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reverse: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fade_position: Option<FadePosition>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub delayed_note_enabled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub short_note_threshold_ms: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub short_note_min_length_px: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_display_delay_ms: Option<u32>,
}

impl Default for TabNoteSettings {
fn default() -> Self {
Self {
frame_limit: None,
speed: None,
track_height: None,
reverse: None,
fade_position: None,
delayed_note_enabled: None,
short_note_threshold_ms: None,
short_note_min_length_px: None,
key_display_delay_ms: None,
}
}
}

/// 탭별 노트 트랙 설정 오버라이드 맵 (키: 탭 ID, 값: TabNoteSettings)
pub type TabNoteOverrides = HashMap<String, TabNoteSettings>;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct JsPlugin {
Expand Down Expand Up @@ -822,6 +866,9 @@ pub struct AppStoreData {
/// 탭별 CSS 오버라이드 (전역 CSS 대신 사용)
#[serde(default)]
pub tab_css_overrides: TabCssOverrides,
/// 탭별 노트 트랙 설정 오버라이드
#[serde(default)]
pub tab_note_overrides: TabNoteOverrides,
#[serde(default)]
pub use_custom_js: bool,
#[serde(default)]
Expand Down Expand Up @@ -879,6 +926,7 @@ impl Default for AppStoreData {
custom_css: CustomCss::default(),
font_settings: FontSettings::default(),
tab_css_overrides: TabCssOverrides::new(),
tab_note_overrides: TabNoteOverrides::new(),
use_custom_js: false,
custom_js: CustomJs::default(),
overlay_resize_anchor: OverlayResizeAnchor::TopLeft,
Expand Down
38 changes: 38 additions & 0 deletions src/renderer/api/dmnoteApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,44 @@ const api: DMNoteAPI = {
),
},
},
noteTab: {
getAll: () =>
invoke<import("@src/types/noteSettings").TabNoteOverrides>(
"note_tab_get_all",
),
get: (tabId: string) =>
invoke<import("@src/types/api").TabNoteResponse>("note_tab_get", {
tabId,
}),
set: (
tabId: string,
settings: import("@src/types/noteSettings").TabNoteSettings | null,
) =>
invoke<import("@src/types/api").TabNoteSetResult>("note_tab_set", {
tabId,
settings,
}),
clear: (tabId: string) =>
invoke<import("@src/types/api").TabNoteClearResult>("note_tab_clear", {
tabId,
}),
onChanged: (
listener: (payload: import("@src/types/api").TabNoteResponse) => void,
) =>
subscribe<import("@src/types/api").TabNoteResponse>(
"tabNote:changed",
listener,
),
onChangedAll: (
listener: (
payload: import("@src/types/noteSettings").TabNoteOverrides,
) => void,
) =>
subscribe<import("@src/types/noteSettings").TabNoteOverrides>(
"tabNote:changed_all",
listener,
),
},
font: {
load: () => invoke<import("@src/types/api").FontLoadResult>("font_load"),
},
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/assets/svgs/note.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions src/renderer/components/main/Grid/Grid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import DraggableKey from "@components/Key";
import { getKeyInfoByGlobalKey } from "@utils/KeyMaps";
import UnifiedKeySetting from "../Modal/content/UnifiedKeySetting";
import TabCssModal from "../Modal/content/TabCssModal";
import TabNoteSettingModal from "../Modal/content/TabNoteSettingModal";
import ListPopup from "../Modal/ListPopup";
import { useKeyStore } from "@stores/useKeyStore";
import { useStatItemStore } from "@stores/useStatItemStore";
Expand Down Expand Up @@ -90,9 +91,12 @@ export default function Grid({
canRedo,
toolbarAddRequest,
onToolbarAddConsumed,
isNoteSettingOpen,
setIsNoteSettingOpen,
}) {
const selectedKeyType = useKeyStore((state) => state.selectedKeyType);
const keyCounterEnabled = useSettingsStore((state) => state.keyCounterEnabled);
const noteEffect = useSettingsStore((state) => state.noteEffect);
const minimapEnabled = useSettingsStore((state) => state.gridSettings.minimapEnabled);
const gridSnapSize = useSettingsStore((state) => state.gridSettings?.gridSnapSize || 5);
const { t, i18n } = useTranslation();
Expand Down Expand Up @@ -135,6 +139,7 @@ export default function Grid({
positions,
locale,
t,
noteEffect,
});

// 선택 상태 관리
Expand Down Expand Up @@ -1124,6 +1129,13 @@ export default function Grid({

// 탭 CSS 모달 상태
const [isTabCssModalOpen, setIsTabCssModalOpen] = useState(false);
// 탭 노트 트랙 설정 모달 상태
const [isTabNoteModalOpen, setIsTabNoteModalOpen] = useState(false);

// 전역 트랙 설정 모달이 열리면 탭 트랙 설정 모달 닫기 (중복 방지)
useEffect(() => {
if (isNoteSettingOpen) setIsTabNoteModalOpen(false);
}, [isNoteSettingOpen]);

// 그리드 영역 호버 상태 (미니맵 표시용) - useUIStore에서 관리
const isGridAreaHovered = useUIStore((state) => state.isGridAreaHovered);
Expand Down Expand Up @@ -2636,6 +2648,9 @@ export default function Grid({
addGraphAtPosition(gridAddLocalPos.dx, gridAddLocalPos.dy);
} else if (id === "tabCss") {
setIsTabCssModalOpen(true);
} else if (id === "tabNote") {
setIsNoteSettingOpen(false);
setIsTabNoteModalOpen(true);
}
setIsGridContextOpen(false);
setGridContextClientPos(null);
Expand Down Expand Up @@ -2866,6 +2881,11 @@ export default function Grid({
onClose={() => setIsTabCssModalOpen(false)}
showAlert={showAlert}
/>
{/* 탭별 노트 트랙 설정 모달 */}
<TabNoteSettingModal
isOpen={isTabNoteModalOpen}
onClose={() => setIsTabNoteModalOpen(false)}
/>
</div>
);
}
Loading