zeroed-some/bashamole / 2148300

Browse files

command ref modal stable

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
2148300f28e9152093e5674b816c6076a34fdb5b
Parents
24ccd41
Tree
ce93427

3 changed files

StatusFile+-
M backend/apps/trees/views.py 99 0
M frontend/src/components/Game.tsx 147 6
M frontend/src/lib/api.ts 27 0
backend/apps/trees/views.pymodified
@@ -17,6 +17,105 @@ class FileSystemTreeViewSet(viewsets.ModelViewSet):
1717
     queryset = FileSystemTree.objects.all()
1818
     serializer_class = FileSystemTreeSerializer
1919
     
20
+    @action(detail=False, methods=['get'])
21
+    def command_reference(self, request):
22
+        """Get command reference"""
23
+        commands = {
24
+            "navigation": [
25
+                {
26
+                    "command": "cd <directory>",
27
+                    "description": "Change to specified directory",
28
+                    "examples": ["cd projects", "cd /home/alice", "cd .."]
29
+                },
30
+                {
31
+                    "command": "cd",
32
+                    "description": "Go to home directory",
33
+                    "examples": ["cd"]
34
+                },
35
+                {
36
+                    "command": "pushd <directory>",
37
+                    "description": "Push directory onto stack and change to it",
38
+                    "examples": ["pushd /var/log", "pushd ~/Documents"]
39
+                },
40
+                {
41
+                    "command": "popd",
42
+                    "description": "Pop directory from stack and change to it",
43
+                    "examples": ["popd"]
44
+                }
45
+            ],
46
+            "exploration": [
47
+                {
48
+                    "command": "ls [-la]",
49
+                    "description": "List directory contents",
50
+                    "examples": ["ls", "ls -l", "ls -la"],
51
+                    "options": {
52
+                        "-l": "Long format with details",
53
+                        "-a": "Show hidden entries (. and ..)"
54
+                    }
55
+                },
56
+                {
57
+                    "command": "pwd",
58
+                    "description": "Print current working directory",
59
+                    "examples": ["pwd"]
60
+                },
61
+                {
62
+                    "command": "tree [-L depth]",
63
+                    "description": "Display directory tree ([X] marks mole location)",
64
+                    "examples": ["tree", "tree -L 2", "tree -L 5"],
65
+                    "options": {
66
+                        "-L": "Limit depth (1-5)"
67
+                    }
68
+                },
69
+                {
70
+                    "command": "dirs",
71
+                    "description": "Display directory stack",
72
+                    "examples": ["dirs"]
73
+                }
74
+            ],
75
+            "utility": [
76
+                {
77
+                    "command": "echo <text>",
78
+                    "description": "Display text or variables",
79
+                    "examples": ["echo hello", "echo $HOME", "echo $PWD"],
80
+                    "variables": {
81
+                        "$HOME": "Home directory path",
82
+                        "$PWD": "Current directory path",
83
+                        "$OLDPWD": "Previous directory path"
84
+                    }
85
+                },
86
+                {
87
+                    "command": "help",
88
+                    "description": "Show command help",
89
+                    "examples": ["help"]
90
+                }
91
+            ],
92
+            "game": [
93
+                {
94
+                    "command": "killall moles",
95
+                    "description": "Eliminate moles when in the same directory",
96
+                    "examples": ["killall moles"]
97
+                }
98
+            ],
99
+            "special_paths": [
100
+                {
101
+                    "path": "~",
102
+                    "description": "Home directory",
103
+                    "examples": ["cd ~", "cd ~/Documents"]
104
+                },
105
+                {
106
+                    "path": "-",
107
+                    "description": "Previous directory",
108
+                    "examples": ["cd -"]
109
+                },
110
+                {
111
+                    "path": "..",
112
+                    "description": "Parent directory",
113
+                    "examples": ["cd ..", "cd ../projects"]
114
+                }
115
+            ]
116
+        }
117
+        return Response(commands)
118
+    
20119
     @action(detail=False, methods=['get'])
21120
     def fhs_reference(self, request):
22121
         """Get FHS directory reference"""
frontend/src/components/Game.tsxmodified
@@ -3,7 +3,7 @@
33
 // src/components/Game.tsx
44
 import React, { useState, useEffect, useRef } from 'react';
55
 import TreeVisualizer from './TreeVisualizer';
6
-import { gameApi, FileSystemTree, FHSDirectory } from '@/lib/api';
6
+import { gameApi, FileSystemTree, FHSDirectory, CommandReferenceResponse } from '@/lib/api';
77
 
88
 interface CommandHistoryEntry {
99
   command: string;
@@ -31,6 +31,8 @@ const Game: React.FC = () => {
3131
   const [hints, setHints] = useState<string[]>([]);
3232
   const [showFHS, setShowFHS] = useState(false);
3333
   const [fhsDirs, setFhsDirs] = useState<FHSDirectory[]>([]);
34
+  const [showCommands, setShowCommands] = useState(false);
35
+  const [commandRef, setCommandRef] = useState<CommandReferenceResponse | null>(null);
3436
   const [terminalMinimized, setTerminalMinimized] = useState(true);
3537
   const [hasPlayedIntro, setHasPlayedIntro] = useState(false);
3638
   const [isDarkMode, setIsDarkMode] = useState(true);
@@ -136,6 +138,19 @@ const Game: React.FC = () => {
136138
     }
137139
   };
138140
 
141
+  // Get command reference
142
+  const getCommandReference = async () => {
143
+    try {
144
+      if (!commandRef) {
145
+        const response = await gameApi.getCommandReference();
146
+        setCommandRef(response);
147
+      }
148
+      setShowCommands(true);
149
+    } catch (error) {
150
+      console.error('Failed to get command reference', error);
151
+    }
152
+  };
153
+
139154
   // Execute a command
140155
   const executeCommand = async (cmd: string) => {
141156
     if (!gameState.tree || !cmd.trim() || executing) return;
@@ -311,7 +326,13 @@ const Game: React.FC = () => {
311326
         <div className={`flex items-center justify-between ${terminalColors.header} px-4 py-2 rounded-t-lg border-b`}>
312327
           <div className="flex items-center gap-2">
313328
             <div className="flex gap-1.5">
314
-              <div className="w-3.5 h-3.5 bg-red-500 rounded-full"></div>
329
+              <button
330
+                onClick={getCommandReference}
331
+                className="w-3.5 h-3.5 bg-red-500 hover:bg-red-400 rounded-full flex items-center justify-center transition-colors relative"
332
+                title="Command Reference"
333
+              >
334
+                <span className="text-[8px] font-bold text-gray-900 absolute">×</span>
335
+              </button>
315336
               {gameState.tree && !gameState.tree.is_completed ? (
316337
                 <button
317338
                   onClick={getHints}
@@ -450,11 +471,131 @@ const Game: React.FC = () => {
450471
         </div>
451472
       )}
452473
 
474
+      {/* Command Reference Modal */}
475
+      {showCommands && commandRef && (
476
+        <div className="absolute top-16 left-4 bg-red-900/95 backdrop-blur-sm border border-red-600 rounded-lg p-6 max-w-2xl max-h-[80vh] overflow-y-auto z-40 shadow-2xl">
477
+          <button
478
+            onClick={() => setShowCommands(false)}
479
+            className="absolute top-3 right-3 text-red-400 hover:text-red-300"
480
+          >
481
+            ✕
482
+          </button>
483
+          <h3 className="text-red-400 font-bold mb-4 text-lg">Command Reference</h3>
484
+          
485
+          <div className="space-y-6">
486
+            {/* Navigation Commands */}
487
+            <div>
488
+              <h4 className="text-red-300 font-semibold mb-3">Navigation Commands</h4>
489
+              <div className="space-y-3">
490
+                {commandRef.navigation.map((cmd, index) => (
491
+                  <div key={index} className="border-l-2 border-red-700 pl-3">
492
+                    <div className="flex items-start gap-3">
493
+                      <code className="text-red-200 font-terminal text-sm">{cmd.command}</code>
494
+                      <span className="text-red-100 text-sm">- {cmd.description}</span>
495
+                    </div>
496
+                    {cmd.examples && (
497
+                      <div className="mt-1">
498
+                        <span className="text-red-300 text-xs">Examples: </span>
499
+                        <code className="text-red-200 text-xs">{cmd.examples.join(', ')}</code>
500
+                      </div>
501
+                    )}
502
+                  </div>
503
+                ))}
504
+              </div>
505
+            </div>
506
+
507
+            {/* Exploration Commands */}
508
+            <div>
509
+              <h4 className="text-red-300 font-semibold mb-3">Exploration Commands</h4>
510
+              <div className="space-y-3">
511
+                {commandRef.exploration.map((cmd, index) => (
512
+                  <div key={index} className="border-l-2 border-red-700 pl-3">
513
+                    <div className="flex items-start gap-3">
514
+                      <code className="text-red-200 font-terminal text-sm">{cmd.command}</code>
515
+                      <span className="text-red-100 text-sm">- {cmd.description}</span>
516
+                    </div>
517
+                    {cmd.options && (
518
+                      <div className="mt-1 ml-4">
519
+                        {Object.entries(cmd.options).map(([opt, desc]) => (
520
+                          <div key={opt} className="text-xs">
521
+                            <code className="text-red-300">{opt}</code>
522
+                            <span className="text-red-200 ml-2">{desc}</span>
523
+                          </div>
524
+                        ))}
525
+                      </div>
526
+                    )}
527
+                  </div>
528
+                ))}
529
+              </div>
530
+            </div>
531
+
532
+            {/* Utility Commands */}
533
+            <div>
534
+              <h4 className="text-red-300 font-semibold mb-3">Utility Commands</h4>
535
+              <div className="space-y-3">
536
+                {commandRef.utility.map((cmd, index) => (
537
+                  <div key={index} className="border-l-2 border-red-700 pl-3">
538
+                    <div className="flex items-start gap-3">
539
+                      <code className="text-red-200 font-terminal text-sm">{cmd.command}</code>
540
+                      <span className="text-red-100 text-sm">- {cmd.description}</span>
541
+                    </div>
542
+                    {cmd.variables && (
543
+                      <div className="mt-1 ml-4">
544
+                        {Object.entries(cmd.variables).map(([variable, desc]) => (
545
+                          <div key={variable} className="text-xs">
546
+                            <code className="text-red-300">{variable}</code>
547
+                            <span className="text-red-200 ml-2">{desc}</span>
548
+                          </div>
549
+                        ))}
550
+                      </div>
551
+                    )}
552
+                  </div>
553
+                ))}
554
+              </div>
555
+            </div>
556
+
557
+            {/* Game Commands */}
558
+            <div>
559
+              <h4 className="text-red-300 font-semibold mb-3">Game Commands</h4>
560
+              <div className="space-y-3">
561
+                {commandRef.game.map((cmd, index) => (
562
+                  <div key={index} className="border-l-2 border-red-700 pl-3">
563
+                    <div className="flex items-start gap-3">
564
+                      <code className="text-red-200 font-terminal text-sm">{cmd.command}</code>
565
+                      <span className="text-red-100 text-sm">- {cmd.description}</span>
566
+                    </div>
567
+                  </div>
568
+                ))}
569
+              </div>
570
+            </div>
571
+
572
+            {/* Special Paths */}
573
+            <div>
574
+              <h4 className="text-red-300 font-semibold mb-3">Special Paths</h4>
575
+              <div className="space-y-3">
576
+                {commandRef.special_paths.map((path, index) => (
577
+                  <div key={index} className="border-l-2 border-red-700 pl-3">
578
+                    <div className="flex items-start gap-3">
579
+                      <code className="text-red-200 font-terminal text-sm">{path.path}</code>
580
+                      <span className="text-red-100 text-sm">- {path.description}</span>
581
+                    </div>
582
+                    <div className="mt-1">
583
+                      <span className="text-red-300 text-xs">Examples: </span>
584
+                      <code className="text-red-200 text-xs">{path.examples.join(', ')}</code>
585
+                    </div>
586
+                  </div>
587
+                ))}
588
+              </div>
589
+            </div>
590
+          </div>
591
+        </div>
592
+      )}
593
+
453594
       {/* Bottom Game Bar - Updated with blue shade */}
454
-      <div className={`absolute bottom-0 left-0 right-0 ${isDarkMode ? 'bg-slate-800/90' : 'bg-blue-50/90'} backdrop-blur-sm border-t ${isDarkMode ? 'border-slate-700' : 'border-blue-200'} p-4 z-20`}>
455
-        <div className="max-w-7xl mx-auto flex justify-between items-center">
595
+      <div className={`absolute bottom-0 left-0 right-0 ${isDarkMode ? 'bg-slate-800/90' : 'bg-blue-50/90'} backdrop-blur-sm border-t ${isDarkMode ? 'border-slate-700' : 'border-blue-200'} p-3 z-20`}>
596
+        <div className="max-w-7xl mx-auto flex justify-between items-center px-4">
456597
           <div className="flex items-center gap-4">
457
-            <h1 className={`text-2xl font-bold ${isDarkMode ? 'text-white' : 'text-gray-900'}`}><span className='font-terminal bg-gray-200 dark:bg-gray-700 text-red-900 dark:text-red-400 px-1 py-0 rounded'>bash</span>amole</h1>
598
+            <h1 className={`text-2xl font-bold ${isDarkMode ? 'text-white' : 'text-gray-900'}`}><span className='font-terminal bg-gray-200 dark:bg-gray-500 text-red-900 dark:text-red-400 px-0.5 py-0 rounded'>bash</span> amole</h1>
458599
           </div>
459600
           
460601
           <div className="flex items-center gap-3">
@@ -464,7 +605,7 @@ const Game: React.FC = () => {
464605
               </div>
465606
             ) : (
466607
               <div className={`text-xs ${isDarkMode ? 'text-slate-400' : 'text-blue-700'}`}>
467
-                Click nodes or use terminal
608
+                click adjacent nodes or use the terminal
468609
               </div>
469610
             )}
470611
             <button
frontend/src/lib/api.tsmodified
@@ -60,6 +60,28 @@ export interface FHSReferenceResponse {
6060
   directories: FHSDirectory[];
6161
 }
6262
 
63
+export interface CommandCategory {
64
+  command: string;
65
+  description: string;
66
+  examples: string[];
67
+  options?: Record<string, string>;
68
+  variables?: Record<string, string>;
69
+}
70
+
71
+export interface SpecialPath {
72
+  path: string;
73
+  description: string;
74
+  examples: string[];
75
+}
76
+
77
+export interface CommandReferenceResponse {
78
+  navigation: CommandCategory[];
79
+  exploration: CommandCategory[];
80
+  utility: CommandCategory[];
81
+  game: CommandCategory[];
82
+  special_paths: SpecialPath[];
83
+}
84
+
6385
 export const gameApi = {
6486
   createGame: async (playerName: string = 'Anonymous'): Promise<GameCreationResponse> => {
6587
     const response = await api.post('/trees/filesystem-trees/create_game/', {
@@ -96,4 +118,9 @@ export const gameApi = {
96118
     const response = await api.get('/trees/filesystem-trees/fhs_reference/');
97119
     return response.data;
98120
   },
121
+
122
+  getCommandReference: async (): Promise<CommandReferenceResponse> => {
123
+    const response = await api.get('/trees/filesystem-trees/command_reference/');
124
+    return response.data;
125
+  },
99126
 };