Skip to content
Merged
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
2ea880f
Add file browser context.
vishalpalaniappan Mar 20, 2026
0c695a2
Load workspace sample tree.
vishalpalaniappan Mar 20, 2026
f5b79e8
Merge branch 'main' into modular-filebrowser
vishalpalaniappan Mar 20, 2026
9798843
Restructure files.
vishalpalaniappan Mar 20, 2026
1cf39e7
Begin adding reducer pattern.
vishalpalaniappan Mar 20, 2026
c3266ea
Breaking: Start gutting component to rebuild it.
vishalpalaniappan Mar 20, 2026
22cea37
Load workspace tree.
vishalpalaniappan Mar 21, 2026
a160d22
Move component to API and reducer.
vishalpalaniappan Mar 21, 2026
10c48cf
Remove unncessary log.
vishalpalaniappan Mar 21, 2026
a377e27
Load tree from args.
vishalpalaniappan Mar 21, 2026
b2a26e8
Remove outdated sample trees.
vishalpalaniappan Mar 21, 2026
4cb1a05
Updated stories.
vishalpalaniappan Mar 21, 2026
a3472a4
Switch to selecting node using UID.
vishalpalaniappan Mar 21, 2026
1494b74
Select tree node after timeout from parent.
vishalpalaniappan Mar 21, 2026
bf13a02
Save node to useDraggable.
vishalpalaniappan Mar 21, 2026
1758378
Remove unused log statement.
vishalpalaniappan Mar 21, 2026
807670d
Use uid for key.
vishalpalaniappan Mar 21, 2026
b5ac9e2
Modify editor to use tabs provided by file browser tree.
vishalpalaniappan Mar 21, 2026
f6a0ba2
Small refactor.
vishalpalaniappan Mar 21, 2026
2260caa
Remove unused variable.
vishalpalaniappan Mar 21, 2026
6f42a45
Use generated preview for tree node.
vishalpalaniappan Mar 21, 2026
b06dad7
Rename dndkit types and add check to ensure unique uid of tabs.
vishalpalaniappan Mar 21, 2026
3843a4b
Add functionality to invoke callback on file select.
vishalpalaniappan Mar 21, 2026
3679684
Cleanup helper.js.
vishalpalaniappan Mar 21, 2026
8fe9c68
Add functionality to insert tab at specific location if it was provided.
vishalpalaniappan Mar 21, 2026
b3fd0c7
Save preview in draggable info.
vishalpalaniappan Mar 21, 2026
99edfd1
Update stories to use more accurate drag position and preview saved i…
vishalpalaniappan Mar 21, 2026
9c118e4
Fix incorrect id for tree node.
vishalpalaniappan Mar 21, 2026
7d68c59
Remove redundant preview api call.
vishalpalaniappan Mar 21, 2026
ae21b18
Remove unused import.
vishalpalaniappan Mar 21, 2026
6099355
Small refactor.
vishalpalaniappan Mar 21, 2026
9058d16
Remove unused imports.
vishalpalaniappan Mar 21, 2026
680a3fe
Set unique ids for dnd components and cleanup.
vishalpalaniappan Mar 21, 2026
2eb5d89
Use correct uids.
vishalpalaniappan Mar 21, 2026
2214894
Update dev version.
vishalpalaniappan Mar 21, 2026
e283188
Add scroll bar for tabs.
vishalpalaniappan Mar 21, 2026
9a13995
Improve style.
vishalpalaniappan Mar 21, 2026
2d13ff3
Fix style for file browser.
vishalpalaniappan Mar 21, 2026
6993ed1
Improve styling.
vishalpalaniappan Mar 22, 2026
7c1fc4d
Fix styling.
vishalpalaniappan Mar 22, 2026
bc8dadd
Remove redundant styles in tree.scss.
vishalpalaniappan Mar 22, 2026
e13d88b
Remove viewer component and boostrap library dependencies.
vishalpalaniappan Mar 22, 2026
575547c
Reset reducer state to address issues with strict mode and remove out…
vishalpalaniappan Mar 22, 2026
225181c
Remove index from exported modules.
vishalpalaniappan Mar 22, 2026
b98224e
Avoid mutating state directly.
vishalpalaniappan Mar 22, 2026
bc44d07
Wrap selectFile with useCallback.
vishalpalaniappan Mar 22, 2026
8872dd8
Set unique id based on parentId for when there are multiple editors.
vishalpalaniappan Mar 22, 2026
7f08ba2
Switch to absolute coordinates so monaco editor automatically fills h…
vishalpalaniappan Mar 22, 2026
48c5514
Minor changes to set some options.
vishalpalaniappan Mar 22, 2026
01b33dd
Disable automatic layout to avoid resizeobserver loop bug.
vishalpalaniappan Mar 22, 2026
2ed1599
Revert automatic layout change.
vishalpalaniappan Mar 22, 2026
e45511a
Disable automatic layout and manually layout editor.
vishalpalaniappan Mar 22, 2026
ff48557
Add UID to initial state of file browser and editor.
vishalpalaniappan Mar 24, 2026
fef5042
Use generated state UID as Parent ID.
vishalpalaniappan Mar 24, 2026
e6b6860
Set unique key.
vishalpalaniappan Mar 24, 2026
3adcfa9
Fix flattened tree type.
vishalpalaniappan Mar 24, 2026
6ed2f54
Add default case.
vishalpalaniappan Mar 24, 2026
4893f56
Return state.
vishalpalaniappan Mar 24, 2026
e57a006
Remove unused function call.
vishalpalaniappan Mar 24, 2026
4ab175e
Clear trees if there are no nodes in collapsed tree.
vishalpalaniappan Mar 24, 2026
57bdbe7
Remove outdated api call.
vishalpalaniappan Mar 24, 2026
1d79161
Disable minimap and remove uplicate scroll beyond last line.
vishalpalaniappan Mar 24, 2026
a560289
Clone payload before mutating it.
vishalpalaniappan Mar 24, 2026
049f0fd
Close tab on center mouse click.
vishalpalaniappan Mar 24, 2026
e1b3621
Reduce size of tree nodes and tabs.
vishalpalaniappan Mar 24, 2026
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
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sample-ui-component-library",
"version": "0.0.7-dev",
"version": "0.0.18-dev",
"description": "A library which contains sample UI elements that can be used for populating layouts.",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down Expand Up @@ -68,8 +68,6 @@
"@dagrejs/dagre": "^1.1.4",
"@monaco-editor/react": "^4.7.0",
"@xyflow/react": "^12.6.0",
"bootstrap": "^5.3.4",
"react-bootstrap": "^2.10.9",
"react-bootstrap-icons": "^1.11.5"
}
}
22 changes: 6 additions & 16 deletions src/components/Editor/Editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';

import { MonacoInstance } from "./MonacoInstance/MonacoInstance";
import { Tabs } from "./Tabs/Tabs";
import { TabPreview } from "./Tabs/Tab/Tab";

import React, {
forwardRef,
useCallback,
Expand Down Expand Up @@ -37,35 +37,25 @@ export const Editor = forwardRef(({ }, ref) => {
dispatch({ type: "MOVE_TAB", payload: { tabId, newIndex } });
}, []);

const addTab = useCallback((tab) => {
dispatch({ type: "ADD_TAB", payload: tab });
const addTab = useCallback((tab, index) => {
dispatch({ type: "ADD_TAB", payload: { tab, index } });
}, []);

const setTabGroupId = useCallback((id) => {
dispatch({ type: "RESET_STATE"});
dispatch({ type: "SET_PARENT_TAB_GROUP_ID", payload: id });
}, []);

const getPreviewElement = useCallback((tabId) => {
// Get the preview element for a tab by its id for use in drag-and-drop operations.
const tab = state.tabs.find(t => t.id === tabId);
if (!tab) {
console.error(`getPreviewElement: tab with id ${tabId} not found.`);
return null;
}
return <TabPreview info={{ label: tab.label }} />;
}, [state]);

const api = useMemo(() => {
return {
state,
addTab,
setTabGroupId,
selectTab,
closeTab,
moveTab,
getPreviewElement
moveTab
};
}, [state, addTab, selectTab, closeTab, moveTab, getPreviewElement, setTabGroupId]);
}, [state, addTab, selectTab, closeTab, moveTab, setTabGroupId]);

useImperativeHandle(ref, () => api, [api]);

Expand Down
16 changes: 10 additions & 6 deletions src/components/Editor/Editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
position:relative;
width:100%;
height:100%;
display:flex;
flex-direction:column;
overflow:hidden;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

.tabContainer{
width: 100%;
height: 40px;
position: absolute;
top: 0;
left: 0;
bottom: 40px;
right:0;
}

.monacoContainer{
flex-grow: 1;
position: absolute;
top: 40px;
left: 0;
bottom: 0;
right:0;
}
45 changes: 33 additions & 12 deletions src/components/Editor/EditorReducer.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
export const initialState = {
tabs: [],
activeTab: null,
parentTabGroupId: null
uid: crypto.randomUUID(),
Comment thread
vishalpalaniappan marked this conversation as resolved.
tabs: [],
activeTab: null,
parentTabGroupId: null
Comment thread
vishalpalaniappan marked this conversation as resolved.
};

export const editorReducer = (state, action) => {
let id;
switch (action.type) {

case "ADD_TAB": {
// TODO: Add some validation for the payload here.
const { tab, index } = action.payload;

const tabInfo = state.tabs.find(obj => obj.uid === tab.uid);
if (tabInfo) {
console.warn(`Tab with id ${tabInfo.uid} already exists`);
return {
...state,
activeTab: tabInfo
};
Comment thread
vishalpalaniappan marked this conversation as resolved.
}

// Insert tab at specific location if it was provided.
let tabs = [...state.tabs];
if (index !== undefined && index < state.tabs.length) {
tabs.splice(index, 0, tab);
} else {
tabs.push(tab);
}

return {
...state,
tabs: [...state.tabs, action.payload],
activeTab: action.payload
tabs: tabs,
activeTab: tab
};
}

Expand All @@ -24,9 +42,8 @@ export const editorReducer = (state, action) => {
};
}


case "SELECT_TAB": {
const tab = state.tabs.find(obj => obj.id === action.payload);
const tab = state.tabs.find(obj => obj.uid === action.payload);
if (!tab) {
console.error(`Tab with id ${action.payload} not found.`);
return state;
Expand All @@ -38,7 +55,7 @@ export const editorReducer = (state, action) => {
}

case "CLOSE_TAB": {
const ind = state.tabs.findIndex(obj => obj.id === action.payload);
const ind = state.tabs.findIndex(obj => obj.uid === action.payload);
if (ind === -1) {
console.warn(`Tab with id ${action.payload} not found.`);
return state;
Expand All @@ -48,7 +65,7 @@ export const editorReducer = (state, action) => {

// If active tab is closed, select the next tab if it exists, otherwise select the previous tab.
let activeTab = state.activeTab;
const isActiveTabClosed = state.activeTab && state.activeTab.id === action.payload;
const isActiveTabClosed = state.activeTab && state.activeTab.uid === action.payload;
if (isActiveTabClosed && ind < newTabs.length) {
activeTab = newTabs[Math.max(0, ind)];
} else if (isActiveTabClosed && ind >= newTabs.length) {
Expand All @@ -65,7 +82,7 @@ export const editorReducer = (state, action) => {
case "MOVE_TAB": {
const prevTabs = [...state.tabs];
const { tabId, newIndex } = action.payload;
const oldIndex = prevTabs.findIndex(t => t.id === tabId);
const oldIndex = prevTabs.findIndex(t => t.uid === tabId);
if (oldIndex === -1) {
console.warn(`Tab with id ${tabId} not found.`);
return state;
Expand All @@ -82,6 +99,10 @@ export const editorReducer = (state, action) => {
tabs: prevTabs
};
}

case "RESET_STATE": {
return initialState;
}

default: {
return state;
Expand Down
38 changes: 32 additions & 6 deletions src/components/Editor/MonacoInstance/MonacoInstance.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const MonacoInstance = ({ }) => {

const editorRef = useRef(null);
const content = useRef();
const containerRef = useRef(null);
const frameRef = useRef(0);

useEffect(() => {
if (state.activeTab) {
Expand Down Expand Up @@ -41,21 +43,45 @@ export const MonacoInstance = ({ }) => {
if (content?.current) {
editorRef.current.setValue(content.current);
}
editorRef.current.layout();
}

// Editor options for Monaco Editor.
const editorOptions = {
scrollBeyondLastLine: false,
fontSize: "13px",
minimap: {
enabled: false
enabled: true
},
padding: {
top: 20,
bottom: 10
}
top: 10
},
renderWhitespace: "none",
wordWrap: "on",
scrollBeyondLastLine: false,
readOnly: true,
automaticLayout: false
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Disable automatic layout and Manually layout the editor to avoid resize observer loops
useEffect(() => {
if (!containerRef.current) return;

const ro = new ResizeObserver(() => {
cancelAnimationFrame(frameRef.current);
frameRef.current = requestAnimationFrame(() => {
editorRef.current?.layout();
});
});

ro.observe(containerRef.current);

return () => {
cancelAnimationFrame(frameRef.current);
ro.disconnect();
};
}, []);

/**
* Render the editor if there is an active tab, otherwise render a placeholder message.
* @returns JSX
Expand All @@ -77,9 +103,9 @@ export const MonacoInstance = ({ }) => {
}

return (
<>
<div className="editor-container" ref={containerRef}>
{renderEditor()}
</>
</div>
)
}

Expand Down
6 changes: 6 additions & 0 deletions src/components/Editor/MonacoInstance/MonacoInstance.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@
display:flex;
align-items:center;
justify-content:center;
}

.editor-container {
position:relative;
width:100%;
height: 100%;
}
2 changes: 1 addition & 1 deletion src/components/Editor/Tabs/Gutter/Gutter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Gutter = ({ id, index, parentId }) => {
const { setNodeRef, isOver } = useDroppable({
id,
data: {
type: "tab-gutter",
type: "EditorTabGutter",
parentId: parentId,
index: index,
},
Expand Down
46 changes: 33 additions & 13 deletions src/components/Editor/Tabs/Tab/Tab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const INACTIVE_TAB_FG_COLOR = "#969690";
* @param {String} label
* @returns
*/
export const Tab = ({ id, parentId, info }) => {
export const Tab = ({ id, parentId, node }) => {
const [tabStyle, setTabStyle] = useState();

const { selectTab, closeTab, state } = useEditor();
Expand All @@ -28,14 +28,15 @@ export const Tab = ({ id, parentId, info }) => {
const { attributes, listeners, setNodeRef, transform } = useDraggable({
id,
data: {
type: "tab-draggable",
type: "EditorTab",
parentId: parentId,
tabId: id,
node: node,
preview: <TabPreview node={node} />
},
});

useEffect(() => {
renderTab(state.activeTab && state.activeTab.id === id);
renderTab(state.activeTab && state.activeTab.uid === node.uid);
}, [state.activeTab]);

const renderTab = (isActive) => {
Expand All @@ -46,12 +47,12 @@ export const Tab = ({ id, parentId, info }) => {

const clickTab = (e) => {
e.stopPropagation();
selectTab(id);
selectTab(node.uid);
}

const clickClose = (e) => {
e.stopPropagation();
closeTab(id);
closeTab(node.uid);
}

return (
Expand All @@ -64,26 +65,45 @@ export const Tab = ({ id, parentId, info }) => {
{...listeners}
{...attributes}
>
<FileEarmark className="icon" style={{ pointerEvents: "none" }} />
<span className="tab-name">{info.label}</span>
<XLg onMouseDown={clickClose} className="close-icon"/>
<div className="tab-content">
<div className="icon">
<FileEarmark size={14} style={{ pointerEvents: "none" }} />
</div>
<div className="tab-name">
<span>{node.name}</span>
</div>
<div className="close-icon" onMouseDown={clickClose}>
<XLg size={18} />
</div>

</div>
</div>
);
}

Tab.propTypes = {
id: PropTypes.string.isRequired,
parentId: PropTypes.string.isRequired,
info: PropTypes.shape({
label: PropTypes.string.isRequired,
node: PropTypes.shape({
name: PropTypes.string.isRequired,
}).isRequired
}


export const TabPreview = ({info}) => {
export const TabPreview = ({node}) => {
return (
<div className="tab" style={{ backgroundColor: ACTIVE_TAB_BG_COLOR, color: ACTIVE_TAB_FG_COLOR, opacity:0.5 }}>
<FileEarmark className="icon" />{info.label}<XLg className="close-icon"/>
<div className="tab-content">
<div className="icon">
<FileEarmark size={14} style={{ pointerEvents: "none" }} />
</div>
<div className="tab-name">
<span>{node.name}</span>
</div>
<div className="close-icon">
<XLg size={18} />
</div>
</div>
</div>
);
}
Loading