Skip to content
Open
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
11 changes: 11 additions & 0 deletions Nginx/dev.conf.d/local.conf
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ server {
alias /usr/share/nginx/django_file_storage/;
}

location /ws/ {
proxy_pass http://django_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 86400;
}

location /sockjs-node {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ services:
django:
image: "docker.pkg.github.com/frg-fossee/esim-cloud/django:dev"
build: ./esim-cloud-backend/
command: "python3 manage.py runserver 0.0.0.0:8000"
command: "daphne -b 0.0.0.0 -p 8000 esimCloud.asgi:application"
ports:
- "8000:8000"
volumes:
Expand Down Expand Up @@ -117,7 +117,7 @@ services:
# - ./mysql_data:/var/lib/mysql

db:
image: postgres
image: postgres:13
volumes:
- ./postgres_data:/var/lib/postgresql/data/
env_file:
Expand Down
2 changes: 1 addition & 1 deletion eda-frontend/src/components/SchematicEditor/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ function Header ({ gridRef }) {
{shared === true
? <input
ref={textAreaRef}
value={`${window.location.protocol}\\\\${window.location.host}/eda/#/editor?id=${schSave.details.save_id}`}
value={`${window.location.protocol}//${window.location.host}/eda/#/editor?id=${schSave.details.save_id}&version=${schSave.details.version}&branch=${schSave.details.branch}`}
readOnly
/>
: <> Turn On sharing </>
Expand Down
3 changes: 3 additions & 0 deletions eda-frontend/src/components/SchematicEditor/Helper/SideBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export function AddComponent (component, imgref) {
return null
}
var funct = function (graph, evt, target, x, y) {
// Viewer mode: editing is disabled — reject component drops silently
if (!graph.isEnabled()) return

var parent = graph.getDefaultParent()
var model = graph.getModel()

Expand Down
36 changes: 36 additions & 0 deletions eda-frontend/src/components/SchematicEditor/Helper/ToolbarTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,42 @@ export function renderGalleryXML(xml) {
var xmlDoc = mxUtils.parseXml(xml)
parseXmlToGraph(xmlDoc, graph)
}

/**
* Register a callback that fires whenever the graph model changes.
* Returns an unregister function — call it on cleanup to avoid leaks.
* Returns a no-op if called before the graph is initialised.
*
* Used by Phase 2 live sync to debounce-and-broadcast XML snapshots.
* Phase 3 incremental events can register additional listeners the same way.
*/
export function registerChangeListener(callback) {
if (!graph) return function () {}
var listener = function () { callback() }
graph.getModel().addListener(mxEvent.CHANGE, listener)
return function unregister() {
graph.getModel().removeListener(listener)
}
}

/**
* Enable or disable viewer mode on the graph.
*
* Viewer mode (isViewer = true):
* - Disables cell selection, moving, connecting, and deleting
* - Keeps panning active so the viewer can navigate
* - Zoom toolbar buttons continue to work (they call graph.zoomIn/Out directly)
* - Keyboard shortcuts that call graph.isEnabled() are blocked automatically
*
* Editor mode (isViewer = false): restores full editing capability.
*/
export function setViewerMode(isViewer) {
if (!graph) return
graph.setEnabled(!isViewer)
if (isViewer) {
graph.setPanning(true)
}
}
// Certain Variables need to be Defined before Saving the Circuit, XML Wire Connections does that
function XMLWireConnections() {
var erc = true
Expand Down
183 changes: 183 additions & 0 deletions eda-frontend/src/components/SchematicEditor/PresencePanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import React, { useEffect } from 'react'
import {
Chip,
Collapse,
Divider,
IconButton,
List,
ListItem,
ListItemIcon,
ListItemText,
Snackbar,
Tooltip,
Typography
} from '@material-ui/core'
import ExpandLessIcon from '@material-ui/icons/ExpandLess'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'
import PersonIcon from '@material-ui/icons/Person'
import CloseIcon from '@material-ui/icons/Close'
import { makeStyles } from '@material-ui/core/styles'
import { useSelector, useDispatch } from 'react-redux'
import { presenceClearNotification } from '../../redux/actions/collaborationActions'
import { useIsEditor } from '../../utils/useIsEditor'

const NOTIFICATION_DURATION_MS = 4000

const useStyles = makeStyles((theme) => ({
root: {
borderTop: `1px solid ${theme.palette.divider}`,
marginTop: theme.spacing(1)
},
header: {
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0.5, 1),
cursor: 'pointer',
userSelect: 'none',
'&:hover': {
backgroundColor: theme.palette.action.hover
}
},
dot: {
fontSize: 10,
marginRight: theme.spacing(0.75),
flexShrink: 0
},
dotOnline: { color: '#52c41a' },
dotOffline: { color: '#bfbfbf' },
countText: {
fontWeight: 600,
fontSize: '0.82rem',
flexGrow: 1
},
userItem: {
paddingTop: 2,
paddingBottom: 2,
paddingLeft: theme.spacing(3)
},
userIcon: {
minWidth: 28,
color: theme.palette.text.secondary
},
userName: {
fontSize: '0.80rem'
},
guestName: {
fontSize: '0.80rem',
color: theme.palette.text.disabled,
fontStyle: 'italic'
},
emptyText: {
padding: theme.spacing(0.5, 3),
fontSize: '0.78rem',
color: theme.palette.text.disabled,
fontStyle: 'italic'
},
roleChip: {
height: 18,
fontSize: '0.68rem',
marginLeft: theme.spacing(0.75)
}
}))

export default function PresencePanel () {
const classes = useStyles()
const dispatch = useDispatch()
const { connected, users, notification } = useSelector(state => state.presenceReducer)
const saveId = useSelector(state => state.saveSchematicReducer.details.save_id)
const isEditor = useIsEditor()

const [expanded, setExpanded] = React.useState(true)

// Auto-dismiss notifications
useEffect(() => {
if (!notification) return
const timer = setTimeout(() => {
dispatch(presenceClearNotification())
}, NOTIFICATION_DURATION_MS)
return () => clearTimeout(timer)
}, [notification, dispatch])

// Don't render until a schematic is saved/loaded (no room to join)
if (!saveId) return null

const onlineCount = users.length
const statusLabel = connected
? `${onlineCount} Online`
: 'Connecting...'

const notificationMessage = notification
? notification.type === 'joined'
? `${notification.username} joined`
: `${notification.username} left`
: ''

return (
<div className={classes.root}>
<div
className={classes.header}
onClick={() => setExpanded(prev => !prev)}
role="button"
tabIndex={0}
onKeyDown={(e) => e.key === 'Enter' && setExpanded(prev => !prev)}
>
<Tooltip title={connected ? 'Connected' : 'Disconnected'}>
<FiberManualRecordIcon
className={`${classes.dot} ${connected ? classes.dotOnline : classes.dotOffline}`}
/>
</Tooltip>
<Typography className={classes.countText}>{statusLabel}</Typography>
{saveId && (
<Chip
label={isEditor ? 'Editing' : 'Viewing'}
size="small"
color={isEditor ? 'primary' : 'default'}
className={classes.roleChip}
/>
)}
{expanded ? <ExpandLessIcon fontSize="small" /> : <ExpandMoreIcon fontSize="small" />}
</div>

<Collapse in={expanded}>
<List disablePadding dense>
{users.length === 0 ? (
<Typography className={classes.emptyText}>No viewers</Typography>
) : (
users.map((u, idx) => (
<ListItem key={u.username + idx} className={classes.userItem} dense>
<ListItemIcon className={classes.userIcon}>
<PersonIcon fontSize="small" />
</ListItemIcon>
<ListItemText
primary={u.username}
primaryTypographyProps={{
className: u.is_anonymous ? classes.guestName : classes.userName
}}
/>
</ListItem>
))
)}
</List>
</Collapse>

<Divider />

{/* Join / leave toast */}
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
open={!!notification}
message={notificationMessage}
action={
<IconButton
size="small"
color="inherit"
onClick={() => dispatch(presenceClearNotification())}
>
<CloseIcon fontSize="small" />
</IconButton>
}
/>
</div>
)
}
13 changes: 12 additions & 1 deletion eda-frontend/src/pages/SchematiEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ import RightSidebar from '../components/SchematicEditor/RightSidebar'
import PropertiesSidebar from '../components/SchematicEditor/PropertiesSidebar'
import LoadGrid from '../components/SchematicEditor/Helper/ComponentDrag.js'
import ComponentProperties from '../components/SchematicEditor/ComponentProperties'
import PresencePanel from '../components/SchematicEditor/PresencePanel'
import '../components/SchematicEditor/Helper/SchematicEditor.css'
import { fetchSchematic, fetchGallerySchematic } from '../redux/actions/index'
import { useDispatch } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'
import { usePresence } from '../utils/usePresence'
import { useCircuitSync } from '../utils/useCircuitSync'
import { useIsEditor } from '../utils/useIsEditor'

const useStyles = makeStyles((theme) => ({
root: {
Expand All @@ -36,6 +40,12 @@ export default function SchematiEditor (props) {
const [mobileOpen, setMobileOpen] = React.useState(false)
const [ltiSimResult, setLtiSimResult] = React.useState(false)

const saveId = useSelector(state => state.saveSchematicReducer.details.save_id)
const isEditor = useIsEditor()

usePresence(saveId)
useCircuitSync(saveId, isEditor)

const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen)
}
Expand Down Expand Up @@ -95,6 +105,7 @@ export default function SchematiEditor (props) {
{/* Schematic editor Right side pane */}
<RightSidebar mobileOpen={mobileOpen} mobileClose={handleDrawerToggle}>
<PropertiesSidebar gridRef={gridRef} outlineRef={outlineRef} />
<PresencePanel />
</RightSidebar>
<ComponentProperties/>
</div>
Expand Down
8 changes: 8 additions & 0 deletions eda-frontend/src/redux/actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,11 @@ export const FETCH_REPORTS = 'FETCH_REPORTS'
export const RESOLVE_REPORTS = 'RESOLVE_REPORTS'
export const GET_STATES = 'GET_STATES'
export const SET_STATE = 'SET_STATE'

// Collaboration — Presence
export const PRESENCE_CONNECTED = 'PRESENCE_CONNECTED'
export const PRESENCE_DISCONNECTED = 'PRESENCE_DISCONNECTED'
export const PRESENCE_LIST_UPDATED = 'PRESENCE_LIST_UPDATED'
export const PRESENCE_USER_JOINED = 'PRESENCE_USER_JOINED'
export const PRESENCE_USER_LEFT = 'PRESENCE_USER_LEFT'
export const PRESENCE_CLEAR_NOTIFICATION = 'PRESENCE_CLEAR_NOTIFICATION'
28 changes: 28 additions & 0 deletions eda-frontend/src/redux/actions/collaborationActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as actions from './actions'

export const presenceConnected = () => ({
type: actions.PRESENCE_CONNECTED
})

export const presenceDisconnected = () => ({
type: actions.PRESENCE_DISCONNECTED
})

export const presenceListUpdated = (users) => ({
type: actions.PRESENCE_LIST_UPDATED,
payload: { users }
})

export const presenceUserJoined = (user, users) => ({
type: actions.PRESENCE_USER_JOINED,
payload: { user, users }
})

export const presenceUserLeft = (user, users) => ({
type: actions.PRESENCE_USER_LEFT,
payload: { user, users }
})

export const presenceClearNotification = () => ({
type: actions.PRESENCE_CLEAR_NOTIFICATION
})
4 changes: 3 additions & 1 deletion eda-frontend/src/redux/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import dashboardReducer from './dashboardReducer'
import accountReducer from './accountReducer'
import projectReducer from './projectReducer'
import galleryReducer from './galleryReducer'
import presenceReducer from './presenceReducer'
export default combineReducers({
schematicEditorReducer,
componentPropertiesReducer,
Expand All @@ -19,5 +20,6 @@ export default combineReducers({
dashboardReducer,
accountReducer,
projectReducer,
galleryReducer
galleryReducer,
presenceReducer
})
Loading