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
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AuthProvider, useAuth } from './context/AuthContext';
import Login from './pages/Login';
import IDE from './pages/IDE';
import AdminDashboard from './pages/AdminDashboard';
import GlobalClock from './components/GlobalClock';
import { useNetworkStatus } from './hooks/useNetworkStatus';

// ── Permission gate ──────────────────────────────────────────────────────────
Expand Down Expand Up @@ -151,7 +152,7 @@ function InternetRestrictedBlock() {
</h2>
<p style={{ maxWidth: '480px', lineHeight: '1.6', margin: 0, color: '#a0a0a0' }}>
Your admin has restricted IDE usage while connected to the internet.
<br/><br/>
<br /><br />
Please disconnect from the internet or disable your network adapter to continue using Sonar Code Editor.
</p>
</div>
Expand All @@ -173,7 +174,7 @@ function AppRoutes() {
if (!user) return <Login />;
if (user.role === 'admin') return (
<Routes>
<Route path="/admin" element={<AdminDashboard />} />
<Route path="/admin/*" element={<AdminDashboard />} />
<Route path="*" element={<Navigate to="/admin" />} />
</Routes>
);
Expand Down
69 changes: 33 additions & 36 deletions src/renderer/components/AdminPanel/AdminSettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface AdminSettingsModalProps {
accentColor: string;
onAccentColorChange: (val: string) => void;
onTeamNameUpdated: (newName: string) => void;
isEmbedded?: boolean;
}

export default function AdminSettingsModal({
Expand All @@ -28,6 +29,7 @@ export default function AdminSettingsModal({
accentColor,
onAccentColorChange,
onTeamNameUpdated,
isEmbedded = false,
}: AdminSettingsModalProps) {
const [activeTab, setActiveTab] = useState('Account');
const [searchQuery, setSearchQuery] = useState('');
Expand Down Expand Up @@ -124,7 +126,7 @@ export default function AdminSettingsModal({
setBlockNonEmptySuccess('');
}, [activeTab]);

if (!isOpen) return null;
if (!isOpen && !isEmbedded) return null;

const matchesSearch = (text: string) =>
searchQuery === '' || text.toLowerCase().includes(searchQuery.toLowerCase());
Expand Down Expand Up @@ -299,7 +301,7 @@ export default function AdminSettingsModal({
>
<Shield size={14} /> Privacy
</li>

<div className="vscode-settings-divider" />

<li
Expand Down Expand Up @@ -397,7 +399,7 @@ export default function AdminSettingsModal({
title="Custom Color"
/>
<span className="custom-color-hex">{accentColor.toUpperCase()}</span>
<button
<button
className="activity-log-btn secondary reset-color-btn"
onClick={() => onAccentColorChange('#3b82f6')}
disabled={accentColor.toLowerCase() === '#3b82f6'}
Expand All @@ -415,56 +417,51 @@ export default function AdminSettingsModal({
{showAbout && (
<div className="vscode-settings-section">
<h2 className="vscode-settings-section-title">About</h2>

<div className="account-card" style={{ padding: '32px 40px', display: 'flex', flexDirection: 'column', gap: '24px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '20px' }}>
<img
src={appIcon}
alt="Sonar Code Editor"
style={{
width: 72, height: 72,
borderRadius: 16,
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.2)',
objectFit: 'cover'
}}

<div className="about-card">
<div className="about-header">
<img
src={appIcon}
alt="Sonar Code Editor"
className="about-app-icon"
/>
<div>
<h3 style={{ margin: 0, fontSize: 28, fontWeight: 700, color: 'var(--text-primary)', letterSpacing: '-0.5px' }}>Sonar Code Editor</h3>
<div style={{ color: 'var(--text-muted)', marginTop: 4, fontSize: 14, display: 'flex', alignItems: 'center', gap: 6 }}>
<h3 className="about-title">Sonar Code Editor</h3>
<div className="about-subtitle">
<span>Version 1.0.0-beta</span>
</div>
</div>
</div>

<div style={{ height: 1, background: 'var(--divider)' }}></div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ fontSize: 13, color: 'var(--text-primary)', lineHeight: 1.6 }}>
<div className="about-divider"></div>

<div className="about-content">
<div className="about-description">
Sonar is a collaborative, real-time code editor built for educational institutions and development teams. It provides seamless coding experiences with integrated monitoring and activity logging.
</div>
<div style={{ display: 'flex', gap: '12px', marginTop: '8px' }}>
<a href="https://github.com/rkvishwa/Sonar-Code-Editor" target="_blank" rel="noopener noreferrer" className="activity-log-btn secondary" style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 8, padding: '8px 16px' }}>
<Github size={14} />

<div className="about-links">
<a href="https://github.com/rkvishwa/Sonar-Code-Editor" target="_blank" rel="noopener noreferrer" className="about-link-btn">
<Github size={16} />
GitHub Repository
</a>
<a href="https://sonar.knurdz.org" target="_blank" rel="noopener noreferrer" className="activity-log-btn secondary" style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 8, padding: '8px 16px' }}>
<Globe size={14} />
<a href="https://sonar.knurdz.org" target="_blank" rel="noopener noreferrer" className="about-link-btn">
<Globe size={16} />
Official Website
</a>
</div>
</div>

<div style={{ height: 1, background: 'var(--divider)', marginTop: '8px' }}></div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 12, color: 'var(--text-muted)' }}>
<div className="about-divider spaced"></div>

<div className="about-footer">
<div>
Released under the <a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)', textDecoration: 'none' }}>MIT License</a>.
Released under the <a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferrer" className="about-footer-link">MIT License</a>.
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
Powered by
<a href="https://knurdz.org" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--text-primary)', textDecoration: 'none', fontWeight: 600, display: 'flex', alignItems: 'center', gap: 4 }}>
Knurdz <ExternalLink size={12} />
<div className="about-powered-by">
Powered by
<a href="https://knurdz.org" target="_blank" rel="noopener noreferrer" className="about-footer-link" style={{ display: 'flex', alignItems: 'center', gap: 4, color: 'var(--text-primary)' }}>
Knurdz <ExternalLink size={14} />
</a>
</div>
</div>
Expand Down Expand Up @@ -562,7 +559,7 @@ export default function AdminSettingsModal({
</div>
</div>
</div>

{restrictionError && <div className="account-error">{restrictionError}</div>}
{restrictionSuccess && <div className="account-success"><CheckCircle2 size={12} /> {restrictionSuccess}</div>}
{blockNonEmptyError && <div className="account-error">{blockNonEmptyError}</div>}
Expand Down
144 changes: 86 additions & 58 deletions src/renderer/components/Editor/EditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { editor } from "monaco-editor";
import { formatKey } from "../../utils/shortcut";
import { OpenTab } from "../../pages/IDE";
import PreviewPanel from "../Preview/PreviewPanel";
import GlobalClock from "../../components/GlobalClock";
import "./EditorPanel.css";

interface EditorPanelProps {
Expand Down Expand Up @@ -47,6 +48,7 @@ interface EditorPanelProps {
onEditorUnmount?: (filePath?: string) => void;
wordWrap?: boolean;
getCollaborativeContent?: (filePath: string) => string | null;
showClock?: boolean;
}

const CustomRadarIcon = ({ size = 24, className = "", color = "currentColor", style, maskId = "needle-mask" }: any) => {
Expand Down Expand Up @@ -78,7 +80,7 @@ const CustomRadarIcon = ({ size = 24, className = "", color = "currentColor", st
<path d="m13.41 10.59 5.66-5.66" stroke="black" strokeWidth="3.5" strokeLinecap="round" fill="none" />
</mask>
</defs>

<g mask={`url(#${maskId})`}>
<path d="M19.07 4.93A10 10 0 0 0 6.99 3.34" />
<path d="M4 6h.01" />
Expand All @@ -87,7 +89,7 @@ const CustomRadarIcon = ({ size = 24, className = "", color = "currentColor", st
<path d="M12 18h.01" />
<path d="M17.99 11.66A6 6 0 0 1 15.77 16.67" />
</g>

<circle cx="12" cy="12" r="2" mask={`url(#${circleMaskId})`} />
<path d="m13.41 10.59 5.66-5.66" />
</svg>
Expand Down Expand Up @@ -188,6 +190,7 @@ const EditorPanel = React.memo(function EditorPanel({
onEditorUnmount,
wordWrap = true,
getCollaborativeContent,
showClock = false,
}: EditorPanelProps) {
const activeTab = tabs.find((t) => t.path === activeTabPath);
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
Expand Down Expand Up @@ -335,8 +338,8 @@ const EditorPanel = React.memo(function EditorPanel({
// or when the last tab is closed (activeTabPath becomes null).
useEffect(() => {
if (activeTabPath !== boundFileRef.current && boundFileRef.current) {
onEditorUnmount?.();
boundFileRef.current = null;
onEditorUnmount?.();
boundFileRef.current = null;
}
}, [activeTabPath, onEditorUnmount]);

Expand Down Expand Up @@ -507,7 +510,7 @@ const EditorPanel = React.memo(function EditorPanel({
<stop offset="30%" stopColor="var(--text-primary)" stopOpacity="0.15" />
<stop offset="100%" stopColor="var(--bg-primary)" stopOpacity="0" />
</linearGradient>

<linearGradient id="shineGradient" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stopColor="var(--text-primary)" stopOpacity="0" />
<stop offset="0.5" stopColor="#ffffff" stopOpacity="0.8" />
Expand Down Expand Up @@ -567,10 +570,29 @@ const EditorPanel = React.memo(function EditorPanel({
if (tabs.length === 0) {
return (
<div className="editor-panel empty">
<div className="welcome-screen folder-opened">
<div className="folder-welcome-hero" style={{ marginTop: '-40px' }}>
<div className="folder-welcome-icon">
<FileCode2 size={64} className="folder-icon" />
<div className="welcome-screen">
<div className="welcome-hero">
<div className="welcome-logo-container">
<svg width="0" height="0" style={{ position: 'absolute' }}>
<defs>
<linearGradient id="logoGradient2" x1="0" y1="0" x2="1" y2="1">
<stop offset="30%" stopColor="var(--text-primary)" stopOpacity="0.15" />
<stop offset="100%" stopColor="var(--bg-primary)" stopOpacity="0" />
</linearGradient>

<linearGradient id="shineGradient2" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stopColor="var(--text-primary)" stopOpacity="0" />
<stop offset="0.5" stopColor="#ffffff" stopOpacity="0.8" />
<stop offset="1" stopColor="var(--text-primary)" stopOpacity="0" />
<animate attributeName="x1" values="-1.5; 1.5; 1.5" dur="5s" keyTimes="0; 0.3; 1" repeatCount="indefinite" />
<animate attributeName="x2" values="-0.5; 2.5; 2.5" dur="5s" keyTimes="0; 0.3; 1" repeatCount="indefinite" />
</linearGradient>
</defs>
</svg>
<div style={{ position: 'relative' }}>
<FileCode2 size={160} className="welcome-logo" color="url(#logoGradient2)" />
<FileCode2 size={160} className="welcome-logo" color="url(#shineGradient2)" style={{ position: 'absolute', top: 0, left: 0 }} />
</div>
</div>
<h1 className="folder-welcome-title">Folder Opened</h1>
<p className="welcome-subtitle">
Expand Down Expand Up @@ -632,59 +654,65 @@ const EditorPanel = React.memo(function EditorPanel({
<div
key={tab.path}
className={`tab ${tab.path === activeTabPath ? "active" : ""}${dragOverIndex === index ? " drag-over" : ""}${dragIndex === index ? " dragging" : ""}`}
onClick={() => onTabClick(tab.path)}
onDoubleClick={() => onTabDoubleClick?.(tab.path)}
draggable
onDragStart={(e) => {
setDragIndex(index);
e.dataTransfer.effectAllowed = "move";
}}
onDragOver={(e) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
setDragOverIndex(index);
}}
onDragLeave={() => setDragOverIndex(null)}
onDrop={(e) => {
e.preventDefault();
if (dragIndex !== null && dragIndex !== index) {
onReorderTabs?.(dragIndex, index);
}
setDragIndex(null);
setDragOverIndex(null);
}}
onDragEnd={() => {
setDragIndex(null);
setDragOverIndex(null);
}}
>
{getTabIcon(tab)}
<span
className="tab-name"
style={{
fontStyle:
tab.isPreviewFile && !tab.isDirty ? "italic" : "normal",
textDecoration: tab.isDeleted ? "line-through" : "none",
color: tab.isDeleted ? "red" : "inherit",
onClick={() => onTabClick(tab.path)}
onDoubleClick={() => onTabDoubleClick?.(tab.path)}
draggable
onDragStart={(e) => {
setDragIndex(index);
e.dataTransfer.effectAllowed = "move";
}}
>
{displayName}
</span>
{tab.isDirty && (
<span className="dirty-dot" title="Unsaved changes"></span>
)}
<button
className="tab-close"
onClick={(e) => {
e.stopPropagation();
onTabClose(tab.path);
onDragOver={(e) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
setDragOverIndex(index);
}}
onDragLeave={() => setDragOverIndex(null)}
onDrop={(e) => {
e.preventDefault();
if (dragIndex !== null && dragIndex !== index) {
onReorderTabs?.(dragIndex, index);
}
setDragIndex(null);
setDragOverIndex(null);
}}
onDragEnd={() => {
setDragIndex(null);
setDragOverIndex(null);
}}
title="Close"
>
<X size={14} />
</button>
{getTabIcon(tab)}
<span
className="tab-name"
style={{
fontStyle:
tab.isPreviewFile && !tab.isDirty ? "italic" : "normal",
textDecoration: tab.isDeleted ? "line-through" : "none",
color: tab.isDeleted ? "red" : "inherit",
}}
>
{displayName}
</span>
{tab.isDirty && (
<span className="dirty-dot" title="Unsaved changes"></span>
)}
<button
className="tab-close"
onClick={(e) => {
e.stopPropagation();
onTabClose(tab.path);
}}
title="Close"
>
<X size={14} />
</button>
</div>
)
})}
{showClock && (
<div style={{ marginLeft: "auto", paddingRight: "16px", display: "flex", alignItems: "center" }}>
<GlobalClock mode="inline" />
</div>
)})}
)}
</div>

{/* Editor, Preview, or Image */}
Expand Down
Loading
Loading