mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 14:53:50 +02:00
feat: add Group Calendar
This commit is contained in:
@@ -697,6 +697,36 @@ const groupReq = {
|
|||||||
};
|
};
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getGroupCalendar(groupId) {
|
||||||
|
return request(`calendar/${groupId}`, {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('../types/api/group').GetCalendars}
|
||||||
|
*/
|
||||||
|
getGroupCalendars(date) {
|
||||||
|
return request(`calendar?date=${date}`, {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('../types/api/group').GetFollowingCalendars}
|
||||||
|
*/
|
||||||
|
getFollowingGroupCalendars(date) {
|
||||||
|
return request(`calendar/following?date=${date}`, {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getFeaturedGroupCalendars(date) {
|
||||||
|
return request(`calendar/featured?date=${date}`, {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRequestedGroups() {
|
// getRequestedGroups() {
|
||||||
|
|||||||
@@ -13,22 +13,22 @@ $--theme-saturation: 0%;
|
|||||||
|
|
||||||
// AMOLED background tones
|
// AMOLED background tones
|
||||||
// #0f0f0f
|
// #0f0f0f
|
||||||
$--theme-bg-1: hsl($--theme-hue, $--theme-saturation, 6%);
|
$--theme-bg-1: hsl($--theme-hue, $--theme-saturation, 6%);
|
||||||
$--theme-bg-2: hsl($--theme-hue, $--theme-saturation, 6%);
|
$--theme-bg-2: hsl($--theme-hue, $--theme-saturation, 6%);
|
||||||
$--theme-bg-3: hsl($--theme-hue, $--theme-saturation, 6%);
|
$--theme-bg-3: hsl($--theme-hue, $--theme-saturation, 6%);
|
||||||
$--theme-bg-4: hsl($--theme-hue, $--theme-saturation, 7%);
|
$--theme-bg-4: hsl($--theme-hue, $--theme-saturation, 7%);
|
||||||
$--theme-bg-5: hsl($--theme-hue, $--theme-saturation, 9%);
|
$--theme-bg-5: hsl($--theme-hue, $--theme-saturation, 9%);
|
||||||
|
|
||||||
$--theme-border-1: hsl($--theme-hue, $--theme-saturation, 8%);
|
$--theme-border-1: hsl($--theme-hue, $--theme-saturation, 8%);
|
||||||
$--theme-border-2: hsla($--theme-hue, $--theme-saturation, 6%, 0.5);
|
$--theme-border-2: hsla($--theme-hue, $--theme-saturation, 6%, 0.5);
|
||||||
$--theme-border-3: hsl($--theme-hue, $--theme-saturation, 12%);
|
$--theme-border-3: hsl($--theme-hue, $--theme-saturation, 12%);
|
||||||
|
|
||||||
$--theme-text-1: hsl($--theme-hue, $--theme-saturation, 85%);
|
$--theme-text-1: hsl($--theme-hue, $--theme-saturation, 85%);
|
||||||
$--theme-text-2: hsl($--theme-hue, $--theme-saturation, 70%);
|
$--theme-text-2: hsl($--theme-hue, $--theme-saturation, 70%);
|
||||||
$--theme-text-3: hsl($--theme-hue, $--theme-saturation, 68%);
|
$--theme-text-3: hsl($--theme-hue, $--theme-saturation, 68%);
|
||||||
$--theme-text-4: hsl($--theme-hue, $--theme-saturation, 38%);
|
$--theme-text-4: hsl($--theme-hue, $--theme-saturation, 38%);
|
||||||
|
|
||||||
$--theme-primary: #B18FFF;
|
$--theme-primary: #b18fff;
|
||||||
$--theme-success: #67c23a;
|
$--theme-success: #67c23a;
|
||||||
$--theme-warning: #e6a23c;
|
$--theme-warning: #e6a23c;
|
||||||
$--theme-danger: #f56c6c;
|
$--theme-danger: #f56c6c;
|
||||||
@@ -128,12 +128,20 @@ $--message-danger-font-color: #ff4d4f;
|
|||||||
|
|
||||||
$--pagination-hover-color: $--theme-text-4;
|
$--pagination-hover-color: $--theme-text-4;
|
||||||
|
|
||||||
|
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||||
|
$--card-background-color: $--theme-bg-4;
|
||||||
|
|
||||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||||
|
|
||||||
@import '~element-ui/packages/theme-chalk/src/index';
|
@import '~element-ui/packages/theme-chalk/src/index';
|
||||||
|
|
||||||
@import '_theme.dark_styles';
|
:root {
|
||||||
|
--group-calendar-event-bg: #{$--calendar-selected-background-color};
|
||||||
|
--group-calendar-badge-following: #{darken($--theme-primary, 20%)};
|
||||||
|
--group-calendar-badge-normal: #{$--theme-info};
|
||||||
|
}
|
||||||
|
|
||||||
|
@import '_theme.dark_styles';
|
||||||
|
|
||||||
.el-table tr,
|
.el-table tr,
|
||||||
.el-table td.el-table__cell,
|
.el-table td.el-table__cell,
|
||||||
@@ -146,7 +154,7 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
|||||||
border: 1px solid hsl($--theme-hue, $--theme-saturation, 12%);
|
border: 1px solid hsl($--theme-hue, $--theme-saturation, 12%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tree{
|
.el-tree {
|
||||||
background: hsl($--theme-hue, $--theme-saturation, 9%);
|
background: hsl($--theme-hue, $--theme-saturation, 9%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +178,6 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
|||||||
color: $--color-primary;
|
color: $--color-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.x-friend-item:hover,
|
.x-friend-item:hover,
|
||||||
.x-change-image-item:hover {
|
.x-change-image-item:hover {
|
||||||
color: $--color-primary;
|
color: $--color-primary;
|
||||||
@@ -178,7 +185,74 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
|||||||
|
|
||||||
// Tag background variants
|
// Tag background variants
|
||||||
@each $type in success, warning, danger {
|
@each $type in success, warning, danger {
|
||||||
.el-tag--plain.el-tag--#{$type} {
|
.el-tag--plain.el-tag--#{$type} {
|
||||||
background-color: hsl($--theme-hue, $--theme-saturation, 9%);
|
background-color: hsl($--theme-hue, $--theme-saturation, 9%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-calendar {
|
||||||
|
background-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar__title {
|
||||||
|
color: $--theme-text-1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
background-color: $--theme-bg-5 !important;
|
||||||
|
border-color: $--theme-border-1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions__body {
|
||||||
|
background-color: $--theme-bg-4 !important;
|
||||||
|
color: $--theme-text-1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__tail {
|
||||||
|
border-left-color: #{darken($--theme-primary, 20%)} !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__node {
|
||||||
|
background-color: #{darken($--theme-primary, 20%)} !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow {
|
||||||
|
border-top-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||||
|
border-top-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||||
|
border-bottom-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||||
|
border-bottom-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow {
|
||||||
|
border-left-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||||
|
border-left-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow {
|
||||||
|
border-right-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||||
|
border-right-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group Calendar Dialog borders
|
||||||
|
.search-container {
|
||||||
|
border-bottom-color: $--border-color-light !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
border-bottom-color: $--border-color-light !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -126,8 +126,84 @@ $--message-danger-font-color: #ff4d4f;
|
|||||||
|
|
||||||
$--pagination-hover-color: $--theme-text-4;
|
$--pagination-hover-color: $--theme-text-4;
|
||||||
|
|
||||||
|
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||||
|
$--card-background-color: $--theme-bg-4;
|
||||||
|
|
||||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||||
|
|
||||||
@import '~element-ui/packages/theme-chalk/src/index';
|
@import '~element-ui/packages/theme-chalk/src/index';
|
||||||
|
|
||||||
@import '_theme.dark_styles';
|
:root {
|
||||||
|
--group-calendar-event-bg: #{$--calendar-selected-background-color};
|
||||||
|
--group-calendar-badge-following: #{$--color-success};
|
||||||
|
--group-calendar-badge-normal: #{$--color-primary};
|
||||||
|
}
|
||||||
|
|
||||||
|
@import '_theme.dark_styles';
|
||||||
|
|
||||||
|
.el-calendar {
|
||||||
|
background-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar__title {
|
||||||
|
color: $--theme-text-1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
background-color: $--theme-bg-5 !important;
|
||||||
|
border-color: $--theme-border-1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions__body {
|
||||||
|
background-color: $--theme-bg-4 !important;
|
||||||
|
color: $--theme-text-1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__tail {
|
||||||
|
border-left-color: $--theme-border-2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__node {
|
||||||
|
background-color: $--theme-border-2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow {
|
||||||
|
border-top-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||||
|
border-top-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||||
|
border-bottom-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||||
|
border-bottom-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow {
|
||||||
|
border-left-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||||
|
border-left-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow {
|
||||||
|
border-right-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||||
|
border-right-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group Calendar Dialog borders
|
||||||
|
.search-container {
|
||||||
|
border-bottom-color: $--border-color-light !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
border-bottom-color: $--border-color-light !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -126,8 +126,84 @@ $--message-danger-font-color: #ff4d4f;
|
|||||||
|
|
||||||
$--pagination-hover-color: $--theme-text-4;
|
$--pagination-hover-color: $--theme-text-4;
|
||||||
|
|
||||||
|
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||||
|
$--card-background-color: $--theme-bg-4;
|
||||||
|
|
||||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||||
|
|
||||||
@import '~element-ui/packages/theme-chalk/src/index';
|
@import '~element-ui/packages/theme-chalk/src/index';
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--group-calendar-event-bg: #{$--calendar-selected-background-color};
|
||||||
|
--group-calendar-badge-following: #{$--color-success};
|
||||||
|
--group-calendar-badge-normal: #{$--color-primary};
|
||||||
|
}
|
||||||
|
|
||||||
@import '_theme.dark_styles';
|
@import '_theme.dark_styles';
|
||||||
|
|
||||||
|
.el-calendar {
|
||||||
|
background-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar__title {
|
||||||
|
color: $--theme-text-1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
background-color: $--theme-bg-5 !important;
|
||||||
|
border-color: $--theme-border-1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions__body {
|
||||||
|
background-color: $--theme-bg-4 !important;
|
||||||
|
color: $--theme-text-1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__tail {
|
||||||
|
border-left-color: $--theme-border-2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__node {
|
||||||
|
background-color: $--theme-border-2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow {
|
||||||
|
border-top-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||||
|
border-top-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||||
|
border-bottom-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||||
|
border-bottom-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow {
|
||||||
|
border-left-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||||
|
border-left-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow {
|
||||||
|
border-right-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||||
|
border-right-color: $--theme-bg-4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group Calendar Dialog borders
|
||||||
|
.search-container {
|
||||||
|
border-bottom-color: $--border-color-light !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
border-bottom-color: $--border-color-light !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -556,11 +556,41 @@ div.options-container[style='margin-top: 45px; border-top: 1px solid rgb(238, 23
|
|||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-popper[x-placement^='top'] .popper__arrow,
|
.el-popover {
|
||||||
.el-popper[x-placement^='left'] .popper__arrow,
|
background-color: var(--dv_bg-top) !important;
|
||||||
.el-popper[x-placement^='right'] .popper__arrow,
|
border-color: var(--dv_bg-mid) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow {
|
||||||
|
border-top-color: var(--dv_bg-top) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||||
|
border-top-color: var(--dv_bg-top) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||||
display: none;
|
border-bottom-color: var(--dv_bg-top) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||||
|
border-bottom-color: var(--dv_bg-top) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow {
|
||||||
|
border-left-color: var(--dv_bg-top) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||||
|
border-left-color: var(--dv_bg-top) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow {
|
||||||
|
border-right-color: var(--dv_bg-top) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||||
|
border-right-color: var(--dv_bg-top) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.x-link,
|
img.x-link,
|
||||||
@@ -760,3 +790,44 @@ div.x-friend-list
|
|||||||
> span.el-input__count {
|
> span.el-input__count {
|
||||||
background-color: var(--mid) !important;
|
background-color: var(--mid) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--group-calendar-event-bg: rgba(var(--dv_bright-rgb), 0.1);
|
||||||
|
--group-calendar-badge-following: var(--dv_muted);
|
||||||
|
--group-calendar-badge-normal: var(--dv_bright);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar {
|
||||||
|
background-color: var(--dv_bg-top) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar__title {
|
||||||
|
color: var(--dv_bright) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
background-color: var(--dv_bg-mid) !important;
|
||||||
|
border-color: var(--dv_bg-bot) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions__body {
|
||||||
|
background-color: var(--dv_bg-top) !important;
|
||||||
|
color: var(--dv_bright) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__tail {
|
||||||
|
border-left-color: var(--dv_muted) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__node {
|
||||||
|
background-color: var(--dv_muted) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group Calendar Dialog borders
|
||||||
|
.search-container {
|
||||||
|
border-bottom: 1px solid var(--dv_muted) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
border-bottom: 2px solid var(--dv_muted) !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
--theme-text-muted: #906d92;
|
--theme-text-muted: #906d92;
|
||||||
--theme-text-rgb: 238, 204, 224;
|
--theme-text-rgb: 238, 204, 224;
|
||||||
--theme-text-muted-rgb: 144, 109, 146;
|
--theme-text-muted-rgb: 144, 109, 146;
|
||||||
|
|
||||||
|
--group-calendar-event-bg: rgba(var(--theme-text-muted-rgb), 0.1);
|
||||||
|
--group-calendar-badge-following: var(--theme-text-muted);
|
||||||
|
--group-calendar-badge-normal: var(--theme-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.options-container[style='margin-top: 45px; border-top: 1px solid rgb(238, 238, 238); padding-top: 30px;']:after {
|
div.options-container[style='margin-top: 45px; border-top: 1px solid rgb(238, 238, 238); padding-top: 30px;']:after {
|
||||||
@@ -372,3 +376,29 @@ div.x-friend-list
|
|||||||
> span.el-input__count {
|
> span.el-input__count {
|
||||||
background-color: var(--mid) !important;
|
background-color: var(--mid) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-calendar {
|
||||||
|
background-color: var(--mid) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar__title {
|
||||||
|
color: var(--theme-text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
background-color: var(--top) !important;
|
||||||
|
border-color: var(--top-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions__body {
|
||||||
|
background-color: var(--top) !important;
|
||||||
|
color: var(--theme-text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__tail {
|
||||||
|
border-left-color: var(--theme-text-muted) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__node {
|
||||||
|
background-color: var(--theme-text-muted) !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -117,6 +117,12 @@ body {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
font-family: var(--md-sys-typescale-body-small-font);
|
font-family: var(--md-sys-typescale-body-small-font);
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
|
|
||||||
|
--group-calendar-event-bg: rgba(var(--md-sys-color-primary), 0.05);
|
||||||
|
--group-calendar-badge-following: rgb(
|
||||||
|
var(--md-sys-color-primary-container)
|
||||||
|
);
|
||||||
|
--group-calendar-badge-normal: rgb(var(--md-sys-color-secondary));
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
@@ -1639,8 +1645,42 @@ img.x-link.el-popover__reference {
|
|||||||
border-top: 1px solid rgb(var(--md-sys-color-outline-variant));
|
border-top: 1px solid rgb(var(--md-sys-color-outline-variant));
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.el-popper .popper__arrow {
|
|
||||||
display: none;
|
.el-popover {
|
||||||
|
background: var(--md-sys-color-surface-3) !important;
|
||||||
|
border-color: rgb(var(--md-sys-color-outline-variant)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow {
|
||||||
|
border-top-color: var(--md-sys-color-surface-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||||
|
border-top-color: var(--md-sys-color-surface-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||||
|
border-bottom-color: var(--md-sys-color-surface-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||||
|
border-bottom-color: var(--md-sys-color-surface-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow {
|
||||||
|
border-left-color: var(--md-sys-color-surface-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||||
|
border-left-color: var(--md-sys-color-surface-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow {
|
||||||
|
border-right-color: var(--md-sys-color-surface-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||||
|
border-right-color: var(--md-sys-color-surface-3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status icon */
|
/* Status icon */
|
||||||
@@ -2113,3 +2153,42 @@ div.x-friend-list
|
|||||||
> span.el-input__count {
|
> span.el-input__count {
|
||||||
background-color: var(--md-sys-color-surface-3) !important;
|
background-color: var(--md-sys-color-surface-3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-calendar {
|
||||||
|
background: var(--md-sys-color-surface-3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar__title {
|
||||||
|
color: rgb(var(--md-sys-color-on-surface)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
background: var(--md-sys-color-surface-2) !important;
|
||||||
|
border-color: rgb(var(--md-sys-color-outline-variant)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions__body {
|
||||||
|
background: var(--md-sys-color-surface-3) !important;
|
||||||
|
color: rgb(var(--md-sys-color-on-surface)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__tail {
|
||||||
|
border-left-color: rgb(var(--md-sys-color-outline-variant)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__node {
|
||||||
|
background-color: rgb(var(--md-sys-color-outline)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x-dialog .top-content {
|
||||||
|
height: 640px !important ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group Calendar Dialog borders
|
||||||
|
.search-container {
|
||||||
|
border-bottom: 1px solid rgba(var(--md-sys-color-outline), 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
border-bottom: 2px solid rgba(var(--md-sys-color-outline), 0.5) !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
--font:
|
--font:
|
||||||
'Poppins', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans TC',
|
'Poppins', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans TC',
|
||||||
'Noto Sans SC', sans-serif;
|
'Noto Sans SC', sans-serif;
|
||||||
|
|
||||||
|
--group-calendar-event-bg: rgba(223, 162, 162, 0.1);
|
||||||
|
--group-calendar-badge-following: var(--theme);
|
||||||
|
--group-calendar-badge-normal: var(--lighter-border);
|
||||||
}
|
}
|
||||||
body,
|
body,
|
||||||
button,
|
button,
|
||||||
@@ -430,3 +434,75 @@ input[type='checkbox']:checked + .el-switch__core {
|
|||||||
background-size: 400% 100%;
|
background-size: 400% 100%;
|
||||||
animation: el-skeleton-loading 1.4s ease infinite;
|
animation: el-skeleton-loading 1.4s ease infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-calendar {
|
||||||
|
background-color: var(--lighter-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar__title {
|
||||||
|
color: var(--theme) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
background-color: var(--light-bg) !important;
|
||||||
|
border-color: var(--lighter-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions__body {
|
||||||
|
background-color: var(--bg) !important;
|
||||||
|
color: var(--theme) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__tail {
|
||||||
|
border-left-color: var(--lighter-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-timeline-item__node {
|
||||||
|
background-color: var(--lighter-border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popover {
|
||||||
|
background-color: var(--bg) !important;
|
||||||
|
border-color: var(--lighter-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow {
|
||||||
|
border-top-color: var(--bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||||
|
border-top-color: var(--bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||||
|
border-bottom-color: var(--bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||||
|
border-bottom-color: var(--bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow {
|
||||||
|
border-left-color: var(--bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||||
|
border-left-color: var(--bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow {
|
||||||
|
border-right-color: var(--bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||||
|
border-right-color: var(--bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group Calendar Dialog borders
|
||||||
|
.search-container {
|
||||||
|
border-bottom: 1px solid var(--lighter-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
border-bottom: 2px solid var(--lighter-bg) !important;
|
||||||
|
}
|
||||||
|
|||||||
493
src/components/dialogs/GroupDialog/GroupCalendarDialog.vue
Normal file
493
src/components/dialogs/GroupDialog/GroupCalendarDialog.vue
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible="visible"
|
||||||
|
:title="t('dialog.group_calendar.header')"
|
||||||
|
:show-close="false"
|
||||||
|
top="10vh"
|
||||||
|
width="90vw"
|
||||||
|
height="80vh"
|
||||||
|
@close="closeDialog">
|
||||||
|
<template #title>
|
||||||
|
<div class="dialog-title-container">
|
||||||
|
<span>{{ t('dialog.group_calendar.header') }}</span>
|
||||||
|
<el-button @click="toggleViewMode" type="primary" size="small" class="view-toggle-btn">
|
||||||
|
{{ viewMode === 'timeline' ? 'List View' : 'Calendar View' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="top-content">
|
||||||
|
<transition name="el-fade-in-linear" mode="out-in">
|
||||||
|
<div v-if="viewMode === 'timeline'" key="timeline" class="timeline-view">
|
||||||
|
<div class="timeline-container">
|
||||||
|
<el-timeline v-if="groupedTimelineEvents.length">
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="(timeGroup, key) of groupedTimelineEvents"
|
||||||
|
:key="key"
|
||||||
|
:timestamp="dayjs(timeGroup.startsAt).format('MM-DD ddd') + ' ' + timeGroup.startTime"
|
||||||
|
placement="top">
|
||||||
|
<div class="time-group-container">
|
||||||
|
<GroupCalendarEventCard
|
||||||
|
v-for="value in timeGroup.events"
|
||||||
|
:key="value.id"
|
||||||
|
:event="value"
|
||||||
|
mode="timeline"
|
||||||
|
:is-following="isEventFollowing(value.id)"
|
||||||
|
:card-class="{ 'grouped-card': timeGroup.events.length > 1 }" />
|
||||||
|
</div>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
<div v-else>No events found</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="calendar-container">
|
||||||
|
<el-calendar v-model="selectedDay" v-loading="isLoading">
|
||||||
|
<template #dateCell="{ date }">
|
||||||
|
<div class="date">
|
||||||
|
<div
|
||||||
|
class="calendar-date-content"
|
||||||
|
:class="{
|
||||||
|
'has-events': filteredCalendar[formatDateKey(date)]?.length
|
||||||
|
}">
|
||||||
|
{{ dayjs(date).format('D') }}
|
||||||
|
<div
|
||||||
|
v-if="filteredCalendar[formatDateKey(date)]?.length"
|
||||||
|
class="calendar-event-badge"
|
||||||
|
:class="
|
||||||
|
followingCalendarDate[formatDateKey(date)]
|
||||||
|
? 'has-following'
|
||||||
|
: 'no-following'
|
||||||
|
">
|
||||||
|
{{ filteredCalendar[formatDateKey(date)]?.length }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-calendar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else key="grid" class="grid-view">
|
||||||
|
<div class="search-container">
|
||||||
|
<el-input
|
||||||
|
v-model="searchQuery"
|
||||||
|
placeholder="Search groups or events..."
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
prefix-icon="el-icon-search"
|
||||||
|
class="search-input" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="groups-grid" v-loading="isLoading">
|
||||||
|
<div v-if="filteredGroupEvents.length" class="groups-container">
|
||||||
|
<div v-for="group in filteredGroupEvents" :key="group.groupId" class="group-row">
|
||||||
|
<div class="group-header" @click="showGroupDialog(group.groupId)">
|
||||||
|
{{ group.groupName }}
|
||||||
|
</div>
|
||||||
|
<div class="events-row">
|
||||||
|
<GroupCalendarEventCard
|
||||||
|
v-for="event in group.events"
|
||||||
|
:key="event.id"
|
||||||
|
:event="event"
|
||||||
|
mode="grid"
|
||||||
|
:is-following="isEventFollowing(event.id)"
|
||||||
|
card-class="grid-card" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="no-events">
|
||||||
|
{{ searchQuery ? 'No matching events found' : 'No events this month' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { groupRequest } from '../../../api';
|
||||||
|
import { useGroupStore } from '../../../stores';
|
||||||
|
import GroupCalendarEventCard from './GroupCalendarEventCard.vue';
|
||||||
|
|
||||||
|
const { cachedGroups } = storeToRefs(useGroupStore());
|
||||||
|
const { showGroupDialog } = useGroupStore();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
|
const calendar = ref([]);
|
||||||
|
const followingCalendar = ref([]);
|
||||||
|
const selectedDay = ref(new Date());
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const viewMode = ref('timeline'); // 'timeline' | 'grid'
|
||||||
|
const searchQuery = ref('');
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
async (newVisible) => {
|
||||||
|
if (newVisible) {
|
||||||
|
isLoading.value = true;
|
||||||
|
await Promise.all([getCalendarData(), getFollowingCalendarData()])
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error fetching calendar data:', error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isLoading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => selectedDay.value,
|
||||||
|
async (newDate, oldDate) => {
|
||||||
|
if (props.visible && oldDate) {
|
||||||
|
const newMonth = dayjs(newDate).format('YYYY-MM');
|
||||||
|
const oldMonth = dayjs(oldDate).format('YYYY-MM');
|
||||||
|
|
||||||
|
if (newMonth !== oldMonth) {
|
||||||
|
isLoading.value = true;
|
||||||
|
await Promise.all([getCalendarData(), getFollowingCalendarData()])
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error fetching calendar data:', error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isLoading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredCalendar = computed(() => {
|
||||||
|
const result = {};
|
||||||
|
calendar.value.forEach((item) => {
|
||||||
|
const currentDate = formatDateKey(item.startsAt);
|
||||||
|
if (!Array.isArray(result[currentDate])) {
|
||||||
|
result[currentDate] = [];
|
||||||
|
}
|
||||||
|
result[currentDate].push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.values(result).forEach((events) => {
|
||||||
|
events.sort((a, b) => dayjs(a.startsAt).diff(dayjs(b.startsAt)));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const followingCalendarDate = computed(() => {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
const followingIds = new Set(followingCalendar.value.map((item) => item.id));
|
||||||
|
|
||||||
|
calendar.value.forEach((event) => {
|
||||||
|
if (followingIds.has(event.id)) {
|
||||||
|
const dateKey = formatDateKey(event.startsAt);
|
||||||
|
if (!result[dateKey]) {
|
||||||
|
result[dateKey] = [];
|
||||||
|
}
|
||||||
|
result[dateKey].push(event.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedSelectedDay = computed(() => {
|
||||||
|
return formatDateKey(selectedDay.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupedTimelineEvents = computed(() => {
|
||||||
|
const eventsForDay = filteredCalendar.value[formattedSelectedDay.value] || [];
|
||||||
|
const timeGroups = {};
|
||||||
|
|
||||||
|
eventsForDay.forEach((event) => {
|
||||||
|
const startTimeKey = dayjs(event.startsAt).format('HH:mm');
|
||||||
|
if (!timeGroups[startTimeKey]) {
|
||||||
|
timeGroups[startTimeKey] = [];
|
||||||
|
}
|
||||||
|
timeGroups[startTimeKey].push(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.entries(timeGroups)
|
||||||
|
.map(([startTime, events]) => ({
|
||||||
|
startTime,
|
||||||
|
events,
|
||||||
|
startsAt: events[0].startsAt,
|
||||||
|
hasFollowing: events.some((event) => isEventFollowing(event.id))
|
||||||
|
}))
|
||||||
|
.sort((a, b) => dayjs(a.startsAt).diff(dayjs(b.startsAt)));
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupedByGroupEvents = computed(() => {
|
||||||
|
const currentMonth = dayjs(selectedDay.value).month();
|
||||||
|
const currentYear = dayjs(selectedDay.value).year();
|
||||||
|
|
||||||
|
const currentMonthEvents = calendar.value.filter((event) => {
|
||||||
|
const eventDate = dayjs(event.startsAt);
|
||||||
|
return eventDate.month() === currentMonth && eventDate.year() === currentYear;
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupMap = new Map();
|
||||||
|
currentMonthEvents.forEach((event) => {
|
||||||
|
const groupId = event.ownerId;
|
||||||
|
if (!groupMap.has(groupId)) {
|
||||||
|
groupMap.set(groupId, []);
|
||||||
|
}
|
||||||
|
groupMap.get(groupId).push(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
Array.from(groupMap.values()).forEach((events) => {
|
||||||
|
events.sort((a, b) => (dayjs(a.startsAt).isBefore(dayjs(b.startsAt)) ? -1 : 1));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(groupMap.entries()).map(([groupId, events]) => ({
|
||||||
|
groupId,
|
||||||
|
groupName: getGroupName(events[0]),
|
||||||
|
events: events
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredGroupEvents = computed(() => {
|
||||||
|
if (!searchQuery.value.trim()) return groupedByGroupEvents.value;
|
||||||
|
|
||||||
|
const query = searchQuery.value.toLowerCase();
|
||||||
|
return groupedByGroupEvents.value.filter((group) => {
|
||||||
|
if (group.groupName.toLowerCase().includes(query)) return true;
|
||||||
|
|
||||||
|
return group.events.some(
|
||||||
|
(event) =>
|
||||||
|
event.title?.toLowerCase().includes(query) || event.description?.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatDateKey = (date) => dayjs(date).format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
function getGroupName(event) {
|
||||||
|
if (!event) return '';
|
||||||
|
return cachedGroups.value.get(event.ownerId)?.name || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCalendarData() {
|
||||||
|
try {
|
||||||
|
const response = await groupRequest.getGroupCalendars(dayjs(selectedDay.value).toISOString());
|
||||||
|
calendar.value = response.results;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching calendars:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFollowingCalendarData() {
|
||||||
|
try {
|
||||||
|
const response = await groupRequest.getFollowingGroupCalendars(dayjs(selectedDay.value).toISOString());
|
||||||
|
followingCalendar.value = response.results;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching following calendars:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEventFollowing(eventId) {
|
||||||
|
return followingCalendar.value.some((item) => item.id === eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleViewMode() {
|
||||||
|
viewMode.value = viewMode.value === 'timeline' ? 'grid' : 'timeline';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDialog() {
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.x-dialog {
|
||||||
|
::v-deep .el-dialog {
|
||||||
|
max-height: 750px;
|
||||||
|
.el-dialog__body {
|
||||||
|
height: 680px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.top-content {
|
||||||
|
height: 640px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
.timeline-view {
|
||||||
|
.timeline-container {
|
||||||
|
::v-deep .el-timeline {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 200px;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 16px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 6px;
|
||||||
|
overflow: auto;
|
||||||
|
.el-timeline-item {
|
||||||
|
padding: 0 20px 20px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.time-group-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.calendar-container {
|
||||||
|
.date {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
.calendar-date-content {
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.has-events {
|
||||||
|
background-color: var(--group-calendar-event-bg, rgba(25, 102, 154, 0.05));
|
||||||
|
}
|
||||||
|
.calendar-event-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
min-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
padding: 0 4px;
|
||||||
|
line-height: 16px;
|
||||||
|
&.has-following {
|
||||||
|
background-color: var(--group-calendar-badge-following, #67c23a);
|
||||||
|
}
|
||||||
|
&.no-following {
|
||||||
|
background-color: var(--group-calendar-badge-normal, #409eff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
.view-toggle-btn {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-view {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.timeline-container {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.calendar-container {
|
||||||
|
width: 609px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-view {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.search-container {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
.search-input {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.groups-grid {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px 20px;
|
||||||
|
.groups-container {
|
||||||
|
overflow: visible;
|
||||||
|
.group-row {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
overflow: visible;
|
||||||
|
.group-header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
padding: 8px 12px 12px 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 0 -12px 16px -12px;
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
background-color: var(--el-color-primary-light-9);
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.events-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.no-events {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 200px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
220
src/components/dialogs/GroupDialog/GroupCalendarEventCard.vue
Normal file
220
src/components/dialogs/GroupDialog/GroupCalendarEventCard.vue
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<template>
|
||||||
|
<el-card :body-style="{ padding: '0px' }" class="event-card" :class="cardClass">
|
||||||
|
<img :src="bannerUrl" class="banner" />
|
||||||
|
<div class="event-content">
|
||||||
|
<div class="event-title">
|
||||||
|
<div v-if="showGroupName" class="event-group-name" @click="onGroupClick">
|
||||||
|
{{ groupName }}
|
||||||
|
</div>
|
||||||
|
<el-popover placement="right" width="500" trigger="hover">
|
||||||
|
<el-descriptions :title="event.title" size="small" :column="2" class="event-title-popover">
|
||||||
|
<template #extra>
|
||||||
|
<div>
|
||||||
|
{{ formatTimeRange(event.startsAt, event.endsAt) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-descriptions-item label="Category">{{
|
||||||
|
capitalizeFirst(event.category)
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Interested User">
|
||||||
|
{{ event.interestedUserCount }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Close Instance After End">
|
||||||
|
{{ event.closeInstanceAfterEndMinutes + ' min' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Created Date">{{
|
||||||
|
dayjs(event.createdAt).format('YYYY-MM-DD HH:mm')
|
||||||
|
}}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Description">{{ event.description }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<div class="event-title-content" slot="reference" @click="onGroupClick">
|
||||||
|
{{ event.title }}
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
<div class="event-info">
|
||||||
|
<div :class="timeClass">
|
||||||
|
{{ formattedTime }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ capitalizeFirst(event.accessType) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="isFollowing" class="following-badge">
|
||||||
|
<i class="el-icon-check"></i>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useGroupStore } from '../../../stores';
|
||||||
|
|
||||||
|
const { cachedGroups } = storeToRefs(useGroupStore());
|
||||||
|
const { showGroupDialog } = useGroupStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
event: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
validator: (value) => ['timeline', 'grid'].includes(value)
|
||||||
|
},
|
||||||
|
isFollowing: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
cardClass: {
|
||||||
|
type: [String, Object, Array],
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const showGroupName = computed(() => props.mode === 'timeline');
|
||||||
|
|
||||||
|
const timeClass = computed(() => (props.mode === 'grid' ? 'event-time' : ''));
|
||||||
|
|
||||||
|
const bannerUrl = computed(() => {
|
||||||
|
if (!props.event) return '';
|
||||||
|
if (props.event.imageUrl) {
|
||||||
|
return props.event.imageUrl;
|
||||||
|
} else {
|
||||||
|
return cachedGroups.value.get(props.event.ownerId)?.bannerUrl || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupName = computed(() => {
|
||||||
|
if (!props.event) return '';
|
||||||
|
return cachedGroups.value.get(props.event.ownerId)?.name || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedTime = computed(() => {
|
||||||
|
if (props.mode === 'timeline') {
|
||||||
|
return formatTimeRange(props.event.startsAt, props.event.endsAt);
|
||||||
|
} else {
|
||||||
|
return `${dayjs(props.event.startsAt).format('MM-DD ddd HH:mm')} - ${dayjs(props.event.endsAt).format('HH:mm')}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatTimeRange = (startsAt, endsAt) =>
|
||||||
|
`${dayjs(startsAt).format('HH:mm')} - ${dayjs(endsAt).format('HH:mm')}`;
|
||||||
|
|
||||||
|
const capitalizeFirst = (str) => str?.charAt(0).toUpperCase() + str?.slice(1);
|
||||||
|
|
||||||
|
const onGroupClick = () => {
|
||||||
|
showGroupDialog(props.event.ownerId);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.event-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
border-radius: 8px;
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
&.grouped-card {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&.grid-card {
|
||||||
|
flex: 0 0 280px;
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
::v-deep .el-card__body {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.banner {
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
.timeline-view & {
|
||||||
|
height: 125px;
|
||||||
|
}
|
||||||
|
.grid-view & {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.following-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
right: -9px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--group-calendar-badge-following, #67c23a);
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.event-content {
|
||||||
|
font-size: 12px;
|
||||||
|
.timeline-view & {
|
||||||
|
padding: 4px 12px 12px 12px;
|
||||||
|
}
|
||||||
|
.grid-view & {
|
||||||
|
padding: 8px 12px 12px 12px;
|
||||||
|
}
|
||||||
|
.event-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.grid-view & {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-group-name {
|
||||||
|
cursor: pointer;
|
||||||
|
.grid-view & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.event-title-content {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.2;
|
||||||
|
cursor: pointer;
|
||||||
|
.timeline-view & {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.event-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.timeline-view & > :first-child {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.grid-view & {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
.event-time {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::v-deep .el-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -42,9 +42,17 @@
|
|||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
addWrapperListeners();
|
addWrapperListeners();
|
||||||
|
removeTitleAttribute();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeTitleAttribute = () => {
|
||||||
|
const wrapper = elDialogRef.value?.$el;
|
||||||
|
if (wrapper && wrapper.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
wrapper.removeAttribute('title');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
emit('close');
|
emit('close');
|
||||||
removeWrapperListeners();
|
removeWrapperListeners();
|
||||||
|
|||||||
@@ -1632,6 +1632,9 @@
|
|||||||
"header": "Allowed Video Player Domains",
|
"header": "Allowed Video Player Domains",
|
||||||
"add_domain": "Add Domain",
|
"add_domain": "Add Domain",
|
||||||
"save": "Save"
|
"save": "Save"
|
||||||
|
},
|
||||||
|
"group_calendar": {
|
||||||
|
"header": "Group Calendar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
|
|||||||
48
src/types/api/group.d.ts
vendored
48
src/types/api/group.d.ts
vendored
@@ -7,6 +7,10 @@ export type GetGroup = (params: {
|
|||||||
params: { groupId: string; includeRoles?: boolean };
|
params: { groupId: string; includeRoles?: boolean };
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type GetCalendars = (date: string) => Promise<CalendarResponse>;
|
||||||
|
|
||||||
|
export type GetFollowingCalendars = (date: string) => Promise<CalendarResponse>;
|
||||||
|
|
||||||
// API response types
|
// API response types
|
||||||
interface GetGroupResponse {
|
interface GetGroupResponse {
|
||||||
badges: any[];
|
badges: any[];
|
||||||
@@ -28,9 +32,51 @@ interface GetGroupResponse {
|
|||||||
membershipStatus: string;
|
membershipStatus: string;
|
||||||
name: string;
|
name: string;
|
||||||
onlineMemberCount: number;
|
onlineMemberCount: number;
|
||||||
|
// groupId
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
privacy: string;
|
privacy: string;
|
||||||
rules: string;
|
rules: string;
|
||||||
shortCode: string;
|
shortCode: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exported interfaces
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group calendar event object
|
||||||
|
*/
|
||||||
|
export interface GroupCalendarEvent {
|
||||||
|
accessType: 'public' | 'group' | string;
|
||||||
|
category: 'hangout' | 'education' | 'roleplaying' | string;
|
||||||
|
closeInstanceAfterEndMinutes: number;
|
||||||
|
createdAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
description: string;
|
||||||
|
endsAt: string;
|
||||||
|
featured: boolean;
|
||||||
|
guestEarlyJoinMinutes: number;
|
||||||
|
hostEarlyJoinMinutes: number;
|
||||||
|
id: string;
|
||||||
|
imageId: string | null;
|
||||||
|
imageUrl?: string;
|
||||||
|
interestedUserCount: number;
|
||||||
|
isDraft: boolean;
|
||||||
|
languages: string[];
|
||||||
|
ownerId: string;
|
||||||
|
platforms: string[];
|
||||||
|
roleIds: string[] | null;
|
||||||
|
startsAt: string;
|
||||||
|
tags: string[];
|
||||||
|
title: string;
|
||||||
|
type: 'event' | string;
|
||||||
|
updatedAt: string;
|
||||||
|
userInterest?: GroupCalendarUserInterest;
|
||||||
|
usesInstanceOverflow: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal response types
|
||||||
|
interface CalendarResponse {
|
||||||
|
hasNext: boolean;
|
||||||
|
results: GroupCalendarEvent[];
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,13 +78,16 @@
|
|||||||
({{ groupInstances.length }})
|
({{ groupInstances.length }})
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
<el-button class="group-calendar-button" icon="el-icon-date" circle @click="openGroupCalendarDialog" />
|
||||||
<GroupsSidebar :group-instances="groupInstances" :group-order="inGameGroupOrder" />
|
<GroupsSidebar :group-instances="groupInstances" :group-order="inGameGroupOrder" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
<GroupCalendarDialog :visible="isGroupCalendarDialogVisible" @close="isGroupCalendarDialogVisible = false" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { userImage } from '../../shared/utils';
|
import { userImage } from '../../shared/utils';
|
||||||
@@ -97,6 +100,7 @@
|
|||||||
} from '../../stores';
|
} from '../../stores';
|
||||||
import FriendsSidebar from './components/FriendsSidebar.vue';
|
import FriendsSidebar from './components/FriendsSidebar.vue';
|
||||||
import GroupsSidebar from './components/GroupsSidebar.vue';
|
import GroupsSidebar from './components/GroupsSidebar.vue';
|
||||||
|
import GroupCalendarDialog from '../../components/dialogs/GroupDialog/GroupCalendarDialog.vue';
|
||||||
|
|
||||||
const { friends, isRefreshFriendsLoading, onlineFriendCount } = storeToRefs(useFriendStore());
|
const { friends, isRefreshFriendsLoading, onlineFriendCount } = storeToRefs(useFriendStore());
|
||||||
const { refreshFriendsList, confirmDeleteFriend } = useFriendStore();
|
const { refreshFriendsList, confirmDeleteFriend } = useFriendStore();
|
||||||
@@ -106,7 +110,26 @@
|
|||||||
const { quickSearchItems } = storeToRefs(useSearchStore());
|
const { quickSearchItems } = storeToRefs(useSearchStore());
|
||||||
const { inGameGroupOrder, groupInstances } = storeToRefs(useGroupStore());
|
const { inGameGroupOrder, groupInstances } = storeToRefs(useGroupStore());
|
||||||
|
|
||||||
|
const isGroupCalendarDialogVisible = ref(false);
|
||||||
|
|
||||||
const isSideBarTabShow = computed(() => {
|
const isSideBarTabShow = computed(() => {
|
||||||
return !(menuActiveIndex.value === 'friendList' || menuActiveIndex.value === 'charts');
|
return !(menuActiveIndex.value === 'friendList' || menuActiveIndex.value === 'charts');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function openGroupCalendarDialog() {
|
||||||
|
isGroupCalendarDialogVisible.value = true;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.group-calendar-button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
box-shadow: 0 0 6px rgba(0, 0, 0, 0.12);
|
||||||
|
border: none;
|
||||||
|
z-index: 5;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user