├── .browserslistrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.cjs
├── .git-blame-ignore-revs
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── ci.yml
│ ├── release.yml
│ └── staging.yml
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── .mergify.yml
├── .npmrc
├── .postcssrc.cjs
├── .prettierignore
├── .prettierrc.json
├── .storybook
├── main.js
└── preview.js
├── CONTRIBUTING.md
├── DEVELOPMENT.md
├── LICENSE
├── README.md
├── RELEASE.md
├── cypress.json
├── docs
├── contributing.md
└── translation.md
├── index.html
├── jsconfig.json
├── package-lock.json
├── package.json
├── public
├── css
│ └── bulma.css
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── fonts
│ └── Lato.woff2
└── robots.txt
├── scripts
├── missinglocales.js
└── release.sh
├── src
├── App.vue
├── assets
│ ├── avatar-wrapper.png
│ ├── background
│ │ ├── player-timeslider.png
│ │ ├── schedule-dark-1-weekend.svg
│ │ ├── schedule-dark-1.svg
│ │ ├── schedule-dark-2-weekend.svg
│ │ ├── schedule-dark-3-weekend.svg
│ │ ├── schedule-dark-4-weekend.svg
│ │ ├── schedule-white-1-weekend.svg
│ │ ├── schedule-white-1.svg
│ │ ├── schedule-white-2-weekend.svg
│ │ ├── schedule-white-3-weekend.svg
│ │ └── schedule-white-4-weekend.svg
│ ├── icons
│ │ ├── assets.png
│ │ ├── casting-not-ready.png
│ │ ├── casting-ready.png
│ │ ├── fi-add-preview.svg
│ │ ├── fi-add-thumbnail.svg
│ │ ├── fi-alert-circle.svg
│ │ ├── fi-annotations.svg
│ │ ├── fi-asset-stats.svg
│ │ ├── fi-asset.svg
│ │ ├── fi-assets.svg
│ │ ├── fi-assign-1.svg
│ │ ├── fi-automations.svg
│ │ ├── fi-big-thumbnail.svg
│ │ ├── fi-box.svg
│ │ ├── fi-breakdown.svg
│ │ ├── fi-calendar.svg
│ │ ├── fi-check-circle.svg
│ │ ├── fi-check-square.svg
│ │ ├── fi-compare.svg
│ │ ├── fi-copy.svg
│ │ ├── fi-delete.svg
│ │ ├── fi-departments.svg
│ │ ├── fi-download.svg
│ │ ├── fi-edit-2.svg
│ │ ├── fi-edit.svg
│ │ ├── fi-edits.svg
│ │ ├── fi-entity-search.svg
│ │ ├── fi-episode-stats.svg
│ │ ├── fi-episodes.svg
│ │ ├── fi-export-csv.svg
│ │ ├── fi-export-ligne.svg
│ │ ├── fi-export-lines.svg
│ │ ├── fi-eye.svg
│ │ ├── fi-filter.svg
│ │ ├── fi-image.svg
│ │ ├── fi-import-csv.svg
│ │ ├── fi-import-edl.svg
│ │ ├── fi-import-files.svg
│ │ ├── fi-info-hide.svg
│ │ ├── fi-info.svg
│ │ ├── fi-laser.svg
│ │ ├── fi-layers.svg
│ │ ├── fi-logs.svg
│ │ ├── fi-maximize.svg
│ │ ├── fi-message-square.svg
│ │ ├── fi-my-productions.svg
│ │ ├── fi-my-tasks.svg
│ │ ├── fi-news.svg
│ │ ├── fi-play.svg
│ │ ├── fi-playlist.svg
│ │ ├── fi-playlists.svg
│ │ ├── fi-productions.svg
│ │ ├── fi-quotas.svg
│ │ ├── fi-repeat.svg
│ │ ├── fi-rotate-ccw.svg
│ │ ├── fi-sequence-stats.svg
│ │ ├── fi-sequences.svg
│ │ ├── fi-settings.svg
│ │ ├── fi-shots.svg
│ │ ├── fi-skip-forward-1.svg
│ │ ├── fi-skip-forward.svg
│ │ ├── fi-skybox-desactive.svg
│ │ ├── fi-skybox.svg
│ │ ├── fi-sliders.svg
│ │ ├── fi-sound-off.svg
│ │ ├── fi-sound-on.svg
│ │ ├── fi-task-status.svg
│ │ ├── fi-task-types.svg
│ │ ├── fi-team-schedule.svg
│ │ ├── fi-text.svg
│ │ ├── fi-timesheets.svg
│ │ ├── fi-trash-2.svg
│ │ ├── fi-user-check.svg
│ │ ├── fi-user.svg
│ │ ├── fi-users.svg
│ │ ├── fi-waveform.svg
│ │ ├── fi-zoom-in.svg
│ │ ├── fi-zoom-out.svg
│ │ ├── fi_user-bot.svg
│ │ ├── movie-thumbnail.png
│ │ ├── sequences.png
│ │ ├── shots.png
│ │ └── trash.svg
│ ├── illustrations
│ │ ├── 404.png
│ │ ├── 500.png
│ │ ├── empty_asset.png
│ │ ├── empty_edit.png
│ │ ├── empty_production.png
│ │ ├── empty_shot.png
│ │ ├── empty_todo.png
│ │ ├── kitsu-band.png
│ │ └── kitsu-with-body.png
│ ├── kitsu-text-dark.svg
│ ├── kitsu-text.png
│ ├── kitsu-text.svg
│ ├── kitsu.png
│ ├── kitsu.svg
│ ├── logo-dark.svg
│ ├── logo.png
│ ├── logo.svg
│ ├── spinner-white.svg
│ └── spinner.svg
├── components
│ ├── Main.vue
│ ├── cells
│ │ ├── BooleanCell.vue
│ │ ├── DepartmentNamesCell.vue
│ │ ├── DescriptionCell.vue
│ │ ├── LastCommentCell.vue
│ │ ├── MetadataHeader.vue
│ │ ├── MetadataInput.vue
│ │ ├── PeopleNameCell.vue
│ │ ├── ProductionNameCell.vue
│ │ ├── RowActionsCell.vue
│ │ ├── StatsCell.vue
│ │ ├── TaskStatusCell.vue
│ │ ├── TaskTypeCell.vue
│ │ ├── TimeSliderCell.vue
│ │ ├── ValidationCell.vue
│ │ └── ValidationHeader.vue
│ ├── layouts
│ │ ├── PageLayout.vue
│ │ └── PageLeftSideLayout.vue
│ ├── lists
│ │ ├── AllTaskList.vue
│ │ ├── AssetList.vue
│ │ ├── AssetTypeList.vue
│ │ ├── BackgroundList.vue
│ │ ├── CustomActionList.vue
│ │ ├── DayOffList.vue
│ │ ├── DepartmentList.vue
│ │ ├── EditList.vue
│ │ ├── EntityTaskList.vue
│ │ ├── EpisodeList.vue
│ │ ├── EpisodeStatsList.vue
│ │ ├── KanbanBoard.vue
│ │ ├── PeopleList.vue
│ │ ├── PeopleTimesheetList.vue
│ │ ├── PreviewFileList.vue
│ │ ├── ProductionAssetTypeList.vue
│ │ ├── ProductionList.vue
│ │ ├── ProductionTeamList.vue
│ │ ├── QuotaShotList.vue
│ │ ├── SequenceList.vue
│ │ ├── SequenceStatsList.vue
│ │ ├── ShotList.vue
│ │ ├── StatusAutomationList.vue
│ │ ├── StudioList.vue
│ │ ├── TaskList.vue
│ │ ├── TaskStatusList.vue
│ │ ├── TaskTypeList.vue
│ │ ├── TimeSpentTaskList.vue
│ │ ├── TimesheetList.vue
│ │ └── TodosList.vue
│ ├── mixins
│ │ ├── annotation.js
│ │ ├── descriptors.js
│ │ ├── dom.js
│ │ ├── entities.js
│ │ ├── entity.js
│ │ ├── entity_list.js
│ │ ├── format.js
│ │ ├── fullscreen.js
│ │ ├── grablist.js
│ │ ├── page.js
│ │ ├── parameters.js
│ │ ├── player.js
│ │ ├── previewRoom.js
│ │ ├── search.js
│ │ ├── selection.js
│ │ ├── task.js
│ │ └── time.js
│ ├── modals
│ │ ├── AddAttachmentModal.vue
│ │ ├── AddMetadataModal.vue
│ │ ├── AddPreviewModal.vue
│ │ ├── AddThumbnailsModal.vue
│ │ ├── BaseModal.vue
│ │ ├── BuildFilterModal.vue
│ │ ├── BuildPeopleFilterModal.vue
│ │ ├── ChangeAvatarModal.vue
│ │ ├── ChangePasswordModal.vue
│ │ ├── ConfirmModal.vue
│ │ ├── CreateTasksModal.vue
│ │ ├── DayOffModal.vue
│ │ ├── DeleteModal.vue
│ │ ├── EditAssetModal.vue
│ │ ├── EditAssetTypeModal.vue
│ │ ├── EditAvatarModal.vue
│ │ ├── EditBackgroundModal.vue
│ │ ├── EditBudgetEntryModal.vue
│ │ ├── EditBudgetModal.vue
│ │ ├── EditCommentModal.vue
│ │ ├── EditCustomActionModal.vue
│ │ ├── EditDepartmentsModal.vue
│ │ ├── EditEditModal.vue
│ │ ├── EditEpisodeModal.vue
│ │ ├── EditHistoryModal.vue
│ │ ├── EditLabelModal.vue
│ │ ├── EditMilestoneModal.vue
│ │ ├── EditPersonModal.vue
│ │ ├── EditPlaylistModal.vue
│ │ ├── EditProductionModal.vue
│ │ ├── EditSearchFilterGroupModal.vue
│ │ ├── EditSearchFilterModal.vue
│ │ ├── EditSequenceModal.vue
│ │ ├── EditShotModal.vue
│ │ ├── EditStatusAutomationModal.vue
│ │ ├── EditStudiosModal.vue
│ │ ├── EditTaskStatusModal.vue
│ │ ├── EditTaskTypeModal.vue
│ │ ├── HardDeleteModal.vue
│ │ ├── ImportEdlModal.vue
│ │ ├── ImportModal.vue
│ │ ├── ImportRenderModal.vue
│ │ ├── ManageShotsModal.vue
│ │ ├── ModalFooter.vue
│ │ ├── NewTokenModal.vue
│ │ ├── PreviewModal.vue
│ │ ├── SelectTaskTypeModal.vue
│ │ ├── SetFramesFromTaskTypePreviewsModal.vue
│ │ ├── ShortcutModal.vue
│ │ ├── ShotHistoryModal.vue
│ │ ├── ViewPlaylistModal.vue
│ │ └── base_modal.js
│ ├── pages
│ │ ├── AllTasks.vue
│ │ ├── Asset.vue
│ │ ├── AssetLibrary.vue
│ │ ├── AssetTypes.vue
│ │ ├── Assets.vue
│ │ ├── Backgrounds.vue
│ │ ├── Bots.vue
│ │ ├── Breakdown.vue
│ │ ├── Brief.vue
│ │ ├── Concepts.vue
│ │ ├── CustomActions.vue
│ │ ├── Departments.vue
│ │ ├── Edit.vue
│ │ ├── Edits.vue
│ │ ├── EntityChats.vue
│ │ ├── EntitySearch.vue
│ │ ├── Episode.vue
│ │ ├── EpisodeStats.vue
│ │ ├── Episodes.vue
│ │ ├── FirstConnection.vue
│ │ ├── Login.vue
│ │ ├── Logs.vue
│ │ ├── MainSchedule.vue
│ │ ├── MyChecks.vue
│ │ ├── NotFound.vue
│ │ ├── Notifications.vue
│ │ ├── OpenProductions.vue
│ │ ├── People.vue
│ │ ├── Person.vue
│ │ ├── Playlist.vue
│ │ ├── ProductionAssetTypes.vue
│ │ ├── ProductionNewsFeed.vue
│ │ ├── ProductionQuota.vue
│ │ ├── ProductionSchedule.vue
│ │ ├── ProductionSettings.vue
│ │ ├── Productions.vue
│ │ ├── Profile.vue
│ │ ├── ResetChangePassword.vue
│ │ ├── ResetPassword.vue
│ │ ├── Sequence.vue
│ │ ├── SequenceStats.vue
│ │ ├── Sequences.vue
│ │ ├── ServerDown.vue
│ │ ├── Settings.vue
│ │ ├── Shot.vue
│ │ ├── Shots.vue
│ │ ├── StatusAutomations.vue
│ │ ├── Studios.vue
│ │ ├── Task.vue
│ │ ├── TaskStatus.vue
│ │ ├── TaskType.vue
│ │ ├── TaskTypes.vue
│ │ ├── Team.vue
│ │ ├── TeamSchedule.vue
│ │ ├── Timesheets.vue
│ │ ├── Todos.vue
│ │ ├── WrongBrowser.vue
│ │ ├── breakdown
│ │ │ ├── AssetBlock.vue
│ │ │ ├── AvailableAssetBlock.vue
│ │ │ └── ShotLine.vue
│ │ ├── budget
│ │ │ ├── Budget.vue
│ │ │ ├── BudgetAnalytics.vue
│ │ │ ├── BudgetHeader.vue
│ │ │ ├── BudgetList.vue
│ │ │ └── SalaryScale.vue
│ │ ├── entities
│ │ │ ├── EntityChat.vue
│ │ │ ├── EntityChatDays.vue
│ │ │ ├── EntityNews.vue
│ │ │ ├── EntityOutputFiles.vue
│ │ │ ├── EntityPreviewFileCard.vue
│ │ │ ├── EntityPreviewFiles.vue
│ │ │ └── EntityTimeLogs.vue
│ │ ├── logs
│ │ │ ├── Events.vue
│ │ │ └── PreviewFiles.vue
│ │ ├── playlists
│ │ │ ├── PlaylistPlayer.vue
│ │ │ ├── PlaylistedEntity.vue
│ │ │ └── RawVideoPlayer.vue
│ │ ├── production
│ │ │ ├── NewProduction.vue
│ │ │ ├── ProductionBackgrounds.vue
│ │ │ ├── ProductionBoard.vue
│ │ │ ├── ProductionBrief.vue
│ │ │ ├── ProductionParameters.vue
│ │ │ ├── ProductionStats.vue
│ │ │ ├── ProductionStatusAutomations.vue
│ │ │ ├── ProductionTaskType.vue
│ │ │ ├── ProductionTaskTypes.vue
│ │ │ └── TimelineItem.vue
│ │ ├── quota
│ │ │ └── Quota.vue
│ │ └── tasktype
│ │ │ └── EstimationHelper.vue
│ ├── previews
│ │ ├── BrowsingBar.vue
│ │ ├── MultiPictureViewer.vue
│ │ ├── ObjectViewer.vue
│ │ ├── PictureViewer.vue
│ │ ├── PlaylistProgress.vue
│ │ ├── PreviewPlayer.vue
│ │ ├── PreviewViewer.vue
│ │ ├── PreviewsPerTaskType.vue
│ │ ├── RevisionPreview.vue
│ │ ├── SoundViewer.vue
│ │ ├── VideoProgress.vue
│ │ └── VideoViewer.vue
│ ├── sides
│ │ ├── ManageLibrary.vue
│ │ ├── PeopleQuotaInfo.vue
│ │ ├── PeopleTimesheetInfo.vue
│ │ ├── Sidebar.vue
│ │ └── TaskInfo.vue
│ ├── spinners
│ │ ├── Origami.vue
│ │ └── SquareGrid.vue
│ ├── tops
│ │ ├── ActionPanel.vue
│ │ ├── GlobalSearchField.vue
│ │ ├── Topbar.vue
│ │ ├── TopbarEpisodeList.vue
│ │ ├── TopbarProductionList.vue
│ │ ├── TopbarSectionList.vue
│ │ └── actions
│ │ │ └── DeleteEntities.vue
│ └── widgets
│ │ ├── AddComment.vue
│ │ ├── AssignationItem.vue
│ │ ├── BigThumbnailsButton.vue
│ │ ├── BooleanField.vue
│ │ ├── BooleanRep.vue
│ │ ├── ButtonHrefLink.vue
│ │ ├── ButtonLink.vue
│ │ ├── ButtonSimple.vue
│ │ ├── Checkbox.vue
│ │ ├── Checklist.vue
│ │ ├── ColorField.vue
│ │ ├── ColorPicker.vue
│ │ ├── Combobox.vue
│ │ ├── ComboboxBoolean.vue
│ │ ├── ComboboxDepartment.vue
│ │ ├── ComboboxMask.vue
│ │ ├── ComboboxModel.vue
│ │ ├── ComboboxNumber.vue
│ │ ├── ComboboxProduction.vue
│ │ ├── ComboboxSimple.vue
│ │ ├── ComboboxStatus.vue
│ │ ├── ComboboxStatusAutomation.vue
│ │ ├── ComboboxStudio.vue
│ │ ├── ComboboxStyled.vue
│ │ ├── ComboboxTag.vue
│ │ ├── ComboboxTaskType.vue
│ │ ├── Comment.vue
│ │ ├── CommentMenu.vue
│ │ ├── ConceptCard.vue
│ │ ├── DateField.vue
│ │ ├── DepartmentName.vue
│ │ ├── EntityPreview.vue
│ │ ├── EntityThumbnail.vue
│ │ ├── ErrorText.vue
│ │ ├── FileUpload.vue
│ │ ├── GroupButton.vue
│ │ ├── InfoQuestionMark.vue
│ │ ├── KitsuIcon.vue
│ │ ├── LightEntityThumbnail.vue
│ │ ├── ListPageHeader.vue
│ │ ├── MetadataField.vue
│ │ ├── MonthField.vue
│ │ ├── NotificationBell.vue
│ │ ├── PageSubtitle.vue
│ │ ├── PageTitle.vue
│ │ ├── PencilPicker.vue
│ │ ├── PeopleAvatar.vue
│ │ ├── PeopleAvatarWithMenu.vue
│ │ ├── PeopleField.vue
│ │ ├── PeopleName.vue
│ │ ├── PreviewRoom.vue
│ │ ├── PreviewRow.vue
│ │ ├── ProductionName.vue
│ │ ├── RouteSectionTabs.vue
│ │ ├── RouteTabs.vue
│ │ ├── Schedule.vue
│ │ ├── SearchField.vue
│ │ ├── SearchQueryList.vue
│ │ ├── SettingImporter.vue
│ │ ├── ShowAssignationsButton.vue
│ │ ├── ShowInfosButton.vue
│ │ ├── SortingInfo.vue
│ │ ├── Spinner.vue
│ │ ├── StatusAutomationItem.vue
│ │ ├── StatusStats.vue
│ │ ├── StudioName.vue
│ │ ├── SubscribeButton.vue
│ │ ├── TableHeaderMenu.vue
│ │ ├── TableInfo.vue
│ │ ├── TableMetadataHeaderMenu.vue
│ │ ├── TableMetadataSelectorMenu.vue
│ │ ├── TaskListNumbers.vue
│ │ ├── TaskTypeName.vue
│ │ ├── TextField.vue
│ │ ├── TextareaField.vue
│ │ ├── TwoFactorAuthentication.vue
│ │ ├── UserCalendar.vue
│ │ └── ValidationTag.vue
├── directives
│ └── resizable-column.js
├── lib
│ ├── array.js
│ ├── auth.js
│ ├── clipboard.js
│ ├── color2.js
│ ├── colors.js
│ ├── crisp.js
│ ├── csv.js
│ ├── descriptors.js
│ ├── drafts.js
│ ├── emojis.js
│ ├── errors.js
│ ├── files.js
│ ├── filtering.js
│ ├── func.js
│ ├── i18n.js
│ ├── indexing.js
│ ├── init.js
│ ├── lang.js
│ ├── models.js
│ ├── pagination.js
│ ├── path.js
│ ├── playlist.js
│ ├── preferences.js
│ ├── productions.js
│ ├── query.js
│ ├── render.js
│ ├── selection.js
│ ├── sentry.js
│ ├── sorting.js
│ ├── stats.js
│ ├── string.js
│ ├── time.js
│ ├── timezone.js
│ ├── video.js
│ └── webauthn.js
├── locales
│ ├── da.json
│ ├── de.js
│ ├── en.js
│ ├── en_nft.js
│ ├── en_video-game.js
│ ├── es.json
│ ├── fa.json
│ ├── fr.json
│ ├── hu.json
│ ├── index.js
│ ├── ja.json
│ ├── ko.json
│ ├── nl.json
│ ├── pt.json
│ ├── ru.json
│ ├── zh.json
│ └── zh_tw.json
├── main.js
├── polyfills.js
├── router
│ ├── index.js
│ └── routes.js
├── store
│ ├── api
│ │ ├── assets.js
│ │ ├── assettypes.js
│ │ ├── backgrounds.js
│ │ ├── breakdown.js
│ │ ├── budget.js
│ │ ├── client.js
│ │ ├── concepts.js
│ │ ├── customactions.js
│ │ ├── departments.js
│ │ ├── edits.js
│ │ ├── entities.js
│ │ ├── files.js
│ │ ├── news.js
│ │ ├── notifications.js
│ │ ├── people.js
│ │ ├── playlists.js
│ │ ├── productions.js
│ │ ├── schedule.js
│ │ ├── shots.js
│ │ ├── statusautomation.js
│ │ ├── studios.js
│ │ ├── tasks.js
│ │ ├── taskstatus.js
│ │ └── tasktypes.js
│ ├── getters.js
│ ├── index.js
│ ├── modules
│ │ ├── assets.js
│ │ ├── assettypes.js
│ │ ├── backgrounds.js
│ │ ├── breakdown.js
│ │ ├── budget.js
│ │ ├── concepts.js
│ │ ├── customactions.js
│ │ ├── departments.js
│ │ ├── edits.js
│ │ ├── entities.js
│ │ ├── episodes.js
│ │ ├── files.js
│ │ ├── login.js
│ │ ├── main.js
│ │ ├── news.js
│ │ ├── notifications.js
│ │ ├── people.js
│ │ ├── playlists.js
│ │ ├── productions.js
│ │ ├── schedule.js
│ │ ├── sequences.js
│ │ ├── shots.js
│ │ ├── statusautomation.js
│ │ ├── studios.js
│ │ ├── tasks.js
│ │ ├── taskstatus.js
│ │ ├── tasktypes.js
│ │ └── user.js
│ └── mutation-types.js
├── stories
│ ├── Button.stories.js
│ ├── Button.vue
│ ├── Header.stories.js
│ ├── Header.vue
│ ├── Introduction.stories.mdx
│ ├── Page.stories.js
│ ├── Page.vue
│ ├── assets
│ │ ├── code-brackets.svg
│ │ ├── colors.svg
│ │ ├── comments.svg
│ │ ├── direction.svg
│ │ ├── flow.svg
│ │ ├── plugin.svg
│ │ ├── repo.svg
│ │ └── stackalt.svg
│ ├── button.css
│ ├── header.css
│ └── page.css
├── substituted-model-viewer.js
├── testrouter
│ ├── index.js
│ └── routes.js
└── variables.scss
├── tests
├── .eslintrc.cjs
├── archive
│ ├── assets.spec.js
│ └── breakdown.spec.js
├── e2e
│ ├── integration
│ │ └── production.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ └── index.js
├── fabric.js
├── setup.js
├── spinner.js
├── substituted-model-viewer.js
├── unit.setup.js
└── unit
│ ├── fixtures
│ ├── person-store.js
│ └── production-store.js
│ ├── lib
│ ├── array.spec.js
│ ├── auth.spec.js
│ ├── colors.spec.js
│ ├── descriptor.spec.js
│ ├── filtering.spec.js
│ ├── func.spec.js
│ ├── indexing.spec.js
│ ├── lang.spec.js
│ ├── models.spec.js
│ ├── path.spec.js
│ ├── query.spec.js
│ ├── render.spec.js
│ ├── selection.spec.js
│ ├── sorting.spec.js
│ ├── stats.spec.js
│ ├── string.spec.js
│ ├── time.spec.js
│ └── video.spec.js
│ ├── modals
│ ├── addthumbnailsmodals.spec.js
│ ├── buildfiltermodal.spec.js
│ └── shothistorymodal.spec.js
│ └── store
│ ├── departments.spec.js
│ ├── news.spec.js
│ ├── productions.spec.js
│ ├── studios.spec.js
│ └── tasktypes.spec.js
└── vite.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 | src/locales/*
4 | src/stories/*
5 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | es2021: true
6 | },
7 | extends: [
8 | 'eslint:recommended',
9 | 'plugin:vue/vue3-recommended',
10 | 'plugin:prettier/recommended'
11 | ],
12 | rules: {
13 | 'no-console':
14 | process.env.NODE_ENV === 'production'
15 | ? ['error', { allow: ['error', 'warn'] }]
16 | : 'off',
17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
18 | 'no-empty-pattern': ['error', { allowObjectPatternsAsParameters: true }],
19 | 'no-unused-vars': ['error', { args: 'none' }],
20 | 'no-var': 'error',
21 | eqeqeq: ['error', 'always', { null: 'ignore' }],
22 | 'prefer-const': [
23 | 'error',
24 | {
25 | destructuring: 'all'
26 | }
27 | ],
28 |
29 | // additional rules for Vue
30 | 'vue/component-definition-name-casing': ['error', 'kebab-case'],
31 | 'vue/component-name-in-template-casing': ['error', 'kebab-case'],
32 | 'vue/custom-event-name-casing': ['error', 'kebab-case'],
33 | 'vue/eqeqeq': ['error', 'always', { null: 'ignore' }],
34 | 'vue/no-unused-emit-declarations': 'error',
35 | 'vue/prop-name-casing': ['error', 'camelCase'],
36 | 'vue/require-explicit-emits': 'error',
37 |
38 | // disabled vue/recommended rules
39 | 'vue/attributes-order': 'off',
40 | 'vue/multi-word-component-names': 'off',
41 | 'vue/no-v-html': 'off',
42 | 'vue/order-in-components': 'off',
43 | 'vue/require-default-prop': 'off',
44 | 'vue/require-prop-types': 'off',
45 | 'vue/no-template-shadow': 'off'
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt
2 | # https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view
3 |
4 | # nicopennec: format with eslint & prettier
5 | 52b161016c4d97f08cd50b01bfc61ac98f62ef92
6 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | liberapay: CGWire
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: NicoPennec
7 |
8 | ---
9 |
10 | **Context**
11 |
12 | Studio name:
13 | Kitsu version:
14 | Production type:
15 |
16 | **Describe the bug**
17 |
18 | A clear and concise description of what the bug is.
19 |
20 | **Screenshots**
21 |
22 | If applicable, add screenshots to help explain your problem.
23 |
24 | **Desktop (please complete the following information):**
25 |
26 | - OS: [e.g. iOS]
27 | - Browser [e.g. chrome, safari]
28 | - Version [e.g. 22]
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 | ---
8 |
9 | Please use https://cgwire.canny.io for any feature request. Every feature request done on GitHub will be closed.
10 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **Problem**
2 |
3 |
4 | **Solution**
5 |
6 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: 'Kitsu CI'
3 |
4 | on: [push, pull_request]
5 |
6 | jobs:
7 | ci:
8 | name: Test with Node ${{ matrix.node }}
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | node: [20, 22, current]
13 | env:
14 | NODE_OPTIONS: '--max_old_space_size=8192'
15 | HUSKY: 0
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Setup node
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version: ${{ matrix.node }}
22 | cache: 'npm'
23 | - name: Install dependencies
24 | run: |
25 | echo "Node.js $(node -v)"
26 | echo "npm v$(npm -v)"
27 | npm ci
28 | - name: Run linter
29 | if: ${{ matrix.node == 'current' }}
30 | run: |
31 | npm run lint -- --quiet
32 | - name: Run tests
33 | run: |
34 | npm run test:unit
35 |
--------------------------------------------------------------------------------
/.github/workflows/staging.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Kitsu to staging environment
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build:
10 | if: github.repository_owner == 'cgwire'
11 | name: Build
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Update Kitsu on staging server
15 | uses: appleboy/ssh-action@v1
16 | env:
17 | HUSKY: 0
18 | NODE_OPTIONS: '--max_old_space_size=8192'
19 | with:
20 | host: ${{ secrets.HOST }}
21 | username: ${{ secrets.USERNAME }}
22 | key: ${{ secrets.KEY }}
23 | port: ${{ secrets.PORT }}
24 | script_stop: true
25 | envs: HUSKY, NODE_OPTIONS
26 | script: |
27 | echo "Node.js $(node -v)"
28 | echo "npm v$(npm -v)"
29 | cd /opt/kitsu
30 | git pull
31 | npm ci
32 | npm run build
33 | GIT_COMMIT="$(git rev-parse HEAD)"
34 | GIT_TAG="$(git describe --tags)"
35 | KITSU_VERSION="$(echo ${GIT_TAG} | sed 's/^v//;s/-build//')"
36 | echo "${KITSU_VERSION}" > dist/.version.txt
37 | echo "${GIT_COMMIT}" > dist/.commit.txt
38 | echo "${GIT_TAG}" > dist/.tag.txt
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | /logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules/
31 | jspm_packages/
32 | yarn.lock
33 |
34 | # Optional npm cache directory
35 | .npm
36 |
37 | # Optional REPL history
38 | .node_repl_history
39 |
40 | # Vue template artifacts to ignore
41 | .DS_Store
42 | dist/
43 | selenium-debug.log
44 | test/unit/coverage
45 | test/e2e/reports
46 | test/test-bundle.js
47 | tests/test-bundle.js
48 |
49 | # IDE
50 | .idea/
51 | .vscode/
52 | .eslintcache
53 |
54 | # Custom
55 | TODO
56 | QA
57 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | lint-staged
2 |
--------------------------------------------------------------------------------
/.mergify.yml:
--------------------------------------------------------------------------------
1 | pull_request_rules:
2 | - name: Automatic assign
3 | conditions: []
4 | actions:
5 | assign:
6 | users: [frankrousseau]
7 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 | legacy-peer-deps=true
3 |
--------------------------------------------------------------------------------
/.postcssrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | src/locales/*
2 | src/stories/*
3 | tests/*
4 | scripts/*
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "semi": false,
4 | "singleQuote": true,
5 | "trailingComma": "none"
6 | }
7 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "stories": [
3 | "../src/**/*.stories.mdx",
4 | "../src/**/*.stories.@(js|jsx|ts|tsx)"
5 | ],
6 | "addons": [
7 | "@storybook/addon-links",
8 | "@storybook/addon-essentials",
9 | "@storybook/addon-interactions"
10 | ],
11 | "framework": "@storybook/vue"
12 | }
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | export const parameters = {
2 | actions: { argTypesRegex: "^on[A-Z].*" },
3 | controls: {
4 | matchers: {
5 | color: /(background|color)$/i,
6 | date: /Date$/,
7 | },
8 | },
9 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://kitsu.cg-wire.com)
2 |
3 | # Kitsu, Collaboration Platform for Animation, VFX, and Video Game Studios
4 |
5 | Kitsu is a web application that allows you to collaborate on your creative productions and
6 | manage your deliveries. It improves the communication between all stakeholders.
7 | Which leads to better results and faster shipping.
8 |
9 | [](https://github.com/cgwire/kitsu/actions/workflows/ci.yml)
10 | [](https://discord.com/invite/VbCxtKN)
11 | [](https://liberapay.com/CGWire/donate)
12 |
13 | ## Documentation
14 |
15 | For further information about features and installation, please refer to the
16 | [documentation website](https://kitsu.cg-wire.com/).
17 |
18 | ## Contributing
19 |
20 | There are many ways to contribute to Kitsu, from simple tasks to most complex ones. We created a
21 | [contributing guide](https://github.com/cgwire/kitsu/blob/main/CONTRIBUTING.md) explaining everything.
22 | You will find all the information you are looking for!
23 |
24 | ## About authors
25 |
26 | Kitsu is written by CGWire, a company based in France. We help animation and VFX studios to collaborate better through efficient tooling.
27 |
28 | More than 300 studios around the world use Kitsu for their projects.
29 |
30 | Visit [cg-wire.com](https://cg-wire.com) for more information.
31 |
32 | [](https://cg-wire.com)
33 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # How to create a new release for Kitsu
2 |
3 | We release Kitsu versions through GitHub.
4 | Every time a new version is ready, we follow this process:
5 |
6 | 1. Rebase sources on the `main` branch.
7 | 2. Up the version number through the `npm` CLI.
8 | 3. Tag the commit with the Kitsu version.
9 | 4. Push changes to the `main` branch.
10 |
11 | You can run the following script to perform these commands at once:
12 |
13 | ```bash
14 | git pull --rebase origin main
15 | npm version patch
16 | git push origin main --tag
17 | ```
18 |
19 | # Deployment
20 |
21 | Kitsu installation has to be updated via Git, your Kitsu folder.
22 | Run the following command to get the latest version of Kitsu:
23 |
24 | ```bash
25 | git pull --rebase origin build
26 | ```
27 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "integrationFolder": "./tests/e2e/integration",
3 | "fixturesFolder": "./tests/e2e/fixtures",
4 | "pluginsFile": "./tests/e2e/plugins/index.js",
5 | "supportFile": "./tests/e2e/support/index.js",
6 | "watchForFileChanges" : false,
7 | "viewportWidth": 1200,
8 | "viewportHeight": 800,
9 | "baseUrl": "http://localhost:8080"
10 | }
11 |
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | ## Recommandations
2 |
3 | Before coding on Kitsu we recommend to:
4 |
5 | * Know the ES6 syntax.
6 | * Read the Vue.js documentation main chapters.
7 | * Understand how Vuex works.
8 |
9 | ## Code guidelines
10 |
11 | * Make your best to keep your lines < 80 chars wide.
12 | * Respect configured ESLint rules.
13 | * Always order object fields in alphabetical order.
14 | * Use Vuex state only when necessary, prefer local state (vuex creates
15 | performance overhead).
16 | * Always use Vuex actions when doing remote API calls.
17 |
--------------------------------------------------------------------------------
/docs/translation.md:
--------------------------------------------------------------------------------
1 | # Add a translation
2 |
3 |
4 | ## Add a language file
5 |
6 | 1. Get the json file from POEditor.
7 | 2. Store it in `src/locales`.
8 | 3. Add corresponding entry in the `src/locales/index.js` file.
9 | 4. Add an entry in the profile file at the option level.
10 | Be careful, you must use a locale name available in Python Babel or it will
11 | break the person entry.
12 |
13 |
14 | ## Add a translation key
15 |
16 | TODO
17 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Kitsu - Collaboration Platform For Animation Studios
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "checkJs": false,
5 | "moduleResolution": "bundler",
6 | "paths": {
7 | "@/*": ["src/*"]
8 | },
9 | "resolveJsonModule": true,
10 | "target": "ES2021"
11 | },
12 | "vueCompilerOptions": {
13 | "target": "auto"
14 | },
15 | "include": ["src/**/*", "tests/**/*"]
16 | }
17 |
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/public/favicon.ico
--------------------------------------------------------------------------------
/public/fonts/Lato.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/public/fonts/Lato.woff2
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
3 |
--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | git pull --rebase origin main
3 | npm version patch
4 | git push origin main --tags
5 |
--------------------------------------------------------------------------------
/src/assets/avatar-wrapper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/avatar-wrapper.png
--------------------------------------------------------------------------------
/src/assets/background/player-timeslider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/background/player-timeslider.png
--------------------------------------------------------------------------------
/src/assets/background/schedule-dark-1-weekend.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/assets/background/schedule-dark-1.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/background/schedule-dark-2-weekend.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/assets/background/schedule-dark-3-weekend.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/assets/background/schedule-dark-4-weekend.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/assets/background/schedule-white-1-weekend.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/assets/background/schedule-white-1.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/background/schedule-white-2-weekend.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/assets/background/schedule-white-3-weekend.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/assets/background/schedule-white-4-weekend.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/assets/icons/assets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/icons/assets.png
--------------------------------------------------------------------------------
/src/assets/icons/casting-not-ready.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/icons/casting-not-ready.png
--------------------------------------------------------------------------------
/src/assets/icons/casting-ready.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/icons/casting-ready.png
--------------------------------------------------------------------------------
/src/assets/icons/fi-add-preview.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-add-thumbnail.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-alert-circle.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-annotations.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-asset-stats.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-assign-1.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-automations.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-big-thumbnail.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-box.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-breakdown.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-calendar.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-check-circle.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-check-square.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-compare.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-copy.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-delete.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-departments.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-download.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-edit-2.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-edits.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-entity-search.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-episode-stats.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-episodes.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-export-csv.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-export-ligne.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-export-lines.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-eye.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-filter.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-image.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-import-files.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-info-hide.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-info.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-laser.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-layers.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-logs.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-maximize.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-message-square.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-my-productions.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-my-tasks.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-news.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-play.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-playlist.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-playlists.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-productions.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-quotas.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-repeat.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-rotate-ccw.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-sequence-stats.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-sequences.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-shots.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-skip-forward-1.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-skip-forward.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-skybox-desactive.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-skybox.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-sliders.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-sound-off.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-sound-on.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-task-status.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-task-types.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-team-schedule.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-text.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-timesheets.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-trash-2.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-user-check.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-user.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-users.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-waveform.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-zoom-in.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/icons/fi-zoom-out.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/icons/fi_user-bot.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/icons/movie-thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/icons/movie-thumbnail.png
--------------------------------------------------------------------------------
/src/assets/icons/sequences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/icons/sequences.png
--------------------------------------------------------------------------------
/src/assets/icons/shots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/icons/shots.png
--------------------------------------------------------------------------------
/src/assets/icons/trash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/illustrations/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/illustrations/404.png
--------------------------------------------------------------------------------
/src/assets/illustrations/500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/illustrations/500.png
--------------------------------------------------------------------------------
/src/assets/illustrations/empty_asset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/illustrations/empty_asset.png
--------------------------------------------------------------------------------
/src/assets/illustrations/empty_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/illustrations/empty_edit.png
--------------------------------------------------------------------------------
/src/assets/illustrations/empty_production.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/illustrations/empty_production.png
--------------------------------------------------------------------------------
/src/assets/illustrations/empty_shot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/illustrations/empty_shot.png
--------------------------------------------------------------------------------
/src/assets/illustrations/empty_todo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/illustrations/empty_todo.png
--------------------------------------------------------------------------------
/src/assets/illustrations/kitsu-band.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/illustrations/kitsu-band.png
--------------------------------------------------------------------------------
/src/assets/illustrations/kitsu-with-body.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/illustrations/kitsu-with-body.png
--------------------------------------------------------------------------------
/src/assets/kitsu-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/kitsu-text.png
--------------------------------------------------------------------------------
/src/assets/kitsu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/kitsu.png
--------------------------------------------------------------------------------
/src/assets/kitsu.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/spinner-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/src/assets/spinner.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/src/components/Main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
28 |
29 |
36 |
--------------------------------------------------------------------------------
/src/components/cells/BooleanCell.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | |
5 |
6 |
7 |
25 |
26 |
31 |
--------------------------------------------------------------------------------
/src/components/cells/DepartmentNamesCell.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | |
11 |
12 |
13 |
47 |
--------------------------------------------------------------------------------
/src/components/cells/PeopleNameCell.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | |
8 |
9 |
10 |
30 |
31 |
36 |
--------------------------------------------------------------------------------
/src/components/cells/TaskStatusCell.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 | {{ entry.short_name }}
13 |
14 | |
15 |
16 |
17 |
55 |
56 |
67 |
--------------------------------------------------------------------------------
/src/components/cells/TaskTypeCell.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 | |
11 |
12 |
13 |
43 |
44 |
54 |
--------------------------------------------------------------------------------
/src/components/layouts/PageLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
24 |
--------------------------------------------------------------------------------
/src/components/layouts/PageLeftSideLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
--------------------------------------------------------------------------------
/src/components/mixins/dom.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Helpers to access dom through vanilla javascript.
3 | */
4 | export const domMixin = {
5 | methods: {
6 | isFocusTextArea() {
7 | return document.activeElement.nodeName === 'TEXTAREA'
8 | },
9 |
10 | clearFocus() {
11 | document.activeElement.blur()
12 | },
13 |
14 | focusInput(inputEl) {
15 | inputEl.focus()
16 | inputEl.select()
17 | inputEl.className = 'input'
18 | },
19 |
20 | onInputBlur(event) {
21 | event.target.className = 'input stylehidden'
22 | },
23 |
24 | onInputMouseOut(event) {
25 | if (document.activeElement !== event.target) {
26 | event.target.className = 'input stylehidden'
27 | }
28 | },
29 |
30 | onInputMouseOver(event) {
31 | event.target.className = 'input'
32 | },
33 |
34 | pauseEvent(e) {
35 | if (e.stopPropagation) e.stopPropagation()
36 | if (e.preventDefault) e.preventDefault()
37 | e.cancelBubble = true
38 | e.returnValue = false
39 | return false
40 | },
41 |
42 | addEvents(events) {
43 | events.forEach(([type, listener]) => {
44 | document.addEventListener(type, listener)
45 | })
46 | },
47 |
48 | removeEvents(events) {
49 | events.forEach(([type, listener]) => {
50 | document.removeEventListener(type, listener)
51 | })
52 | },
53 |
54 | getClientX(event) {
55 | return event.touches?.[0].clientX || event.clientX
56 | },
57 |
58 | getClientY(event) {
59 | return event.touches?.[0].clientY || event.clientY
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/mixins/fullscreen.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Utilities to deal with full screen state.
3 | */
4 | export const fullScreenMixin = {
5 | computed: {
6 | isFullScreenEnabled() {
7 | return !!(
8 | document.fullscreenEnabled ||
9 | document.mozFullScreenEnabled ||
10 | document.msFullscreenEnabled ||
11 | document.webkitSupportsFullscreen ||
12 | document.webkitFullscreenEnabled ||
13 | document.createElement('picture').webkitRequestFullScreen
14 | )
15 | }
16 | },
17 |
18 | methods: {
19 | isFullScreen() {
20 | return !!(
21 | document.fullscreen ||
22 | document.webkitIsFullScreen ||
23 | document.mozFullScreen ||
24 | document.msFullscreenElement ||
25 | document.fullscreenElement
26 | )
27 | },
28 |
29 | documentExitFullScreen() {
30 | if (document.exitFullscreen) {
31 | return document.exitFullscreen()
32 | } else if (document.mozCancelFullScreen) {
33 | document.mozCancelFullScreen()
34 | } else if (document.webkitCancelFullScreen) {
35 | document.webkitCancelFullScreen()
36 | } else if (document.msExitFullscreen) {
37 | document.msExitFullscreen()
38 | }
39 | },
40 |
41 | documentSetFullScreen(element) {
42 | if (element.requestFullscreen) {
43 | return element.requestFullscreen()
44 | } else if (element.mozRequestFullScreen) {
45 | element.mozRequestFullScreen()
46 | } else if (element.webkitRequestFullScreen) {
47 | element.webkitRequestFullScreen()
48 | } else if (element.msRequestFullscreen) {
49 | element.msRequestFullscreen()
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/mixins/page.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This mixin is used to format the page title based on the current context.
3 | */
4 | import { mapGetters } from 'vuex'
5 |
6 | export const pageMixin = {
7 | computed: {
8 | ...mapGetters(['currentProduction, currentEpisode, isTVShow'])
9 | },
10 |
11 | methods: {},
12 |
13 | head() {
14 | if (this.currentProduction) {
15 | if (this.isTVSHow && this.currentEpisode) {
16 | return {
17 | title:
18 | `${this.currentProduction.name} - ` +
19 | `${this.currentEpisode.name} | {this.pageTitle} - Kitsu`
20 | }
21 | } else {
22 | return {
23 | title: `${this.currentProduction.name} | ${this.pageTitle} - Kitsu`
24 | }
25 | }
26 | } else {
27 | return {
28 | title: `${this.pageTitle} - Kitsu`
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/mixins/parameters.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Helpers to apply and save parameters to the url and local storage.
3 | */
4 | import preferences from '@/lib/preferences'
5 |
6 | export const parametersMixin = {
7 | methods: {
8 | applyParametersToUrl() {
9 | const query = Object.keys(this.parameters).reduce((acc, key) => {
10 | if (this.parameters[key]) {
11 | acc[key] = this.parameters[key]
12 | if (acc[key] === 'true') {
13 | acc[key] = true
14 | } else if (acc[key] === 'false') {
15 | acc[key] = false
16 | }
17 | }
18 | return acc
19 | }, {})
20 | this.$router.replace({ query })
21 | },
22 |
23 | getParametersFromUrl() {
24 | return this.$route.query
25 | },
26 |
27 | applyQueryParameters(params) {
28 | const urlParams = this.getParametersFromUrl()
29 | Object.assign(params, urlParams || {})
30 | return params
31 | },
32 |
33 | saveParametersToPreferences() {
34 | preferences.setObjectPreference(
35 | `parameters:${this.parameterNamespace}`,
36 | this.parameters
37 | )
38 | },
39 |
40 | getParametersFromPreferences(defaultParameters) {
41 | return (
42 | preferences.getObjectPreference(
43 | `parameters:${this.parameterNamespace}`
44 | ) || defaultParameters
45 | )
46 | }
47 | },
48 |
49 | watch: {
50 | parameters: {
51 | deep: true,
52 | handler() {
53 | this.applyParametersToUrl()
54 | this.saveParametersToPreferences()
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/mixins/time.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Set of functions to facilitate usage of a date and timezones.
3 | */
4 | import moment from 'moment-timezone'
5 |
6 | import { formatFullDateWithTimezone } from '@/lib/time'
7 |
8 | export const timeMixin = {
9 | computed: {
10 | timezone() {
11 | return this.user.timezone || moment.tz.guess()
12 | },
13 |
14 | today() {
15 | return moment().toDate()
16 | }
17 | },
18 |
19 | methods: {
20 | formatDate(eventDate) {
21 | return formatFullDateWithTimezone(eventDate, this.timezone)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/modals/BaseModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 | {{ title }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
59 |
60 |
69 |
--------------------------------------------------------------------------------
/src/components/modals/ConfirmModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
30 |
31 |
32 |
70 |
71 |
79 |
--------------------------------------------------------------------------------
/src/components/modals/ModalFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
23 |
24 |
25 |
59 |
60 |
73 |
--------------------------------------------------------------------------------
/src/components/modals/base_modal.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Mixin to add common features to modals including:
3 | *
4 | * * Close modal by using the escape key.
5 | */
6 |
7 | export const modalMixin = {
8 | beforeUnmount() {
9 | window.removeEventListener('keydown', this.onKeyDown)
10 | },
11 |
12 | methods: {
13 | /*
14 | * Allow to close the modal when escape key is pressed.
15 | */
16 | onKeyDown(event) {
17 | if (event.key === 'Escape') {
18 | this.$emit('cancel')
19 | }
20 | }
21 | },
22 |
23 | watch: {
24 | /*
25 | * Make sure that the keydown event is removed each time the modal is hidden.
26 | */
27 | active: {
28 | immediate: true,
29 | handler() {
30 | if (this.active) {
31 | window.addEventListener('keydown', this.onKeyDown, false)
32 | } else {
33 | window.removeEventListener('keydown', this.onKeyDown)
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/pages/Brief.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
39 |
40 |
54 |
--------------------------------------------------------------------------------
/src/components/pages/Logs.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
65 |
66 |
80 |
--------------------------------------------------------------------------------
/src/components/pages/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
{{ $t('not_found.title') }}
5 |
6 | {{ $t('not_found.text') }}
7 |
8 |
9 | {{ $t('main.home') }}
10 |
11 |
12 |
13 |
14 |
19 |
20 |
42 |
--------------------------------------------------------------------------------
/src/components/pages/ServerDown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 |
{{ $t('server_down.title') }}
7 |
8 | {{ $t('server_down.text') }}
9 |
10 |
11 |
12 |
13 |
32 |
33 |
61 |
--------------------------------------------------------------------------------
/src/components/pages/WrongBrowser.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('wrong_browser.title') }}
4 |
5 | {{ $t('wrong_browser.text') }}
6 |
7 |
8 |
9 |
10 |
15 |
--------------------------------------------------------------------------------
/src/components/tops/actions/DeleteEntities.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 | {{ errorText }}
13 |
14 |
15 |
16 |
17 |
49 |
50 |
58 |
--------------------------------------------------------------------------------
/src/components/widgets/AssignationItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
51 |
--------------------------------------------------------------------------------
/src/components/widgets/BigThumbnailsButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
69 |
70 |
75 |
--------------------------------------------------------------------------------
/src/components/widgets/BooleanRep.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
16 |
17 |
18 |
37 |
38 |
43 |
--------------------------------------------------------------------------------
/src/components/widgets/ButtonHrefLink.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 | {{ text }}
14 |
15 |
16 |
17 |
18 |
56 |
57 |
69 |
--------------------------------------------------------------------------------
/src/components/widgets/ButtonLink.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
18 | {{ text }}
19 |
20 |
21 |
22 |
23 |
66 |
--------------------------------------------------------------------------------
/src/components/widgets/ComboboxBoolean.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
61 |
--------------------------------------------------------------------------------
/src/components/widgets/ComboboxMask.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
23 |
--------------------------------------------------------------------------------
/src/components/widgets/CommentMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
41 |
42 |
70 |
--------------------------------------------------------------------------------
/src/components/widgets/DepartmentName.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | {{ department.name }}
10 |
11 |
12 |
13 |
14 |
34 |
35 |
57 |
--------------------------------------------------------------------------------
/src/components/widgets/ErrorText.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 | {{ text }}
10 |
11 |
12 |
13 |
32 |
--------------------------------------------------------------------------------
/src/components/widgets/GroupButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
25 |
26 |
62 |
--------------------------------------------------------------------------------
/src/components/widgets/InfoQuestionMark.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
40 |
41 |
73 |
--------------------------------------------------------------------------------
/src/components/widgets/ListPageHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
54 |
--------------------------------------------------------------------------------
/src/components/widgets/PageSubtitle.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ text }}
3 |
4 |
5 |
16 |
17 |
24 |
--------------------------------------------------------------------------------
/src/components/widgets/PageTitle.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 | {{ text }}
10 |
11 |
12 |
13 |
28 |
29 |
40 |
--------------------------------------------------------------------------------
/src/components/widgets/PeopleName.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 | {{ person.full_name }}
14 |
15 |
16 | {{ person.full_name }}
17 |
18 |
19 |
20 |
35 |
36 |
41 |
--------------------------------------------------------------------------------
/src/components/widgets/PreviewRow.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
45 |
46 |
71 |
--------------------------------------------------------------------------------
/src/components/widgets/RouteSectionTabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
11 |
12 | {{ tab.label }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
51 |
52 |
61 |
--------------------------------------------------------------------------------
/src/components/widgets/RouteTabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
9 |
14 | {{ tab.label }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
37 |
38 |
47 |
--------------------------------------------------------------------------------
/src/components/widgets/SortingInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
37 |
38 |
43 |
--------------------------------------------------------------------------------
/src/components/widgets/Spinner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |

7 |
8 |
9 |
10 |
48 |
--------------------------------------------------------------------------------
/src/components/widgets/StatusStats.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 | {{ stat.name }} : {{ stat.value }}
14 |
15 |
16 |
17 |
18 |
19 |
40 |
41 |
73 |
--------------------------------------------------------------------------------
/src/components/widgets/StudioName.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | {{ studio.name }}
10 |
11 |
12 |
13 |
14 |
34 |
35 |
57 |
--------------------------------------------------------------------------------
/src/components/widgets/SubscribeButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
39 |
--------------------------------------------------------------------------------
/src/components/widgets/TableInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | {{ $t('main.loading_error') }}
11 |
12 |
13 |
14 |
15 |
16 |
38 |
--------------------------------------------------------------------------------
/src/components/widgets/TableMetadataHeaderMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
41 |
42 |
67 |
--------------------------------------------------------------------------------
/src/components/widgets/TextareaField.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
18 |
19 |
20 |
65 |
66 |
83 |
--------------------------------------------------------------------------------
/src/lib/array.js:
--------------------------------------------------------------------------------
1 | /* Returns the intersection of several arrays */
2 | export const intersection = arrays => {
3 | let [first, ...rest] = arrays
4 | rest = rest.map(x => new Set(x))
5 | return first.filter(x => rest.every(a => a.has(x)))
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/clipboard.js:
--------------------------------------------------------------------------------
1 | // Utility suite for copy pasting annotations or text.
2 |
3 | let annotationClipboard = []
4 | let castingClipboard = []
5 |
6 | export default {
7 | copyAnnotations(annotations) {
8 | annotationClipboard = annotations
9 | },
10 |
11 | pasteAnnotations() {
12 | return annotationClipboard
13 | },
14 |
15 | copyCasting(casting) {
16 | castingClipboard = casting
17 | },
18 |
19 | pasteCasting() {
20 | return castingClipboard
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/lib/crisp.js:
--------------------------------------------------------------------------------
1 | export default {
2 | init(token, isVisible) {
3 | window.$crisp = []
4 | window.CRISP_WEBSITE_ID = token
5 | const run = () => {
6 | const d = document
7 | const s = d.createElement('script')
8 | s.src = 'https://client.crisp.chat/l.js'
9 | s.async = 1
10 | d.getElementsByTagName('head')[0].appendChild(s)
11 | s.addEventListener('load', () => {
12 | setTimeout(() => this.setChatVisibility(isVisible), 800)
13 | })
14 | }
15 | run()
16 | },
17 |
18 | setChatVisibility(isVisible) {
19 | const crispEls = document.getElementsByClassName('crisp-client')
20 | if (crispEls[0]) {
21 | const crispEl = crispEls[0]
22 | if (isVisible) {
23 | crispEl.style.display = ''
24 | } else {
25 | crispEl.style.display = 'none'
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/descriptors.js:
--------------------------------------------------------------------------------
1 | export default {
2 | getChoicesOptions(descriptor) {
3 | const values = descriptor.choices.map(c => ({ label: c, value: c }))
4 | return [{ label: '', value: '' }, ...values]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/drafts.js:
--------------------------------------------------------------------------------
1 | const drafts = {
2 | setTaskDraft(taskId, text) {
3 | return localStorage.setItem('draft-' + taskId, text)
4 | },
5 |
6 | getTaskDraft(taskId) {
7 | return localStorage.getItem('draft-' + taskId)
8 | },
9 |
10 | clearTaskDraft(taskId) {
11 | return localStorage.removeItem('draft-' + taskId)
12 | }
13 | }
14 |
15 | export default drafts
16 |
--------------------------------------------------------------------------------
/src/lib/errors.js:
--------------------------------------------------------------------------------
1 | const errors = {
2 | backToLogin() {
3 | if (window.location !== '/login') {
4 | window.location.replace('/login')
5 | }
6 | }
7 | }
8 | export default errors
9 |
--------------------------------------------------------------------------------
/src/lib/files.js:
--------------------------------------------------------------------------------
1 | const ALL_EXTENSIONS = [
2 | 'png',
3 | 'jpg',
4 | 'kra',
5 | 'mp4',
6 | 'mov',
7 | 'obj',
8 | 'glb',
9 | 'gltf',
10 | 'pdf',
11 | 'ma',
12 | 'mb',
13 | 'zip',
14 | 'rar',
15 | 'jpeg',
16 | 'svg',
17 | 'blend',
18 | 'wmv',
19 | 'm4v',
20 | 'ai',
21 | 'comp',
22 | 'exr',
23 | 'psd',
24 | 'hip',
25 | 'gif',
26 | 'ae',
27 | 'fla',
28 | 'flv',
29 | 'sai',
30 | 'sai2',
31 | 'swf',
32 | 'sbbkp',
33 | 'wav',
34 | 'mp3',
35 | 'webm',
36 | 'avi',
37 | 'clip',
38 | 'mkv'
39 | ]
40 | const ALL_EXTENSIONS_STRING = ALL_EXTENSIONS.map(e => `.${e}`).join(',')
41 |
42 | const IMG_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'svg']
43 | const IMG_EXTENSIONS_STRING = IMG_EXTENSIONS.map(e => `.${e}`).join(',')
44 |
45 | export default {
46 | ALL_EXTENSIONS,
47 | ALL_EXTENSIONS_STRING,
48 | IMG_EXTENSIONS,
49 | IMG_EXTENSIONS_STRING
50 | }
51 |
--------------------------------------------------------------------------------
/src/lib/func.js:
--------------------------------------------------------------------------------
1 | export default {
2 | runPromiseMapAsSeries(array, promise) {
3 | return array.reduce((accumulatorPromise, item) => {
4 | return accumulatorPromise.then(() => promise(item))
5 | }, Promise.resolve())
6 | },
7 |
8 | runPromiseAsSeries(promises) {
9 | return promises.reduce((accumulatorPromise, promise) => {
10 | return accumulatorPromise.then(() => promise)
11 | }, Promise.resolve())
12 | },
13 |
14 | throttle(fn, delay) {
15 | let lastCall = 0
16 | return function (...args) {
17 | const now = new Date().getTime()
18 | if (now - lastCall < delay) return
19 | lastCall = now
20 | return fn(...args)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/lib/i18n.js:
--------------------------------------------------------------------------------
1 | import { createI18n } from 'vue-i18n'
2 |
3 | import locales from '@/locales'
4 |
5 | const i18n = new createI18n({
6 | legacy: true,
7 | locale: 'en',
8 | fallbackLocale: 'en',
9 | messages: locales
10 | })
11 |
12 | export default i18n
13 |
--------------------------------------------------------------------------------
/src/lib/init.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | import { DATA_LOADING_START, DATA_LOADING_END } from '@/store/mutation-types'
4 |
5 | /**
6 | * Load base data required to display properly all information.
7 | */
8 | const init = callback => {
9 | store.commit(DATA_LOADING_START)
10 | return store
11 | .dispatch('loadContext')
12 | .then(() => {
13 | return store.dispatch('getOrganisation')
14 | })
15 | .then(() => {
16 | // We run login success mutation when done because init
17 | // happens either after successful login or at first connexion
18 | // when the user have an active session.
19 | store.commit(DATA_LOADING_END)
20 | callback()
21 | })
22 | .catch(err => {
23 | console.error('An init operation failed: ', err)
24 | })
25 | }
26 |
27 | export default init
28 |
--------------------------------------------------------------------------------
/src/lib/lang.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment-timezone'
2 | import i18n from '@/lib/i18n'
3 |
4 | export default {
5 | /**
6 | * Set the locale for the application (vue-i18n + moment.js)
7 | * @param {string} locale
8 | */
9 | setLocale(locale) {
10 | let language = locale.substring(0, 2)
11 |
12 | if (locale === 'zh_Hans_CN') {
13 | moment.locale('zh_CN')
14 | } else if (locale === 'zh_Hant_TW') {
15 | moment.locale('zh_TW')
16 | language = 'zw'
17 | } else {
18 | moment.locale(language)
19 | }
20 |
21 | i18n.global.locale = language
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/lib/pagination.js:
--------------------------------------------------------------------------------
1 | export const PAGE_SIZE = 100
2 |
--------------------------------------------------------------------------------
/src/lib/playlist.js:
--------------------------------------------------------------------------------
1 | export const DEFAULT_NB_FRAMES_PICTURE = 48
2 |
--------------------------------------------------------------------------------
/src/lib/preferences.js:
--------------------------------------------------------------------------------
1 | export default {
2 | setPreference(key, item) {
3 | return localStorage.setItem(key, item)
4 | },
5 |
6 | getPreference(key) {
7 | return localStorage.getItem(key)
8 | },
9 |
10 | getBoolPreference(key, defaultValue = false) {
11 | const item = this.getPreference(key)
12 | return item === null ? defaultValue : item === 'true'
13 | },
14 |
15 | getIntPreference(key, defaultValue = 0) {
16 | const item = this.getPreference(key)
17 | const value = parseInt(item)
18 | return isNaN(value) ? defaultValue : value
19 | },
20 |
21 | setObjectPreference(key, data) {
22 | return localStorage.setItem(key, JSON.stringify(data))
23 | },
24 |
25 | getObjectPreference(key) {
26 | const item = this.getPreference(key)
27 | try {
28 | return JSON.parse(item)
29 | } catch (e) {
30 | return null
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/lib/productions.js:
--------------------------------------------------------------------------------
1 | export const PRODUCTION_TYPE_OPTIONS = [
2 | {
3 | label: 'short',
4 | value: 'short'
5 | },
6 | {
7 | label: 'tvshow',
8 | value: 'tvshow'
9 | },
10 | {
11 | label: 'featurefilm',
12 | value: 'featurefilm'
13 | },
14 | {
15 | label: 'assets',
16 | value: 'assets'
17 | },
18 | {
19 | label: 'shots',
20 | value: 'shots'
21 | }
22 | ]
23 |
24 | export const PRODUCTION_STYLE_OPTIONS = [
25 | { label: '2d', value: '2d' },
26 | { label: '2dpaper', value: '2dpaper' },
27 | { label: '3d', value: '3d' },
28 | { label: '2d3d', value: '2d3d' },
29 | { label: 'vfx', value: 'vfx' },
30 | { label: 'commercial', value: 'commercial' },
31 | { label: 'vr', value: 'vr' },
32 | { label: 'motion_design', value: 'motion-design' },
33 | { label: 'archviz', value: 'archviz' },
34 | { label: 'stop_motion', value: 'stop-motion' },
35 | { label: 'catalog', value: 'catalog' },
36 | { label: 'nft', value: 'nft' },
37 | { label: 'video_game', value: 'video-game' },
38 | { label: 'immersive', value: 'immersive' },
39 | { label: 'ar', value: 'ar' }
40 | ]
41 |
42 | export const HOME_PAGE_OPTIONS = [
43 | { label: 'assets', value: 'assets' },
44 | { label: 'shots', value: 'shots' },
45 | { label: 'sequences', value: 'sequences' }
46 | ]
47 |
48 | export function getTaskTypePriorityOfProd(taskType, production) {
49 | if (!taskType) {
50 | return 1
51 | }
52 | const productionPriority = production?.task_types_priority?.[taskType.id]
53 | return productionPriority || taskType.priority
54 | }
55 |
56 | export function getTaskStatusPriorityOfProd(taskStatus, production) {
57 | if (!taskStatus) {
58 | return 1
59 | }
60 | const productionPriority =
61 | production?.task_statuses_link?.[taskStatus.id]?.priority
62 | return productionPriority || taskStatus.priority
63 | }
64 |
--------------------------------------------------------------------------------
/src/lib/query.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds a query string by concatenating the given path and parameters.
3 | * Example: buildQueryString('/api/data/tasks/open-tasks', { status: 'open' })
4 | * returns '/api/data/tasks/open-tasks?status=open'
5 | *
6 | * @param {string} path - The base path for the query.
7 | * @param {Object} params - The parameters to be included in the query string.
8 | * @returns {string} The generated query string.
9 | */
10 | export const buildQueryString = (path, params) => {
11 | const result = `${path}?`
12 | const couples = []
13 | Object.keys(params).forEach(key => {
14 | if (
15 | params[key] ||
16 | (typeof params[key] === 'boolean' && params[key] === false)
17 | )
18 | couples.push(`${key}=${params[key]}`)
19 | })
20 | return result + couples.join('&')
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/selection.js:
--------------------------------------------------------------------------------
1 | export const buildSelectionGrid = (maxX, maxY) => {
2 | const result = {}
3 | for (let i = 0; i < maxX; i++) {
4 | if (!result[i]) result[i] = {}
5 | for (let j = 0; j < maxY; j++) {
6 | result[i][j] = false
7 | }
8 | }
9 | return result
10 | }
11 |
12 | export const appendSelectionGrid = (grid, previousX, maxX, maxY) => {
13 | const result = { ...grid }
14 | for (let i = previousX; i < maxX; i++) {
15 | if (!result[i]) result[i] = {}
16 | for (let j = 0; j < maxY; j++) {
17 | result[i][j] = false
18 | }
19 | }
20 | return result
21 | }
22 |
23 | export const clearSelectionGrid = selectionGrid => {
24 | const maxX = Object.keys(selectionGrid).length
25 | const maxY = selectionGrid[0] ? Object.keys(selectionGrid[0]).length : 0
26 | for (let i = 0; i < maxX; i++) {
27 | for (let j = 0; j < maxY; j++) {
28 | if (selectionGrid[i][j]) selectionGrid[i][j] = false
29 | }
30 | }
31 | return selectionGrid
32 | }
33 |
--------------------------------------------------------------------------------
/src/lib/sentry.js:
--------------------------------------------------------------------------------
1 | import * as Sentry from '@sentry/vue'
2 |
3 | import { name, version } from '@/../package.json'
4 |
5 | export default {
6 | init(app, router, { dsn, sampleRate = 0.1 }) {
7 | Sentry.init({
8 | Vue: app,
9 | dsn,
10 | enabled: process.env.NODE_ENV === 'production',
11 | release: `${name}@${version}`,
12 | integrations: [
13 | Sentry.browserTracingIntegration({
14 | router
15 | })
16 | ],
17 | tracesSampleRate: sampleRate // capture Trace for % of transactions for performance monitoring
18 | })
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/lib/string.js:
--------------------------------------------------------------------------------
1 | export default {
2 | /*
3 | * Detect if number are present in the name and generate the next
4 | * increment. If there is no number, we consider that a new name
5 | * must be written.
6 | */
7 | generateNextName(name, padding = 1) {
8 | const matches = name.match(/\d+$/)
9 | if (matches) {
10 | const number = matches[0]
11 | const rootName = name.substring(0, name.length - number.length)
12 | let numberInt = parseInt(number)
13 | if (numberInt === 1 && padding === 10) numberInt = 10
14 | else numberInt += padding
15 | return rootName + String(numberInt).padStart(number.length, '0')
16 | } else {
17 | return ''
18 | }
19 | },
20 |
21 | shortenText(text, maxLength) {
22 | let result = text || ''
23 | if (text?.length > maxLength) {
24 | result = text.slice(0, maxLength) + '...'
25 | result = result.replace(/\n/g, ' ')
26 | }
27 | return result
28 | },
29 |
30 | slugify(str) {
31 | str = str.replace(/^\s+|\s+$/g, '').toLowerCase()
32 |
33 | const from = 'àáäâèéëêìíïîòóöôùúüûñç·/_,:'
34 | const to = 'aaaaeeeeiiiioooouuuunc------'
35 | for (let i = 0, l = from.length; i < l; i++) {
36 | str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i))
37 | }
38 |
39 | return str
40 | .replace(/[^a-z0-9 -]/g, '')
41 | .replace(/\s+/g, '_')
42 | .replace(/-+/g, '_')
43 | },
44 |
45 | capitalize(str) {
46 | return str.charAt(0).toUpperCase() + str.slice(1)
47 | },
48 |
49 | filenameWithoutExtension(filename) {
50 | return filename.replace(/\.[^/.]+$/, '')
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/lib/timezone.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment-timezone'
2 |
3 | import store from '@/store'
4 |
5 | export default {
6 | /*
7 | * Configure moment libs with the timezone extracted from user information.
8 | */
9 | setTimezone() {
10 | const timezone = store.state.user.user.timezone || moment.tz.guess()
11 | moment.tz.setDefault(timezone)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/locales/index.js:
--------------------------------------------------------------------------------
1 | import en from '@/locales/en'
2 | import en_nft from '@/locales/en_nft'
3 | import en_video_game from '@/locales/en_video-game'
4 | import da from '@/locales/da.json'
5 | import de from '@/locales/de.js'
6 | import nl from '@/locales/nl.json'
7 | import es from '@/locales/es.json'
8 | import fa from '@/locales/fa.json'
9 | import fr from '@/locales/fr.json'
10 | import hu from '@/locales/hu.json'
11 | import ja from '@/locales/ja.json'
12 | import ko from '@/locales/ko.json'
13 | import pt from '@/locales/pt.json'
14 | import ru from '@/locales/ru.json'
15 | import zh from '@/locales/zh.json'
16 | import zhtw from '@/locales/zh_tw.json'
17 |
18 | export default {
19 | da: da.default,
20 | de: de,
21 | en: en,
22 | en_nft: en_nft,
23 | "en_video-game": en_video_game,
24 | es: es.default,
25 | fa: fa.default,
26 | fr: fr.default,
27 | hu: hu.default,
28 | ja: ja.default,
29 | ko: ko.default,
30 | nl: nl.default,
31 | pt: pt.default,
32 | ru: ru.default,
33 | zh: zh.default,
34 | zw: zhtw.default
35 | }
36 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import './polyfills'
2 |
3 | import { createApp } from 'vue'
4 | import { createHead, VueHeadMixin } from '@unhead/vue/client'
5 | import { sync } from 'vuex-router-sync'
6 |
7 | import App from '@/App'
8 | import i18n from '@/lib/i18n'
9 | import resizableColumn from '@/directives/resizable-column'
10 | import router from '@/router'
11 | import store from '@/store'
12 |
13 | import Autosize from 'v-autosize/src/plugin.js'
14 | import VueChartkick from 'vue-chartkick'
15 | import 'chartkick/chart.js'
16 | import VueWebsocket from 'vue-websocket-next'
17 | import IO from 'socket.io-client'
18 | import VueAnimXYZ from '@animxyz/vue3'
19 | import '@animxyz/core'
20 |
21 | import VueDatePicker from '@vuepic/vue-datepicker'
22 | import '@vuepic/vue-datepicker/dist/main.css'
23 |
24 | const app = createApp({
25 | components: { App },
26 | template: ''
27 | })
28 | const head = createHead()
29 |
30 | app.use(i18n)
31 | app.use(head)
32 | app.mixin(VueHeadMixin)
33 | app.use(router)
34 | app.use(store)
35 | app.use(resizableColumn)
36 | app.use(VueWebsocket, IO, '/events')
37 | app.use(Autosize)
38 | app.use(VueChartkick)
39 | app.use(VueAnimXYZ)
40 |
41 | app.component('vue-date-picker', VueDatePicker)
42 |
43 | // Make the current route part of the main state.
44 | sync(store, router)
45 |
46 | // Global custom directive to enable automatic focus on field after page loading.
47 | app.directive('focus', {
48 | mounted(el, binding) {
49 | el.focus(binding.value)
50 | }
51 | })
52 |
53 | app.config.compilerOptions.whitespace = 'preserve'
54 |
55 | app.mount('#app')
56 |
--------------------------------------------------------------------------------
/src/polyfills.js:
--------------------------------------------------------------------------------
1 | import 'core-js/features/array/at' // ES2022
2 | import 'core-js/features/array/find-last' // ES2023
3 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router'
2 |
3 | import { routes } from '@/router/routes'
4 |
5 | const loadSavedScrollPosition = (to, from, savedPosition) => {
6 | if (savedPosition) {
7 | return savedPosition
8 | }
9 | return { left: 0, top: 0 }
10 | }
11 |
12 | const router = createRouter({
13 | history: createWebHistory(),
14 | scrollBehavior: loadSavedScrollPosition,
15 | routes
16 | })
17 |
18 | export default router
19 |
--------------------------------------------------------------------------------
/src/store/api/assettypes.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 |
3 | export default {
4 | getAssetTypes(callback) {
5 | client.get('/api/data/asset-types', callback)
6 | },
7 |
8 | getAssetType(assetTypeId, callback) {
9 | client.get(`/api/data/entity-types/${assetTypeId}`, callback)
10 | },
11 |
12 | newAssetType(assetType) {
13 | const data = {
14 | name: assetType.name,
15 | short_name: assetType.short_name,
16 | description: assetType.description,
17 | task_types: assetType.task_types
18 | }
19 | return client.ppost('/api/data/entity-types', data)
20 | },
21 |
22 | updateAssetType(assetType) {
23 | const data = {
24 | name: assetType.name,
25 | short_name: assetType.short_name,
26 | description: assetType.description,
27 | task_types: assetType.task_types,
28 | archived: assetType.archived === 'true'
29 | }
30 | return client.pput(`/api/data/entity-types/${assetType.id}`, data)
31 | },
32 |
33 | deleteAssetType(assetType) {
34 | return client.pdel(`/api/data/entity-types/${assetType.id}`)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/store/api/backgrounds.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 |
3 | export default {
4 | getBackgrounds() {
5 | return client.pget('/api/data/preview-background-files')
6 | },
7 |
8 | getBackground(backgroundId) {
9 | return client.pget(`/api/data/preview-background-files/${backgroundId}`)
10 | },
11 |
12 | newBackground(background) {
13 | return client.ppost('/api/data/preview-background-files/', background)
14 | },
15 |
16 | updateBackground(background) {
17 | return client.pput(
18 | `/api/data/preview-background-files/${background.id}`,
19 | background
20 | )
21 | },
22 |
23 | deleteBackground(background) {
24 | return client.pdel(`/api/data/preview-background-files/${background.id}`)
25 | },
26 |
27 | uploadBackgroundImage(background, formData) {
28 | return client.ppost(
29 | `/api/pictures/preview-background-files/${background.id}`,
30 | formData
31 | )
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/store/api/breakdown.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 |
3 | export default {
4 | getProductionEpisodesCasting(productionId) {
5 | const path = `/api/data/projects/${productionId}/episodes/casting`
6 | return client.pget(path)
7 | },
8 |
9 | getSequenceCasting(productionId, sequenceId, episodeId) {
10 | let path = `/api/data/projects/${productionId}/sequences/${sequenceId}/casting`
11 | if (episodeId && episodeId !== 'all') {
12 | path = `/api/data/projects/${productionId}/episodes/${episodeId}/sequences/all/casting`
13 | }
14 | return client.pget(path)
15 | },
16 |
17 | getAssetTypeCasting(productionId, assetTypeId) {
18 | const path = `/api/data/projects/${productionId}/asset-types/${assetTypeId}/casting`
19 | return client.pget(path)
20 | },
21 |
22 | updateCasting(productionId, entityId, casting, callback) {
23 | const path = `/api/data/projects/${productionId}/entities/${entityId}/casting`
24 | return client.pput(path, casting)
25 | },
26 |
27 | postCastingCsv(production, formData) {
28 | const path = `/api/import/csv/projects/${production.id}/casting`
29 | return client.ppost(path, formData)
30 | },
31 |
32 | getAssetCastIn(asset) {
33 | return client.pget(`/api/data/assets/${asset.id}/cast-in`)
34 | },
35 |
36 | getEpisodeCasting(episode) {
37 | const path = `/api/data/projects/${episode.project_id}/entities/${episode.id}/casting`
38 | return client.pget(path)
39 | },
40 |
41 | getShotCasting(shot) {
42 | const path = `/api/data/projects/${shot.project_id}/entities/${shot.id}/casting`
43 | return client.pget(path)
44 | },
45 |
46 | getAssetCasting(asset) {
47 | const projectId = asset.project_id || asset.production_id
48 | const path = `/api/data/projects/${projectId}/entities/${asset.id}/casting`
49 | return client.pget(path)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/store/api/concepts.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 |
3 | export default {
4 | getConcepts(production) {
5 | return client.pget(
6 | `/api/data/concepts/with-tasks?project_id=${production.id}`
7 | )
8 | },
9 |
10 | getConcept(conceptId) {
11 | return client.getModel('concepts', conceptId, true)
12 | },
13 |
14 | newConcept(concept) {
15 | const data = {
16 | name: concept.name,
17 | description: concept.description
18 | }
19 | return client.ppost(
20 | `/api/data/projects/${concept.project_id}/concepts`,
21 | data
22 | )
23 | },
24 |
25 | updateConcept(concept) {
26 | return client.pput(`/api/data/entities/${concept.id}`, concept)
27 | },
28 |
29 | deleteConcept(concept) {
30 | return client.pdel(`/api/data/concepts/${concept.id}?force=true`)
31 | },
32 |
33 | getEntityLinked(entity) {
34 | return client.pget(
35 | `/api/data/entities/${entity.id}/entities-linked/with-tasks`
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/store/api/customactions.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 | import superagent from 'superagent'
3 |
4 | export default {
5 | getCustomActions(callback) {
6 | client.get('/api/data/custom-actions', callback)
7 | },
8 |
9 | newCustomAction(customAction) {
10 | const data = {
11 | name: customAction.name,
12 | url: customAction.url
13 | }
14 | return client.ppost('/api/data/custom-actions/', data)
15 | },
16 |
17 | updateCustomAction(customAction) {
18 | const data = {
19 | name: customAction.name,
20 | url: customAction.url,
21 | entity_type: customAction.entityType,
22 | is_ajax: customAction.isAjax === 'true'
23 | }
24 | return client.pput(`/api/data/custom-actions/${customAction.id}`, data)
25 | },
26 |
27 | deleteCustomAction(customAction) {
28 | return client.pdel(`/api/data/custom-actions/${customAction.id}`)
29 | },
30 |
31 | postCustomAction(url, data) {
32 | return new Promise((resolve, reject) => {
33 | superagent
34 | .post(url)
35 | .withCredentials()
36 | .send(data)
37 | .end((err, res) => {
38 | if (err) reject(err)
39 | else resolve()
40 | })
41 | })
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/store/api/departments.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 |
3 | export default {
4 | getDepartments() {
5 | return client.pget('/api/data/departments')
6 | },
7 |
8 | getDepartment(departmentId) {
9 | return client.pget(`/api/data/departments/${departmentId}`)
10 | },
11 |
12 | newDepartment(department) {
13 | const data = {
14 | name: department.name,
15 | color: department.color,
16 | archived: department.archived === 'true'
17 | }
18 | return client.ppost('/api/data/departments', data)
19 | },
20 |
21 | editDepartment(department) {
22 | const data = {
23 | name: department.name,
24 | color: department.color,
25 | archived: department.archived === 'true'
26 | }
27 | return client.pput(`/api/data/departments/${department.id}`, data)
28 | },
29 |
30 | deleteDepartment(department) {
31 | return client.pdel(`/api/data/departments/${department.id}`)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/store/api/entities.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 |
3 | export default {
4 | getEntityNews(entityId) {
5 | return client.pget(`/api/data/entities/${entityId}/news`)
6 | },
7 |
8 | getEntityPreviewFiles(entityId) {
9 | return client.pget(`/api/data/entities/${entityId}/preview-files`)
10 | },
11 |
12 | getEntityTimeLogs(entityId) {
13 | return client.pget(`/api/data/entities/${entityId}/time-spents`)
14 | },
15 |
16 | getEntityChats() {
17 | return client.pget(`/api/data/user/chats`)
18 | },
19 |
20 | getChat(entityId) {
21 | return client.pget(`/api/data/entities/${entityId}/chat`)
22 | },
23 |
24 | getChatMessages(entityId) {
25 | return client.pget(`/api/data/entities/${entityId}/chat/messages`)
26 | },
27 |
28 | getChatMessage(entityId, messageId) {
29 | return client.pget(
30 | `/api/data/entities/${entityId}/chat/messages/${messageId}`
31 | )
32 | },
33 |
34 | joinChat(entityId) {
35 | return client.ppost(`/api/actions/user/chats/${entityId}/join`, {})
36 | },
37 |
38 | leaveChat(entityId) {
39 | return client.pdel(`/api/actions/user/chats/${entityId}/join`)
40 | },
41 |
42 | sendMessage(entityId, message, attachments) {
43 | let data = { message }
44 | if (attachments?.length) {
45 | data = new FormData()
46 | attachments.forEach((attachment, index) => {
47 | data.append(`file-${index}`, attachment.get('file'))
48 | })
49 | data.set('message', message)
50 | }
51 | return client.ppost(`/api/data/entities/${entityId}/chat/messages`, data)
52 | },
53 |
54 | deleteMessage(entityId, messageId) {
55 | return client.pdel(
56 | `/api/data/entities/${entityId}/chat/messages/${messageId}`
57 | )
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/store/api/files.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 |
3 | export default {
4 | getFileStatuses() {
5 | return client.pget('/api/data/file-status')
6 | },
7 |
8 | getOutputTypes() {
9 | return client.pget('/api/data/output-types')
10 | },
11 |
12 | getEntityOutputFiles(entityId) {
13 | return client.pget(`/api/data/entities/${entityId}/output-files`)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/store/api/news.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 | import { buildQueryString } from '@/lib/query'
3 |
4 | export default {
5 | getLastNews(params) {
6 | const { isStudio, productionId } = params
7 | if (isStudio) {
8 | delete params.isStudio
9 | const path = buildQueryString(`/api/data/projects/news`, params)
10 | return client.pget(path)
11 | } else if (productionId) {
12 | delete params.productionId
13 | const path = buildQueryString(
14 | `/api/data/projects/${productionId}/news`,
15 | params
16 | )
17 | return client.pget(path)
18 | } else {
19 | return Promise.resolve({ data: [], total: 0, stats: [] })
20 | }
21 | },
22 |
23 | getNews(projectId, newsId) {
24 | const path = `/api/data/projects/${projectId}/news/${newsId}`
25 | return client.pget(path)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/store/api/notifications.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 | import { buildQueryString } from '@/lib/query'
3 |
4 | export default {
5 | getNotifications(params) {
6 | const path = buildQueryString('/api/data/user/notifications', params)
7 | return client.pget(path)
8 | },
9 |
10 | getNotification(notificationId) {
11 | return client.pget(`/api/data/user/notifications/${notificationId}`)
12 | },
13 |
14 | updateNotificationReadStatus(notificationId, readStatus) {
15 | return client.pput(`/api/data/user/notifications/${notificationId}`, {
16 | read: readStatus
17 | })
18 | },
19 |
20 | markAllNotificationsAsRead() {
21 | return client.ppost('/api/actions/user/notifications/mark-all-as-read', {})
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/store/api/studios.js:
--------------------------------------------------------------------------------
1 | import client from '@/store/api/client'
2 |
3 | export default {
4 | getStudios() {
5 | return client.pget('/api/data/studios')
6 | },
7 |
8 | newStudio(studio) {
9 | const data = {
10 | name: studio.name,
11 | color: studio.color,
12 | archived: studio.archived === 'true'
13 | }
14 | return client.ppost('/api/data/studios', data)
15 | },
16 |
17 | editStudio(studio) {
18 | const data = {
19 | name: studio.name,
20 | color: studio.color,
21 | archived: studio.archived === 'true'
22 | }
23 | return client.pput(`/api/data/studios/${studio.id}`, data)
24 | },
25 |
26 | deleteStudio(studio) {
27 | return client.pdel(`/api/data/studios/${studio.id}`)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | export const route = state => {
2 | return state.route
3 | }
4 |
--------------------------------------------------------------------------------
/src/store/modules/entities.js:
--------------------------------------------------------------------------------
1 | import entitiesApi from '@/store/api/entities'
2 | import { RESET_ALL } from '@/store/mutation-types'
3 |
4 | const initialState = {}
5 |
6 | const state = { ...initialState }
7 |
8 | const getters = {}
9 |
10 | const actions = {
11 | async getEntityNews({ commit }, entityId) {
12 | return entitiesApi.getEntityNews(entityId)
13 | },
14 |
15 | async getEntityPreviewFiles({ commit }, entityId) {
16 | return entitiesApi.getEntityPreviewFiles(entityId)
17 | },
18 |
19 | async getEntityTimeLogs({ commit }, entityId) {
20 | return entitiesApi.getEntityTimeLogs(entityId)
21 | },
22 |
23 | async getEntityChats({ commit }) {
24 | return entitiesApi.getEntityChats()
25 | },
26 |
27 | async getEntityChat({ commit }, entityId) {
28 | return entitiesApi.getChat(entityId)
29 | },
30 |
31 | async joinEntityChat({ commit }, entityId) {
32 | return entitiesApi.joinChat(entityId)
33 | },
34 |
35 | async leaveEntityChat({ commit }, entityId) {
36 | return entitiesApi.leaveChat(entityId)
37 | },
38 |
39 | async sendChatMessage({ commit }, { entityId, message, attachments }) {
40 | return entitiesApi.sendMessage(entityId, message, attachments)
41 | },
42 |
43 | async getChatMessage({ commit }, { entityId, messageId }) {
44 | return entitiesApi.getChatMessage(entityId, messageId)
45 | },
46 |
47 | async deleteChatMessage({ commit }, { entityId, messageId }) {
48 | return entitiesApi.deleteMessage(entityId, messageId)
49 | },
50 |
51 | async getEntityChatMessages({ commit }, entityId) {
52 | return entitiesApi.getChatMessages(entityId)
53 | }
54 | }
55 |
56 | const mutations = {
57 | [RESET_ALL](state) {
58 | Object.assign(state, { ...initialState })
59 | }
60 | }
61 |
62 | export default {
63 | state,
64 | getters,
65 | actions,
66 | mutations
67 | }
68 |
--------------------------------------------------------------------------------
/src/store/modules/files.js:
--------------------------------------------------------------------------------
1 | import filesApi from '@/store/api/files'
2 |
3 | import {
4 | SET_FILE_STATUSES,
5 | SET_OUTPUT_FILE_TYPES,
6 | RESET_ALL
7 | } from '@/store/mutation-types'
8 |
9 | const initialState = {
10 | fileStatuses: [],
11 | fileStatusMap: new Map(),
12 | outputFileTypes: [],
13 | outputFileTypeMap: new Map(),
14 | entitiyOutputFiles: []
15 | }
16 |
17 | const state = { ...initialState }
18 |
19 | const getters = {
20 | fileStatusMap: state => state.fileStatusMap,
21 | outputFileTypeMap: state => state.outputFileTypeMap
22 | }
23 |
24 | const actions = {
25 | async loadFileStatuses({ commit }) {
26 | const fileStatuses = await filesApi.getFileStatuses()
27 | commit(SET_FILE_STATUSES, fileStatuses)
28 | },
29 |
30 | async loadOutputTypes({ commit }) {
31 | const outputFileTypes = await filesApi.getOutputTypes()
32 | commit(SET_OUTPUT_FILE_TYPES, outputFileTypes)
33 | },
34 |
35 | async loadEntityOutputFiles({ commit }, entityId) {
36 | try {
37 | const entityOutputFiles = await filesApi.getEntityOutputFiles(entityId)
38 | return entityOutputFiles
39 | } catch (error) {
40 | console.error('Error loading entity output files', error)
41 | return []
42 | }
43 | }
44 | }
45 |
46 | const mutations = {
47 | [RESET_ALL](state) {
48 | Object.assign(state, { ...initialState })
49 | },
50 |
51 | [SET_FILE_STATUSES](state, fileStatuses) {
52 | state.fileStatuses = fileStatuses
53 | state.fileStatusMap = new Map(
54 | fileStatuses.map(status => [status.id, status])
55 | )
56 | },
57 |
58 | [SET_OUTPUT_FILE_TYPES](state, outputFileTypes) {
59 | state.outputFileTypes = outputFileTypes
60 | state.outputFileTypeMap = new Map(outputFileTypes.map(oft => [oft.id, oft]))
61 | }
62 | }
63 |
64 | export default {
65 | state,
66 | getters,
67 | actions,
68 | mutations
69 | }
70 |
--------------------------------------------------------------------------------
/src/stories/Button.stories.js:
--------------------------------------------------------------------------------
1 | import ButtonSimple from '../components/widgets/ButtonSimple.vue'
2 |
3 | // More on default export: https://storybook.js.org/docs/vue/writing-stories/introduction#default-export
4 | export default {
5 | title: 'Example/Button',
6 | component: ButtonSimple,
7 | // More on argTypes: https://storybook.js.org/docs/vue/api/argtypes
8 | argTypes: {
9 | backgroundColor: { control: 'color' },
10 | size: {
11 | control: { type: 'select' },
12 | options: ['small', 'medium', 'large'],
13 | },
14 | },
15 | };
16 |
17 | // More on component templates: https://storybook.js.org/docs/vue/writing-stories/introduction#using-args
18 | const Template = (args, { argTypes }) => ({
19 | props: Object.keys(argTypes),
20 | components: { ButtonSimple },
21 | template: '',
22 | });
23 |
24 | export const Primary = Template.bind({});
25 | // More on args: https://storybook.js.org/docs/vue/writing-stories/args
26 | Primary.args = {
27 | primary: true,
28 | label: 'Button',
29 | };
30 |
31 | export const Secondary = Template.bind({});
32 | Secondary.args = {
33 | label: 'Button',
34 | };
35 |
36 | export const Large = Template.bind({});
37 | Large.args = {
38 | size: 'large',
39 | label: 'Button',
40 | };
41 |
42 | export const Small = Template.bind({});
43 | Small.args = {
44 | size: 'small',
45 | label: 'Button',
46 | };
47 |
--------------------------------------------------------------------------------
/src/stories/Button.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
55 |
--------------------------------------------------------------------------------
/src/stories/Header.stories.js:
--------------------------------------------------------------------------------
1 | import MyHeader from './Header';
2 |
3 | export default {
4 | title: 'Example/Header',
5 | component: MyHeader,
6 | parameters: {
7 | // More on Story layout: https://storybook.js.org/docs/vue/configure/story-layout
8 | layout: 'fullscreen',
9 | },
10 | };
11 |
12 | const Template = (args, { argTypes }) => ({
13 | props: Object.keys(argTypes),
14 | components: { MyHeader },
15 | template:
16 | '',
17 | });
18 |
19 | export const LoggedIn = Template.bind({});
20 | LoggedIn.args = {
21 | user: {
22 | name: 'Jane Doe',
23 | },
24 | };
25 |
26 | export const LoggedOut = Template.bind({});
27 | LoggedOut.args = {};
28 |
--------------------------------------------------------------------------------
/src/stories/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
31 |
32 |
33 |
61 |
--------------------------------------------------------------------------------
/src/stories/Page.stories.js:
--------------------------------------------------------------------------------
1 | import { within, userEvent } from '@storybook/testing-library';
2 |
3 | import MyPage from './Page';
4 |
5 | export default {
6 | title: 'Example/Page',
7 | component: MyPage,
8 | parameters: {
9 | // More on Story layout: https://storybook.js.org/docs/vue/configure/story-layout
10 | layout: 'fullscreen',
11 | },
12 | };
13 |
14 | const Template = () => ({
15 | components: { MyPage },
16 | template: '',
17 | });
18 |
19 | export const LoggedOut = Template.bind({});
20 |
21 | // More on interaction testing: https://storybook.js.org/docs/vue/writing-tests/interaction-testing
22 | export const LoggedIn = Template.bind({});
23 | LoggedIn.play = async ({ canvasElement }) => {
24 | const canvas = within(canvasElement);
25 | const loginButton = await canvas.getByRole('button', { name: /Log in/i });
26 | await userEvent.click(loginButton);
27 | };
28 |
--------------------------------------------------------------------------------
/src/stories/assets/code-brackets.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/stories/assets/comments.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/stories/assets/direction.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/stories/assets/flow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/stories/assets/repo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/stories/button.css:
--------------------------------------------------------------------------------
1 | .storybook-button {
2 | font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | font-weight: 700;
4 | border: 0;
5 | border-radius: 3em;
6 | cursor: pointer;
7 | display: inline-block;
8 | line-height: 1;
9 | }
10 | .storybook-button--primary {
11 | color: white;
12 | background-color: #1ea7fd;
13 | }
14 | .storybook-button--secondary {
15 | color: #333;
16 | background-color: transparent;
17 | box-shadow: rgba(0, 0, 0, 0.15) 0 0 0 1px inset;
18 | }
19 | .storybook-button--small {
20 | font-size: 12px;
21 | padding: 10px 16px;
22 | }
23 | .storybook-button--medium {
24 | font-size: 14px;
25 | padding: 11px 20px;
26 | }
27 | .storybook-button--large {
28 | font-size: 16px;
29 | padding: 12px 24px;
30 | }
31 |
--------------------------------------------------------------------------------
/src/stories/header.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | border-bottom: 1px solid rgba(0, 0, 0, 0.1);
4 | padding: 15px 20px;
5 | display: flex;
6 | align-items: center;
7 | justify-content: space-between;
8 | }
9 |
10 | svg {
11 | display: inline-block;
12 | vertical-align: top;
13 | }
14 |
15 | h1 {
16 | font-weight: 900;
17 | font-size: 20px;
18 | line-height: 1;
19 | margin: 6px 0 6px 10px;
20 | display: inline-block;
21 | vertical-align: top;
22 | }
23 |
24 | button + button {
25 | margin-left: 10px;
26 | }
27 |
28 | .welcome {
29 | color: #333;
30 | font-size: 14px;
31 | margin-right: 10px;
32 | }
33 |
--------------------------------------------------------------------------------
/src/stories/page.css:
--------------------------------------------------------------------------------
1 | section {
2 | font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | font-size: 14px;
4 | line-height: 24px;
5 | padding: 48px 20px;
6 | margin: 0 auto;
7 | max-width: 600px;
8 | color: #333;
9 | }
10 |
11 | section h2 {
12 | font-weight: 900;
13 | font-size: 32px;
14 | line-height: 1;
15 | margin: 0 0 4px;
16 | display: inline-block;
17 | vertical-align: top;
18 | }
19 |
20 | section p {
21 | margin: 1em 0;
22 | }
23 |
24 | section a {
25 | text-decoration: none;
26 | color: #1ea7fd;
27 | }
28 |
29 | section ul {
30 | padding-left: 30px;
31 | margin: 1em 0;
32 | }
33 |
34 | section li {
35 | margin-bottom: 8px;
36 | }
37 |
38 | section .tip {
39 | display: inline-block;
40 | border-radius: 1em;
41 | font-size: 11px;
42 | line-height: 12px;
43 | font-weight: 700;
44 | background: #e7fdd8;
45 | color: #66bf3c;
46 | padding: 4px 12px;
47 | margin-right: 10px;
48 | vertical-align: top;
49 | }
50 |
51 | section .tip-wrapper {
52 | font-size: 13px;
53 | line-height: 20px;
54 | margin-top: 40px;
55 | margin-bottom: 40px;
56 | }
57 |
58 | section .tip-wrapper svg {
59 | display: inline-block;
60 | height: 12px;
61 | width: 12px;
62 | margin-right: 4px;
63 | vertical-align: top;
64 | margin-top: 3px;
65 | }
66 |
67 | section .tip-wrapper svg path {
68 | fill: #1ea7fd;
69 | }
70 |
--------------------------------------------------------------------------------
/src/substituted-model-viewer.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/src/substituted-model-viewer.js
--------------------------------------------------------------------------------
/src/testrouter/index.js:
--------------------------------------------------------------------------------
1 | import VueRouter from 'vue-router'
2 | import { routes } from '@/router/routes'
3 |
4 | export default new VueRouter({
5 | routes
6 | })
7 |
--------------------------------------------------------------------------------
/tests/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true
4 | },
5 | globals: {
6 | vi: true,
7 | cy: true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tests/e2e/integration/production.js:
--------------------------------------------------------------------------------
1 | describe('Production creation', () => {
2 | beforeEach(() => {
3 | cy.request('POST', '/api/auth/login', {
4 | email: 'admin@example.com',
5 | password: 'mysecretpassword'
6 | })
7 | .its('body')
8 | .as('currentUser')
9 | })
10 |
11 | it('sets auth cookie when logging in via form submission', function () {
12 | cy.visit('/open-productions')
13 | cy.get('#create-production-button').click()
14 | cy.get('.modal.is-active').should('contain', 'Add')
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/tests/e2e/plugins/index.js:
--------------------------------------------------------------------------------
1 | module.exports = function (on, config) {
2 | // configure plugins here
3 | }
4 |
--------------------------------------------------------------------------------
/tests/e2e/support/index.js:
--------------------------------------------------------------------------------
1 | module.exports = function (on, config) {
2 | // configure plugins here
3 | }
4 |
--------------------------------------------------------------------------------
/tests/fabric.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/tests/fabric.js
--------------------------------------------------------------------------------
/tests/setup.js:
--------------------------------------------------------------------------------
1 | const noop = () => {}
2 | Object.defineProperty(window, 'scrollTo', { value: noop, writable: true })
3 |
--------------------------------------------------------------------------------
/tests/spinner.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/tests/spinner.js
--------------------------------------------------------------------------------
/tests/substituted-model-viewer.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cgwire/kitsu/b572c9cef17db58729c59a8ecc3aff1927e76b95/tests/substituted-model-viewer.js
--------------------------------------------------------------------------------
/tests/unit.setup.js:
--------------------------------------------------------------------------------
1 | import { config } from "@vue/test-utils"
2 | import moment from 'moment'
3 |
4 | moment.locale('en')
5 | config.mocks = {
6 | $t: tKey => tKey // just return translation key
7 | }
8 | config.global.mocks = {
9 | $t: tKey => tKey // just return translation key
10 | }
11 |
--------------------------------------------------------------------------------
/tests/unit/fixtures/person-store.js:
--------------------------------------------------------------------------------
1 | export default {
2 | getters: {
3 | isCurrentUserVendor: () => false,
4 | people: () => [
5 | { id: 'person-1', name: 'John', active: true },
6 | { id: 'person-2', name: 'James', active: true },
7 | { id: 'person-3', name: 'Ema', active: true }
8 | ],
9 | personMap: () => new Map(Object.entries({
10 | 'person-1': { id: 'person-1', name: 'John', active: true },
11 | 'person-2': { id: 'person-2', name: 'James', active: true },
12 | 'person-3': { id: 'person-3', name: 'Ema', active: true }
13 | }))
14 | },
15 | actions: {}
16 | }
17 |
--------------------------------------------------------------------------------
/tests/unit/fixtures/production-store.js:
--------------------------------------------------------------------------------
1 | export default {
2 | getters: {
3 | currentProduction: () => ({
4 | id: 'production-1',
5 | name: 'Caminandes',
6 | team: ['person-2', 'person-3'],
7 | task_statuses: []
8 | }),
9 | productionTaskStatuses: () => [
10 | { id: 'task-status-1', name: 'Done', short_name: 'done' },
11 | { id: 'task-status-2', name: 'Wip', short_name: 'wip' }
12 | ],
13 | productionTaskTypes: () => [
14 | { id: 'task-type-1', name: 'Modeling' },
15 | { id: 'task-type-2', name: 'Animation' }
16 | ],
17 | productionAssetTypes: () => [
18 | { id: 'asset-type-1', name: 'Characters' },
19 | { id: 'asset-type-2', name: 'Sets' }
20 | ],
21 | productionShotTaskTypes: () => [
22 | { id: 'task-type-1', name: 'Animation' },
23 | ],
24 | isTVShow: () => false
25 | },
26 | actions: {}
27 | }
28 |
--------------------------------------------------------------------------------
/tests/unit/lib/array.spec.js:
--------------------------------------------------------------------------------
1 | import {
2 | intersection
3 | } from '@/lib/array'
4 |
5 | describe('array', () => {
6 | it('intersection', () => {
7 | const ids1 = ['asset-1', 'asset-2', 'asset-3']
8 | const ids2 = ['asset-1', 'asset-2', 'asset-3', 'asset-4']
9 | const ids3 = ['asset-1', 'asset-2']
10 | const ids4 = ['asset-2']
11 | let result = intersection([ids1])
12 | expect(result).toEqual(ids1)
13 | result = intersection([ids1, ids2, ids3])
14 | expect(result).toEqual(['asset-1', 'asset-2'])
15 | result = intersection([ids1, ids2, ids3, ids4])
16 | expect(result).toEqual(['asset-2'])
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/tests/unit/lib/auth.spec.js:
--------------------------------------------------------------------------------
1 | import auth from '@/lib/auth'
2 |
3 | describe('auth', () => {
4 | test('isPasswordValid', () => {
5 | expect(auth.isPasswordValid('', '')).toBeFalsy()
6 | expect(auth.isPasswordValid('abc', 'abc')).toBeFalsy()
7 | expect(auth.isPasswordValid('abcdefg', 'abcdefgh')).toBeFalsy()
8 | expect(auth.isPasswordValid('abcdefg', 'abcdefg')).toBeTruthy()
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/tests/unit/lib/colors.spec.js:
--------------------------------------------------------------------------------
1 | import colors from '@/lib/colors'
2 |
3 | describe('colors', () => {
4 | test('validationTextColor', () => {
5 | const taskTodo = {
6 | id: 'task-1',
7 | task_status_short_name: 'todo',
8 | is_default: true
9 | }
10 | const taskRunning = {
11 | id: 'task-1',
12 | task_status_short_name: 'wip'
13 | }
14 | expect(colors.validationTextColor(taskTodo)).toEqual('#333')
15 | expect(colors.validationTextColor(taskRunning)).toEqual('white')
16 | })
17 |
18 | test('hexToRGBa', () => {
19 | expect(colors.hexToRGBa('#FFFFFF', 0.4)).toEqual(
20 | 'rgba(255, 255, 255, 0.4)'
21 | )
22 | expect(colors.hexToRGBa('#000000', 0.2)).toEqual('rgba(0, 0, 0, 0.2)')
23 | expect(colors.hexToRGBa('#CC3211')).toEqual('rgb(204, 50, 17)')
24 | })
25 |
26 | test('darkenColor', () => {
27 | expect(colors.darkenColor('#FFFFFF')).toEqual(
28 | { color: [0, 0, 70], model: 'hsl', valpha: 1 }
29 | )
30 | expect(colors.darkenColor('#000000')).toEqual(
31 | { color: [0, 0, 0], model: 'hsl', valpha: 1 }
32 | )
33 | expect(colors.darkenColor('#123456')).toEqual(
34 | {
35 | color: [210, 104.61538461538464, 14.274509803921568],
36 | model: 'hsl',
37 | valpha: 1
38 | }
39 | )
40 | })
41 |
42 | test('lightenColor', () => {
43 | // TODO
44 | })
45 |
46 | test('fromString', () => {
47 | expect(colors.fromString('123456')).toEqual('#f0ee75')
48 | expect(colors.fromString('Jhon Doe')).toEqual('#9e75f0')
49 | })
50 | })
51 |
--------------------------------------------------------------------------------
/tests/unit/lib/descriptor.spec.js:
--------------------------------------------------------------------------------
1 | import descriptors from '@/lib/descriptors'
2 |
3 | describe('descriptors', () => {
4 | test('getChoiceOptions', () => {
5 | const descriptor = {
6 | field_name: 'difficulty',
7 | choices: ['easy', 'medium', 'difficult']
8 | }
9 | expect(descriptors.getChoicesOptions(descriptor)).toEqual([
10 | { label: '', value: '' },
11 | { label: 'easy', value: 'easy' },
12 | { label: 'medium', value: 'medium' },
13 | { label: 'difficult', value: 'difficult' }
14 | ])
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/tests/unit/lib/func.spec.js:
--------------------------------------------------------------------------------
1 | import func from '@/lib/func'
2 |
3 | describe('func', () => {
4 | test('runPromiseMapAsSeries', () => new Promise((done) => {
5 | let counter = 0
6 | const mapFunc = (item) => {
7 | counter += item
8 | return Promise.resolve(item)
9 | }
10 | func.runPromiseMapAsSeries([1, 5], mapFunc)
11 | .then(() => {
12 | expect(counter).toEqual(6)
13 | done()
14 | })
15 | }))
16 | })
17 |
--------------------------------------------------------------------------------
/tests/unit/lib/lang.spec.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment-timezone'
2 | import lang from '@/lib/lang'
3 | import timezone from '@/lib/timezone'
4 |
5 | import i18n from '@/lib/i18n'
6 | import store from '@/store'
7 |
8 | class ColorHash {
9 | constructor (colorData) {
10 | }
11 |
12 | hex (str) {
13 | return str
14 | }
15 | }
16 |
17 | global.ColorHash = ColorHash
18 |
19 | describe('lang', () => {
20 | store.commit('USER_LOGIN', {
21 | id: 'user-1',
22 | locale: 'fr_FR',
23 | timezone: 'Europe/Paris'
24 | })
25 | test('setLocale', () => {
26 | lang.setLocale('french')
27 | expect(moment.locale()).toEqual('fr')
28 | expect(i18n.global.locale).toEqual('fr')
29 | })
30 | })
31 |
32 | describe('timezone', () => {
33 | test('setTimezone', () => {
34 | expect(moment().tz()).toBeUndefined()
35 | timezone.setTimezone()
36 | expect(moment().tz()).toEqual('Europe/Paris')
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/tests/unit/lib/query.spec.js:
--------------------------------------------------------------------------------
1 | import { buildQueryString } from '@/lib/query'
2 |
3 | describe('query', () => {
4 | describe('Mount', () => {
5 | test('buildQueryString', () => {
6 | const path = buildQueryString('/data', {
7 | page: 2,
8 | id: 'entity-id'
9 | })
10 | expect(path).toEqual('/data?page=2&id=entity-id')
11 | })
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/tests/unit/lib/selection.spec.js:
--------------------------------------------------------------------------------
1 | import {
2 | buildSelectionGrid,
3 | appendSelectionGrid,
4 | clearSelectionGrid
5 | } from '@/lib/selection'
6 |
7 | describe('selection', () => {
8 | test('buildSelectionGrid', () => {
9 | let result = buildSelectionGrid(2, 2)
10 | expect(result).toEqual({
11 | 0: { 0: false, 1: false },
12 | 1: { 0: false, 1: false }
13 | })
14 | result = buildSelectionGrid(0, 0)
15 | expect(result).toEqual({})
16 | result = buildSelectionGrid(3, 2)
17 | expect(result).toEqual({
18 | 0: { 0: false, 1: false },
19 | 1: { 0: false, 1: false },
20 | 2: { 0: false, 1: false }
21 | })
22 | })
23 |
24 | test('appendSelectionGrid', () => {
25 | const selectionGrid = {
26 | 0: { 0: true, 1: false },
27 | 1: { 0: false, 1: true }
28 | }
29 | const result = appendSelectionGrid(selectionGrid, 1, 4, 2)
30 | expect(result).toEqual({
31 | 0: { 0: true, 1: false },
32 | 1: { 0: false, 1: false },
33 | 2: { 0: false, 1: false },
34 | 3: { 0: false, 1: false }
35 | })
36 | })
37 |
38 | test('clearSelectionGrid', () => {
39 | const selectionGrid = {
40 | 0: { 0: true, 1: false },
41 | 1: { 0: false, 1: true }
42 | }
43 | const result = clearSelectionGrid(selectionGrid)
44 | expect(result).toEqual({
45 | 0: { 0: false, 1: false },
46 | 1: { 0: false, 1: false }
47 | })
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/tests/unit/modals/addthumbnailsmodals.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import { createStore } from 'vuex'
3 | import { createMemoryHistory, createRouter } from 'vue-router'
4 |
5 | import i18n from '@/lib/i18n'
6 | import auth from '@/lib/auth'
7 |
8 | import AddThumbnailsModal from '@/components/modals/AddThumbnailsModal.vue'
9 |
10 | const router = createRouter({
11 | history: createMemoryHistory(),
12 | routes: []
13 | })
14 |
15 | describe('AddThumbnailsModal', () => {
16 | let store, shotStore
17 | let wrapper
18 |
19 | beforeEach(() => {
20 | shotStore = {
21 | state() {
22 | return {}
23 | },
24 | getters: {
25 | isAssets: () => true,
26 | assetValidationColumns: () => [],
27 | shotValidationColumns: () => [],
28 | },
29 | actions: {
30 | }
31 | }
32 | store = createStore({
33 | strict: true,
34 | modules: {
35 | shots: shotStore
36 | }
37 | })
38 |
39 | wrapper = shallowMount(AddThumbnailsModal, {
40 | store,
41 | i18n,
42 | router,
43 | auth,
44 | global: {
45 | mocks: {
46 | $store: store,
47 | }
48 | },
49 | props: {
50 | entityType: 'Asset',
51 | parent: 'assets'
52 | }
53 | })
54 | })
55 |
56 | describe('Mount', () => {
57 | it('empty', () => {
58 | wrapper.findComponent(AddThumbnailsModal)
59 | })
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import path from 'path'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [
8 | vue({
9 | template: {
10 | compilerOptions: {
11 | isCustomElement: tag => ['drag', 'drop', 'model-viewer'].includes(tag)
12 | }
13 | }
14 | })
15 | ],
16 | build: {
17 | sourcemap: true,
18 | target: 'es2020'
19 | },
20 | resolve: {
21 | extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
22 | alias: {
23 | '@': path.resolve(__dirname, 'src/'),
24 | vue: 'vue/dist/vue.esm-bundler.js',
25 | '@arch-inc/fabricjs-psbrush': path.resolve(
26 | __dirname,
27 | 'node_modules/@arch-inc/fabricjs-psbrush/dist/index.js'
28 | )
29 | }
30 | },
31 | css: {
32 | preprocessorOptions: {
33 | scss: {
34 | additionalData: `@use "@/variables.scss" as *;`,
35 | api: 'modern'
36 | }
37 | }
38 | },
39 | server: {
40 | host: '127.0.0.1',
41 | port: 8080,
42 | proxy: {
43 | '/api': {
44 | target: process.env.KITSU_API_TARGET || 'http://127.0.0.1:5000',
45 | changeOrigin: true,
46 | rewrite: path => path.replace(/^\/api/, '')
47 | },
48 | '/socket.io': {
49 | target: process.env.KITSU_EVENT_TARGET || 'http://127.0.0.1:5001',
50 | ws: true
51 | }
52 | }
53 | },
54 | test: {
55 | globals: true,
56 | threads: false,
57 | environment: 'jsdom',
58 | setupFiles: ['vitest-localstorage-mock', 'tests/unit.setup.js'],
59 | mockReset: false
60 | }
61 | })
62 |
--------------------------------------------------------------------------------