chore: remove packages/bundle (Directus extension)
Some checks failed
Build and Push Docker Image to Gitea / build-and-push (push) Failing after 1m26s
Some checks failed
Build and Push Docker Image to Gitea / build-and-push (push) Failing after 1m26s
All custom logic (endpoints, hooks, gamification) has been ported to packages/backend. The Directus bundle is no longer needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"name": "@sexy.pivoine.art/bundle",
|
||||
"description": "Please enter a description for your extension",
|
||||
"icon": "extension",
|
||||
"version": "1.0.0",
|
||||
"keywords": [
|
||||
"directus",
|
||||
"directus-extension",
|
||||
"directus-extension-bundle"
|
||||
],
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"directus:extension": {
|
||||
"type": "bundle",
|
||||
"path": {
|
||||
"app": "dist/app.js",
|
||||
"api": "dist/api.js"
|
||||
},
|
||||
"entries": [
|
||||
{
|
||||
"name": "endpoint",
|
||||
"type": "endpoint",
|
||||
"source": "src/endpoint"
|
||||
},
|
||||
{
|
||||
"name": "hook",
|
||||
"type": "hook",
|
||||
"source": "src/hook"
|
||||
},
|
||||
{
|
||||
"name": "theme",
|
||||
"type": "theme",
|
||||
"source": "src/theme"
|
||||
}
|
||||
],
|
||||
"host": "^11.11.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "directus-extension build",
|
||||
"dev": "directus-extension build -w --no-minify",
|
||||
"link": "directus-extension link",
|
||||
"validate": "directus-extension validate",
|
||||
"add": "directus-extension add"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@directus/extensions-sdk": "17.0.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sindresorhus/slugify": "^3.0.0",
|
||||
"fluent-ffmpeg": "^2.1.3"
|
||||
}
|
||||
}
|
||||
@@ -1,336 +0,0 @@
|
||||
/**
|
||||
* Gamification Helper Functions
|
||||
* Handles points, achievements, and user stats for recording-focused gamification system
|
||||
*/
|
||||
|
||||
import type { Knex } from "knex";
|
||||
|
||||
/**
|
||||
* Point values for different actions
|
||||
*/
|
||||
export const POINT_VALUES = {
|
||||
RECORDING_CREATE: 50,
|
||||
RECORDING_PLAY: 10,
|
||||
RECORDING_COMPLETE: 5,
|
||||
COMMENT_CREATE: 5,
|
||||
RECORDING_FEATURED: 100,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Time decay constant for weighted scoring
|
||||
* λ = 0.005 means ~14% decay per month
|
||||
*/
|
||||
const DECAY_LAMBDA = 0.005;
|
||||
|
||||
/**
|
||||
* Award points to a user for a specific action
|
||||
*/
|
||||
export async function awardPoints(
|
||||
database: Knex,
|
||||
userId: string,
|
||||
action: keyof typeof POINT_VALUES,
|
||||
recordingId?: string,
|
||||
): Promise<void> {
|
||||
const points = POINT_VALUES[action];
|
||||
|
||||
await database("sexy_user_points").insert({
|
||||
user_id: userId,
|
||||
action,
|
||||
points,
|
||||
recording_id: recordingId || null,
|
||||
date_created: new Date(),
|
||||
});
|
||||
|
||||
// Update cached stats
|
||||
await updateUserStats(database, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate time-weighted score using exponential decay
|
||||
* Score = Σ (points × e^(-λ × age_in_days))
|
||||
*/
|
||||
export async function calculateWeightedScore(
|
||||
database: Knex,
|
||||
userId: string,
|
||||
): Promise<number> {
|
||||
const now = new Date();
|
||||
|
||||
const result = await database("sexy_user_points")
|
||||
.where({ user_id: userId })
|
||||
.select(
|
||||
database.raw(`
|
||||
SUM(
|
||||
points * EXP(-${DECAY_LAMBDA} * EXTRACT(EPOCH FROM (? - date_created)) / 86400)
|
||||
) as weighted_score
|
||||
`, [now]),
|
||||
);
|
||||
|
||||
return result[0]?.weighted_score || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update or create user stats cache
|
||||
*/
|
||||
export async function updateUserStats(database: Knex, userId: string): Promise<void> {
|
||||
const now = new Date();
|
||||
|
||||
// Calculate raw points
|
||||
const rawPointsResult = await database("sexy_user_points")
|
||||
.where({ user_id: userId })
|
||||
.sum("points as total");
|
||||
const totalRawPoints = rawPointsResult[0]?.total || 0;
|
||||
|
||||
// Calculate weighted points
|
||||
const totalWeightedPoints = await calculateWeightedScore(database, userId);
|
||||
|
||||
// Get recordings count
|
||||
const recordingsResult = await database("sexy_recordings")
|
||||
.where({ user_created: userId, status: "published" })
|
||||
.count("* as count");
|
||||
const recordingsCount = recordingsResult[0]?.count || 0;
|
||||
|
||||
// Get playbacks count (excluding own recordings)
|
||||
const playbacksResult = await database("sexy_recording_plays")
|
||||
.where({ user_id: userId })
|
||||
.whereNotIn("recording_id", function () {
|
||||
this.select("id").from("sexy_recordings").where("user_created", userId);
|
||||
})
|
||||
.count("* as count");
|
||||
const playbacksCount = playbacksResult[0]?.count || 0;
|
||||
|
||||
// Get comments count (on recordings only)
|
||||
const commentsResult = await database("comments")
|
||||
.where({ user_created: userId, collection: "sexy_recordings" })
|
||||
.count("* as count");
|
||||
const commentsCount = commentsResult[0]?.count || 0;
|
||||
|
||||
// Get achievements count
|
||||
const achievementsResult = await database("sexy_user_achievements")
|
||||
.where({ user_id: userId })
|
||||
.whereNotNull("date_unlocked")
|
||||
.count("* as count");
|
||||
const achievementsCount = achievementsResult[0]?.count || 0;
|
||||
|
||||
// Upsert stats
|
||||
const existing = await database("sexy_user_stats")
|
||||
.where({ user_id: userId })
|
||||
.first();
|
||||
|
||||
if (existing) {
|
||||
await database("sexy_user_stats")
|
||||
.where({ user_id: userId })
|
||||
.update({
|
||||
total_raw_points: totalRawPoints,
|
||||
total_weighted_points: totalWeightedPoints,
|
||||
recordings_count: recordingsCount,
|
||||
playbacks_count: playbacksCount,
|
||||
comments_count: commentsCount,
|
||||
achievements_count: achievementsCount,
|
||||
last_updated: now,
|
||||
});
|
||||
} else {
|
||||
await database("sexy_user_stats").insert({
|
||||
user_id: userId,
|
||||
total_raw_points: totalRawPoints,
|
||||
total_weighted_points: totalWeightedPoints,
|
||||
recordings_count: recordingsCount,
|
||||
playbacks_count: playbacksCount,
|
||||
comments_count: commentsCount,
|
||||
achievements_count: achievementsCount,
|
||||
last_updated: now,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and update achievement progress for a user
|
||||
*/
|
||||
export async function checkAchievements(
|
||||
database: Knex,
|
||||
userId: string,
|
||||
category?: string,
|
||||
): Promise<void> {
|
||||
// Get all achievements (optionally filtered by category)
|
||||
let achievementsQuery = database("sexy_achievements")
|
||||
.where({ status: "published" });
|
||||
|
||||
if (category) {
|
||||
achievementsQuery = achievementsQuery.where({ category });
|
||||
}
|
||||
|
||||
const achievements = await achievementsQuery;
|
||||
|
||||
for (const achievement of achievements) {
|
||||
const progress = await getAchievementProgress(database, userId, achievement);
|
||||
|
||||
// Check if already unlocked
|
||||
const existing = await database("sexy_user_achievements")
|
||||
.where({ user_id: userId, achievement_id: achievement.id })
|
||||
.first();
|
||||
|
||||
const isUnlocked = progress >= achievement.required_count;
|
||||
const wasUnlocked = existing?.date_unlocked !== null;
|
||||
|
||||
if (existing) {
|
||||
// Update progress
|
||||
await database("sexy_user_achievements")
|
||||
.where({ user_id: userId, achievement_id: achievement.id })
|
||||
.update({
|
||||
progress,
|
||||
date_unlocked: isUnlocked ? (existing.date_unlocked || new Date()) : null,
|
||||
});
|
||||
} else {
|
||||
// Insert new progress
|
||||
await database("sexy_user_achievements").insert({
|
||||
user_id: userId,
|
||||
achievement_id: achievement.id,
|
||||
progress,
|
||||
date_unlocked: isUnlocked ? new Date() : null,
|
||||
});
|
||||
}
|
||||
|
||||
// Award bonus points if newly unlocked
|
||||
if (isUnlocked && !wasUnlocked && achievement.points_reward > 0) {
|
||||
await database("sexy_user_points").insert({
|
||||
user_id: userId,
|
||||
action: `ACHIEVEMENT_${achievement.code}`,
|
||||
points: achievement.points_reward,
|
||||
recording_id: null,
|
||||
date_created: new Date(),
|
||||
});
|
||||
|
||||
// Refresh stats after awarding bonus
|
||||
await updateUserStats(database, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress for a specific achievement
|
||||
*/
|
||||
async function getAchievementProgress(
|
||||
database: Knex,
|
||||
userId: string,
|
||||
achievement: any,
|
||||
): Promise<number> {
|
||||
const { code } = achievement;
|
||||
|
||||
// Recordings achievements
|
||||
if (code === "first_recording" || code === "recording_10" || code === "recording_50" || code === "recording_100") {
|
||||
const result = await database("sexy_recordings")
|
||||
.where({ user_created: userId, status: "published" })
|
||||
.count("* as count");
|
||||
return result[0]?.count || 0;
|
||||
}
|
||||
|
||||
// Featured recording
|
||||
if (code === "featured_recording") {
|
||||
const result = await database("sexy_recordings")
|
||||
.where({ user_created: userId, status: "published", featured: true })
|
||||
.count("* as count");
|
||||
return result[0]?.count || 0;
|
||||
}
|
||||
|
||||
// Playback achievements (excluding own recordings)
|
||||
if (code === "first_play" || code === "play_100" || code === "play_500") {
|
||||
const result = await database("sexy_recording_plays as rp")
|
||||
.leftJoin("sexy_recordings as r", "rp.recording_id", "r.id")
|
||||
.where({ "rp.user_id": userId })
|
||||
.where("r.user_created", "!=", userId)
|
||||
.count("* as count");
|
||||
return result[0]?.count || 0;
|
||||
}
|
||||
|
||||
// Completionist achievements
|
||||
if (code === "completionist_10" || code === "completionist_100") {
|
||||
const result = await database("sexy_recording_plays")
|
||||
.where({ user_id: userId, completed: true })
|
||||
.count("* as count");
|
||||
return result[0]?.count || 0;
|
||||
}
|
||||
|
||||
// Social achievements
|
||||
if (code === "first_comment" || code === "comment_50" || code === "comment_250") {
|
||||
const result = await database("comments")
|
||||
.where({ user_created: userId, collection: "sexy_recordings" })
|
||||
.count("* as count");
|
||||
return result[0]?.count || 0;
|
||||
}
|
||||
|
||||
// Special: Early adopter (joined in first month)
|
||||
if (code === "early_adopter") {
|
||||
const user = await database("directus_users")
|
||||
.where({ id: userId })
|
||||
.first();
|
||||
|
||||
if (user) {
|
||||
const joinDate = new Date(user.date_created);
|
||||
const platformLaunch = new Date("2025-01-01"); // Adjust to actual launch date
|
||||
const oneMonthAfterLaunch = new Date(platformLaunch);
|
||||
oneMonthAfterLaunch.setMonth(oneMonthAfterLaunch.getMonth() + 1);
|
||||
|
||||
return joinDate <= oneMonthAfterLaunch ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Special: One year anniversary
|
||||
if (code === "one_year") {
|
||||
const user = await database("directus_users")
|
||||
.where({ id: userId })
|
||||
.first();
|
||||
|
||||
if (user) {
|
||||
const joinDate = new Date(user.date_created);
|
||||
const oneYearAgo = new Date();
|
||||
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
||||
|
||||
return joinDate <= oneYearAgo ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Special: Balanced creator (50 recordings + 100 plays)
|
||||
if (code === "balanced_creator") {
|
||||
const recordings = await database("sexy_recordings")
|
||||
.where({ user_created: userId, status: "published" })
|
||||
.count("* as count");
|
||||
const plays = await database("sexy_recording_plays as rp")
|
||||
.leftJoin("sexy_recordings as r", "rp.recording_id", "r.id")
|
||||
.where({ "rp.user_id": userId })
|
||||
.where("r.user_created", "!=", userId)
|
||||
.count("* as count");
|
||||
|
||||
const recordingsCount = recordings[0]?.count || 0;
|
||||
const playsCount = plays[0]?.count || 0;
|
||||
|
||||
return (recordingsCount >= 50 && playsCount >= 100) ? 1 : 0;
|
||||
}
|
||||
|
||||
// Special: Top 10 rank
|
||||
if (code === "top_10_rank") {
|
||||
const userStats = await database("sexy_user_stats")
|
||||
.where({ user_id: userId })
|
||||
.first();
|
||||
|
||||
if (!userStats) return 0;
|
||||
|
||||
const rank = await database("sexy_user_stats")
|
||||
.where("total_weighted_points", ">", userStats.total_weighted_points)
|
||||
.count("* as count");
|
||||
|
||||
const userRank = (rank[0]?.count || 0) + 1;
|
||||
return userRank <= 10 ? 1 : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate all weighted scores (for cron job)
|
||||
*/
|
||||
export async function recalculateAllWeightedScores(database: Knex): Promise<void> {
|
||||
const users = await database("sexy_user_stats").select("user_id");
|
||||
|
||||
for (const user of users) {
|
||||
await updateUserStats(database, user.user_id);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,145 +0,0 @@
|
||||
import { createRequire } from "module";
|
||||
global.require = createRequire(import.meta.url);
|
||||
import { defineHook } from "@directus/extensions-sdk";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import ffmpeg from "fluent-ffmpeg";
|
||||
import { awardPoints, checkAchievements } from "../endpoint/gamification.js";
|
||||
|
||||
async function processVideo(
|
||||
meta,
|
||||
{ schema, accountability },
|
||||
services,
|
||||
logger,
|
||||
) {
|
||||
const { FilesService } = services;
|
||||
const itemId = meta.key;
|
||||
const videoPath = `/directus/uploads/${meta.payload.filename_disk}`; // Adjust path as needed
|
||||
const videoService = new FilesService({ schema, accountability }); // Replace with your collection name
|
||||
|
||||
try {
|
||||
const durationInSeconds = await new Promise((resolve, reject) => {
|
||||
ffmpeg.ffprobe(videoPath, function (err, metadata) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(parseInt(metadata.format.duration));
|
||||
});
|
||||
});
|
||||
// Update the item with the duration
|
||||
await videoService.updateOne(itemId, { duration: durationInSeconds });
|
||||
logger.info(`Video ${itemId} duration updated to ${durationInSeconds}`);
|
||||
} catch (error) {
|
||||
logger.error(`Error processing video ${itemId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
export default defineHook(async ({ filter, action }, { services, logger, database, getSchema }) => {
|
||||
action("files.upload", async (meta, context) => {
|
||||
await processVideo(meta, context, services, logger);
|
||||
});
|
||||
|
||||
filter(
|
||||
"users.create",
|
||||
(payload: {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
artist_name: string;
|
||||
slug: string;
|
||||
}) => {
|
||||
const artist_name = `${payload.first_name}-${new Date().getTime()}`;
|
||||
const slug = slugify(artist_name);
|
||||
const join_date = new Date();
|
||||
return { ...payload, artist_name, slug, join_date };
|
||||
},
|
||||
);
|
||||
|
||||
filter(
|
||||
"users.update",
|
||||
(payload: {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
artist_name: string;
|
||||
slug: string;
|
||||
}) => {
|
||||
if (payload.artist_name) {
|
||||
const slug = slugify(payload.artist_name);
|
||||
return { ...payload, slug };
|
||||
}
|
||||
return payload;
|
||||
},
|
||||
);
|
||||
|
||||
// =========================================
|
||||
// GAMIFICATION HOOKS
|
||||
// =========================================
|
||||
|
||||
// Hook: Award points when recording is published
|
||||
action("items.create", async (meta, { collection, accountability }) => {
|
||||
if (collection === "sexy_recordings") {
|
||||
const { payload, key } = meta;
|
||||
|
||||
// Award points if recording is published
|
||||
if (payload.status === "published" && accountability?.user) {
|
||||
try {
|
||||
await awardPoints(database, accountability.user, "RECORDING_CREATE", key);
|
||||
await checkAchievements(database, accountability.user, "recordings");
|
||||
logger.info(`Awarded RECORDING_CREATE points to user ${accountability.user}`);
|
||||
} catch (error) {
|
||||
logger.error("Failed to award recording creation points:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Hook: Award points when recording status changes to published or featured
|
||||
action("items.update", async (meta, { collection, accountability, schema }) => {
|
||||
if (collection === "sexy_recordings") {
|
||||
const { payload, keys } = meta;
|
||||
|
||||
try {
|
||||
const { ItemsService } = services;
|
||||
const recordingsService = new ItemsService("sexy_recordings", {
|
||||
schema: await getSchema(),
|
||||
});
|
||||
|
||||
for (const key of keys) {
|
||||
const recording = await recordingsService.readOne(key);
|
||||
|
||||
// Award points if status changed from non-published to published
|
||||
if (payload.status === "published" && recording.status !== "published" && recording.user_created) {
|
||||
await awardPoints(database, recording.user_created, "RECORDING_CREATE", key);
|
||||
await checkAchievements(database, recording.user_created, "recordings");
|
||||
logger.info(`Awarded RECORDING_CREATE points to user ${recording.user_created}`);
|
||||
}
|
||||
|
||||
// Award bonus points if recording becomes featured
|
||||
if (payload.featured === true && !recording.featured && recording.user_created) {
|
||||
await awardPoints(database, recording.user_created, "RECORDING_FEATURED", key);
|
||||
await checkAchievements(database, recording.user_created, "recordings");
|
||||
logger.info(`Awarded RECORDING_FEATURED points to user ${recording.user_created}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Failed to award recording update points:", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Hook: Award points when user creates a comment on a recording
|
||||
action("comments.create", async (meta, { accountability }) => {
|
||||
if (!accountability?.user) return;
|
||||
|
||||
try {
|
||||
const { payload } = meta;
|
||||
|
||||
// Check if comment is on a recording
|
||||
if (payload.collection === "sexy_recordings") {
|
||||
await awardPoints(database, accountability.user, "COMMENT_CREATE");
|
||||
await checkAchievements(database, accountability.user, "social");
|
||||
logger.info(`Awarded COMMENT_CREATE points to user ${accountability.user}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Failed to award comment points:", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
import { defineTheme } from "@directus/extensions-sdk";
|
||||
import "./style.css";
|
||||
|
||||
export default defineTheme({
|
||||
id: "@sexy.pivoine.art/theme",
|
||||
name: "Sexy.Art Dark",
|
||||
appearance: "dark",
|
||||
rules: {
|
||||
borderRadius: "6px",
|
||||
borderWidth: "2px",
|
||||
foreground: "#c9d1d9",
|
||||
foregroundSubdued: "#666672",
|
||||
foregroundAccent: "#f0f6fc",
|
||||
background: "#0D1117",
|
||||
backgroundNormal: "#21262E",
|
||||
backgroundAccent: "#30363D",
|
||||
backgroundSubdued: "#161B22",
|
||||
borderColor: "#21262E",
|
||||
borderColorAccent: "#30363D",
|
||||
borderColorSubdued: "#161B22",
|
||||
primary: "#ce47eb",
|
||||
secondary: "#613dff",
|
||||
success: "#87ff66",
|
||||
warning: "#ffbf66",
|
||||
danger: "#ff6467",
|
||||
navigation: {
|
||||
background: "#21262E",
|
||||
backgroundAccent: "#30363D",
|
||||
borderWidth: "0px",
|
||||
borderColor: "transparent",
|
||||
project: {
|
||||
background: "#30363D",
|
||||
borderWidth: "0px",
|
||||
borderColor: "transparent",
|
||||
},
|
||||
modules: {
|
||||
borderWidth: "0px",
|
||||
borderColor: "transparent",
|
||||
button: {
|
||||
foregroundHover: "#fff",
|
||||
background: "transparent",
|
||||
backgroundHover: "transparent",
|
||||
backgroundActive: "#21262E",
|
||||
},
|
||||
},
|
||||
list: {
|
||||
background: "transparent",
|
||||
backgroundHover: "#30363D",
|
||||
backgroundActive: "#30363D",
|
||||
divider: {
|
||||
borderColor: "#30363D",
|
||||
},
|
||||
},
|
||||
},
|
||||
header: {
|
||||
borderWidth: "0px",
|
||||
borderColor: "transparent",
|
||||
boxShadow: "0 4px 7px -4px black",
|
||||
},
|
||||
form: {
|
||||
columnGap: "32px",
|
||||
rowGap: "40px",
|
||||
field: {
|
||||
label: {
|
||||
fontWeight: "600",
|
||||
},
|
||||
input: {
|
||||
borderColor: "#21262E",
|
||||
borderColorHover: "#30363D",
|
||||
boxShadow: "none",
|
||||
boxShadowHover: "none",
|
||||
height: "60px",
|
||||
padding: "16px",
|
||||
},
|
||||
},
|
||||
},
|
||||
sidebar: {
|
||||
background: "#21262E",
|
||||
borderWidth: "0px",
|
||||
borderColor: "transparent",
|
||||
section: {
|
||||
toggle: {
|
||||
background: "#30363D",
|
||||
borderWidth: "0px",
|
||||
borderColor: "transparent",
|
||||
},
|
||||
form: {
|
||||
field: {
|
||||
input: {
|
||||
height: "52px",
|
||||
padding: "12px",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
public: {
|
||||
art: {
|
||||
background: "#21262E",
|
||||
speed: "1",
|
||||
},
|
||||
},
|
||||
popover: {
|
||||
menu: {
|
||||
background: "#30363D",
|
||||
boxShadow: "0px 0px 6px 0px black",
|
||||
},
|
||||
},
|
||||
banner: {
|
||||
background: "#161B22",
|
||||
padding: "40px",
|
||||
avatar: {
|
||||
background: "#fff",
|
||||
borderRadius: "50%",
|
||||
},
|
||||
headline: {
|
||||
foreground: "#fff",
|
||||
},
|
||||
title: {
|
||||
foreground: "#fff",
|
||||
},
|
||||
subtitle: {
|
||||
foreground: "#969696",
|
||||
},
|
||||
art: {
|
||||
foreground: "#21262E",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "node",
|
||||
"strict": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedParameters": true,
|
||||
"alwaysStrict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"resolveJsonModule": false,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"isolatedModules": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["./src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user