From 139e7d0ef5ac5728985d1deedb45d06bb253770b Mon Sep 17 00:00:00 2001 From: BinarySandia04 Date: Thu, 30 Apr 2026 19:39:53 +0200 Subject: [PATCH] Widgets work --- backend/src/routes/campaign.js | 15 +- frontend/README.md | 19 + frontend/app/app.vue | 10 +- .../{partials => layouts}/ColorPicker.vue | 0 .../{partials => layouts}/IconButton.vue | 0 .../components/managers/ContentManager.vue | 265 +------------ .../components/partials/ContentSidebar.vue | 354 ++++++++++++++++++ frontend/app/components/viewer/TopBar.vue | 22 +- .../app/components/viewer/content/Note.vue | 24 +- .../components/viewer/widgets/TableWidget.vue | 3 + .../components/viewer/widgets/TestWidget.vue | 12 +- .../windows/CreateCampaignWindow.vue | 2 +- .../windows/partials/FixedBottomButtons.vue | 2 +- frontend/app/services/Campaign.js | 33 +- frontend/app/services/Marker.js | 44 +++ frontend/app/services/User.js | 3 +- 16 files changed, 512 insertions(+), 296 deletions(-) create mode 100644 frontend/README.md rename frontend/app/components/{partials => layouts}/ColorPicker.vue (100%) rename frontend/app/components/{partials => layouts}/IconButton.vue (100%) create mode 100644 frontend/app/components/partials/ContentSidebar.vue create mode 100644 frontend/app/components/viewer/widgets/TableWidget.vue diff --git a/backend/src/routes/campaign.js b/backend/src/routes/campaign.js index b435eb7..686a9b2 100644 --- a/backend/src/routes/campaign.js +++ b/backend/src/routes/campaign.js @@ -30,4 +30,17 @@ router.get('/list', async (req, res) => { } }); -module.exports = router; \ No newline at end of file +router.get('/retrieve/:id', async (req, res) => { + try { + if (!req.user?.id) return res.status(401).json({ status: "error", msg: "errors.unauthorized" }); + + const campaign = await Campaign.findOne({ _id: req.params.id, createdBy: req.user.id }); + if (!campaign) return res.json({ status: "error", msg: "errors.not-found" }); + + res.json({ status: "ok", campaign }); + } catch (err) { + res.json({ status: "error", msg: "errors.internal", err }); + } +}); + +module.exports = router; diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..bb74a5c --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,19 @@ +# Frontend + +This folder contains the React frontend for Dragonroll, an open-source role-playing game helper. + +## Features + +- Campaign management with character tracking +- Player note sharing (markdown) +- Audio for in-person campaigns +- Encounter planning +- Item and spell management + +## Files + +- `app/` - Application components and services +- `app/services/` - Frontend services (Campaign, User, Window, etc.) +- Styling and React components + +See the main README for complete information. diff --git a/frontend/app/app.vue b/frontend/app/app.vue index 19755c8..157b560 100644 --- a/frontend/app/app.vue +++ b/frontend/app/app.vue @@ -7,10 +7,16 @@ import { CreateWindow } from '@/services/Windows' import { GetUser, HasAdmin } from './services/User'; import TooltipManager from './components/managers/TooltipManager.vue'; import ContextMenuManager from './components/managers/ContextMenuManager.vue'; +import { useCampaignService } from './services/Campaign'; + +const { RestoreCampaign } = useCampaignService(); async function start(){ if(GetUser()){ - CreateWindow('main_menu'); + const restoredCampaign = await RestoreCampaign(); + if (!restoredCampaign) { + CreateWindow('main_menu'); + } return; } @@ -56,4 +62,4 @@ onMounted(() => { width: 100%; height: 100vh; } - \ No newline at end of file + diff --git a/frontend/app/components/partials/ColorPicker.vue b/frontend/app/components/layouts/ColorPicker.vue similarity index 100% rename from frontend/app/components/partials/ColorPicker.vue rename to frontend/app/components/layouts/ColorPicker.vue diff --git a/frontend/app/components/partials/IconButton.vue b/frontend/app/components/layouts/IconButton.vue similarity index 100% rename from frontend/app/components/partials/IconButton.vue rename to frontend/app/components/layouts/IconButton.vue diff --git a/frontend/app/components/managers/ContentManager.vue b/frontend/app/components/managers/ContentManager.vue index e2d3067..4959c0d 100644 --- a/frontend/app/components/managers/ContentManager.vue +++ b/frontend/app/components/managers/ContentManager.vue @@ -1,115 +1,17 @@ @@ -117,55 +19,7 @@ watch(Campaign, () => {
- - +
@@ -184,117 +38,4 @@ watch(Campaign, () => { flex: 1; display: flex; } - -.notes-sidebar { - width: 280px; - min-width: 280px; - border-right: 1px solid var(--color-border); - background-color: var(--color-background-light); - display: flex; - flex-direction: column; - transition: width 0.2s ease, min-width 0.2s ease; -} - -.notes-sidebar.collapsed { - width: 54px; - min-width: 54px; -} - -.sidebar-header { - padding: 12px; - display: flex; - align-items: flex-start; - gap: 10px; - border-bottom: 1px solid var(--color-border); -} - -.sidebar-toggle { - width: 30px; - height: 30px; - flex-shrink: 0; - border: 1px solid var(--color-border); - border-radius: 8px; - background: var(--color-background-soft); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; -} - -.sidebar-toggle-icon { - width: 18px; - height: 18px; - filter: invert(var(--color-icon-invert)); -} - -.sidebar-copy { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; -} - -.sidebar-eyebrow, -.sidebar-meta { - font-size: 12px; - opacity: 0.7; -} - -.sidebar-title { - line-height: 1.2; - word-break: break-word; -} - -.sidebar-list { - padding: 10px; - display: flex; - flex-direction: column; - overflow-y: auto; -} - -.sidebar-state { - padding: 12px; - border-radius: 10px; - background: var(--color-background-soft); - font-size: 14px; -} - -.sidebar-state.error { - color: #9e2a2b; -} - -.note-link { - padding: 6px; - margin: 0; - box-shadow: none; - border: none; - display: flex; - flex-direction: column; - align-items: flex-start; - text-align: left; - cursor: pointer; - transition: transform 0.15s ease, background-color 0.15s ease; -} - -.note-link:hover { - transform: translateX(2px); - background: var(--color-background-light); -} - -.note-link-title { - font-weight: 600; - word-break: break-word; -} - -.note-link-date { - font-size: 12px; - opacity: 0.7; -} - -@media (max-width: 900px) { - .notes-sidebar { - width: 220px; - min-width: 220px; - } -} diff --git a/frontend/app/components/partials/ContentSidebar.vue b/frontend/app/components/partials/ContentSidebar.vue new file mode 100644 index 0000000..243f32c --- /dev/null +++ b/frontend/app/components/partials/ContentSidebar.vue @@ -0,0 +1,354 @@ + + + + + diff --git a/frontend/app/components/viewer/TopBar.vue b/frontend/app/components/viewer/TopBar.vue index 544cbf7..a67d97b 100644 --- a/frontend/app/components/viewer/TopBar.vue +++ b/frontend/app/components/viewer/TopBar.vue @@ -2,8 +2,9 @@ import TopSearchBar from './topbar/TopSearchBar.vue'; import { useCampaignService } from '~/services/Campaign'; import { CreateWindow } from '~/services/Windows'; +import { SetShowContent } from '~/services/Content'; -const { Campaign } = useCampaignService(); +const { Campaign, SetCampaign } = useCampaignService(); const campaignName = computed(() => { return Campaign.value?.name ?? 'Campaign'; @@ -17,6 +18,12 @@ function openCreateNoteWindow() { CreateWindow('create_note'); } +function exitToMainMenu() { + SetCampaign(null); + SetShowContent(false); + CreateWindow('main_menu'); +} +