[ui] Composition API migration + mobile responsive pass#2011
Open
frankrousseau wants to merge 35 commits into
Open
[ui] Composition API migration + mobile responsive pass#2011frankrousseau wants to merge 35 commits into
frankrousseau wants to merge 35 commits into
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the spinner shown by TableInfo while a list loads with Slack-style skeleton rows that appear one by one and restart in a continuous wave. Three variants are dispatched through TableInfo: - list (default): thumbnail + name + N cell bars + actions; entity lists (asset, shot, sequence, edit, episode) opt into big-cells for validation-shaped blocks - kanban: 4 columns of stacked card placeholders, used by KanbanBoard - grid: rows of name + day-cells + actions, used by PeopleTimesheetList Each list passes the props that match its actual column shape (cells, with-thumbnail, with-actions). The shared cycle/restart and fade-in/out logic lives in a new useSkeletonCycle composable. A --skeleton-rgb variable was added so the placeholder color stays visible against both light and dark backgrounds. Also: source the asset page filter list directly from userFilters so the filter pills stay visible during asset list loading instead of being briefly cleared by LOAD_ASSETS_START. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each two-factor method (TOTP, email OTP, FIDO) is now displayed in its own card with an icon, status badge and inline configuration flow. Clicking enable expands the QR code, OTP input or device name input inside the card itself instead of stacking them above three full-width buttons. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The toggle knob was sized exactly half the track width (1.3rem of 2.6rem) and placed flush against the left/right edges, so the track only showed as a thin halo top/bottom and disappeared horizontally. Shrunk the knob slightly (1.2rem) and offset it 0.15rem inside the track on both sides so the off / on positions read correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Migrate the page from Options API to <script setup>. - Replace the green header band + negative-offset avatar trick with a centered avatar, display name and role label that adapts to the current theme. - Split content into three cards (Information, Notifications, Change password / 2FA) with right-aligned Save buttons scoped to each card. - Switch the timezone and language pickers from raw <select> to the shared Combobox, keep the English language order, append the native name in parentheses. - Replace ComboboxBoolean notification dropdowns with the toggle Checkbox; in-channel user-id text fields appear only when the channel is enabled. - Use a two-column grid for first/last name and email/phone above 768px. - Theme cleanup: page on var(--background-page), cards on var(--background) / var(--background-alt), explicit color: var(--text) inside .card and on form labels so they no longer fall back to the legacy grey color. - Add profile.notifications_title locale key in en/fr. - store/api/people: accept booleans for notifications_* flags via a small toBool helper so the new boolean-typed form keeps working alongside the legacy 'true'/'false' strings used by EditPersonModal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous Change Avatar modal sent the original file as-is. Now the modal lets the user reposition and zoom the picture inside a circular preview frame; on confirm the visible region is drawn into an offscreen 400x400 canvas, exported as a JPEG blob and packed into a fresh FormData that is forwarded to the upload action. - ChangeAvatarModal: migrated to <script setup>, drives drag (mouse + touch) and a zoom slider against a fixed preview frame, with scale bounds that keep the image always covering the circle. Object URLs are released on reset / unmount. - Profile: forwards the cropped FormData from `confirm` to the `uploadAvatar` dispatch instead of ignoring the payload. - store/modules/user.uploadAvatar: accepts an optional formData argument and falls back to state.avatarFormData so existing call sites stay working. - Locales: tighten profile.avatar.preview_hint to describe the new drag + zoom interaction (en + fr). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The plugin entries used Lucide's <icon /> without a size override, so the SVG kept its default 24x24 attribute pair. The .nav-icon / .section-icon classes only constrain width to 20px, leaving the height at 24 and stretching the icon vertically next to the other 20x20 sidebar / topbar entries. Pass :size="20" on the plugin <icon /> in the sidebar and in both topbar section-list render sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Convert Notifications.vue to <script setup>; replace mapGetters /
mapActions with useStore-backed computeds and direct dispatches.
- Inline the parametersMixin logic (URL query persistence + local
preferences) since the mixin only had this page as a consumer.
- Replace the legacy `socket: { events: ... }` option with explicit
on/off registration against the global $socket grabbed via
getCurrentInstance in onMounted / onBeforeUnmount.
- Group the five filter watchers into one watcher on the relevant
parameter keys.
- Use useHead for the page title.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a @media (max-width: 768px) block that wraps the filter row selectors across multiple lines (50% each), resets the hardcoded width on ComboboxTaskType / ComboboxStatus / ComboboxStyled so they stretch with the flex layout, tightens page / filter-bar padding, and on each notification row hides the date, entity thumbnail and read toggle while bumping padding for breathing room. - Filter bar: add a 1em top margin so the card sits below the topbar edge instead of flush against it. - Notification rows: drop the border from 4px to 3px, lighten the box shadow (0 1px 2px rgba(0,0,0,0.06) light / 0.25 dark), scale the entity thumbnail down to 30px to match the avatar so the inline metadata aligns on a single baseline, swap the `flexrow-item` margin-soup for a single `gap: 0.6em` on `.notification-header`, and move the validation tag from after the entity link to right after the type icon so the task status reads first. - Push the date to the right of the row (after the filler, before the read toggle). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace the spinner with a card-shaped skeleton loader (six rounded
blocks, staggered fade-in / fade-out via the shared
useSkeletonCycle composable). Lower the dark-mode opacity from 0.45
to 0.15 so the blocks don't glow on the dark page background.
- Add a notificationAuthor() helper that returns a placeholder
(initials "?", grey background, t('main.unknown') name) when the
notification's author isn't in personMap so the avatar slot never
silently disappears. Add main.unknown locale key (en + fr).
- Add a project avatar (ProductionName with only-avatar) next to the
task-type chip; tooltip carries the project name.
- Two-row notification body:
Row 1 — type icon, project avatar, task-type chip, entity
thumbnail, entity link, then filler / date / read toggle.
Row 2 — actor avatar, actor name, verb, optional "to" + status
(validation-tag). Only renders when the notification is
expanded (showComments or selected).
- Move the verb chain out of the expanded comment area onto the actor
row so the action description sits next to the avatar.
- Append "to {status}" inline when the notification carries a status
change, with a new notifications.to_status locale key (en / fr).
- Gate the .comment-content wrapper on showComments / isSelected so
the bottom comment area no longer leaves an empty padding strip.
- Mobile (<= 768px): hide .date, .thumbnail-wrapper, .has-text-right,
.actor-name, .verb; the row collapses to icon + project + task-type
+ entity link.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous Change Avatar modal pinned a generic file-upload button at the top with a separate preview circle below, so the empty state and the cropping interaction lived in two disconnected zones. The strings were also leaking from the CSV import flow (`main.csv.select_file` / `main.csv.upload_file`). - Drop the FileUpload widget in favour of a hidden <input type="file"> triggered by clicking the preview circle. The same circle accepts drag-and-drop files and is keyboard reachable (Enter / Space). - When no photo is loaded the circle renders as a dashed dropzone with a Lucide UploadIcon + "Click or drop a photo here"; hover, focus and drag-over paint the border green for affordance. - Once a file is selected the circle hosts the preview as before (drag to reposition, zoom slider unchanged) with a small "Choose another photo" text link and the drag/zoom hint stacked under it. - Bump the preview from 140px to 180px so drag/zoom precision is comfortable; output canvas stays at 400px. - Replace the CSV-leakage copy with three new locale keys — profile.avatar.intro, profile.avatar.drop_or_click and profile.avatar.replace — in en.js and fr.json. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The crop / drag / zoom logic that lived inside ChangeAvatarModal now
lives in a standalone widget consumed by both ChangeAvatarModal (avatar)
and EditProductionModal (production picture).
- Add src/components/widgets/ImageCropper.vue. Props: `shape` ('circle'
or 'rounded'), `size` (display, default 180px), `outputSize` (canvas,
default 400px), `outputType` / `outputQuality`. Emits `fileselected`
with the raw FormData when an image is picked or dropped. Exposes
`cropToFormData()` and `reset()` via defineExpose so parents can
request the framed FormData on confirm.
- Slim ChangeAvatarModal down to a thin wrapper that forwards
`fileselected` and calls `cropToFormData()` from its confirm handler.
- Replace the FileUpload + label block in EditProductionModal's
picture field with the cropper (shape="rounded"); make
`runConfirmation` async so the framed FormData is re-emitted via
`fileselected` before the existing `confirm` payload reaches the
parent. The existing storeProductionPicture + uploadProductionAvatar
flow keeps working since it consumes whatever was last stored.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Generate button buried in the modal footer left users guessing how
to create a link, the active link rows blended into the form, and the
revoke X was a single click away from cutting off external reviewers.
Refresh the layout so each section has a clear visual home.
- Wrap each active share link in its own card (background + border +
rounded corners) so the list reads as a stack of items instead of
bare rows. Cards swap surface in dark mode so the contained input
still pops.
- Tighten the per-row action buttons: input takes the remaining space
via flex: 1, the three icon buttons sit packed at the right with a
0.2em gap, and the revoke button is always visible (was opacity 0
until hover).
- Revoke now requires confirmation: clicking X swaps the row for a red
inline confirm strip ("Revoke this link? Anyone using it will lose
access." + Cancel / red Revoke).
- Hide the create form behind an "Add a new link" button. The button
expands the form, the form auto-closes on successful create.
- Wrap the form in a card with an uppercase "Create a new link" title.
Allow comments toggle is now a Checkbox toggle (was ComboboxBoolean,
string 'true'/'false') and sits above the expiration date. Both
fields are centered, label on top.
- Replace the modal footer's two-button row with a plain right-aligned
Close button.
- Add an empty-state placeholder when no share link exists yet.
Locale: add playlists.share_modal.revoke_confirm, .no_links and
.add_link (en).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Settings page reuses ChangeAvatarModal for the studio / organisation logo, where a circular crop doesn't match how the logo is displayed. Expose a `shape` prop on the modal (default 'circle') that is forwarded to ImageCropper, and pass shape="rounded" from Settings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apply the Profile page's card-on-page rhythm so the studio settings
read as grouped sections instead of one long form in a single card.
- Page background uses var(--background-page); content column at
720px max with a centered studio header (rounded logo frame + studio
name) and three cards stacked below.
- Header logo: 120x120 rounded frame (matches the new shape="rounded"
cropper) with inner padding so the image is inset. Falls back to a
dashed frame showing the studio's first letter when no logo is set.
Text-link actions ("Set / Change logo", "Remove logo") sit below.
- Cards: Studio info (name + hours-by-day), Preferences (five
Checkbox toggle widgets, was ComboboxBoolean with 'true' / 'false'
strings), Integrations (Slack / Discord / Mattermost tokens with
the webhook error inline). Each card ends in a right-aligned green
Save button.
- Form values are now real booleans; the watcher coerces with
Boolean(...) when hydrating from the store and saveSettings spreads
the form directly rather than mapping each '=== "true"' field.
- Theme tokens replace the hardcoded white / $dark-grey-lighter card
backgrounds. Cards stack with 1.5rem margin-bottom between
siblings.
- New locale key: settings.preferences_title (en + fr).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Profile and Settings duplicated the same card + save button + error markup three times each, the save buttons shared a single loading flag so clicking one spun all three, and the TOTP widget had the same issue across nine actions. - Add src/components/widgets/Card.vue. Props: title, error, success, saveLabel, loading, disabled. Renders the card surface, uppercase title, the slotted body, and (optionally) a right-aligned green save button that emits @save. Error / success messages render automatically when the corresponding prop is set. - Profile / Settings now use <card> directly: local per-section loading + errors refs (loading.info / notifications in Profile, loading.studio / preferences / integrations in Settings) replace the shared Vuex flag, so each card spins independently. The duplicated .card / .card-title / .card-actions / .save-button CSS is gone from both pages. - user.saveProfile now returns the promise it kicks off and rethrows on error so the page-level handlers can await it. - TwoFactorAuthenticationSetup: replace the single twoFA.isLoading boolean (shared by every button in the widget) with twoFA.loadingAction (string identifying which action is in flight: enableTOTP, preEnableTOTP, disableTOTP, enableEmailOTP, preEnableEmailOTP, disableEmailOTP, registerFIDO, unregisterFIDO, newRecoveryCodes, or null). Each button checks its own action name so clicking on one TOTP action no longer spins the rest. Buttons that only open a validation flow (disable TOTP / disable email OTP / generate new recovery codes / pre-register FIDO) drop the loading state entirely — they never actually waited for a request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Convert AssetLibrary.vue to <script setup>; replace mapGetters / mapActions with useStore-backed computeds and direct dispatches. - Drop the searchMixin dependency: this page only relied on the inherited setSearchInUrl helper (it overrides onSearchChange and doesn't use the route watcher), so inline a small setSearchInUrl that pushes the search query into the URL. - Group imports per CLAUDE.md (third-party / @/lib / components by folder: layouts → sides → widgets). - Use useHead for the page title. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Hide the "Add to library" side panel under 768px by listening to a
matchMedia query in setup and gating PageLayout's `:side` prop.
- Wrap the filter row on mobile: each filter stretches to 100% width
and stacks vertically with a clean 0.75em gap. Reset the global
.field margin-bottom (only ComboboxProduction and the sort Combobox
carry it; SearchField doesn't) so the rhythm doesn't read as tight
above one filter and loose below the next.
- Tighten the page padding (2.5em → 4.5em on top so the title clears
the topbar, narrower side margins) and explicit margin-bottom on
the header and the filter row.
- Reset the global `ul { margin-left: 1em }` on `.items` so
justify-content: center actually centers the asset cards on mobile.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Page: convert AssetTypes.vue to <script setup>; replace mapGetters /
mapActions with useStore-backed computeds + dispatches. Drop the
carried-over `choices: []` data field that was never read. Rebuild
the confirm handlers around try / await / catch instead of nested
.then chains, with a `{ ...form, id }` spread so the incoming form
is never mutated. Switch the export builder from
`[headers].concat(rows)` to `[headers, ...rows]`. Make `tabs` a
computed so the labels follow locale changes.
- List: convert AssetTypeList.vue to <script setup>; switch the
deprecated `$tc('asset_types.number', n)` call to the equivalent
`$t('asset_types.number', n)` (vue-i18n 9 pipe pluralization).
Rename the `sortTaskTypes` method to `sortedTaskTypes` to avoid
shadowing the imported helper of the same name.
- Both: apply the Studios.vue responsive recipe — `.asset-types`
gets tighter side padding under 768px; the list collapses the
fixed-width `.name` column and tightens table padding at the same
breakpoint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Under 768px transform each table row of AssetTypeList into a self
contained card so the page no longer relies on horizontal scrolling.
- Tag every <td> with a data-label so it can carry its own caption.
- Drop <thead>, switch every table element to display: block, and
render each <tr> as a rounded card (var(--background) light /
var(--background-alt) dark, 1px border, 12px radius, 0.75em gap).
- A `::before { content: attr(data-label) }` pseudo-element prints
the small uppercase muted caption above every cell value. The name
cell doubles as the card title, so its label is hidden and its
weight is bumped.
- Hide the action buttons on mobile (`.datatable-row .actions { display
: none }`) — the edit / delete flow stays available on desktop.
- Hide empty short-name cells via a data-label-less placeholder <td>
plus a `:not([data-label])` selector that keeps the desktop column
layout intact.
- Keep the wrapper's vertical overflow scrolling (only override
`overflow-x: visible`) so the list still scrolls inside .fixed-page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Page: convert Departments.vue to <script setup>; replace mapGetters /
mapActions with useStore-backed computeds and dispatches. Rewrite the
edit confirm handler around try / await with `{ ...form, id }` spread
so the incoming form isn't mutated. Switch the export builder from
`[headers].concat(rows)` to `[headers, ...rows]`. Make `tabs` a
computed so labels follow locale changes.
- List: convert DepartmentList.vue to <script setup> and switch the
deprecated `$tc('departments.number', n)` to the pipe-pluralization
form `$t('departments.number', n)`.
- Both: apply the Studios.vue responsive recipe — page gets tighter
side padding under 768px and the list folds rows into mobile cards
(data-label captions, hidden head/actions) like AssetTypeList.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Under 768px the linked-hardware / linked-software tabs collapse the three-column layout into a vertical stack, and the "Available items" column is hidden — linking new items stays a desktop-only flow. The list (departments + items linked to the selected department) is what mobile actually needs. Also reset the .color cell's hard-coded 20x20 sizing inside the mobile DepartmentList card so the color row gets its own line with the label above and the colored circle below. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Page: convert TaskStatus.vue to <script setup>; replace mapGetters /
mapActions with useStore-backed computeds and dispatches. Rebuild the
edit confirm handler around try / await with a `{ ...form, id }`
spread so the incoming form isn't mutated. Switch the export builder
from `[headers].concat(rows)` to `[headers, ...rows]`. Make `tabs`
and `entityTabs` computed so the labels follow locale changes. Move
`deleteText` from a method to a computed for cheap reactivity.
- List: convert TaskStatusList.vue to <script setup>; drop the unused
formatListMixin (no format helper is actually used in the template).
Replace deprecated `$tc('task_status.number', n)` with the
pipe-pluralization form `$t('task_status.number', n)`. Initialise
the draggable copy with a fresh array spread so reordering doesn't
mutate the parent's prop.
- Both: apply the Studios.vue responsive recipe — page gets tighter
side padding under 768px and the list folds rows into mobile cards
(data-label captions, hidden head/actions) like AssetTypeList.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the short-name (status chip) column before the name column in both the header and the row template, and drop the hardcoded class="name" from TaskStatusCell so it no longer steals the parent list's .name styling. Add class="datatable-row-footer" on the row actions so the edit / delete buttons stay hidden on desktop and slide in sticky-right on row hover, matching the People page behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Convert HardwareItems.vue and HardwareItemList.vue to <script setup>:
swap mapGetters/mapActions for useStore-backed computeds and dispatches,
make tabs a computed so labels react to locale changes, replace head()
with useHead, and switch the deprecated $tc('hardware_items.number') for
$t with pipe pluralization. Stop mutating the form arg in the edit
confirm handler by spreading { ...form, id }, and rewrite the
usedAmounts / remainingHardwareItems accumulators with reduce.
Add the standard 768px responsive parity block on the page and the
shared mobile card layout on the list (hide thead, stack cells as a
labelled card via data-label attrs, hide the actions column).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Convert SalaryScale.vue to <script setup>: data() refs, mapGetters becomes a useStore-backed computed, mapActions becomes inline store.dispatch calls, mounted runs the setSalaryScale loader. Add a parallel mobile rendering that loops departments x positions x seniorities and stacks each department as a card with position blocks and labelled inputs styled to match the desktop input-editor. Hide the table under 768px and force the desktop table to 100% width so it no longer sits narrow on the left. Disable the empty side column via :side="false" so the page fills the full width. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrate SoftwareLicenses.vue and SoftwareLicenseList.vue to
<script setup>: mapGetters/mapActions becomes useStore-backed computeds
and dispatches, tabs become a computed so labels track locale changes,
head() becomes useHead, and the deprecated $tc('software_licenses.number')
is replaced with $t pipe pluralization. The usedAmounts /
remainingSoftwareLicenses accumulators become reduce, and the edit
confirm handler stops mutating its form arg. Add the standard 768px
responsive parity block on the page and the shared mobile card layout
on the list, including the datatable-row-footer class so action
buttons fly in on hover like on the People page.
Fix the mobile card background bug on both SoftwareLicenseList and
HardwareItemList: the global App.vue rule
`.datatable-row:last-child:nth-child(even) td { background-color:
var(--background-alt) }` was painting the last row's cells a different
color from the row itself, breaking the uniform card look. Force the
tr to var(--background) (or var(--background-alt) in dark mode) with
!important and neutralize the td background-color so the card color
shows through cleanly for every entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Convert TaskTypes.vue and TaskTypeList.vue to <script setup>:
mapGetters/mapActions become useStore-backed computeds and dispatches,
tabs and entityTabs become computeds so labels track locale changes,
head() becomes useHead, $tc('task_types.number') is replaced with $t
pipe pluralization. The export builder uses [headers, ...rows], the
edit confirm handler stops mutating its form arg, and updatePriority
rewrites the forEach+push accumulator as a single .map. The savePriorities
throttling state moves from implicit `this` properties to plain `let`
variables in the module scope.
Add the standard 768px responsive parity block on the list (mobile card
layout with data-label attributes, last-card background override, td
background neutralization) plus the datatable-row-footer class on
row-actions so edit/delete buttons fly in on hover. Disable drag-and-drop
on mobile via an isMobile ref bound to a window.resize listener, and
reset the grab cursor under 768px. Drop two dead CSS selectors
(.priority and .color) that did not match any rendered element.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Annotations were silently lost when the save round-trip failed: the mixin wiped additions/updates/deletions before the emit and the Vuex action swallowed errors behind a misleading alert. Now the mixin swaps active arrays into a pendingSave buffer, and the parent's async handler confirms (drops buffer) or restores (merges back + schedules retry) based on the action's outcome. The Vuex action captures failures to Sentry with payload context and rethrows. Guard against an undefined annotations payload in UPDATE_PREVIEW_ANNOTATION mutation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bind `<draggable :disabled="isMobile">` and track viewport width with an isMobile ref attached to window.resize (registered in onMounted, torn down in onBeforeUnmount). Reset the cursor: grab to default under 768px so the row no longer suggests a draggable interaction it can't fulfill there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Convert EntitySearch.vue to <script setup>: mapGetters/mapActions become useStore-backed computeds and dispatches, data() splits into refs for scalars and reactive blocks for results / searchFilter, the template ref is renamed to searchField and bound directly, the keydown listener moves to onMounted/onBeforeUnmount and head() becomes useHead. The string-keyed `this[isLoadingMore + capitalize(name)]` branching is replaced with a direct ref ternary, so the stringHelpers import is no longer needed. Drop the dead personPath method and getPersonPath import that were not referenced from the template. Center the result cards under 768px so the wrapped grid no longer left-aligns with empty space on the right. In ComboboxProduction, force ProductionName's .avatar-name back on inside the combobox under 768px - the global rule hides it to save space everywhere else, but inside a production picker the name is the whole point. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… loading
Convert ProductionNewsFeed.vue to <script setup>: mapGetters/mapActions
become useStore-backed computeds and dispatches, the timeMixin becomes
useTime(), the socket events: { ... } block becomes socket.on/off in
onMounted/onBeforeUnmount (socket pulled via
getCurrentInstance().appContext.config.globalProperties.$socket, same
pattern as Notifications.vue), the array-style :ref="`news-${id}`"
becomes a setNewsRef function-ref backed by a plain Map, head() becomes
useHead, and watchers split into one watch() per source. Drop dead code:
isStatsDisplayed, toggleStats, renderedStats, statMax, personName,
getPreviewPath, getPreviewDlPath and the orphan .stats CSS block.
Extract three page-scoped components under src/components/pages/news/:
NewsRow owns a single feed entry (comments + previews modes, own
helpers and store getters, emits @select), NewsSkeleton wraps the
useSkeletonCycle loading placeholder, NewsFilters owns the entire
filter strip (episode/status/task-type/person/from/to/preview-mode)
plus its own toggleFilters state and option-list computeds. The parent
talks to them via multiple defineModel v-models.
Replace the spinner with a skeleton-cards loader while news are
fetching. Fade the timeline rail's blue left-border to transparent
when loading or empty (isTimelineBlank computed + .timeline-blank
class with a 0.2s transition) so the rail doesn't run alongside an
empty section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mobile drawer for the task panel: under 1024px the .side-column
switches to position: fixed and slides in from the right when a news
is selected. A `.drawer-backdrop` sibling catches outside taps to
close, and a sticky close button at the top right of the drawer
(plus Escape) provides explicit dismissal. Drawer sits at z-index
250 above the topbar (204) so it covers the full viewport, and uses
`min(100vw, 420px)` for the width — full-screen on phones, capped at
420px on tablets. Overrides App.vue's global `.side-column` width
and margin-top with !important to win the cascade.
NewsRow gets a single root `<div ref="root" class="news-entry">` and
exposes the ref via defineExpose so the parent can call
getBoundingClientRect on a real DOM node instead of a Vue component
proxy.
Mobile timeline cleanup under 768px:
- Hide the blue rail (`.timeline { border-left: 0 }`) and big-dots in
the day headers — chronology still reads via the sticky day labels.
- Hide the per-row dot, reset task-type/validation/date min-widths so
short tags hug their content, wrap `.comment-content` to a second
line with `flex-basis: 100%` and a padding-left aligning the entity
thumbnail with the avatar above.
- Hide the filter toggle, episode filter and person filter from
NewsFilters; only task-status and task-type stay.
- Add `:active` next to `:hover` so the border highlight shows on
touch taps the same way it does on mouse hover.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PictureViewer.getDimensions() started from the image's natural width and only ever shrunk it to fit the container, so small images stayed at their natural size in fullscreen instead of filling the viewport. Mirror the VideoViewer behaviour in fullscreen mode: scale to the container width and clamp by defaultHeight while preserving the aspect ratio. Fixes cgwire#1990 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problems
<script setup>components and forcing legacy$tc/head()patternsnews:newsocket event arrives#2d2d2d) is barely visible against its#212121background.mp4Solutions
<script setup>with proper section layout,useStoregetters,useHead, and pipe pluralization: AssetLibrary, AssetTypes, Departments, TaskStatus, TaskTypes, HardwareItems, SoftwareLicenses, SalaryScale, EntitySearch, ProductionNewsFeed, Profile, Settings, Notifications, plus their tightly-coupled list components<thead>, hidden actions, last-card background fix). Add a 1024 px breakpoint for the news task panel that becomes a slide-in drawer with backdrop + sticky close button + Escape dismissalNewsRow,NewsSkeleton,NewsFiltersfrom the page, add an illustrated empty state, sticky day headers (position: stickywith the compound.timeline-entry.day-stickyselector to outrank.timeline-entry { position: relative }), and a slide-down + light-green pulse animation for entries arriving via thenews:newsocket<draggable>on mobile (:disabled="isMobile"+ window.resize tracking) for TaskStatus and TaskTypes listsCardwidget owning its own save button, error and success state; per-action loading instead of a single page-wide flagImageCropperwidget that crops on the client before upload (circle shape for avatars, rounded for production pictures); reused by bothChangeAvatarModalandEditProductionModal, with the avatar preview circle becoming the centerpiece of the modaluseSkeletonCyclecomposable; shipGridLoadingSkeleton,ListLoadingSkeleton,KanbanLoadingSkeleton,NewsSkeleton--dp-border-coloron.dp__theme_darkto#3a3a3awith!important(the lib CSS is imported after App.vue in main.js)