Skip to content

Commit c53d66d

Browse files
authored
Merge pull request #61 from moorestech/feature/someting
いろいろな機能を追加
2 parents 8767e1d + 73b4939 commit c53d66d

File tree

12 files changed

+502
-31
lines changed

12 files changed

+502
-31
lines changed

frontend/src-tauri/Cargo.lock

Lines changed: 287 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src-tauri/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ tauri-build = { version = "1.5.1", features = [] }
1717
[dependencies]
1818
serde_json = "1.0"
1919
serde = { version = "1.0", features = ["derive"] }
20-
tauri = { version = "1.5.4", features = ["dialog-all", "fs-all", "path-all", "protocol-asset"] }
20+
tauri = { version = "1.5.4", features = [ "shell-open", "dialog-all", "fs-all", "path-all", "protocol-asset"] }
21+
arboard = "3.3.0"
2122

2223
[features]
2324
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.

frontend/src-tauri/src/main.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use tauri::{Manager, State};
55
use std::sync::Mutex;
66
use std::path::PathBuf;
7+
use arboard::Clipboard;
78

89
// State to hold the current project path
910
struct ProjectState {
@@ -41,6 +42,54 @@ fn get_project_path(state: State<ProjectState>) -> Result<Option<String>, String
4142
Ok(project_path.clone())
4243
}
4344

45+
#[tauri::command]
46+
fn read_clipboard_text() -> Result<String, String> {
47+
let mut clipboard = Clipboard::new().map_err(|e| e.to_string())?;
48+
clipboard.get_text().map_err(|e| e.to_string())
49+
}
50+
51+
#[tauri::command]
52+
fn open_project_directory(path: String) -> Result<(), String> {
53+
#[cfg(target_os = "windows")]
54+
{
55+
std::process::Command::new("explorer")
56+
.arg(&path)
57+
.spawn()
58+
.map_err(|e| format!("Failed to open directory: {}", e))?;
59+
}
60+
61+
#[cfg(target_os = "macos")]
62+
{
63+
std::process::Command::new("open")
64+
.arg(&path)
65+
.spawn()
66+
.map_err(|e| format!("Failed to open directory: {}", e))?;
67+
}
68+
69+
#[cfg(target_os = "linux")]
70+
{
71+
// Try common file managers
72+
let file_managers = ["xdg-open", "nautilus", "dolphin", "thunar", "pcmanfm"];
73+
let mut opened = false;
74+
75+
for fm in &file_managers {
76+
if let Ok(_) = std::process::Command::new(fm)
77+
.arg(&path)
78+
.spawn()
79+
{
80+
opened = true;
81+
break;
82+
}
83+
}
84+
85+
if !opened {
86+
return Err("Failed to open directory: No suitable file manager found".to_string());
87+
}
88+
}
89+
90+
Ok(())
91+
}
92+
4493
fn main() {
4594
tauri::Builder::default()
4695
.setup(|app| {
@@ -56,7 +105,9 @@ fn main() {
56105
.invoke_handler(tauri::generate_handler![
57106
get_commands_path,
58107
set_project_path,
59-
get_project_path
108+
get_project_path,
109+
read_clipboard_text,
110+
open_project_directory
60111
]) // Register all commands
61112
.run(tauri::generate_context!())
62113
.expect("error while running tauri application");

frontend/src-tauri/tauri.conf.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
"protocol": {
3737
"asset": true,
3838
"assetScope": ["**"]
39+
},
40+
"shell": {
41+
"open": true
3942
}
4043
},
4144
"bundle": {

frontend/src/components/skit/CommandEditor.tsx

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { CommandDefinition, PropertyDefinition } from '../../types';
88
import { SkitCommand } from '../../types';
99
import { formatCommandPreview } from '../../utils/commandFormatting';
1010
import { ColorPicker } from '../ui/color-picker';
11+
import { HexColorPicker } from 'react-colorful';
12+
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
1113
import { VectorInput } from '../ui/vector-input';
1214
import { useTranslation } from 'react-i18next';
1315
import { useCommandTranslation } from '../../hooks/useCommandTranslation';
@@ -98,6 +100,10 @@ export function CommandEditor() {
98100
const isBgMixed = !backgroundColors.every(c => c === backgroundColors[0]);
99101
const bgColorValue = isBgMixed ? "#ffffff" : backgroundColors[0];
100102

103+
const labelColors = selectedCommands.map(cmd => cmd.commandLabelColor || (commandsMap.get(cmd.type)?.defaultCommandLabelColor ?? "#000000"));
104+
const isLabelColorMixed = !labelColors.every(c => c === labelColors[0]);
105+
const labelColorValue = isLabelColorMixed ? "#000000" : labelColors[0];
106+
101107
const selectedLabels = selectedCommands.map(cmd => commandsMap.get(cmd.type)?.label || cmd.type);
102108
const uniqueLabels = Array.from(new Set(selectedLabels));
103109

@@ -111,15 +117,55 @@ export function CommandEditor() {
111117
</CardTitle>
112118
</CardHeader>
113119
<CardContent className="space-y-4">
114-
{/* Background Color Picker */}
120+
{/* Color Pickers - Inline Side by Side */}
115121
<div className="space-y-2">
116-
<Label htmlFor="backgroundColor">{t('editor.backgroundColor')}</Label>
117-
<ColorPicker
118-
value={bgColorValue}
119-
onChange={(value) => handlePropertyChange("backgroundColor", value)}
120-
placeholder={isBgMixed ? '-' : undefined}
121-
isMixed={isBgMixed}
122-
/>
122+
<div className="flex items-center gap-6">
123+
{/* Background Color Picker */}
124+
<div className="flex items-center gap-2">
125+
<Label htmlFor="backgroundColor" className="min-w-[60px]">{t('editor.backgroundColor')}</Label>
126+
<Popover>
127+
<PopoverTrigger asChild>
128+
<button
129+
className="w-8 h-8 rounded border border-zinc-200 dark:border-zinc-800 cursor-pointer"
130+
style={{ backgroundColor: bgColorValue }}
131+
aria-label="Pick background color"
132+
/>
133+
</PopoverTrigger>
134+
<PopoverContent className="w-auto p-3">
135+
<HexColorPicker color={bgColorValue} onChange={(value) => handlePropertyChange("backgroundColor", value)} />
136+
</PopoverContent>
137+
</Popover>
138+
<Input
139+
value={isBgMixed ? '' : bgColorValue}
140+
onChange={(e) => handlePropertyChange("backgroundColor", e.target.value)}
141+
placeholder={isBgMixed ? '-' : undefined}
142+
className="w-28"
143+
/>
144+
</div>
145+
146+
{/* Command Label Color Picker */}
147+
<div className="flex items-center gap-2">
148+
<Label htmlFor="commandLabelColor" className="min-w-[60px]">{t('editor.commandLabelColor')}</Label>
149+
<Popover>
150+
<PopoverTrigger asChild>
151+
<button
152+
className="w-8 h-8 rounded border border-zinc-200 dark:border-zinc-800 cursor-pointer"
153+
style={{ backgroundColor: labelColorValue }}
154+
aria-label="Pick text color"
155+
/>
156+
</PopoverTrigger>
157+
<PopoverContent className="w-auto p-3">
158+
<HexColorPicker color={labelColorValue} onChange={(value) => handlePropertyChange("commandLabelColor", value)} />
159+
</PopoverContent>
160+
</Popover>
161+
<Input
162+
value={isLabelColorMixed ? '' : labelColorValue}
163+
onChange={(e) => handlePropertyChange("commandLabelColor", e.target.value)}
164+
placeholder={isLabelColorMixed ? '-' : undefined}
165+
className="w-28"
166+
/>
167+
</div>
168+
</div>
123169
</div>
124170

125171
{/* Command Properties */}

frontend/src/components/skit/CommandList.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,10 @@ const CommandItem = memo(({
351351
return lines;
352352
}, [nestLevel, command.type]);
353353

354-
// Get command definition to check for defaultBackgroundColor
354+
// Get command definition to check for defaultBackgroundColor and defaultCommandLabelColor
355355
const commandDef = commandsMap?.get(command.type);
356356
const defaultBgColor = commandDef?.defaultBackgroundColor || "#ffffff";
357+
const defaultLabelColor = commandDef?.defaultCommandLabelColor || "";
357358

358359
const bgColor = command.backgroundColor || defaultBgColor;
359360

@@ -362,7 +363,12 @@ const CommandItem = memo(({
362363
...(isSelected ? {} : { backgroundColor: bgColor })
363364
};
364365

365-
const getTextColor = (bgColor: string) => {
366+
const getTextColorClass = (bgColor: string, labelColor?: string) => {
367+
if (labelColor) {
368+
// If label color is set, use it directly as inline style
369+
return '';
370+
}
371+
366372
if (!bgColor || bgColor === '') return '';
367373

368374
const hex = bgColor.replace('#', '');
@@ -375,7 +381,11 @@ const CommandItem = memo(({
375381
return brightness < 128 ? 'text-white' : 'text-black';
376382
};
377383

378-
const textColorClass = getTextColor(bgColor);
384+
const labelColor = command.commandLabelColor || defaultLabelColor;
385+
const textColorClass = getTextColorClass(bgColor, labelColor);
386+
387+
// Add inline style for custom label color
388+
const textColorStyle = labelColor && !isSelected ? { color: labelColor } : {};
379389

380390
return (
381391
<div
@@ -386,7 +396,7 @@ const CommandItem = memo(({
386396
} ${isActive ? 'opacity-50' : ''}`}
387397
onClick={(e) => handleCommandClick(command.id, e)}
388398
data-testid={`command-item-${command.id}`}
389-
style={backgroundColorStyle}
399+
style={{...backgroundColorStyle, ...textColorStyle}}
390400
>
391401
{/* ネストレベルを示す垂直ライン */}
392402
{nestLines}

frontend/src/components/skit/Toolbar.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
Save,
1010
ChevronDown,
1111
ClipboardPaste,
12-
Scissors
12+
Scissors,
13+
FolderOpen
1314
} from 'lucide-react';
1415
import { SidebarTrigger } from '../ui/sidebar';
1516
import {
@@ -25,6 +26,7 @@ import { DraggableCommand } from '../dnd/DraggableCommand';
2526
import { DropZone } from '../dnd/DropZone';
2627
import { useTranslation } from 'react-i18next';
2728
import { useCommandTranslation } from '../../hooks/useCommandTranslation';
29+
import { openProjectDirectory } from '../../utils/fileSystem';
2830

2931
// Helper component to display translated command label
3032
function CommandMenuLabel({ commandId, label }: { commandId: string; label?: string }) {
@@ -47,6 +49,7 @@ export function Toolbar() {
4749
redo,
4850
saveSkit,
4951
commandDefinitions,
52+
projectPath,
5053
} = useSkitStore();
5154

5255
const handleAddCommand = (commandType: string) => {
@@ -83,6 +86,16 @@ export function Toolbar() {
8386
}
8487
};
8588

89+
const handleOpenProjectDirectory = async () => {
90+
if (!projectPath) return;
91+
92+
try {
93+
await openProjectDirectory(projectPath);
94+
} catch (error) {
95+
toast.error(t('editor.toolbar.openProjectFolderFailed', { error: error instanceof Error ? error.message : String(error) }));
96+
}
97+
};
98+
8699
const isDisabled = !currentSkitId;
87100
const isCommandSelected = selectedCommandIds.length > 0;
88101

@@ -189,10 +202,21 @@ export function Toolbar() {
189202
size="sm"
190203
disabled={isDisabled}
191204
onClick={handleSave}
205+
className="mr-2"
192206
>
193207
<Save className="h-4 w-4 mr-1" />
194208
{t('editor.menu.file.save')}
195209
</Button>
210+
211+
<Button
212+
variant="outline"
213+
size="sm"
214+
disabled={!projectPath}
215+
onClick={handleOpenProjectDirectory}
216+
>
217+
<FolderOpen className="h-4 w-4 mr-1" />
218+
{t('editor.toolbar.openProjectFolder')}
219+
</Button>
196220
</div>
197221
</div>
198222
</div>

frontend/src/i18n/config.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const editorTranslations = {
3434
projectLoadFailed: 'Failed to load project',
3535
skitSaved: 'Skit saved',
3636
saveFailed: 'Failed to save: {{error}}',
37-
openProjectFolder: 'Open project folder',
37+
openProjectFolder: 'Open folder',
3838
folder: 'Folder',
3939
currentDirectory: 'Current Directory',
4040
noFolderOpen: 'No folder open',
@@ -66,6 +66,7 @@ const editorTranslations = {
6666
noCommandSelected: 'No command selected',
6767
commandDefinitionNotFound: 'Command definition not found',
6868
backgroundColor: 'Background Color',
69+
commandLabelColor: 'Text Color',
6970
itemsSelected: '{{count}} items selected',
7071
newSkit: 'New Skit',
7172
createNewSkit: 'Create New Skit',
@@ -122,7 +123,7 @@ const editorTranslations = {
122123
projectLoadFailed: 'プロジェクトの読み込みに失敗しました',
123124
skitSaved: 'スキットを保存しました',
124125
saveFailed: '保存に失敗しました: {{error}}',
125-
openProjectFolder: 'プロジェクトフォルダを開く',
126+
openProjectFolder: 'フォルダを開く',
126127
folder: 'フォルダ',
127128
currentDirectory: '現在のディレクトリ',
128129
noFolderOpen: 'フォルダが開かれていません',
@@ -154,6 +155,7 @@ const editorTranslations = {
154155
noCommandSelected: 'コマンドが選択されていません',
155156
commandDefinitionNotFound: 'コマンド定義が見つかりません',
156157
backgroundColor: '背景色',
158+
commandLabelColor: '文字色',
157159
itemsSelected: '{{count}}個選択中',
158160
newSkit: '新規作成',
159161
createNewSkit: '新規スキット作成',

frontend/src/store/skitStore.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,21 @@ export const useSkitStore = create<SkitState>()(
758758
},
759759

760760
pasteCommandsFromClipboard: async () => {
761-
const text = await navigator.clipboard.readText();
761+
let text: string;
762+
try {
763+
// Try to use Tauri's clipboard API if available
764+
if (typeof window !== 'undefined' && '__TAURI__' in window) {
765+
const { invoke } = await import('@tauri-apps/api');
766+
text = await invoke<string>('read_clipboard_text');
767+
} else {
768+
// Fallback to browser API for development
769+
text = await navigator.clipboard.readText();
770+
}
771+
} catch (error) {
772+
console.error('Failed to read clipboard:', error);
773+
return;
774+
}
775+
762776
let commands: SkitCommand[];
763777
try {
764778
commands = JSON.parse(text) as SkitCommand[];

frontend/src/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface CommandDefinition {
3434
description: string;
3535
commandListLabelFormat: string;
3636
defaultBackgroundColor?: string;
37+
defaultCommandLabelColor?: string;
3738
properties: Record<string, PropertyDefinition>;
3839
}
3940

@@ -54,6 +55,7 @@ export interface SkitCommand {
5455
id: number;
5556
type: string;
5657
backgroundColor?: string; // Background color for the command
58+
commandLabelColor?: string; // Text color for the command
5759
isCollapsed?: boolean; // For group_start commands
5860
groupName?: string; // For group_start commands
5961
// 必須プロパティの型も含めたインデックスシグネチャ

0 commit comments

Comments
 (0)