Add new links card and feature flag system for incremental dev. (#1714)
* Add new links card and feature flag system for incremental dev. * Switch to env variable for dev flags * Add members card * fix order of creators card * Fix owner icon color and bring org owner to top of list * lint + other fixes * Revamp feature flag system, add flag config page * Add button to flags page in dev mode * fix env overrides * make typescript happy with the refs
This commit is contained in:
parent
5b2d36e976
commit
1d9fe0c03d
1
assets/images/utils/book-text.svg
Normal file
1
assets/images/utils/book-text.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-text"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/><path d="M8 7h6"/><path d="M8 11h8"/></svg>
|
||||
|
After Width: | Height: | Size: 330 B |
@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
.known-error .multiselect__tags {
|
||||
border-color: var(--color-special-red) !important;
|
||||
border-color: var(--color-red) !important;
|
||||
background-color: var(--color-warning-bg) !important;
|
||||
|
||||
&::placeholder {
|
||||
@ -104,7 +104,7 @@
|
||||
font-weight: bold;
|
||||
|
||||
.required {
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
&.size-card-header {
|
||||
@ -285,6 +285,10 @@
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
:first-child {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
@ -476,7 +480,7 @@
|
||||
}
|
||||
|
||||
&.danger-button {
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,12 +640,12 @@ tr.button-transparent {
|
||||
}
|
||||
|
||||
.danger-button {
|
||||
--background-color: var(--color-special-red);
|
||||
--background-color: var(--color-red);
|
||||
--text-color: var(--color-brand-inverted);
|
||||
}
|
||||
|
||||
.moderation-button {
|
||||
--background-color: var(--color-special-orange);
|
||||
--background-color: var(--color-orange);
|
||||
--text-color: var(--color-brand-inverted);
|
||||
}
|
||||
|
||||
@ -670,7 +674,7 @@ tr.button-transparent {
|
||||
}
|
||||
|
||||
.known-error .multiselect__tags {
|
||||
border-color: var(--color-special-red) !important;
|
||||
border-color: var(--color-red) !important;
|
||||
background-color: var(--color-warning-bg) !important;
|
||||
|
||||
&::placeholder {
|
||||
@ -913,7 +917,7 @@ tr.button-transparent {
|
||||
.text-input-wrapper.known-error,
|
||||
input.known-error,
|
||||
textarea.known-error {
|
||||
outline: 2px solid var(--color-special-red);
|
||||
outline: 2px solid var(--color-red);
|
||||
background-color: var(--color-warning-bg) !important;
|
||||
|
||||
&::placeholder {
|
||||
@ -923,7 +927,7 @@ textarea.known-error {
|
||||
|
||||
.known-errors {
|
||||
min-height: 0;
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
@ -1269,3 +1273,179 @@ a.subtle-link {
|
||||
svg.inline-svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// START STUFF FOR OMORPHIA
|
||||
|
||||
.experimental-styles-within {
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap-4);
|
||||
}
|
||||
|
||||
.tag-list__item {
|
||||
background-color: var(--color-button-bg);
|
||||
padding: var(--gap-2) var(--gap-6);
|
||||
border-radius: var(--radius-max);
|
||||
font-weight: var(--weight-bold);
|
||||
font-size: var(--text-14);
|
||||
display: flex;
|
||||
gap: var(--gap-4);
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
color: var(--_color, var(--color-secondary));
|
||||
|
||||
svg {
|
||||
width: var(--icon-14);
|
||||
height: var(--icon-14);
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.status-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-8);
|
||||
padding-left: var(--gap-6);
|
||||
|
||||
color: var(--color-base);
|
||||
font-weight: var(--weight-bold);
|
||||
}
|
||||
|
||||
.status-list__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--gap-4);
|
||||
|
||||
svg {
|
||||
width: var(--icon-16);
|
||||
height: var(--icon-16);
|
||||
margin-right: var(--gap-4);
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--color-secondary);
|
||||
font-style: italic;
|
||||
font-weight: var(--weight-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.status-list__item--color-green svg {
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
.status-list__item--color-orange svg {
|
||||
color: var(--color-orange);
|
||||
}
|
||||
|
||||
.status-list__item--color-red svg {
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.status-list__item--color-blue svg {
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
.status-list__item--color-purple svg {
|
||||
color: var(--color-purple);
|
||||
}
|
||||
|
||||
&.flex-card,
|
||||
.flex-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-12);
|
||||
padding: var(--gap-16) var(--gap-24);
|
||||
|
||||
h2 {
|
||||
font-size: var(--text-18);
|
||||
font-weight: var(--weight-extrabold);
|
||||
color: var(--color-contrast);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--text-16);
|
||||
font-weight: var(--weight-bold);
|
||||
color: var(--color-base);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-8);
|
||||
}
|
||||
}
|
||||
|
||||
.list-style {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-12);
|
||||
font-weight: var(--weight-bold);
|
||||
|
||||
hr {
|
||||
width: 100%;
|
||||
border-color: var(--color-button-border);
|
||||
margin-block: var(--gap-2);
|
||||
}
|
||||
}
|
||||
|
||||
.iconified-list-item {
|
||||
display: flex;
|
||||
gap: var(--gap-8);
|
||||
vertical-align: middle;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
|
||||
svg {
|
||||
width: var(--icon-16);
|
||||
height: var(--icon-16);
|
||||
}
|
||||
}
|
||||
|
||||
.links-list {
|
||||
@extend .list-style;
|
||||
|
||||
> a {
|
||||
@extend .iconified-list-item;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.details-list {
|
||||
@extend .list-style;
|
||||
}
|
||||
|
||||
.details-list__item {
|
||||
@extend .iconified-list-item;
|
||||
|
||||
.details-list__item__text--style-secondary {
|
||||
color: var(--color-secondary);
|
||||
font-weight: var(--weight-normal);
|
||||
font-size: var(--text-14);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
--_size: 1rem;
|
||||
width: var(--_size, var(--icon-16)) !important;
|
||||
height: var(--_size, var(--icon-16)) !important;
|
||||
border: 1px solid var(--color-button-border);
|
||||
|
||||
&[data-size='32'] {
|
||||
--_size: var(--icon-32);
|
||||
}
|
||||
|
||||
&[data-shape='circle'] {
|
||||
border-radius: var(--radius-max) !important;
|
||||
}
|
||||
|
||||
&[data-shape='square'] {
|
||||
border-radius: calc(2.25 * (var(--_size) / 16)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,130 @@ html {
|
||||
--color-text-secondary: var(--color-icon);
|
||||
}
|
||||
|
||||
// TO BE MOVED TO OMORPHIA
|
||||
:root {
|
||||
--gap-2: 0.125rem;
|
||||
--gap-4: calc(2 * var(--gap-2));
|
||||
--gap-6: calc(3 * var(--gap-2));
|
||||
--gap-8: calc(2 * var(--gap-4));
|
||||
--gap-12: calc(3 * var(--gap-4));
|
||||
--gap-16: calc(2 * var(--gap-8));
|
||||
--gap-24: calc(3 * var(--gap-8));
|
||||
|
||||
--radius-card: 1rem;
|
||||
--radius-max: 999999999px;
|
||||
|
||||
--radius-btn: 0.75rem;
|
||||
--radius-btn-large: 1rem;
|
||||
--radius-btn-circle: var(--radius-max);
|
||||
|
||||
--text-14: 0.875rem;
|
||||
--text-16: 1rem;
|
||||
--text-18: 1.125rem;
|
||||
--text-24: 1.5rem;
|
||||
--text-32: 2rem;
|
||||
|
||||
--weight-normal: 500; // used for general body text
|
||||
--weight-bold: 600; // used for text needing extra emphasis
|
||||
--weight-extrabold: 800; // used for main titles and headings
|
||||
|
||||
--icon-14: 0.875rem; // used for icons inside a small container alongside a label
|
||||
--icon-16: 1rem; // used for normal-sized icons alongside a label
|
||||
--icon-20: 1.25rem; // used for icons in normal sized buttons
|
||||
--icon-24: 1.5rem; // used for icons that are used as a primary label or in large buttons
|
||||
--icon-32: 2rem;
|
||||
}
|
||||
|
||||
.experimental-styles-within {
|
||||
// Reset deprecated properties
|
||||
--color-icon: initial !important;
|
||||
--color-text: initial !important;
|
||||
--color-text-inactive: initial !important;
|
||||
--color-text-dark: initial !important;
|
||||
--color-heading: initial !important;
|
||||
--color-divider: initial !important;
|
||||
--color-divider-dark: initial !important;
|
||||
--color-text-inverted: initial !important;
|
||||
--color-bg-inverted: initial !important;
|
||||
|
||||
--color-brand-green: initial !important;
|
||||
--color-brand: var(--color-green) !important;
|
||||
--color-brand-inverted: initial !important;
|
||||
|
||||
--tab-underline-hovered: initial !important;
|
||||
|
||||
--color-button-text: initial !important;
|
||||
--color-button-bg-hover: initial !important;
|
||||
--color-button-text-hover: initial !important;
|
||||
--color-button-bg-active: initial !important;
|
||||
--color-button-text-active: initial !important;
|
||||
|
||||
--color-grey-link: inherit !important;
|
||||
--color-grey-link-hover: inherit !important; // DEPRECATED, use filters in future
|
||||
--color-grey-link-active: inherit !important; // DEPRECATED, use filters in future
|
||||
--color-link: var(--color-blue) !important;
|
||||
--color-link-hover: var(--color-blue) !important; // DEPRECATED, use filters in future
|
||||
--color-link-active: var(--color-blue) !important; // DEPRECATED, use filters in future
|
||||
}
|
||||
|
||||
.light-mode,
|
||||
.light {
|
||||
.experimental-styles-within,
|
||||
&.experimental-styles-within {
|
||||
--color-bg: #ebebeb;
|
||||
--color-raised-bg: #ffffff;
|
||||
--color-button-bg: #f5f5f5;
|
||||
--color-base: #2c2e31;
|
||||
--color-secondary: #484d54;
|
||||
--color-accent-contrast: #ffffff;
|
||||
|
||||
--color-platform-fabric: #8a7b71;
|
||||
--color-platform-quilt: #8b61b4;
|
||||
--color-platform-forge: #5b6197;
|
||||
--color-platform-neoforge: #dc895c;
|
||||
--color-platform-liteloader: #4c90de;
|
||||
--color-platform-bukkit: #e78362;
|
||||
--color-platform-bungeecord: #c69e39;
|
||||
--color-platform-folia: #6aa54f;
|
||||
--color-platform-paper: #e67e7e;
|
||||
--color-platform-purpur: #7763a3;
|
||||
--color-platform-spigot: #cd7a21;
|
||||
--color-platform-velocity: #4b98b0;
|
||||
--color-platform-waterfall: #5f83cb;
|
||||
--color-platform-sponge: #c49528;
|
||||
|
||||
--color-button-border: rgba(161, 161, 161, 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
.dark-mode,
|
||||
.dark {
|
||||
.experimental-styles-within,
|
||||
&.experimental-styles-within {
|
||||
--color-button-bg: #33363d;
|
||||
|
||||
--color-platform-fabric: #dbb69b;
|
||||
--color-platform-quilt: #c796f9;
|
||||
--color-platform-forge: #959eef;
|
||||
--color-platform-neoforge: #f99e6b;
|
||||
--color-platform-liteloader: #7ab0ee;
|
||||
--color-platform-bukkit: #f6af7b;
|
||||
--color-platform-bungeecord: #d2c080;
|
||||
--color-platform-folia: #a5e388;
|
||||
--color-platform-paper: #eeaaaa;
|
||||
--color-platform-purpur: #c3abf7;
|
||||
--color-platform-spigot: #f1cc84;
|
||||
--color-platform-velocity: #83d5ef;
|
||||
--color-platform-waterfall: #78a4fb;
|
||||
--color-platform-sponge: #f9e580;
|
||||
|
||||
--color-button-border: rgba(193, 190, 209, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
.light-mode {
|
||||
--color-icon: #6b7280;
|
||||
--color-secondary: #6b7280;
|
||||
--color-icon: var(--color-secondary);
|
||||
--color-text: hsl(221, 39%, 11%);
|
||||
--color-text-inactive: hsl(215, 14%, 34%);
|
||||
--color-text-dark: #1a202c;
|
||||
@ -59,13 +181,6 @@ html {
|
||||
--color-link-hover: #1a76e7;
|
||||
--color-link-active: #146fd7;
|
||||
|
||||
--color-special-red: #cb2245;
|
||||
--color-special-orange: #e08325;
|
||||
--color-special-green: var(--color-brand-green);
|
||||
--color-special-blue: #1f68c0;
|
||||
--color-special-purple: #8e32f3;
|
||||
--color-special-gray: #595b61;
|
||||
|
||||
--color-red-bg: rgba(204, 35, 69, 0.1);
|
||||
|
||||
--color-warning-bg: hsl(355, 70%, 88%);
|
||||
@ -77,7 +192,7 @@ html {
|
||||
|
||||
--color-info-banner-text: var(--color-text);
|
||||
--color-info-banner-bg: var(--color-ad);
|
||||
--color-info-banner-side: var(--color-special-blue);
|
||||
--color-info-banner-side: var(--color-blue);
|
||||
|
||||
--color-block-quote: var(--color-tooltip-bg);
|
||||
--color-header-underline: var(--color-divider-dark);
|
||||
@ -140,8 +255,12 @@ html {
|
||||
--landing-raw-bg: #fff;
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
--color-icon: #96a2b0;
|
||||
.dark,
|
||||
.dark-mode,
|
||||
.oled-mode,
|
||||
.retro-mode {
|
||||
--color-secondary: #96a2b0;
|
||||
--color-icon: var(--color-secondary);
|
||||
--color-text: var(--dark-color-text);
|
||||
--color-text-inactive: #929aa3;
|
||||
--color-text-dark: var(--dark-color-text-dark);
|
||||
@ -154,13 +273,6 @@ html {
|
||||
--color-text-inverted: var(--color-bg);
|
||||
--color-bg-inverted: var(--color-text);
|
||||
|
||||
--color-special-red: #ff496e;
|
||||
--color-special-orange: #ffa347;
|
||||
--color-special-green: var(--color-brand-green);
|
||||
--color-special-blue: #4f9cff;
|
||||
--color-special-purple: #c78aff;
|
||||
--color-special-gray: #9fa4b3;
|
||||
|
||||
--color-red-bg: rgba(255, 74, 110, 0.2);
|
||||
|
||||
--color-brand-green: #1bd96a;
|
||||
@ -209,7 +321,7 @@ html {
|
||||
|
||||
--color-info-banner-text: var(--color-text);
|
||||
--color-info-banner-bg: var(--color-ad);
|
||||
--color-info-banner-side: var(--color-special-blue);
|
||||
--color-info-banner-side: var(--color-blue);
|
||||
|
||||
--color-block-quote: var(--color-code-bg);
|
||||
--color-header-underline: var(--color-divider-dark);
|
||||
@ -274,7 +386,6 @@ html {
|
||||
}
|
||||
|
||||
.oled-mode {
|
||||
@extend .dark-mode;
|
||||
--color-bg: #000000;
|
||||
--color-raised-bg: #101013;
|
||||
|
||||
|
||||
@ -65,7 +65,7 @@ defineProps({
|
||||
.badge {
|
||||
font-weight: bold;
|
||||
width: fit-content;
|
||||
--badge-color: var(--color-special-gray);
|
||||
--badge-color: var(--color-gray);
|
||||
color: var(--badge-color);
|
||||
white-space: nowrap;
|
||||
|
||||
@ -88,7 +88,7 @@ defineProps({
|
||||
&.type--withheld,
|
||||
&.type--rejected,
|
||||
&.red {
|
||||
--badge-color: var(--color-special-red);
|
||||
--badge-color: var(--color-red);
|
||||
}
|
||||
|
||||
&.type--pending,
|
||||
@ -96,7 +96,7 @@ defineProps({
|
||||
&.type--processing,
|
||||
&.type--scheduled,
|
||||
&.orange {
|
||||
--badge-color: var(--color-special-orange);
|
||||
--badge-color: var(--color-orange);
|
||||
}
|
||||
|
||||
&.type--accepted,
|
||||
@ -104,23 +104,23 @@ defineProps({
|
||||
&.type--success,
|
||||
&.type--approved-general,
|
||||
&.green {
|
||||
--badge-color: var(--color-special-green);
|
||||
--badge-color: var(--color-green);
|
||||
}
|
||||
|
||||
&.type--creator,
|
||||
&.type--approved,
|
||||
&.blue {
|
||||
--badge-color: var(--color-special-blue);
|
||||
--badge-color: var(--color-blue);
|
||||
}
|
||||
|
||||
&.type--unlisted,
|
||||
&.purple {
|
||||
--badge-color: var(--color-special-purple);
|
||||
--badge-color: var(--color-purple);
|
||||
}
|
||||
|
||||
&.type--private,
|
||||
&.gray {
|
||||
--badge-color: var(--color-special-gray);
|
||||
--badge-color: var(--color-gray);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -249,7 +249,7 @@
|
||||
>
|
||||
<CheckIcon /> Mark as read
|
||||
</button>
|
||||
<CopyCode v-if="cosmetics.developerMode" :text="notification.id" />
|
||||
<CopyCode v-if="flags.developerMode" :text="notification.id" />
|
||||
</div>
|
||||
<div v-else class="input-group">
|
||||
<nuxt-link
|
||||
@ -281,7 +281,7 @@
|
||||
>
|
||||
<CheckIcon /> Mark as read
|
||||
</button>
|
||||
<CopyCode v-if="cosmetics.developerMode" :text="notification.id" />
|
||||
<CopyCode v-if="flags.developerMode" :text="notification.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -335,7 +335,7 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const cosmetics = useCosmetics()
|
||||
const flags = useFeatureFlags()
|
||||
const tags = useTags()
|
||||
|
||||
const type = computed(() =>
|
||||
@ -549,7 +549,7 @@ function getMessages() {
|
||||
}
|
||||
|
||||
.unknown-type {
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.title-link {
|
||||
@ -560,11 +560,11 @@ function getMessages() {
|
||||
}
|
||||
|
||||
.moderation-color {
|
||||
color: var(--color-special-orange);
|
||||
color: var(--color-orange);
|
||||
}
|
||||
|
||||
.creator-color {
|
||||
color: var(--color-special-blue);
|
||||
color: var(--color-blue);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -26,8 +26,8 @@ function stopTimer(notif) {
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.vue-notification {
|
||||
background: var(--color-special-blue) !important;
|
||||
border-left: 5px solid var(--color-special-blue) !important;
|
||||
background: var(--color-blue) !important;
|
||||
border-left: 5px solid var(--color-blue) !important;
|
||||
color: var(--color-brand-inverted) !important;
|
||||
|
||||
box-sizing: border-box;
|
||||
@ -37,18 +37,18 @@ function stopTimer(notif) {
|
||||
margin: 0 5px 5px;
|
||||
|
||||
&.success {
|
||||
background: var(--color-special-green) !important;
|
||||
border-left-color: var(--color-special-green) !important;
|
||||
background: var(--color-green) !important;
|
||||
border-left-color: var(--color-green) !important;
|
||||
}
|
||||
|
||||
&.warn {
|
||||
background: var(--color-special-orange) !important;
|
||||
border-left-color: var(--color-special-orange) !important;
|
||||
background: var(--color-orange) !important;
|
||||
border-left-color: var(--color-orange) !important;
|
||||
}
|
||||
|
||||
&.error {
|
||||
background: var(--color-special-red) !important;
|
||||
border-left-color: var(--color-special-red) !important;
|
||||
background: var(--color-red) !important;
|
||||
border-left-color: var(--color-red) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -414,7 +414,7 @@ export default {
|
||||
|
||||
svg {
|
||||
width: auto;
|
||||
color: var(--color-special-orange);
|
||||
color: var(--color-orange);
|
||||
height: 1.5rem;
|
||||
margin-bottom: -0.25rem;
|
||||
}
|
||||
|
||||
@ -428,15 +428,15 @@ const submitForReview = async () => {
|
||||
align-items: center;
|
||||
|
||||
.required {
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.suggestion {
|
||||
color: var(--color-special-purple);
|
||||
color: var(--color-purple);
|
||||
}
|
||||
|
||||
.review {
|
||||
color: var(--color-special-orange);
|
||||
color: var(--color-orange);
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,7 +467,7 @@ const submitForReview = async () => {
|
||||
.circle {
|
||||
--circle-size: 2rem;
|
||||
--background-color: var(--color-bg);
|
||||
--content-color: var(--color-special-gray);
|
||||
--content-color: var(--color-gray);
|
||||
width: var(--circle-size);
|
||||
height: var(--circle-size);
|
||||
border-radius: 50%;
|
||||
@ -483,19 +483,19 @@ const submitForReview = async () => {
|
||||
}
|
||||
|
||||
&.required {
|
||||
--content-color: var(--color-special-red);
|
||||
--content-color: var(--color-red);
|
||||
}
|
||||
|
||||
&.suggestion {
|
||||
--content-color: var(--color-special-purple);
|
||||
--content-color: var(--color-purple);
|
||||
}
|
||||
|
||||
&.review {
|
||||
--content-color: var(--color-special-orange);
|
||||
--content-color: var(--color-orange);
|
||||
}
|
||||
|
||||
&.done {
|
||||
--background-color: var(--color-special-green);
|
||||
--background-color: var(--color-green);
|
||||
--content-color: var(--color-brand-inverted);
|
||||
}
|
||||
}
|
||||
|
||||
@ -507,7 +507,7 @@ const defaultRanges: Record<number, [string, number] | string> = {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
align-items: flex-start;
|
||||
gap: var(--gap-md);
|
||||
|
||||
.chart-controls__buttons {
|
||||
|
||||
@ -82,7 +82,7 @@
|
||||
<span v-tooltip="$dayjs(report.created).format('MMMM D, YYYY [at] h:mm A')">{{
|
||||
fromNow(report.created)
|
||||
}}</span>
|
||||
<CopyCode v-if="cosmetics.developerMode" :text="report.id" class="report-id" />
|
||||
<CopyCode v-if="flags.developerMode" :text="report.id" class="report-id" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -124,7 +124,7 @@ defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const cosmetics = useCosmetics()
|
||||
const flags = useFeatureFlags()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<div v-if="cosmetics.developerMode" class="thread-id">
|
||||
<div v-if="flags.developerMode" class="thread-id">
|
||||
Thread ID: <CopyCode :text="thread.id" />
|
||||
</div>
|
||||
<div v-if="sortedMessages.length > 0" class="messages universal-card recessed">
|
||||
@ -247,7 +247,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update-thread'])
|
||||
|
||||
const app = useNuxtApp()
|
||||
const cosmetics = useCosmetics()
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const members = computed(() => {
|
||||
const members = {}
|
||||
|
||||
@ -289,7 +289,7 @@ a:active + .message__author a,
|
||||
|
||||
.moderation-color,
|
||||
role-moderator {
|
||||
color: var(--color-special-orange);
|
||||
color: var(--color-orange);
|
||||
}
|
||||
|
||||
.role-admin {
|
||||
@ -297,11 +297,11 @@ role-moderator {
|
||||
}
|
||||
|
||||
.reporter-icon {
|
||||
color: var(--color-special-purple);
|
||||
color: var(--color-purple);
|
||||
}
|
||||
|
||||
.private-icon {
|
||||
color: var(--color-special-gray);
|
||||
color: var(--color-gray);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
|
||||
@ -14,7 +14,6 @@ export const useCosmetics = () =>
|
||||
projectLayout: false,
|
||||
advancedRendering: true,
|
||||
externalLinksNewTab: true,
|
||||
developerMode: false,
|
||||
notUsingBlockers: false,
|
||||
hideModrinthAppPromos: false,
|
||||
preferredDarkTheme: 'dark',
|
||||
@ -38,6 +37,9 @@ export const useCosmetics = () =>
|
||||
export const saveCosmetics = () => {
|
||||
const cosmetics = useCosmetics()
|
||||
|
||||
console.log('SAVING COSMETICS:')
|
||||
console.log(cosmetics)
|
||||
|
||||
const cosmeticsCookie = useCookie('cosmetics', {
|
||||
maxAge: 60 * 60 * 24 * 365 * 10,
|
||||
sameSite: 'lax',
|
||||
|
||||
105
composables/featureFlags.ts
Normal file
105
composables/featureFlags.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { CookieOptions } from '#app'
|
||||
|
||||
export type ProjectDisplayMode = 'list' | 'grid' | 'gallery'
|
||||
export type DarkColorTheme = 'dark' | 'oled' | 'retro'
|
||||
|
||||
export interface NumberFlag {
|
||||
min: number
|
||||
max: number
|
||||
}
|
||||
|
||||
export type BooleanFlag = boolean
|
||||
|
||||
export type RadioFlag = ProjectDisplayMode | DarkColorTheme
|
||||
|
||||
export type FlagValue = BooleanFlag /* | NumberFlag | RadioFlag */
|
||||
|
||||
const validateValues = <K extends PropertyKey>(flags: Record<K, FlagValue>) => flags
|
||||
|
||||
export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
// Developer flags
|
||||
developerMode: false,
|
||||
|
||||
// In-development features, flags will be removed over time
|
||||
newProjectLinks: true,
|
||||
newProjectMembers: false,
|
||||
newProjectDetails: true,
|
||||
projectCompatibility: false,
|
||||
removeFeaturedVersions: false,
|
||||
|
||||
// Alt layouts
|
||||
// searchSidebarRight: false,
|
||||
// projectSidebarRight: false,
|
||||
|
||||
// Feature toggles
|
||||
// advancedRendering: true,
|
||||
// externalLinksNewTab: true,
|
||||
// notUsingBlockers: false,
|
||||
// hideModrinthAppPromos: false,
|
||||
// preferredDarkTheme: 'dark',
|
||||
// hideStagingBanner: false,
|
||||
|
||||
// Project display modes
|
||||
// modSearchDisplayMode: 'list',
|
||||
// pluginSearchDisplayMode: 'list',
|
||||
// resourcePackSearchDisplayMode: 'gallery',
|
||||
// modpackSearchDisplayMode: 'list',
|
||||
// shaderSearchDisplayMode: 'gallery',
|
||||
// dataPackSearchDisplayMode: 'list',
|
||||
// userProjectDisplayMode: 'list',
|
||||
// collectionProjectDisplayMode: 'list',
|
||||
} as const)
|
||||
|
||||
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
|
||||
|
||||
export type AllFeatureFlags = {
|
||||
[key in FeatureFlag]: (typeof DEFAULT_FEATURE_FLAGS)[key]
|
||||
}
|
||||
|
||||
export type PartialFeatureFlags = Partial<AllFeatureFlags>
|
||||
|
||||
const COOKIE_OPTIONS: CookieOptions<PartialFeatureFlags> = {
|
||||
maxAge: 60 * 60 * 24 * 365 * 10,
|
||||
sameSite: 'lax',
|
||||
secure: true,
|
||||
httpOnly: false,
|
||||
path: '/',
|
||||
}
|
||||
|
||||
export const useFeatureFlags = () =>
|
||||
useState<AllFeatureFlags>('featureFlags', () => {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const savedFlags = useCookie<PartialFeatureFlags>('featureFlags', COOKIE_OPTIONS)
|
||||
|
||||
if (!savedFlags.value) {
|
||||
savedFlags.value = {}
|
||||
}
|
||||
|
||||
const flags: AllFeatureFlags = JSON.parse(JSON.stringify(DEFAULT_FEATURE_FLAGS))
|
||||
|
||||
const overrides = config.public.featureFlagOverrides as PartialFeatureFlags
|
||||
for (const key in overrides) {
|
||||
if (key in flags) {
|
||||
const flag = key as FeatureFlag
|
||||
const value = overrides[flag] as (typeof flags)[FeatureFlag]
|
||||
flags[flag] = value
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in savedFlags.value) {
|
||||
if (key in flags) {
|
||||
const flag = key as FeatureFlag
|
||||
const value = savedFlags.value[flag] as (typeof flags)[FeatureFlag]
|
||||
flags[flag] = value
|
||||
}
|
||||
}
|
||||
|
||||
return flags
|
||||
})
|
||||
|
||||
export const saveFeatureFlags = () => {
|
||||
const flags = useFeatureFlags()
|
||||
const cookie = useCookie<PartialFeatureFlags>('featureFlags', COOKIE_OPTIONS)
|
||||
cookie.value = flags.value
|
||||
}
|
||||
@ -81,3 +81,172 @@ export const PRIVATE_PROJECT_STATUSES = ['private', 'rejected', 'processing']
|
||||
export const REJECTED_PROJECT_STATUSES = ['rejected', 'withheld']
|
||||
export const UNDER_REVIEW_PROJECT_STATUSES = ['processing']
|
||||
export const DRAFT_PROJECT_STATUSES = ['draft']
|
||||
|
||||
export function getVersionsToDisplay(project, overrideTags) {
|
||||
const tags = overrideTags ?? useTags().value
|
||||
|
||||
const projectVersions = project.game_versions.slice()
|
||||
const allVersions = tags.gameVersions.slice()
|
||||
|
||||
const allSnapshots = allVersions.filter((version) => version.version_type === 'snapshot')
|
||||
const allReleases = allVersions.filter((version) => version.version_type === 'release')
|
||||
const allLegacy = allVersions.filter(
|
||||
(version) => version.version_type !== 'snapshot' && version.version_type !== 'release'
|
||||
)
|
||||
|
||||
{
|
||||
const indices = allVersions.reduce((map, gameVersion, index) => {
|
||||
map[gameVersion.version] = index
|
||||
return map
|
||||
}, {})
|
||||
projectVersions.sort((a, b) => indices[a] - indices[b])
|
||||
}
|
||||
|
||||
const releaseVersions = projectVersions.filter((projVer) =>
|
||||
allReleases.some((gameVer) => gameVer.version === projVer)
|
||||
)
|
||||
|
||||
const latestReleaseVersionDate = Date.parse(
|
||||
allReleases.find((version) => version.version === releaseVersions[0])?.date
|
||||
)
|
||||
const latestSnapshot = projectVersions.find((projVer) =>
|
||||
allSnapshots.some(
|
||||
(gameVer) =>
|
||||
gameVer.version === projVer &&
|
||||
(!latestReleaseVersionDate || latestReleaseVersionDate < Date.parse(gameVer.date))
|
||||
)
|
||||
)
|
||||
|
||||
const allReleasesGrouped = groupVersions(
|
||||
allReleases.map((release) => release.version),
|
||||
false
|
||||
)
|
||||
const projectVersionsGrouped = groupVersions(releaseVersions, true)
|
||||
|
||||
const releaseVersionsAsRanges = projectVersionsGrouped.map(({ major, minor }) => {
|
||||
if (minor.length === 1) {
|
||||
return formatVersion(major, minor[0])
|
||||
}
|
||||
|
||||
if (
|
||||
allReleasesGrouped
|
||||
.find((x) => x.major === major)
|
||||
.minor.every((value, index) => value === minor[index])
|
||||
) {
|
||||
return `${major}.x`
|
||||
}
|
||||
|
||||
return `${formatVersion(major, minor[0])}–${formatVersion(major, minor[minor.length - 1])}`
|
||||
})
|
||||
|
||||
const legacyVersionsAsRanges = groupConsecutiveIndices(
|
||||
projectVersions.filter((projVer) => allLegacy.some((gameVer) => gameVer.version === projVer)),
|
||||
allLegacy
|
||||
)
|
||||
|
||||
let output = [...legacyVersionsAsRanges]
|
||||
|
||||
// show all snapshots if there's no release versions
|
||||
if (releaseVersionsAsRanges.length === 0) {
|
||||
const snapshotVersionsAsRanges = groupConsecutiveIndices(
|
||||
projectVersions.filter((projVer) =>
|
||||
allSnapshots.some((gameVer) => gameVer.version === projVer)
|
||||
),
|
||||
allSnapshots
|
||||
)
|
||||
output = [...snapshotVersionsAsRanges, ...output]
|
||||
} else {
|
||||
output = [...releaseVersionsAsRanges, ...output]
|
||||
}
|
||||
|
||||
if (latestSnapshot) {
|
||||
output = [latestSnapshot, ...output]
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
const mcVersionRegex = /^([0-9]+.[0-9]+)(.[0-9]+)?$/
|
||||
|
||||
function groupVersions(versions, consecutive = false) {
|
||||
return versions
|
||||
.slice()
|
||||
.reverse()
|
||||
.reduce((ranges, version) => {
|
||||
const matchesVersion = version.match(mcVersionRegex)
|
||||
|
||||
if (matchesVersion) {
|
||||
const majorVersion = matchesVersion[1]
|
||||
const minorVersion = matchesVersion[2]
|
||||
const minorNumeric = minorVersion ? parseInt(minorVersion.replace('.', '')) : 0
|
||||
|
||||
let prevInRange
|
||||
if (
|
||||
(prevInRange = ranges.find(
|
||||
(x) => x.major === majorVersion && (!consecutive || x.minor.at(-1) === minorNumeric - 1)
|
||||
))
|
||||
) {
|
||||
prevInRange.minor.push(minorNumeric)
|
||||
return ranges
|
||||
}
|
||||
|
||||
return [...ranges, { major: majorVersion, minor: [minorNumeric] }]
|
||||
}
|
||||
|
||||
return ranges
|
||||
}, [])
|
||||
.reverse()
|
||||
}
|
||||
|
||||
function groupConsecutiveIndices(versions, referenceList) {
|
||||
if (!versions || versions.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const referenceMap = new Map()
|
||||
referenceList.forEach((item, index) => {
|
||||
referenceMap.set(item.version, index)
|
||||
})
|
||||
|
||||
const sortedList = versions.slice().sort((a, b) => referenceMap.get(a) - referenceMap.get(b))
|
||||
|
||||
const ranges = []
|
||||
let start = sortedList[0]
|
||||
let previous = sortedList[0]
|
||||
|
||||
for (let i = 1; i < sortedList.length; i++) {
|
||||
const current = sortedList[i]
|
||||
if (referenceMap.get(current) !== referenceMap.get(previous) + 1) {
|
||||
ranges.push(validateRange(`${previous}–${start}`))
|
||||
start = current
|
||||
}
|
||||
previous = current
|
||||
}
|
||||
|
||||
ranges.push(validateRange(`${previous}–${start}`))
|
||||
|
||||
return ranges
|
||||
}
|
||||
|
||||
function validateRange(range) {
|
||||
switch (range) {
|
||||
case 'rd-132211–b1.8.1':
|
||||
return 'All legacy versions'
|
||||
case 'a1.0.4–b1.8.1':
|
||||
return 'All alpha and beta versions'
|
||||
case 'a1.0.4–a1.2.6':
|
||||
return 'All alpha versions'
|
||||
case 'b1.0–b1.8.1':
|
||||
return 'All beta versions'
|
||||
case 'rd-132211–inf20100618':
|
||||
return 'All pre-alpha versions'
|
||||
}
|
||||
const splitRange = range.split('–')
|
||||
if (splitRange && splitRange[0] === splitRange[1]) {
|
||||
return splitRange[0]
|
||||
}
|
||||
return range
|
||||
}
|
||||
|
||||
function formatVersion(major, minor) {
|
||||
return minor === 0 ? major : `${major}.${minor}`
|
||||
}
|
||||
|
||||
@ -125,6 +125,10 @@
|
||||
<ModerationIcon class="icon" />
|
||||
<span class="title">{{ formatMessage(commonMessages.moderationLabel) }}</span>
|
||||
</NuxtLink>
|
||||
<NuxtLink v-if="flags.developerMode" class="item button-transparent" to="/flags">
|
||||
<ReportIcon class="icon" />
|
||||
<span class="title">Feature flags</span>
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-if="!cosmetics.hideModrinthAppPromos"
|
||||
class="item button-transparent primary-color"
|
||||
@ -228,6 +232,10 @@
|
||||
<ModerationIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.moderationLabel) }}
|
||||
</NuxtLink>
|
||||
<NuxtLink v-if="flags.developerMode" class="iconified-button" to="/flags">
|
||||
<ReportIcon aria-hidden="true" />
|
||||
Feature flags
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<NuxtLink class="iconified-button" to="/settings">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
@ -411,7 +419,15 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { LogInIcon, DownloadIcon, LibraryIcon, XIcon, IssuesIcon, Button } from 'omorphia'
|
||||
import {
|
||||
LogInIcon,
|
||||
DownloadIcon,
|
||||
LibraryIcon,
|
||||
XIcon,
|
||||
IssuesIcon,
|
||||
Button,
|
||||
ReportIcon,
|
||||
} from 'omorphia'
|
||||
import HamburgerIcon from '~/assets/images/utils/hamburger.svg'
|
||||
import CrossIcon from '~/assets/images/utils/x.svg'
|
||||
import SearchIcon from '~/assets/images/utils/search.svg'
|
||||
@ -440,6 +456,7 @@ const { formatMessage } = useVIntl()
|
||||
const app = useNuxtApp()
|
||||
const auth = await useAuth()
|
||||
const cosmetics = useCosmetics()
|
||||
const flags = useFeatureFlags()
|
||||
const tags = useTags()
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
@ -663,9 +680,10 @@ watch(
|
||||
|
||||
function developerModeIncrement() {
|
||||
if (developerModeCounter.value >= 5) {
|
||||
cosmetics.value.developerMode = !cosmetics.value.developerMode
|
||||
flags.value.developerMode = !flags.value.developerMode
|
||||
developerModeCounter.value = 0
|
||||
if (cosmetics.value.developerMode) {
|
||||
saveFeatureFlags()
|
||||
if (flags.value.developerMode) {
|
||||
app.$notify({
|
||||
group: 'main',
|
||||
title: 'Developer mode activated',
|
||||
|
||||
@ -798,7 +798,7 @@
|
||||
"message": "Enable or disable certain features on this device."
|
||||
},
|
||||
"settings.display.flags.title": {
|
||||
"message": "Feature flags"
|
||||
"message": "Toggle features"
|
||||
},
|
||||
"settings.display.project-list-layouts.datapack": {
|
||||
"message": "Data Packs page"
|
||||
|
||||
@ -288,6 +288,8 @@ export default defineNuxtConfig({
|
||||
public: {
|
||||
apiBaseUrl: getApiUrl(),
|
||||
siteUrl: getDomain(),
|
||||
production: isProduction(),
|
||||
featureFlagOverrides: getFeatureFlagOverrides(),
|
||||
|
||||
owner: process.env.VERCEL_GIT_REPO_OWNER || 'modrinth',
|
||||
slug: process.env.VERCEL_GIT_REPO_SLUG || 'knossos',
|
||||
@ -380,6 +382,14 @@ function getApiUrl() {
|
||||
return process.env.BROWSER_BASE_URL ?? globalThis.BROWSER_BASE_URL ?? STAGING_API_URL
|
||||
}
|
||||
|
||||
function isProduction() {
|
||||
return process.env.NODE_ENV === 'production'
|
||||
}
|
||||
|
||||
function getFeatureFlagOverrides() {
|
||||
return JSON.parse(process.env.FLAG_OVERRIDES ?? '{}')
|
||||
}
|
||||
|
||||
function getDomain() {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (process.env.SITE_URL) {
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
}/settings/license`"
|
||||
label="License"
|
||||
>
|
||||
<LicenseIcon />
|
||||
<CopyrightIcon />
|
||||
</NavStackItem>
|
||||
<NavStackItem
|
||||
:link="`/${project.project_type}/${
|
||||
@ -243,7 +243,7 @@
|
||||
follower<span v-if="project.followers !== 1">s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dates">
|
||||
<div v-if="!flags.newProjectDetails" class="dates">
|
||||
<div
|
||||
v-tooltip="$dayjs(project.published).format('MMMM D, YYYY [at] h:mm A')"
|
||||
class="date"
|
||||
@ -269,8 +269,8 @@
|
||||
<span class="label">Submitted</span>
|
||||
<span class="value">{{ fromNow(project.queued) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="card-divider" />
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<template v-if="auth.user">
|
||||
<button
|
||||
@ -333,8 +333,8 @@
|
||||
:direction="cosmetics.projectLayout ? 'left' : 'right'"
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
<template #report> <ReportIcon /> Report</template>
|
||||
<template #copy-id> <ClipboardCopyIcon /> Copy ID</template>
|
||||
<template #report> <ReportIcon /> Report </template>
|
||||
<template #copy-id> <ClipboardCopyIcon /> Copy ID </template>
|
||||
</OverflowMenu>
|
||||
</template>
|
||||
<template v-else>
|
||||
@ -360,13 +360,103 @@
|
||||
:direction="cosmetics.projectLayout ? 'left' : 'right'"
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
<template #report> <ReportIcon /> Report</template>
|
||||
<template #copy-id> <ClipboardCopyIcon /> Copy ID</template>
|
||||
<template #report> <ReportIcon /> Report </template>
|
||||
<template #copy-id> <ClipboardCopyIcon /> Copy ID </template>
|
||||
</OverflowMenu>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="flags.projectCompatibility && versions.length > 0"
|
||||
class="card flex-card experimental-styles-within"
|
||||
>
|
||||
<h2>Compatibility</h2>
|
||||
<section>
|
||||
<h3>Minecraft: Java Edition</h3>
|
||||
<div class="tag-list">
|
||||
<div
|
||||
v-for="version in getVersionsToDisplay(project)"
|
||||
:key="`version-tag-${version}`"
|
||||
class="tag-list__item"
|
||||
>
|
||||
{{ version }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Platforms</h3>
|
||||
<div class="tag-list">
|
||||
<div
|
||||
v-for="platform in project.loaders"
|
||||
:key="`platform-tag-${platform}`"
|
||||
:class="`tag-list__item`"
|
||||
:style="`--_color: var(--color-platform-${platform})`"
|
||||
>
|
||||
<svg v-html="tags.loaders.find((x) => x.name === platform).icon"></svg>
|
||||
{{ $formatCategory(platform) }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Environments</h3>
|
||||
<div class="status-list">
|
||||
<div class="status-list__item status-list__item--color-green">
|
||||
<CheckIcon /> Singleplayer
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
project.client_side !== 'unsupported' && project.server_side !== 'unsupported'
|
||||
"
|
||||
class="status-list__item status-list__item--color-green"
|
||||
>
|
||||
<CheckIcon /> Client and server
|
||||
</div>
|
||||
<div
|
||||
v-if="project.client_side === 'required' && project.server_side === 'unsupported'"
|
||||
class="status-list__item status-list__item--color-green"
|
||||
>
|
||||
<CheckIcon /> Client
|
||||
</div>
|
||||
<div
|
||||
v-if="project.server_side === 'required' && project.client_side === 'unsupported'"
|
||||
class="status-list__item status-list__item--color-green"
|
||||
>
|
||||
<CheckIcon /> Server
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
project.client_side === 'optional' ||
|
||||
(project.client_side === 'required' && project.server_side === 'optional')
|
||||
"
|
||||
class="status-list__item status-list__item--color-orange"
|
||||
>
|
||||
<CheckIcon /> Client <span>(Limited functionality)</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
project.server_side === 'optional' ||
|
||||
(project.server_side === 'required' && project.client_side === 'optional')
|
||||
"
|
||||
class="status-list__item status-list__item--color-orange"
|
||||
>
|
||||
<CheckIcon /> Server <span>(Limited functionality)</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="project.client_side === 'unsupported'"
|
||||
class="status-list__item status-list__item--color-red"
|
||||
>
|
||||
<XIcon /> Client
|
||||
</div>
|
||||
<div
|
||||
v-if="project.server_side === 'unsupported'"
|
||||
class="status-list__item status-list__item--color-red"
|
||||
>
|
||||
<XIcon /> Server
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<section class="normal-page__content">
|
||||
<ProjectMemberHeader
|
||||
@ -467,15 +557,114 @@
|
||||
:route="route"
|
||||
/>
|
||||
</section>
|
||||
<div class="universal-card normal-page__info">
|
||||
<template
|
||||
<div class="normal-page__info">
|
||||
<div
|
||||
v-if="
|
||||
project.issues_url ||
|
||||
flags.newProjectLinks &&
|
||||
(project.issues_url ||
|
||||
project.source_url ||
|
||||
project.wiki_url ||
|
||||
project.discord_url ||
|
||||
project.donation_urls.length > 0)
|
||||
"
|
||||
class="card flex-card experimental-styles-within"
|
||||
>
|
||||
<h2>Links</h2>
|
||||
<div class="links-list">
|
||||
<a
|
||||
v-if="project.issues_url"
|
||||
:href="project.issues_url"
|
||||
:target="$external()"
|
||||
rel="noopener nofollow ugc"
|
||||
>
|
||||
<IssuesIcon aria-hidden="true" />
|
||||
Report issues
|
||||
<ExternalIcon aria-hidden="true" class="external-icon" />
|
||||
</a>
|
||||
<a
|
||||
v-if="project.source_url"
|
||||
:href="project.source_url"
|
||||
:target="$external()"
|
||||
rel="noopener nofollow ugc"
|
||||
>
|
||||
<CodeIcon aria-hidden="true" />
|
||||
View source
|
||||
<ExternalIcon aria-hidden="true" class="external-icon" />
|
||||
</a>
|
||||
<a
|
||||
v-if="project.wiki_url"
|
||||
:href="project.wiki_url"
|
||||
:target="$external()"
|
||||
rel="noopener nofollow ugc"
|
||||
>
|
||||
<WikiIcon aria-hidden="true" />
|
||||
Visit wiki
|
||||
<ExternalIcon aria-hidden="true" class="external-icon" />
|
||||
</a>
|
||||
<a
|
||||
v-if="project.discord_url"
|
||||
:href="project.discord_url"
|
||||
:target="$external()"
|
||||
rel="noopener nofollow ugc"
|
||||
>
|
||||
<DiscordIcon class="shrink" aria-hidden="true" />
|
||||
Join Discord server
|
||||
<ExternalIcon aria-hidden="true" class="external-icon" />
|
||||
</a>
|
||||
<hr
|
||||
v-if="
|
||||
(project.issues_url ||
|
||||
project.source_url ||
|
||||
project.wiki_url ||
|
||||
project.discord_url) &&
|
||||
project.donation_urls.length > 0
|
||||
"
|
||||
/>
|
||||
<a
|
||||
v-for="(donation, index) in project.donation_urls"
|
||||
:key="index"
|
||||
:href="donation.url"
|
||||
:target="$external()"
|
||||
rel="noopener nofollow ugc"
|
||||
>
|
||||
<BuyMeACoffeeLogo v-if="donation.id === 'bmac'" aria-hidden="true" />
|
||||
<PatreonIcon v-else-if="donation.id === 'patreon'" aria-hidden="true" />
|
||||
<KoFiIcon v-else-if="donation.id === 'ko-fi'" aria-hidden="true" />
|
||||
<PayPalIcon v-else-if="donation.id === 'paypal'" aria-hidden="true" />
|
||||
<OpenCollectiveIcon
|
||||
v-else-if="donation.id === 'open-collective'"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<HeartIcon v-else-if="donation.id === 'github'" />
|
||||
<UnknownIcon v-else />
|
||||
<span v-if="donation.id === 'bmac'">Buy Me a Coffee</span>
|
||||
<span v-else-if="donation.id === 'patreon'">Donate on Patreon</span>
|
||||
<span v-else-if="donation.id === 'paypal'">Donate on PayPal</span>
|
||||
<span v-else-if="donation.id === 'ko-fi'">Donate on Ko-fi</span>
|
||||
<span v-else-if="donation.id === 'github'">Sponsor on GitHub</span>
|
||||
<span v-else>Donate</span>
|
||||
<ExternalIcon aria-hidden="true" class="external-icon" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
showFeaturedVersions ||
|
||||
!flags.newProjectLinks ||
|
||||
!flags.newProjectDetails ||
|
||||
!flags.newProjectMembers
|
||||
"
|
||||
class="universal-card"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
!flags.newProjectLinks &&
|
||||
(project.issues_url ||
|
||||
project.source_url ||
|
||||
project.wiki_url ||
|
||||
project.discord_url ||
|
||||
project.donation_urls.length > 0)
|
||||
"
|
||||
>
|
||||
<h2 class="card-header">External resources</h2>
|
||||
<div class="links">
|
||||
@ -543,9 +732,12 @@
|
||||
<span v-else>Donate</span>
|
||||
</a>
|
||||
</div>
|
||||
<hr class="card-divider" />
|
||||
<hr
|
||||
v-if="showFeaturedVersions || !flags.newProjectMembers || !flags.newProjectDetails"
|
||||
class="card-divider"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="featuredVersions.length > 0">
|
||||
<template v-if="showFeaturedVersions">
|
||||
<div class="featured-header">
|
||||
<h2 class="card-header">Featured versions</h2>
|
||||
<nuxt-link
|
||||
@ -600,8 +792,9 @@
|
||||
<Badge v-else-if="version.version_type === 'alpha'" type="alpha" color="red" />
|
||||
</div>
|
||||
</div>
|
||||
<hr class="card-divider" />
|
||||
<hr v-if="!flags.newProjectMembers || !flags.newProjectDetails" class="card-divider" />
|
||||
</template>
|
||||
<template v-if="!flags.newProjectMembers">
|
||||
<h2 class="card-header">Project members</h2>
|
||||
<nuxt-link
|
||||
v-if="organization"
|
||||
@ -626,14 +819,17 @@
|
||||
|
||||
<div class="member-info">
|
||||
<p class="name">
|
||||
{{ member.name }} <CrownIcon v-if="member.is_owner" v-tooltip="'Project owner'" />
|
||||
{{ member.name }}
|
||||
<CrownIcon v-if="member.is_owner" v-tooltip="'Project owner'" />
|
||||
</p>
|
||||
<p class="role">
|
||||
{{ member.role }}
|
||||
</p>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
<hr class="card-divider" />
|
||||
<hr v-if="!flags.newProjectDetails" class="card-divider" />
|
||||
</template>
|
||||
<template v-if="!flags.newProjectDetails">
|
||||
<h2 class="card-header">Technical information</h2>
|
||||
<div class="infos">
|
||||
<div class="info">
|
||||
@ -727,6 +923,134 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="flags.newProjectMembers" class="card flex-card experimental-styles-within">
|
||||
<h2>Creators</h2>
|
||||
<div class="details-list">
|
||||
<template v-if="organization">
|
||||
<nuxt-link
|
||||
class="details-list__item details-list__item--type-large"
|
||||
:to="`/organization/${organization.slug}`"
|
||||
>
|
||||
<Avatar
|
||||
:src="organization.icon_url"
|
||||
:alt="organization.name"
|
||||
class="icon"
|
||||
data-size="32"
|
||||
data-shape="square"
|
||||
/>
|
||||
<div class="rows">
|
||||
<span>
|
||||
{{ organization.name }}
|
||||
</span>
|
||||
<span class="details-list__item__text--style-secondary">Organization</span>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
<hr />
|
||||
</template>
|
||||
<nuxt-link
|
||||
v-for="member in members"
|
||||
:key="`member-${member.id}`"
|
||||
class="details-list__item details-list__item--type-large"
|
||||
:to="'/user/' + member.user.username"
|
||||
>
|
||||
<Avatar
|
||||
:src="member.avatar_url"
|
||||
:alt="member.name"
|
||||
class="icon"
|
||||
data-size="32"
|
||||
data-shape="circle"
|
||||
/>
|
||||
<div class="rows">
|
||||
<span>
|
||||
{{ member.name }}
|
||||
<CrownIcon
|
||||
v-if="member.is_owner"
|
||||
v-tooltip="'Project owner'"
|
||||
class="project-owner-icon"
|
||||
/>
|
||||
</span>
|
||||
<span class="details-list__item__text--style-secondary">{{ member.role }}</span>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="flags.newProjectDetails" class="card flex-card experimental-styles-within">
|
||||
<h2>Details</h2>
|
||||
<div class="details-list">
|
||||
<div class="details-list__item">
|
||||
<LicenseIcon aria-hidden="true" />
|
||||
<div>
|
||||
Licensed
|
||||
<a
|
||||
v-if="project.license.url"
|
||||
class="text-link"
|
||||
:href="project.license.url"
|
||||
:target="$external()"
|
||||
rel="noopener nofollow ugc"
|
||||
>
|
||||
{{ licenseIdDisplay }} <ExternalIcon aria-hidden="true" class="external-icon" />
|
||||
</a>
|
||||
<span
|
||||
v-else-if="
|
||||
project.license.id === 'LicenseRef-All-Rights-Reserved' ||
|
||||
!project.license.id.includes('LicenseRef')
|
||||
"
|
||||
class="text-link"
|
||||
@click="getLicenseData()"
|
||||
>
|
||||
{{ licenseIdDisplay }}
|
||||
</span>
|
||||
<span v-else>{{ licenseIdDisplay }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="project.approved"
|
||||
v-tooltip="$dayjs(project.approved).format('MMMM D, YYYY [at] h:mm A')"
|
||||
class="details-list__item"
|
||||
>
|
||||
<CalendarIcon aria-hidden="true" />
|
||||
<div>
|
||||
Published
|
||||
<span>{{ fromNow(project.approved) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
v-tooltip="$dayjs(project.published).format('MMMM D, YYYY [at] h:mm A')"
|
||||
class="details-list__item"
|
||||
>
|
||||
<CalendarIcon aria-hidden="true" />
|
||||
<div>
|
||||
Created
|
||||
<span>{{ fromNow(project.published) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="project.status === 'processing' && project.queued"
|
||||
v-tooltip="$dayjs(project.queued).format('MMMM D, YYYY [at] h:mm A')"
|
||||
class="details-list__item"
|
||||
>
|
||||
<ModeratorIcon aria-hidden="true" />
|
||||
<div>
|
||||
Submitted
|
||||
<span>{{ fromNow(project.queued) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="versions.length > 0 && project.updated"
|
||||
v-tooltip="$dayjs(project.updated).format('MMMM D, YYYY [at] h:mm A')"
|
||||
class="details-list__item"
|
||||
>
|
||||
<VersionIcon aria-hidden="true" />
|
||||
<div>
|
||||
Updated
|
||||
<span>{{ fromNow(project.updated) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ModerationChecklist
|
||||
@ -753,6 +1077,8 @@ import {
|
||||
isRejected,
|
||||
isUnderReview,
|
||||
isStaff,
|
||||
CheckIcon,
|
||||
XIcon,
|
||||
} from 'omorphia'
|
||||
import CrownIcon from '~/assets/images/utils/crown.svg'
|
||||
import CalendarIcon from '~/assets/images/utils/calendar.svg'
|
||||
@ -790,7 +1116,8 @@ import UsersIcon from '~/assets/images/utils/users.svg'
|
||||
import CategoriesIcon from '~/assets/images/utils/tags.svg'
|
||||
import DescriptionIcon from '~/assets/images/utils/align-left.svg'
|
||||
import LinksIcon from '~/assets/images/utils/link.svg'
|
||||
import LicenseIcon from '~/assets/images/utils/copyright.svg'
|
||||
import CopyrightIcon from '~/assets/images/utils/copyright.svg'
|
||||
import LicenseIcon from '~/assets/images/utils/book-text.svg'
|
||||
import GalleryIcon from '~/assets/images/utils/image.svg'
|
||||
import VersionIcon from '~/assets/images/utils/version.svg'
|
||||
import { reportProject } from '~/utils/report-helpers.ts'
|
||||
@ -799,6 +1126,8 @@ import { userCollectProject } from '~/composables/user.js'
|
||||
import CollectionCreateModal from '~/components/ui/CollectionCreateModal.vue'
|
||||
import OrganizationIcon from '~/assets/images/utils/organization.svg'
|
||||
import ModerationChecklist from '~/components/ui/ModerationChecklist.vue'
|
||||
import ModeratorIcon from '~/assets/images/sidebar/admin.svg'
|
||||
import { getVersionsToDisplay } from '~/helpers/projects.js'
|
||||
|
||||
const data = useNuxtApp()
|
||||
const route = useRoute()
|
||||
@ -808,6 +1137,7 @@ const auth = await useAuth()
|
||||
const user = await useUser()
|
||||
const cosmetics = useCosmetics()
|
||||
const tags = useTags()
|
||||
const flags = useFeatureFlags()
|
||||
|
||||
const displayCollectionsSearch = ref('')
|
||||
const collections = computed(() =>
|
||||
@ -927,8 +1257,14 @@ if (project.value.project_type !== route.params.type || route.params.id !== proj
|
||||
// The rest of the members should be sorted by role, then by name
|
||||
const members = computed(() => {
|
||||
const acceptedMembers = allMembers.value.filter((x) => x.accepted)
|
||||
const owner = acceptedMembers.find((x) => x.is_owner)
|
||||
const rest = acceptedMembers.filter((x) => !x.is_owner) || []
|
||||
const owner = acceptedMembers.find((x) =>
|
||||
organization.value
|
||||
? organization.value.members.some(
|
||||
(orgMember) => orgMember.user.id === x.user.id && orgMember.is_owner
|
||||
)
|
||||
: x.is_owner
|
||||
)
|
||||
const rest = acceptedMembers.filter((x) => x.user.id !== owner.user.id) || []
|
||||
|
||||
rest.sort((a, b) => {
|
||||
if (a.role === b.role) {
|
||||
@ -1173,10 +1509,15 @@ if (process.client && history && history.state && history.state.showChecklist) {
|
||||
showModerationChecklist.value = true
|
||||
futureProjects.value = history.state.projects
|
||||
}
|
||||
|
||||
const showFeaturedVersions = computed(
|
||||
() => !flags.value.removeFeaturedVersions && featuredVersions.value.length > 0
|
||||
)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
grid-area: header;
|
||||
|
||||
.title {
|
||||
overflow-wrap: break-word;
|
||||
margin: var(--spacing-card-xs) 0;
|
||||
@ -1243,11 +1584,13 @@ if (process.client && history && history.state && history.state.showChecklist) {
|
||||
.project__gallery {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.has-featured-image {
|
||||
.project__gallery {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 10rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 10rem;
|
||||
@ -1256,6 +1599,7 @@ if (process.client && history && history.state && history.state.showChecklist) {
|
||||
border-radius: var(--size-rounded-card) var(--size-rounded-card) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.project__icon {
|
||||
margin-top: calc(-3rem - var(--spacing-card-lg) - 4px);
|
||||
margin-left: -4px;
|
||||
@ -1263,11 +1607,13 @@ if (process.client && history && history.state && history.state.showChecklist) {
|
||||
box-shadow: -2px -2px 0 2px var(--color-raised-bg), 2px -2px 0 2px var(--color-raised-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.project__header__content {
|
||||
margin: 0;
|
||||
background: none;
|
||||
border-radius: unset;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
@ -1468,6 +1814,7 @@ if (process.client && history && history.state && history.state.showChecklist) {
|
||||
.modal-license {
|
||||
padding: var(--spacing-card-bg);
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -1495,6 +1842,7 @@ if (process.client && history && history.state && history.state.showChecklist) {
|
||||
.popout-checkbox {
|
||||
padding: var(--gap-sm) var(--gap-md);
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.95);
|
||||
}
|
||||
@ -1531,4 +1879,12 @@ if (process.client && history && history.state && history.state.showChecklist) {
|
||||
margin: var(--gap-sm) var(--gap-md);
|
||||
padding: var(--gap-sm);
|
||||
}
|
||||
|
||||
.normal-page__info:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.project-owner-icon {
|
||||
color: var(--color-orange);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -157,18 +157,18 @@ function switchPage(page) {
|
||||
}
|
||||
|
||||
.changelog-bar {
|
||||
--color: var(--color-special-green);
|
||||
--color: var(--color-green);
|
||||
|
||||
&.alpha {
|
||||
--color: var(--color-special-red);
|
||||
--color: var(--color-red);
|
||||
}
|
||||
|
||||
&.release {
|
||||
--color: var(--color-special-green);
|
||||
--color: var(--color-green);
|
||||
}
|
||||
|
||||
&.beta {
|
||||
--color: var(--color-special-orange);
|
||||
--color: var(--color-orange);
|
||||
}
|
||||
|
||||
left: 0;
|
||||
|
||||
@ -204,12 +204,12 @@ svg {
|
||||
}
|
||||
|
||||
&.bad {
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--color-special-orange);
|
||||
color: var(--color-orange);
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -421,11 +421,11 @@ svg {
|
||||
}
|
||||
|
||||
&.bad {
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
&.warn {
|
||||
color: var(--color-special-orange);
|
||||
color: var(--color-orange);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -519,7 +519,6 @@
|
||||
<script setup>
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
import { Avatar, Badge, Card, Checkbox, TransferIcon, CheckIcon, UsersIcon } from 'omorphia'
|
||||
import { defineProps, ref, watch } from 'vue'
|
||||
|
||||
import ModalConfirm from '~/components/ui/ModalConfirm.vue'
|
||||
import DropdownIcon from '~/assets/images/utils/dropdown.svg'
|
||||
|
||||
@ -1374,7 +1374,7 @@ useSeoMeta({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--gap-sm) 0;
|
||||
color: var(--color-special-gray);
|
||||
color: var(--color-gray);
|
||||
|
||||
&.important {
|
||||
color: var(--color-contrast);
|
||||
|
||||
@ -617,7 +617,7 @@ export default defineNuxtComponent({
|
||||
gap: var(--spacing-card-xs);
|
||||
|
||||
svg {
|
||||
color: var(--color-special-orange);
|
||||
color: var(--color-orange);
|
||||
}
|
||||
}
|
||||
|
||||
@ -645,7 +645,7 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
.label-button[data-active='true'] {
|
||||
--background-color: var(--color-special-red);
|
||||
--background-color: var(--color-red);
|
||||
--text-color: var(--color-brand-inverted);
|
||||
}
|
||||
|
||||
|
||||
@ -506,7 +506,7 @@ async function withdraw() {
|
||||
}
|
||||
|
||||
.invalid {
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.confirm-text {
|
||||
|
||||
63
pages/flags.vue
Normal file
63
pages/flags.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { FeatureFlag, DEFAULT_FEATURE_FLAGS, saveFeatureFlags } from '~/composables/featureFlags.ts'
|
||||
|
||||
const flags = shallowReactive(useFeatureFlags().value)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<h1>Feature flags</h1>
|
||||
<div class="flags">
|
||||
<div
|
||||
v-for="flag in Object.keys(flags) as FeatureFlag[]"
|
||||
:key="`flag-${flag}`"
|
||||
class="adjacent-input small card"
|
||||
>
|
||||
<label :for="`toggle-${flag}`">
|
||||
<span class="label__title">
|
||||
{{ flag.replaceAll('_', ' ') }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
<p>
|
||||
Default:
|
||||
<span
|
||||
:style="`color:var(--color-${
|
||||
DEFAULT_FEATURE_FLAGS[flag] === false ? 'red' : 'green'
|
||||
})`"
|
||||
>{{ DEFAULT_FEATURE_FLAGS[flag] }}</span
|
||||
>
|
||||
</p>
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
:id="`toggle-${flag}`"
|
||||
v-model="flags[flag]"
|
||||
class="switch stylized-toggle"
|
||||
type="checkbox"
|
||||
@change="() => saveFeatureFlags()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
width: calc(100% - 2 * var(--spacing-card-md));
|
||||
max-width: 800px;
|
||||
margin-inline: auto;
|
||||
box-sizing: border-box;
|
||||
margin-block: var(--spacing-card-md);
|
||||
}
|
||||
|
||||
.flags {
|
||||
}
|
||||
|
||||
.label__title {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.label__description p {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@ -233,11 +233,11 @@ async function goToProjects() {
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--color-special-orange);
|
||||
color: var(--color-orange);
|
||||
}
|
||||
|
||||
.danger {
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
@ -630,7 +630,7 @@ const onBulkEditLinks = useClientTry(async () => {
|
||||
gap: var(--spacing-card-xs);
|
||||
|
||||
svg {
|
||||
color: var(--color-special-orange);
|
||||
color: var(--color-orange);
|
||||
}
|
||||
}
|
||||
|
||||
@ -658,7 +658,7 @@ const onBulkEditLinks = useClientTry(async () => {
|
||||
}
|
||||
|
||||
.label-button[data-active='true'] {
|
||||
--background-color: var(--color-special-red);
|
||||
--background-color: var(--color-red);
|
||||
--text-color: var(--color-brand-inverted);
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<MessageBanner v-if="cosmetics.developerMode" message-type="warning" class="developer-message">
|
||||
<MessageBanner v-if="flags.developerMode" message-type="warning" class="developer-message">
|
||||
<CodeIcon />
|
||||
<IntlFormatted :message-id="developerModeBanner.description">
|
||||
<template #strong="{ children }">
|
||||
@ -137,15 +137,15 @@
|
||||
</div>
|
||||
</section>
|
||||
<section class="universal-card">
|
||||
<h2>{{ formatMessage(featureFlags.title) }}</h2>
|
||||
<p>{{ formatMessage(featureFlags.description) }}</p>
|
||||
<h2>{{ formatMessage(toggleFeatures.title) }}</h2>
|
||||
<p>{{ formatMessage(toggleFeatures.description) }}</p>
|
||||
<div class="adjacent-input small">
|
||||
<label for="advanced-rendering">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(featureFlags.advancedRenderingTitle) }}
|
||||
{{ formatMessage(toggleFeatures.advancedRenderingTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(featureFlags.advancedRenderingDescription) }}
|
||||
{{ formatMessage(toggleFeatures.advancedRenderingDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
@ -159,10 +159,10 @@
|
||||
<div class="adjacent-input small">
|
||||
<label for="external-links-new-tab">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(featureFlags.externalLinksNewTabTitle) }}
|
||||
{{ formatMessage(toggleFeatures.externalLinksNewTabTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(featureFlags.externalLinksNewTabDescription) }}
|
||||
{{ formatMessage(toggleFeatures.externalLinksNewTabDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
@ -176,10 +176,10 @@
|
||||
<div class="adjacent-input small">
|
||||
<label for="modrinth-app-promos">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(featureFlags.hideModrinthAppPromosTitle) }}
|
||||
{{ formatMessage(toggleFeatures.hideModrinthAppPromosTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(featureFlags.hideModrinthAppPromosDescription) }}
|
||||
{{ formatMessage(toggleFeatures.hideModrinthAppPromosDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
@ -193,10 +193,10 @@
|
||||
<div class="adjacent-input small">
|
||||
<label for="search-layout-toggle">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(featureFlags.rightAlignedSearchSidebarTitle) }}
|
||||
{{ formatMessage(toggleFeatures.rightAlignedSearchSidebarTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(featureFlags.rightAlignedSearchSidebarDescription) }}
|
||||
{{ formatMessage(toggleFeatures.rightAlignedSearchSidebarDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
@ -210,10 +210,10 @@
|
||||
<div class="adjacent-input small">
|
||||
<label for="project-layout-toggle">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(featureFlags.rightAlignedProjectSidebarTitle) }}
|
||||
{{ formatMessage(toggleFeatures.rightAlignedProjectSidebarTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(featureFlags.rightAlignedProjectSidebarDescription) }}
|
||||
{{ formatMessage(toggleFeatures.rightAlignedProjectSidebarDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
@ -331,10 +331,10 @@ const projectListLayouts = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const featureFlags = defineMessages({
|
||||
const toggleFeatures = defineMessages({
|
||||
title: {
|
||||
id: 'settings.display.flags.title',
|
||||
defaultMessage: 'Feature flags',
|
||||
defaultMessage: 'Toggle features',
|
||||
},
|
||||
description: {
|
||||
id: 'settings.display.flags.description',
|
||||
@ -386,6 +386,7 @@ const featureFlags = defineMessages({
|
||||
})
|
||||
|
||||
const cosmetics = useCosmetics()
|
||||
const flags = useFeatureFlags()
|
||||
const tags = useTags()
|
||||
|
||||
const systemTheme = ref('light')
|
||||
@ -394,7 +395,7 @@ const theme = useTheme()
|
||||
|
||||
const themeOptions = computed(() => {
|
||||
const options = ['system', 'light', 'dark', 'oled']
|
||||
if (cosmetics.value.developerMode || theme.value.preference === 'retro') {
|
||||
if (flags.value.developerMode || theme.value.preference === 'retro') {
|
||||
options.push('retro')
|
||||
}
|
||||
return options
|
||||
@ -430,8 +431,8 @@ function updateColorTheme(value) {
|
||||
}
|
||||
|
||||
function disableDeveloperMode() {
|
||||
cosmetics.value.developerMode = !cosmetics.value.developerMode
|
||||
saveCosmetics()
|
||||
flags.value.developerMode = !flags.value.developerMode
|
||||
saveFeatureFlags()
|
||||
addNotification({
|
||||
group: 'main',
|
||||
title: 'Developer mode deactivated',
|
||||
|
||||
@ -414,10 +414,10 @@ function getItemLabel(locale: Locale) {
|
||||
}
|
||||
|
||||
&.errored {
|
||||
border-color: var(--color-special-red);
|
||||
border-color: var(--color-red);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-special-red);
|
||||
border-color: var(--color-red);
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,7 +473,7 @@ function getItemLabel(locale: Locale) {
|
||||
}
|
||||
|
||||
.language-load-error {
|
||||
color: var(--color-special-red);
|
||||
color: var(--color-red);
|
||||
font-size: var(--font-size-sm);
|
||||
margin-left: 0.3rem;
|
||||
display: flex;
|
||||
|
||||
@ -502,7 +502,7 @@ export default defineNuxtComponent({
|
||||
// 5 wide
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: start;
|
||||
justify-content: flex-start;
|
||||
|
||||
grid-gap: var(--gap-sm);
|
||||
margin-top: 0.5rem;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user