add experimental feature gate
This commit is contained in:
parent
f0a0a657b9
commit
59307392d4
10 changed files with 150 additions and 7 deletions
|
@ -2008,3 +2008,10 @@ _deck:
|
||||||
list: "List"
|
list: "List"
|
||||||
mentions: "Mentions"
|
mentions: "Mentions"
|
||||||
direct: "Direct messages"
|
direct: "Direct messages"
|
||||||
|
_experiments:
|
||||||
|
title: "Experiments"
|
||||||
|
alpha: "Alpha"
|
||||||
|
beta: "Beta"
|
||||||
|
release: "Release"
|
||||||
|
enableExperimentalPostEditing: "Enable experimental post editing"
|
||||||
|
experimentalPostEditingCaption: "Enables the option for users to edit their posts in post options"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
export class ExperimentalFeatures1683980686995 {
|
||||||
|
name = "ExperimentalFeatures1683980686995";
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE "meta"
|
||||||
|
ADD "experimentalFeatures" jsonb NOT NULL DEFAULT '{}'
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE "meta" DROP COLUMN "experimentalFeatures"
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -516,4 +516,9 @@ export class Meta {
|
||||||
default: true,
|
default: true,
|
||||||
})
|
})
|
||||||
public enableActiveEmailValidation: boolean;
|
public enableActiveEmailValidation: boolean;
|
||||||
|
|
||||||
|
@Column('jsonb', {
|
||||||
|
default: {},
|
||||||
|
})
|
||||||
|
public experimentalFeatures: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import config from "@/config/index.js";
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||||
import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js";
|
import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js";
|
||||||
import define from "../../define.js";
|
import define from "../../define.js";
|
||||||
|
import { Exp } from "@tensorflow/tfjs";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["meta"],
|
tags: ["meta"],
|
||||||
|
@ -470,10 +471,20 @@ export const meta = {
|
||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
|
experimentalFeatures: {
|
||||||
|
type: "object",
|
||||||
|
optional: true,
|
||||||
|
nullable: true,
|
||||||
|
ref: "MetaExperimentalFeatures",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export type MetaExperimentalFeatures = {
|
||||||
|
postEditing: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {},
|
properties: {},
|
||||||
|
|
|
@ -170,6 +170,13 @@ export const paramDef = {
|
||||||
objectStorageS3ForcePathStyle: { type: "boolean" },
|
objectStorageS3ForcePathStyle: { type: "boolean" },
|
||||||
enableIpLogging: { type: "boolean" },
|
enableIpLogging: { type: "boolean" },
|
||||||
enableActiveEmailValidation: { type: "boolean" },
|
enableActiveEmailValidation: { type: "boolean" },
|
||||||
|
experimentalFeatures: {
|
||||||
|
type: "object",
|
||||||
|
nullable: true,
|
||||||
|
properties: {
|
||||||
|
postEditing: { type: "boolean" },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -304,6 +304,7 @@ export type LiteInstanceMetadata = {
|
||||||
url: string;
|
url: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
}[];
|
}[];
|
||||||
|
experimentalFeatures?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DetailedInstanceMetadata = LiteInstanceMetadata & {
|
export type DetailedInstanceMetadata = LiteInstanceMetadata & {
|
||||||
|
|
83
packages/client/src/pages/admin/experiments.vue
Normal file
83
packages/client/src/pages/admin/experiments.vue
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<template>
|
||||||
|
<MkStickyContainer>
|
||||||
|
<FormSuspense :p="init">
|
||||||
|
<FormSwitch
|
||||||
|
v-model="enableExperimentalPostEditing"
|
||||||
|
@update:modelValue="save"
|
||||||
|
class="_formBlock"
|
||||||
|
>
|
||||||
|
<template #label
|
||||||
|
><i class="ph-pencil-line ph-bold ph-lg"></i
|
||||||
|
>{{ i18n.ts._experiments.enableExperimentalPostEditing
|
||||||
|
}}<span class="level alpha"
|
||||||
|
>({{ i18n.ts._experiments.alpha }})</span
|
||||||
|
></template
|
||||||
|
>
|
||||||
|
<template #caption>{{
|
||||||
|
i18n.ts._experiments.experimentalPostEditingCaption
|
||||||
|
}}</template>
|
||||||
|
</FormSwitch>
|
||||||
|
</FormSuspense>
|
||||||
|
</MkStickyContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {} from "vue";
|
||||||
|
import MkStickyContainer from "@/components/global/MkStickyContainer.vue";
|
||||||
|
import FormSuspense from "@/components/form/suspense.vue";
|
||||||
|
import FormSwitch from "@/components/form/switch.vue";
|
||||||
|
import * as os from "@/os";
|
||||||
|
import { fetchInstance } from "@/instance";
|
||||||
|
import { i18n } from "@/i18n";
|
||||||
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
|
|
||||||
|
let enableExperimentalPostEditing = $ref(false);
|
||||||
|
let meta = $ref<MetaExperiments | null>(null);
|
||||||
|
|
||||||
|
type MetaExperiments = {
|
||||||
|
experimentalFeatures?: {
|
||||||
|
postEditing?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
meta = (await os.api("admin/meta")) as MetaExperiments;
|
||||||
|
if (!meta) return;
|
||||||
|
|
||||||
|
enableExperimentalPostEditing =
|
||||||
|
meta.experimentalFeatures?.postEditing ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
const experiments: MetaExperiments = {
|
||||||
|
experimentalFeatures: {
|
||||||
|
postEditing: enableExperimentalPostEditing,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
os.apiWithDialog("admin/update-meta", experiments).then(() => {
|
||||||
|
fetchInstance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
definePageMetadata({
|
||||||
|
title: i18n.ts._experiments.title,
|
||||||
|
icon: "ph-flask ph-bold ph-lg",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.level {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: var(--color-gray-500);
|
||||||
|
text-transform: uppercase;
|
||||||
|
&.alpha {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
&.beta {
|
||||||
|
color: #0ea5e9;
|
||||||
|
}
|
||||||
|
&.release {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -300,6 +300,12 @@ const menuDef = $computed(() => [
|
||||||
to: "/admin/database",
|
to: "/admin/database",
|
||||||
active: currentPage?.route.name === "database",
|
active: currentPage?.route.name === "database",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: "ph-flask ph-bold ph-lg",
|
||||||
|
text: i18n.ts._experiments.title,
|
||||||
|
to: "/admin/experiments",
|
||||||
|
active: currentPage?.route.name === "experiments",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -540,6 +540,11 @@ export const routes = [
|
||||||
name: "other-settings",
|
name: "other-settings",
|
||||||
component: page(() => import("./pages/admin/custom-css.vue")),
|
component: page(() => import("./pages/admin/custom-css.vue")),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/experiments",
|
||||||
|
name: "experiments",
|
||||||
|
component: page(() => import("./pages/admin/experiments.vue")),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
component: page(() => import("./pages/_empty_.vue")),
|
component: page(() => import("./pages/_empty_.vue")),
|
||||||
|
|
|
@ -421,7 +421,7 @@ export function getNoteMenu(props: {
|
||||||
|
|
||||||
null,
|
null,
|
||||||
|
|
||||||
isAppearAuthor
|
instance.experimentalFeatures?.postEditing && isAppearAuthor
|
||||||
? {
|
? {
|
||||||
icon: "ph-pencil-line ph-bold ph-lg",
|
icon: "ph-pencil-line ph-bold ph-lg",
|
||||||
text: i18n.ts.edit,
|
text: i18n.ts.edit,
|
||||||
|
@ -430,12 +430,14 @@ export function getNoteMenu(props: {
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
||||||
{
|
instance.experimentalFeatures?.postEditing
|
||||||
|
? {
|
||||||
icon: "ph-copy ph-bold ph-lg",
|
icon: "ph-copy ph-bold ph-lg",
|
||||||
text: i18n.ts.duplicate,
|
text: i18n.ts.duplicate,
|
||||||
textStyle: "color: var(--accent)",
|
textStyle: "color: var(--accent)",
|
||||||
action: duplicate,
|
action: duplicate,
|
||||||
},
|
}
|
||||||
|
: undefined,
|
||||||
|
|
||||||
isAppearAuthor || isModerator
|
isAppearAuthor || isModerator
|
||||||
? {
|
? {
|
||||||
|
|
Loading…
Reference in a new issue