mirror of
https://github.com/thepeacockproject/Peacock
synced 2024-11-16 11:03:30 +01:00
feat: official -> peacock progression transfer (#426)
* Support multiple CPD configs * Initial work on progression transfer - challenges * Add transfer of profile data * Change how ActionXp is stored We make the UserDefault contain an empty object, saves having to add all the locations. They're only added when XP is actually gained in that sublocation, this mimics official behaviour. * Fix player profile data * Sync escalation and arcade progress * Sync freelancer CPD data * Fix linting * Don't override previous challenge progression * Bump default profile version * Overriding challenge progression is inevitable * Remove pointless cast * Only get arcade and freelancer data in H3 * Remove debug print * Support 2016 progression transfer * Transfer default loadouts * Don't clone LocationsData for getSublocations Co-authored-by: Reece Dunham <me@rdil.rocks>
This commit is contained in:
parent
6db75c3772
commit
beb610c340
@ -233,6 +233,10 @@ export abstract class ChallengeRegistry {
|
||||
return this.challenges[gameVersion].get(challengeId)
|
||||
}
|
||||
|
||||
getChallengeIds(gameVersion: GameVersion): string[] {
|
||||
return Array.from(this.challenges[gameVersion].keys())
|
||||
}
|
||||
|
||||
removeChallenge(challengeId: string, gameVersion: GameVersion): boolean {
|
||||
const challenge = this.challenges[gameVersion].get(challengeId)
|
||||
if (!challenge) return false
|
||||
|
@ -240,22 +240,14 @@ export class ProgressionService {
|
||||
// Update the SubLocation data
|
||||
const profileData = userProfile.Extensions.progression.PlayerProfileXP
|
||||
|
||||
let foundSubLocation = profileData.Sublocations.find(
|
||||
(e) => e.Location === parentLocationId,
|
||||
)
|
||||
|
||||
if (!foundSubLocation) {
|
||||
foundSubLocation = {
|
||||
Location: parentLocationId,
|
||||
profileData.Sublocations[contract.Metadata.Location] ??= {
|
||||
Xp: 0,
|
||||
ActionXp: 0,
|
||||
}
|
||||
|
||||
profileData.Sublocations.push(foundSubLocation)
|
||||
}
|
||||
|
||||
foundSubLocation.Xp += masteryXp
|
||||
foundSubLocation.ActionXp += actionXp
|
||||
profileData.Sublocations[contract.Metadata.Location].Xp += masteryXp
|
||||
profileData.Sublocations[contract.Metadata.Location].ActionXp +=
|
||||
actionXp
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ import MultiplayerPresets from "../static/MultiplayerPresets.json"
|
||||
import LobbySlimTemplate from "../static/LobbySlimTemplate.json"
|
||||
import MasteryDataForLocationTemplate from "../static/MasteryDataForLocationTemplate.json"
|
||||
import LegacyMasteryLocationTemplate from "../static/LegacyMasteryLocationTemplate.json"
|
||||
import DefaultCpdConfig from "../static/DefaultCpdConfig.json"
|
||||
import DefaultCpdConfigs from "../static/DefaultCpdConfigs.json"
|
||||
import EvergreenGameChangerProperties from "../static/EvergreenGameChangerProperties.json"
|
||||
import AreaMap from "../static/AreaMap.json"
|
||||
import ArcadePageTemplate from "../static/ArcadePageTemplate.json"
|
||||
@ -217,7 +217,7 @@ const configs = {
|
||||
LobbySlimTemplate,
|
||||
MasteryDataForLocationTemplate,
|
||||
LegacyMasteryLocationTemplate,
|
||||
DefaultCpdConfig,
|
||||
DefaultCpdConfigs,
|
||||
EvergreenGameChangerProperties,
|
||||
AreaMap,
|
||||
ArcadePageTemplate,
|
||||
|
@ -22,6 +22,10 @@ import { ContractProgressionData } from "./types/types"
|
||||
import { getFlag } from "./flags"
|
||||
import { EVERGREEN_LEVEL_INFO } from "./utils"
|
||||
|
||||
type DefaultCpdConfigs = {
|
||||
[cpdId: string]: ContractProgressionData
|
||||
}
|
||||
|
||||
export function setCpd(
|
||||
data: ContractProgressionData,
|
||||
uID: string,
|
||||
@ -42,15 +46,22 @@ export function getCpd(uID: string, cpdID: string): ContractProgressionData {
|
||||
|
||||
if (!Object.keys(userData.Extensions.CPD).includes(cpdID)) {
|
||||
const defaultCPD = getConfig(
|
||||
"DefaultCpdConfig",
|
||||
"DefaultCpdConfigs",
|
||||
false,
|
||||
) as ContractProgressionData
|
||||
) as DefaultCpdConfigs
|
||||
|
||||
setCpd(defaultCPD, uID, cpdID)
|
||||
setCpd(
|
||||
Object.keys(defaultCPD).includes(cpdID) ? defaultCPD[cpdID] : {},
|
||||
uID,
|
||||
cpdID,
|
||||
)
|
||||
}
|
||||
|
||||
// NOTE: Override the EvergreenLevel with the latest Mastery Level
|
||||
if (getFlag("gameplayUnlockAllFreelancerMasteries")) {
|
||||
if (
|
||||
getFlag("gameplayUnlockAllFreelancerMasteries") &&
|
||||
cpdID === "f8ec92c2-4fa2-471e-ae08-545480c746ee"
|
||||
) {
|
||||
userData.Extensions.CPD[cpdID]["EvergreenLevel"] =
|
||||
EVERGREEN_LEVEL_INFO.length
|
||||
}
|
||||
|
@ -95,6 +95,19 @@ import { getPlayerProfileData } from "./menus/playerProfile"
|
||||
|
||||
const menuDataRouter = Router()
|
||||
|
||||
// We make this lookup table to quickly get it, there's no other quick way for it.
|
||||
export const SNIPER_UNLOCK_TO_LOCATION: Record<string, string> = {
|
||||
FIREARMS_SC_HERO_SNIPER_HM: "LOCATION_PARENT_AUSTRIA",
|
||||
FIREARMS_SC_HERO_SNIPER_KNIGHT: "LOCATION_PARENT_AUSTRIA",
|
||||
FIREARMS_SC_HERO_SNIPER_STONE: "LOCATION_PARENT_AUSTRIA",
|
||||
FIREARMS_SC_SEAGULL_HM: "LOCATION_PARENT_SALTY",
|
||||
FIREARMS_SC_SEAGULL_KNIGHT: "LOCATION_PARENT_SALTY",
|
||||
FIREARMS_SC_SEAGULL_STONE: "LOCATION_PARENT_SALTY",
|
||||
FIREARMS_SC_FALCON_HM: "LOCATION_PARENT_CAGED",
|
||||
FIREARMS_SC_FALCON_KNIGHT: "LOCATION_PARENT_CAGED",
|
||||
FIREARMS_SC_FALCON_STONE: "LOCATION_PARENT_CAGED",
|
||||
}
|
||||
|
||||
// /profiles/page/
|
||||
|
||||
menuDataRouter.get(
|
||||
@ -1400,25 +1413,12 @@ menuDataRouter.get(
|
||||
"/GetMasteryCompletionDataForUnlockable",
|
||||
// @ts-expect-error Has jwt props.
|
||||
(req: RequestWithJwt<GetMasteryCompletionDataForUnlockableQuery>, res) => {
|
||||
// We make this lookup table to quickly get it, there's no other quick way for it.
|
||||
const unlockToLoc: Record<string, string> = {
|
||||
FIREARMS_SC_HERO_SNIPER_HM: "LOCATION_PARENT_AUSTRIA",
|
||||
FIREARMS_SC_HERO_SNIPER_KNIGHT: "LOCATION_PARENT_AUSTRIA",
|
||||
FIREARMS_SC_HERO_SNIPER_STONE: "LOCATION_PARENT_AUSTRIA",
|
||||
FIREARMS_SC_SEAGULL_HM: "LOCATION_PARENT_SALTY",
|
||||
FIREARMS_SC_SEAGULL_KNIGHT: "LOCATION_PARENT_SALTY",
|
||||
FIREARMS_SC_SEAGULL_STONE: "LOCATION_PARENT_SALTY",
|
||||
FIREARMS_SC_FALCON_HM: "LOCATION_PARENT_CAGED",
|
||||
FIREARMS_SC_FALCON_KNIGHT: "LOCATION_PARENT_CAGED",
|
||||
FIREARMS_SC_FALCON_STONE: "LOCATION_PARENT_CAGED",
|
||||
}
|
||||
|
||||
res.json({
|
||||
template: null,
|
||||
data: {
|
||||
CompletionData: controller.masteryService.getLocationCompletion(
|
||||
unlockToLoc[req.query.unlockableId],
|
||||
unlockToLoc[req.query.unlockableId],
|
||||
SNIPER_UNLOCK_TO_LOCATION[req.query.unlockableId],
|
||||
SNIPER_UNLOCK_TO_LOCATION[req.query.unlockableId],
|
||||
req.gameVersion,
|
||||
req.jwt.unique_name,
|
||||
"sniper",
|
||||
|
@ -30,6 +30,13 @@ import { getDestinationCompletion } from "./destinations"
|
||||
import { getUserData } from "../databaseHandler"
|
||||
import { isSniperLocation } from "../utils"
|
||||
|
||||
type XpData = {
|
||||
[location: string]: {
|
||||
Xp: number
|
||||
ActionXp: number
|
||||
}
|
||||
}
|
||||
|
||||
export function getPlayerProfileData(
|
||||
gameVersion: GameVersion,
|
||||
userId: string,
|
||||
@ -47,7 +54,27 @@ export function getPlayerProfileData(
|
||||
|
||||
playerProfilePage.SubLocationData = []
|
||||
|
||||
const userProfile = getUserData(userId, gameVersion)
|
||||
const subLocationMap =
|
||||
userProfile.Extensions.progression.PlayerProfileXP.Sublocations
|
||||
const xpData: XpData = {}
|
||||
|
||||
for (const subLocationKey in locationData.children) {
|
||||
const subLocation = locationData.children[subLocationKey]
|
||||
const parentLocation =
|
||||
locationData.parents[subLocation.Properties.ParentLocation || ""]
|
||||
|
||||
// We find all sublocations and add their XP for the season data
|
||||
const subLocationData = subLocationMap[subLocationKey]
|
||||
|
||||
xpData[parentLocation.Id] ??= {
|
||||
Xp: 0,
|
||||
ActionXp: 0,
|
||||
}
|
||||
|
||||
xpData[parentLocation.Id].Xp += subLocationData?.Xp ?? 0
|
||||
xpData[parentLocation.Id].ActionXp += subLocationData?.ActionXp ?? 0
|
||||
|
||||
// Ewww...
|
||||
if (
|
||||
subLocationKey === "LOCATION_ICA_FACILITY_ARRIVAL" ||
|
||||
@ -56,10 +83,6 @@ export function getPlayerProfileData(
|
||||
continue
|
||||
}
|
||||
|
||||
const subLocation = locationData.children[subLocationKey]
|
||||
const parentLocation =
|
||||
locationData.parents[subLocation.Properties.ParentLocation || ""]
|
||||
|
||||
const completionData = generateCompletionData(
|
||||
subLocation.Id,
|
||||
userId,
|
||||
@ -109,24 +132,17 @@ export function getPlayerProfileData(
|
||||
})
|
||||
}
|
||||
|
||||
const userProfile = getUserData(userId, gameVersion)
|
||||
playerProfilePage.PlayerProfileXp.Total =
|
||||
userProfile.Extensions.progression.PlayerProfileXP.Total
|
||||
playerProfilePage.PlayerProfileXp.Level =
|
||||
userProfile.Extensions.progression.PlayerProfileXP.ProfileLevel
|
||||
|
||||
const subLocationMap = new Map(
|
||||
userProfile.Extensions.progression.PlayerProfileXP.Sublocations.map(
|
||||
(obj) => [obj.Location, obj],
|
||||
),
|
||||
)
|
||||
|
||||
for (const season of playerProfilePage.PlayerProfileXp.Seasons) {
|
||||
for (const location of season.Locations) {
|
||||
const subLocationData = subLocationMap.get(location.LocationId)
|
||||
const locationData = xpData[location.LocationId]
|
||||
|
||||
location.Xp = subLocationData?.Xp || 0
|
||||
location.ActionXp = subLocationData?.ActionXp || 0
|
||||
location.Xp = locationData?.Xp || 0
|
||||
location.ActionXp = locationData?.ActionXp || 0
|
||||
|
||||
if (
|
||||
location.LocationProgression &&
|
||||
|
@ -511,10 +511,11 @@ export type UserProfile = {
|
||||
*/
|
||||
Total: number
|
||||
Sublocations: {
|
||||
Location: string
|
||||
[location: string]: {
|
||||
Xp: number
|
||||
ActionXp: number
|
||||
}[]
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If the mastery location has subpackages and not drops, it will
|
||||
@ -560,6 +561,7 @@ export type UserProfile = {
|
||||
[opportunityId: RepositoryId]: boolean
|
||||
}
|
||||
CPD: CPDStore
|
||||
LastOfficialSync: Date | string | null
|
||||
}
|
||||
ETag: string | null
|
||||
Gamertag: string
|
||||
@ -1572,3 +1574,9 @@ export type SMFLastDeploy = {
|
||||
peacockPlugins?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export type OfficialSublocation = {
|
||||
Location: string
|
||||
Xp: number
|
||||
ActionXp: number
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import type { NextFunction, Response } from "express"
|
||||
import type {
|
||||
GameVersion,
|
||||
MissionManifestObjective,
|
||||
OfficialSublocation,
|
||||
PeacockLocationsData,
|
||||
RepositoryId,
|
||||
RequestWithJwt,
|
||||
@ -66,7 +67,7 @@ export const contractCreationTutorialId = "d7e2607c-6916-48e2-9588-976c7d8998bb"
|
||||
*
|
||||
* See docs/USER_PROFILES.md for more.
|
||||
*/
|
||||
export const LATEST_PROFILE_VERSION = 1
|
||||
export const LATEST_PROFILE_VERSION = 2
|
||||
|
||||
export async function checkForUpdates(): Promise<void> {
|
||||
if (getFlag("updateChecking") === false) {
|
||||
@ -249,7 +250,6 @@ export function clampValue(value: number, min: number, max: number) {
|
||||
*
|
||||
* @param profile The user profile to update
|
||||
* @param gameVersion The game version
|
||||
* @returns The updated user profile.
|
||||
*/
|
||||
function updateUserProfile(
|
||||
profile: UserProfile,
|
||||
@ -266,6 +266,29 @@ function updateUserProfile(
|
||||
case LATEST_PROFILE_VERSION:
|
||||
// This profile updated to the latest version, we're done.
|
||||
return
|
||||
case 1: {
|
||||
/* ////// VERSION 2 ////// */
|
||||
|
||||
const sublocations = profile.Extensions.progression.PlayerProfileXP
|
||||
.Sublocations as unknown as OfficialSublocation[]
|
||||
|
||||
profile.Extensions.progression.PlayerProfileXP.Sublocations =
|
||||
Object.fromEntries(
|
||||
sublocations.map((value) => [
|
||||
value.Location,
|
||||
{
|
||||
Xp: value.Xp,
|
||||
ActionXp: value.ActionXp,
|
||||
},
|
||||
]),
|
||||
)
|
||||
|
||||
profile.Extensions.LastOfficialSync = null
|
||||
|
||||
profile.Version = 2
|
||||
|
||||
return updateUserProfile(profile, gameVersion)
|
||||
}
|
||||
default: {
|
||||
// Check that the profile version is indeed undefined. If it isn't,
|
||||
// we've forgotten to add a version to the switch.
|
||||
@ -726,3 +749,26 @@ export function isSuit(repoId: string): boolean {
|
||||
? suitsToTypeMap[repoId] !== "disguise"
|
||||
: false
|
||||
}
|
||||
|
||||
type SublocationMap = {
|
||||
[parentId: string]: string[]
|
||||
}
|
||||
|
||||
export function getSublocations(gameVersion: GameVersion): SublocationMap {
|
||||
const sublocations: SublocationMap = {}
|
||||
const locations = getVersionedConfig<PeacockLocationsData>(
|
||||
"LocationsData",
|
||||
gameVersion,
|
||||
false,
|
||||
)
|
||||
|
||||
for (const child of Object.values(locations.children)) {
|
||||
if (!child.Properties.ParentLocation) continue
|
||||
|
||||
sublocations[child.Properties.ParentLocation] ??= []
|
||||
|
||||
sublocations[child.Properties.ParentLocation].push(child.Id)
|
||||
}
|
||||
|
||||
return sublocations
|
||||
}
|
||||
|
@ -19,12 +19,43 @@
|
||||
import { NextFunction, Request, Response, Router } from "express"
|
||||
import { getConfig } from "./configSwizzleManager"
|
||||
import { readdir, readFile } from "fs/promises"
|
||||
import { GameVersion, UserProfile } from "./types/types"
|
||||
import {
|
||||
ChallengeProgressionData,
|
||||
GameVersion,
|
||||
HitsCategoryCategory,
|
||||
OfficialSublocation,
|
||||
ProgressionData,
|
||||
UserProfile,
|
||||
} from "./types/types"
|
||||
import { join } from "path"
|
||||
import { uuidRegex, versions } from "./utils"
|
||||
import {
|
||||
getRemoteService,
|
||||
getSublocations,
|
||||
isSniperLocation,
|
||||
levelForXp,
|
||||
uuidRegex,
|
||||
versions,
|
||||
} from "./utils"
|
||||
import { getUserData, loadUserData, writeUserData } from "./databaseHandler"
|
||||
import { controller } from "./controller"
|
||||
import { log, LogLevel } from "./loggingInterop"
|
||||
import { OfficialServerAuth, userAuths } from "./officialServerAuth"
|
||||
import { AxiosError } from "axios"
|
||||
import { SNIPER_UNLOCK_TO_LOCATION } from "./menuData"
|
||||
|
||||
type OfficialProfileResponse = UserProfile & {
|
||||
Extensions: {
|
||||
progression: {
|
||||
Unlockables: {
|
||||
[unlockableId: string]: ProgressionData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SubPackageData = {
|
||||
[id: string]: ProgressionData
|
||||
}
|
||||
|
||||
const webFeaturesRouter = Router()
|
||||
|
||||
@ -108,9 +139,21 @@ webFeaturesRouter.get("/local-users", async (req: CommonRequest, res) => {
|
||||
(name) => name !== "lop.json",
|
||||
)
|
||||
|
||||
const result = []
|
||||
/**
|
||||
* Sync this type with `webui/src/utils`!
|
||||
*/
|
||||
type BasicUser = Readonly<{
|
||||
id: string
|
||||
name: string
|
||||
platform: string
|
||||
lastOfficialSync: string | null
|
||||
}>
|
||||
|
||||
const result: BasicUser[] = []
|
||||
|
||||
for (const file of files) {
|
||||
if (file === "lop.json") continue
|
||||
|
||||
const read = JSON.parse(
|
||||
(await readFile(join(dir, file))).toString(),
|
||||
) as UserProfile
|
||||
@ -119,6 +162,8 @@ webFeaturesRouter.get("/local-users", async (req: CommonRequest, res) => {
|
||||
id: read.Id,
|
||||
name: read.Gamertag,
|
||||
platform: read.EpicId ? "Epic" : "Steam",
|
||||
lastOfficialSync:
|
||||
read.Extensions.LastOfficialSync?.toString() || null,
|
||||
})
|
||||
}
|
||||
|
||||
@ -217,4 +262,339 @@ webFeaturesRouter.get(
|
||||
},
|
||||
)
|
||||
|
||||
type EscalationData = {
|
||||
PeacockEscalations: {
|
||||
[escalationId: string]: number
|
||||
}
|
||||
PeacockCompletedEscalations: string[]
|
||||
}
|
||||
|
||||
type OfficialHitsCategory = {
|
||||
data: HitsCategoryCategory
|
||||
}
|
||||
|
||||
async function getHitsCategory(
|
||||
auth: OfficialServerAuth,
|
||||
remoteService: string,
|
||||
category: string,
|
||||
page: number,
|
||||
): Promise<[results: EscalationData, hasMore: boolean]> {
|
||||
const data: EscalationData = {
|
||||
PeacockEscalations: {},
|
||||
PeacockCompletedEscalations: [],
|
||||
}
|
||||
|
||||
const hits = await auth._useService<OfficialHitsCategory>(
|
||||
`https://${remoteService}.hitman.io/profiles/page/HitsCategory?page=${page}&type=${category}&mode=dataonly`,
|
||||
true,
|
||||
)
|
||||
|
||||
for (const hit of hits.data.data.Data.Hits) {
|
||||
data.PeacockEscalations[hit.Id] =
|
||||
hit.UserCentricContract.Data.EscalationCompletedLevels! + 1
|
||||
|
||||
if (hit.UserCentricContract.Data.EscalationCompleted)
|
||||
data.PeacockCompletedEscalations.push(hit.Id)
|
||||
}
|
||||
|
||||
return [data, hits.data.data.Data.HasMore]
|
||||
}
|
||||
|
||||
async function getAllHitsCategory(
|
||||
auth: OfficialServerAuth,
|
||||
remoteService: string,
|
||||
category: string,
|
||||
): Promise<EscalationData> {
|
||||
const data: EscalationData = {
|
||||
PeacockEscalations: {},
|
||||
PeacockCompletedEscalations: [],
|
||||
}
|
||||
|
||||
let page = 0
|
||||
let hasMore = true
|
||||
|
||||
while (hasMore) {
|
||||
const [results, more] = await getHitsCategory(
|
||||
auth,
|
||||
remoteService,
|
||||
category,
|
||||
page,
|
||||
)
|
||||
|
||||
data.PeacockEscalations = {
|
||||
...data.PeacockEscalations,
|
||||
...results.PeacockEscalations,
|
||||
}
|
||||
|
||||
data.PeacockCompletedEscalations = [
|
||||
...data.PeacockCompletedEscalations,
|
||||
...results.PeacockCompletedEscalations,
|
||||
]
|
||||
|
||||
page++
|
||||
hasMore = more
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
webFeaturesRouter.post(
|
||||
"/sync-progress",
|
||||
commonValidationMiddleware,
|
||||
async (req: CommonRequest, res) => {
|
||||
const remoteService = getRemoteService(req.query.gv)
|
||||
const auth = userAuths.get(req.query.user)
|
||||
|
||||
if (!auth) {
|
||||
formErrorMessage(
|
||||
res,
|
||||
"Failed to get official authentication data. Please connect to Peacock first.",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const userdata = getUserData(req.query.user, req.query.gv)
|
||||
|
||||
try {
|
||||
// Challenge Progression
|
||||
const challengeProgression = await auth._useService<
|
||||
ChallengeProgressionData[]
|
||||
>(
|
||||
`https://${remoteService}.hitman.io/authentication/api/userchannel/ChallengesService/GetProgression`,
|
||||
false,
|
||||
{
|
||||
profileid: req.query.user,
|
||||
challengeids: controller.challengeService.getChallengeIds(
|
||||
req.query.gv,
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
userdata.Extensions.ChallengeProgression = Object.fromEntries(
|
||||
challengeProgression.data.map((data) => {
|
||||
return [
|
||||
data.ChallengeId,
|
||||
{
|
||||
Ticked: data.Completed,
|
||||
Completed: data.Completed,
|
||||
CurrentState:
|
||||
(data.State["CurrentState"] as string) ??
|
||||
"Start",
|
||||
State: data.State,
|
||||
},
|
||||
]
|
||||
}),
|
||||
)
|
||||
|
||||
// Profile Progression
|
||||
const exts = await auth._useService<OfficialProfileResponse>(
|
||||
`https://${remoteService}.hitman.io/authentication/api/userchannel/ProfileService/GetProfile`,
|
||||
false,
|
||||
{
|
||||
id: req.query.user,
|
||||
extensions: [
|
||||
"achievements",
|
||||
"friends",
|
||||
"gameclient",
|
||||
"gamepersistentdata",
|
||||
"opportunityprogression",
|
||||
"progression",
|
||||
"defaultloadout",
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
if (req.query.gv !== "h1") {
|
||||
const sublocations = exts.data.Extensions.progression
|
||||
.PlayerProfileXP
|
||||
.Sublocations as unknown as OfficialSublocation[]
|
||||
|
||||
userdata.Extensions.progression.PlayerProfileXP = {
|
||||
...userdata.Extensions.progression.PlayerProfileXP,
|
||||
Total: exts.data.Extensions.progression.PlayerProfileXP
|
||||
.Total,
|
||||
ProfileLevel: levelForXp(
|
||||
exts.data.Extensions.progression.PlayerProfileXP.Total,
|
||||
),
|
||||
Sublocations: Object.fromEntries(
|
||||
sublocations.map((value) => [
|
||||
value.Location,
|
||||
{
|
||||
Xp: value.Xp,
|
||||
ActionXp: value.ActionXp,
|
||||
},
|
||||
]),
|
||||
),
|
||||
}
|
||||
|
||||
userdata.Extensions.opportunityprogression = Object.fromEntries(
|
||||
Object.keys(
|
||||
exts.data.Extensions.opportunityprogression,
|
||||
).map((value) => [value, true]),
|
||||
)
|
||||
|
||||
for (const [unlockId, data] of Object.entries(
|
||||
exts.data.Extensions.progression.Unlockables,
|
||||
)) {
|
||||
const unlockableId = unlockId.toUpperCase()
|
||||
|
||||
if (!(unlockableId in SNIPER_UNLOCK_TO_LOCATION)) continue
|
||||
;(
|
||||
userdata.Extensions.progression.Locations[
|
||||
SNIPER_UNLOCK_TO_LOCATION[unlockableId]
|
||||
] as SubPackageData
|
||||
)[unlockableId] = {
|
||||
Xp: data.Xp,
|
||||
Level: data.Level,
|
||||
PreviouslySeenXp: data.PreviouslySeenXp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userdata.Extensions.gamepersistentdata =
|
||||
exts.data.Extensions.gamepersistentdata
|
||||
|
||||
const sublocations = getSublocations(req.query.gv)
|
||||
userdata.Extensions.defaultloadout ??= {}
|
||||
|
||||
if (exts.data.Extensions.defaultloadout) {
|
||||
for (const [parent, loadout] of Object.entries(
|
||||
exts.data.Extensions.defaultloadout,
|
||||
)) {
|
||||
for (const child of sublocations[parent]) {
|
||||
userdata.Extensions.defaultloadout[child] = loadout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userdata.Extensions.achievements = exts.data.Extensions.achievements
|
||||
|
||||
for (const [locId, data] of Object.entries(
|
||||
exts.data.Extensions.progression.Locations,
|
||||
)) {
|
||||
const location = (
|
||||
locId.startsWith("location_parent")
|
||||
? locId
|
||||
: locId.replace("location_", "location_parent_")
|
||||
).toUpperCase()
|
||||
|
||||
if (isSniperLocation(location)) continue
|
||||
|
||||
if (req.query.gv === "h1") {
|
||||
const parent = location.endsWith("PRO1")
|
||||
? location.substring(0, location.length - 5)
|
||||
: location
|
||||
|
||||
const packageId: string = location.endsWith("PRO1")
|
||||
? "pro1"
|
||||
: "normal"
|
||||
|
||||
;(
|
||||
userdata.Extensions.progression.Locations[
|
||||
parent
|
||||
] as SubPackageData
|
||||
)[packageId] = {
|
||||
Xp: data.Xp as number,
|
||||
Level: data.Level as number,
|
||||
PreviouslySeenXp: data.Xp as number,
|
||||
}
|
||||
} else {
|
||||
userdata.Extensions.progression.Locations[location] = {
|
||||
Xp: data.Xp as number,
|
||||
Level: data.Level as number,
|
||||
PreviouslySeenXp: data.PreviouslySeenXp as number,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Escalation & Arcade Progression
|
||||
const escalations = await getAllHitsCategory(
|
||||
auth,
|
||||
remoteService!,
|
||||
"ContractAttack",
|
||||
)
|
||||
|
||||
const arcade =
|
||||
req.query.gv === "h3"
|
||||
? await getAllHitsCategory(auth, remoteService!, "Arcade")
|
||||
: {
|
||||
PeacockEscalations: {},
|
||||
PeacockCompletedEscalations: [],
|
||||
}
|
||||
|
||||
userdata.Extensions.PeacockEscalations = {
|
||||
...userdata.Extensions.PeacockEscalations,
|
||||
...escalations.PeacockEscalations,
|
||||
...arcade.PeacockEscalations,
|
||||
}
|
||||
|
||||
userdata.Extensions.PeacockCompletedEscalations = [
|
||||
...userdata.Extensions.PeacockCompletedEscalations,
|
||||
...escalations.PeacockCompletedEscalations,
|
||||
...arcade.PeacockCompletedEscalations,
|
||||
]
|
||||
|
||||
for (const id of userdata.Extensions.PeacockCompletedEscalations) {
|
||||
userdata.Extensions.PeacockPlayedContracts[id] = {
|
||||
LastPlayedAt: new Date().getTime(),
|
||||
Completed: true,
|
||||
IsEscalation: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Freelancer Progression
|
||||
// TODO: Try and see if there is a less intensive way to do this
|
||||
// GetForPlay2 is quite intensive on IOI's side as it starts a session
|
||||
if (req.query.gv === "h3") {
|
||||
await auth._useService(
|
||||
`https://${remoteService}.hitman.io/authentication/api/configuration/Init?configName=pc-prod&lockedContentDisabled=false&isFreePrologueUser=false&isIntroPackUser=false&isFullExperienceUser=true`,
|
||||
true,
|
||||
)
|
||||
|
||||
const freelancerSession = await auth._useService<{
|
||||
ContractProgressionData: Record<
|
||||
string,
|
||||
string | number | boolean
|
||||
>
|
||||
}>(
|
||||
`https://${remoteService}.hitman.io/authentication/api/userchannel/ContractsService/GetForPlay2`,
|
||||
false,
|
||||
{
|
||||
id: "f8ec92c2-4fa2-471e-ae08-545480c746ee",
|
||||
locationId: "",
|
||||
extraGameChangerIds: [],
|
||||
difficultyLevel: 0,
|
||||
},
|
||||
)
|
||||
|
||||
userdata.Extensions.CPD[
|
||||
"f8ec92c2-4fa2-471e-ae08-545480c746ee"
|
||||
] = freelancerSession.data.ContractProgressionData
|
||||
}
|
||||
|
||||
userdata.Extensions.LastOfficialSync = new Date().toISOString()
|
||||
|
||||
writeUserData(req.query.user, req.query.gv)
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
formErrorMessage(
|
||||
res,
|
||||
`Failed to sync official data: got ${error.response?.status} ${error.response?.statusText}.`,
|
||||
)
|
||||
return
|
||||
} else {
|
||||
formErrorMessage(
|
||||
res,
|
||||
`Failed to sync official data: got ${JSON.stringify(error)}.`,
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
export { webFeaturesRouter }
|
||||
|
@ -23,3 +23,10 @@ Version 1 introduced the profile versioning system. The changes are:
|
||||
- Sniper locations now have their unlockables contained inside the location.
|
||||
- Unused properties were removed from locations in `u.Extensions.progression.Locations`.
|
||||
- `u.Extensions.progression.Unlockables` has been removed.
|
||||
|
||||
## Version 2
|
||||
|
||||
Version 2 introduced support for syncing official progress to Peacock. The changes are:
|
||||
|
||||
- `u.Extensions.LastOfficialSync` has been added which records a date and time of the last official sync.
|
||||
- `u.Extensions.progression.PlayerProfileXp.Sublocations` has been changed to an object.
|
||||
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"EvergreenLevel": 1.0
|
||||
}
|
5
static/DefaultCpdConfigs.json
Normal file
5
static/DefaultCpdConfigs.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"f8ec92c2-4fa2-471e-ae08-545480c746ee": {
|
||||
"EvergreenLevel": 1.0
|
||||
}
|
||||
}
|
@ -53,78 +53,7 @@
|
||||
},
|
||||
"PlayerProfileXP": {
|
||||
"Total": 0,
|
||||
"Sublocations": [
|
||||
{
|
||||
"Location": "LOCATION_ICA_FACILITY",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_PARIS",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COASTALTOWN",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COASTALTOWN_MOVIESET",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COASTALTOWN_NIGHT",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COASTALTOWN_EBOLA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_MARRAKECH",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_MARRAKECH_NIGHT",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_BANGKOK",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_BANGKOK_ZIKA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COLORADO",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COLORADO_RABIES",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_HOKKAIDO",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_HOKKAIDO_FLU",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
}
|
||||
],
|
||||
"Sublocations": {},
|
||||
"PreviouslySeenTotal": 0,
|
||||
"ProfileLevel": 0,
|
||||
"PreviouslySeenStaging": null
|
||||
@ -162,7 +91,8 @@
|
||||
},
|
||||
"opportunityprogression": {},
|
||||
"inventory": [],
|
||||
"friends": []
|
||||
"friends": [],
|
||||
"LastOfficialSync": null
|
||||
},
|
||||
"ETag": null,
|
||||
"Gamertag": null,
|
||||
@ -173,5 +103,5 @@
|
||||
"XboxLiveId": null,
|
||||
"PSNAccountId": null,
|
||||
"PSNOnlineId": null,
|
||||
"Version": 1
|
||||
"Version": 2
|
||||
}
|
||||
|
@ -184,168 +184,7 @@
|
||||
},
|
||||
"PlayerProfileXP": {
|
||||
"Total": 0,
|
||||
"Sublocations": [
|
||||
{
|
||||
"Location": "LOCATION_ICA_FACILITY",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_PARIS",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COASTALTOWN",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COASTALTOWN_MOVIESET",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COASTALTOWN_NIGHT",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COASTALTOWN_EBOLA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_MARRAKECH",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_MARRAKECH_NIGHT",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_BANGKOK",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_BANGKOK_ZIKA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COLORADO",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COLORADO_RABIES",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_HOKKAIDO",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_HOKKAIDO_FLU",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_NEWZEALAND",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_MIAMI",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_MIAMI_COTTONMOUTH",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_COLOMBIA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_MUMBAI",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_NORTHAMERICA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_NORTHSEA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_GREEDY_RACCOON",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_OPULENT_STINGRAY",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_AUSTRIA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_SALTY_SEAGULL",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_CAGED_FALCON",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_GOLDEN_GECKO",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_ANCESTRAL_BULLDOG",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_EDGY_FOX",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_WET_RAT",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_ELEGANT_LLAMA",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
},
|
||||
{
|
||||
"Location": "LOCATION_TRAPPED_WOLVERINE",
|
||||
"Xp": 0,
|
||||
"ActionXp": 0
|
||||
}
|
||||
],
|
||||
"Sublocations": {},
|
||||
"PreviouslySeenTotal": 0,
|
||||
"ProfileLevel": 0,
|
||||
"PreviouslySeenStaging": null
|
||||
@ -384,7 +223,8 @@
|
||||
},
|
||||
"opportunityprogression": {},
|
||||
"friends": [],
|
||||
"CPD": {}
|
||||
"CPD": {},
|
||||
"LastOfficialSync": null
|
||||
},
|
||||
"ETag": null,
|
||||
"Gamertag": null,
|
||||
@ -395,5 +235,5 @@
|
||||
"XboxLiveId": null,
|
||||
"PSNAccountId": null,
|
||||
"PSNOnlineId": null,
|
||||
"Version": 1
|
||||
"Version": 2
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"axios": "^1.6.8",
|
||||
"clsx": "^2.1.0",
|
||||
"immer": "^10.0.4",
|
||||
|
@ -114,3 +114,33 @@ body {
|
||||
.pagination-nav__item:first-child .pagination-nav__label::before {
|
||||
content: "" !important;
|
||||
}
|
||||
|
||||
/* RADIX DIALOG */
|
||||
.AlertDialogOverlay {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.AlertDialogContent {
|
||||
background: white;
|
||||
border: solid 2px black;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 90vw;
|
||||
max-width: 640px;
|
||||
max-height: 85vh;
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.AlertDialogTitle {
|
||||
margin: 0 0 1rem;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.AlertDialogDescription {
|
||||
margin-bottom: 20px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import "infima/dist/css/default/default.css"
|
||||
import "./App.css"
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom"
|
||||
import { DevToolsPage } from "./pages/DevToolsPage"
|
||||
import { TransferPage } from "./pages/TransferPage"
|
||||
import { LoadoutPage } from "./pages/LoadoutPage"
|
||||
import { RootPage } from "./pages/RootPage"
|
||||
import { Home } from "./pages/Home"
|
||||
@ -41,6 +42,10 @@ const router = createBrowserRouter([
|
||||
element: <DevToolsPage />,
|
||||
}
|
||||
: null!,
|
||||
{
|
||||
path: "ui/transfer",
|
||||
element: <TransferPage />,
|
||||
},
|
||||
{
|
||||
path: "ui/loadouts",
|
||||
element: <LoadoutPage />,
|
||||
|
@ -26,7 +26,7 @@ export interface LoadoutPreviewProps {
|
||||
function adjustCase(input: string): string {
|
||||
return input
|
||||
.split(" ")
|
||||
.map((i) => `${i[0].toUpperCase()}${i.substr(1).toLowerCase()}`)
|
||||
.map((i) => `${i[0].toUpperCase()}${i.substring(1).toLowerCase()}`)
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,6 @@ export function LoadoutsForGameVersion({
|
||||
}
|
||||
|
||||
// note: putting these 2 consts together make intellij and prettier conflict each other
|
||||
// @ts-expect-error fake news
|
||||
const theGameVer = data[`h${gameVersion}`] as LoadoutsGameVersion
|
||||
|
||||
const loadoutsForVersion: Loadout[] = theGameVer.loadouts
|
||||
@ -62,7 +61,6 @@ export function LoadoutsForGameVersion({
|
||||
<nav className="pagination-nav">
|
||||
{loadoutsForVersion.map((loadout) => {
|
||||
const isActive =
|
||||
// @ts-expect-error also fake news
|
||||
data[`h${gameVersion}`].selected === loadout.id
|
||||
|
||||
return (
|
||||
|
90
webui/src/TransferAlert.tsx
Normal file
90
webui/src/TransferAlert.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* The Peacock Project - a HITMAN server replacement.
|
||||
* Copyright (C) 2021-2024 The Peacock Project Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from "react"
|
||||
import * as AlertDialog from "@radix-ui/react-alert-dialog"
|
||||
|
||||
type TransferAlertProps = {
|
||||
trigger: React.ReactElement
|
||||
doSync: () => void
|
||||
}
|
||||
|
||||
function Speech() {
|
||||
return (
|
||||
<>
|
||||
This will <b>delete existing progress on this Peacock profile</b>.
|
||||
<br />
|
||||
Your progress will be replaced with whatever you have completed on
|
||||
the official servers.
|
||||
<br />
|
||||
<br />
|
||||
<b>THIS CANNOT BE UNDONE. Are you SURE you want to continue?</b>
|
||||
<br />
|
||||
<br />
|
||||
<b>
|
||||
You may want to make a copy of your <code>userdata</code> folder
|
||||
first.
|
||||
</b>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function TransferAlert(props: TransferAlertProps) {
|
||||
return (
|
||||
<AlertDialog.Root>
|
||||
<AlertDialog.Trigger asChild>{props.trigger}</AlertDialog.Trigger>
|
||||
|
||||
<AlertDialog.Portal>
|
||||
<AlertDialog.Overlay className="AlertDialogOverlay" />
|
||||
|
||||
<AlertDialog.Content className="AlertDialogContent">
|
||||
<AlertDialog.Title className="AlertDialogTitle">
|
||||
<b>Danger!</b> Are you sure you want to continue?
|
||||
</AlertDialog.Title>
|
||||
|
||||
<AlertDialog.Description className="AlertDialogDescription">
|
||||
<Speech />
|
||||
</AlertDialog.Description>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 25,
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<AlertDialog.Cancel asChild>
|
||||
<button className="button button--primary">
|
||||
Cancel
|
||||
</button>
|
||||
</AlertDialog.Cancel>
|
||||
|
||||
<AlertDialog.Action asChild>
|
||||
<button
|
||||
className="button button--danger"
|
||||
onClick={props.doSync}
|
||||
>
|
||||
Yes, sync now
|
||||
</button>
|
||||
</AlertDialog.Action>
|
||||
</div>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Portal>
|
||||
</AlertDialog.Root>
|
||||
)
|
||||
}
|
114
webui/src/TransferDetails.tsx
Normal file
114
webui/src/TransferDetails.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* The Peacock Project - a HITMAN server replacement.
|
||||
* Copyright (C) 2021-2024 The Peacock Project Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from "react"
|
||||
import { axiosClient, BasicUser } from "./utils"
|
||||
import { TransferAlert } from "./TransferAlert"
|
||||
|
||||
type TransferDetailsProps = {
|
||||
user: BasicUser
|
||||
gv: "h1" | "h2" | "h3"
|
||||
}
|
||||
|
||||
function InProgressAlert() {
|
||||
return (
|
||||
<div className="alert alert--primary" role="alert">
|
||||
Performing the transfer, this may take a bit...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TransferSuccessfulAlert() {
|
||||
return (
|
||||
<div className="alert alert--success" role="alert">
|
||||
Transfer completed successfully!
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TransferErrorAlert({ error }: { error: string }) {
|
||||
return (
|
||||
<div className="alert alert--danger" role="alert">
|
||||
Failed to perform transfer. {error}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function TransferDetails({ user, gv }: TransferDetailsProps) {
|
||||
const [fetching, setFetching] = React.useState<boolean>(false)
|
||||
const [error, setError] = React.useState<string | null>(null)
|
||||
const [lastSucceeded, setLastSucceeded] = React.useState<boolean | null>(
|
||||
null,
|
||||
)
|
||||
|
||||
const doSync = React.useCallback(async () => {
|
||||
try {
|
||||
const resp = await axiosClient.post(
|
||||
"/_wf/sync-progress",
|
||||
{},
|
||||
{
|
||||
params: {
|
||||
gv,
|
||||
user: user.id,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (resp.data.success) {
|
||||
setFetching(false)
|
||||
setLastSucceeded(true)
|
||||
setError(null)
|
||||
} else {
|
||||
setFetching(false)
|
||||
setLastSucceeded(false)
|
||||
setError(resp.data.error)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
setFetching(false)
|
||||
setError("Failed to perform internal request to begin transfer.")
|
||||
setLastSucceeded(false)
|
||||
}
|
||||
}, [gv, user.id])
|
||||
|
||||
return (
|
||||
<div className={"margin-top"}>
|
||||
<p>
|
||||
Selected profile: {user.name} ({user.platform})
|
||||
</p>
|
||||
<p>Last official server sync: {user.lastOfficialSync || "never"}</p>
|
||||
|
||||
{fetching ? <InProgressAlert /> : null}
|
||||
|
||||
{lastSucceeded === true ? <TransferSuccessfulAlert /> : null}
|
||||
|
||||
{lastSucceeded === false && error ? (
|
||||
<TransferErrorAlert error={error} />
|
||||
) : null}
|
||||
|
||||
<TransferAlert
|
||||
trigger={
|
||||
<button className={"button button--success"}>
|
||||
Sync Now
|
||||
</button>
|
||||
}
|
||||
doSync={doSync}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -17,10 +17,10 @@
|
||||
*/
|
||||
|
||||
import * as React from "react"
|
||||
import { IUser } from "../utils"
|
||||
import { BasicUser } from "../utils"
|
||||
|
||||
export interface SelectUserProps {
|
||||
users: IUser[]
|
||||
users: BasicUser[]
|
||||
setUser: React.Dispatch<React.SetStateAction<string | undefined>>
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
import * as React from "react"
|
||||
import { Hero } from "../components/Hero"
|
||||
import useSWR from "swr"
|
||||
import { baseURL, fetcher, IUser } from "../utils"
|
||||
import { baseURL, BasicUser, fetcher } from "../utils"
|
||||
import { SelectUser } from "../components/SelectUser"
|
||||
import { GameVersionTabs } from "../components/GameVersionTabs"
|
||||
import { EscalationLevelPicker } from "../EscalationLevelPicker"
|
||||
@ -60,7 +60,7 @@ export function EscalationLevelPage() {
|
||||
const isReadyToSelectUser = Boolean(
|
||||
user === undefined &&
|
||||
userData &&
|
||||
(userData as { error: string } & IUser[])?.error !== "bad gv",
|
||||
(userData as { error: string } & BasicUser[])?.error !== "bad gv",
|
||||
)
|
||||
|
||||
function getStatus(): string {
|
||||
@ -93,7 +93,10 @@ export function EscalationLevelPage() {
|
||||
/>
|
||||
)}
|
||||
{isReadyToSelectUser && (
|
||||
<SelectUser users={userData as IUser[]} setUser={setUser} />
|
||||
<SelectUser
|
||||
users={userData as BasicUser[]}
|
||||
setUser={setUser}
|
||||
/>
|
||||
)}
|
||||
{Boolean(codenameData) &&
|
||||
gameVersion !== 0 &&
|
||||
|
96
webui/src/pages/TransferPage.tsx
Normal file
96
webui/src/pages/TransferPage.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* The Peacock Project - a HITMAN server replacement.
|
||||
* Copyright (C) 2021-2024 The Peacock Project Team
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Hero } from "../components/Hero"
|
||||
import * as React from "react"
|
||||
import useSWR from "swr"
|
||||
import { baseURL, BasicUser, fetcher } from "../utils"
|
||||
import { GameVersionTabs } from "../components/GameVersionTabs"
|
||||
import { SelectUser } from "../components/SelectUser"
|
||||
import { TransferDetails } from "../TransferDetails"
|
||||
|
||||
export function TransferPage() {
|
||||
const [user, setUser] = React.useState<string | undefined>(undefined)
|
||||
const [gameVersion, setGameVersion] = React.useState<number>(0)
|
||||
const { data: userData, error: userFetchError } = useSWR(
|
||||
`${baseURL}/_wf/local-users?gv=h${gameVersion}`,
|
||||
fetcher,
|
||||
)
|
||||
|
||||
if (userFetchError) {
|
||||
console.error(userFetchError)
|
||||
}
|
||||
|
||||
const isReadyToSelectUser = Boolean(
|
||||
gameVersion !== 0 &&
|
||||
user === undefined &&
|
||||
userData &&
|
||||
(userData as { error: string } & BasicUser[])?.error !== "bad gv",
|
||||
)
|
||||
|
||||
function getStatus(): string {
|
||||
if (gameVersion === 0) {
|
||||
return "Select your game version."
|
||||
}
|
||||
|
||||
if (isReadyToSelectUser) {
|
||||
return "Select target user profile."
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<Hero
|
||||
title="Official Server Transfer Tool"
|
||||
subtext={getStatus()}
|
||||
/>
|
||||
</header>
|
||||
|
||||
<main className="container">
|
||||
{gameVersion === 0 && (
|
||||
<GameVersionTabs
|
||||
gameVersion={gameVersion}
|
||||
setGameVersion={setGameVersion}
|
||||
/>
|
||||
)}
|
||||
{isReadyToSelectUser && (
|
||||
<SelectUser
|
||||
users={userData as BasicUser[]}
|
||||
setUser={setUser}
|
||||
/>
|
||||
)}
|
||||
{gameVersion !== 0 &&
|
||||
!isReadyToSelectUser &&
|
||||
user &&
|
||||
userData && (
|
||||
<TransferDetails
|
||||
gv={`h${gameVersion as 1 | 2 | 3}`}
|
||||
user={
|
||||
(userData as BasicUser[]).find(
|
||||
(u) => u.id === user,
|
||||
)!
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
@ -55,11 +55,12 @@ export interface Loadout {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IUser {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly platform: string
|
||||
}
|
||||
export type BasicUser = Readonly<{
|
||||
id: string
|
||||
name: string
|
||||
platform: string
|
||||
lastOfficialSync: string | null
|
||||
}>
|
||||
|
||||
export interface LoadoutsGameVersion {
|
||||
selected: string
|
||||
|
449
yarn.lock
449
yarn.lock
@ -44,6 +44,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.13.10":
|
||||
version: 7.24.4
|
||||
resolution: "@babel/runtime@npm:7.24.4"
|
||||
dependencies:
|
||||
regenerator-runtime: "npm:^0.14.0"
|
||||
checksum: 10/8ec8ce2c145bc7e31dd39ab66df124f357f65c11489aefacb30f431bae913b9aaa66aa5efe5321ea2bf8878af3fcee338c87e7599519a952e3a6f83aa1b03308
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0":
|
||||
version: 1.6.0
|
||||
resolution: "@colors/colors@npm:1.6.0"
|
||||
@ -570,6 +579,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@peacockproject/web-ui@workspace:webui"
|
||||
dependencies:
|
||||
"@radix-ui/react-alert-dialog": "npm:^1.0.5"
|
||||
"@types/react": "npm:^18.2.74"
|
||||
"@types/react-dom": "npm:^18.2.23"
|
||||
axios: "npm:^1.6.8"
|
||||
@ -606,6 +616,319 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/primitive@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/primitive@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
checksum: 10/2b93e161d3fdabe9a64919def7fa3ceaecf2848341e9211520c401181c9eaebb8451c630b066fad2256e5c639c95edc41de0ba59c40eff37e799918d019822d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-alert-dialog@npm:^1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "@radix-ui/react-alert-dialog@npm:1.0.5"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/primitive": "npm:1.0.1"
|
||||
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||
"@radix-ui/react-context": "npm:1.0.1"
|
||||
"@radix-ui/react-dialog": "npm:1.0.5"
|
||||
"@radix-ui/react-primitive": "npm:1.0.3"
|
||||
"@radix-ui/react-slot": "npm:1.0.2"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 10/966eeef94056caa4105278686018bc8ec5ec10584191d9e878944b1785ae88a355cbff0b754af4c3878bd97af8d954583b7a2e4819b928ccf6839cd2962e8a09
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-compose-refs@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-compose-refs@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/2b9a613b6db5bff8865588b6bf4065f73021b3d16c0a90b2d4c23deceeb63612f1f15de188227ebdc5f88222cab031be617a9dd025874c0487b303be3e5cc2a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-context@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-context@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/a02187a3bae3a0f1be5fab5ad19c1ef06ceff1028d957e4d9994f0186f594a9c3d93ee34bacb86d1fa8eb274493362944398e1c17054d12cb3b75384f9ae564b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-dialog@npm:1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "@radix-ui/react-dialog@npm:1.0.5"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/primitive": "npm:1.0.1"
|
||||
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||
"@radix-ui/react-context": "npm:1.0.1"
|
||||
"@radix-ui/react-dismissable-layer": "npm:1.0.5"
|
||||
"@radix-ui/react-focus-guards": "npm:1.0.1"
|
||||
"@radix-ui/react-focus-scope": "npm:1.0.4"
|
||||
"@radix-ui/react-id": "npm:1.0.1"
|
||||
"@radix-ui/react-portal": "npm:1.0.4"
|
||||
"@radix-ui/react-presence": "npm:1.0.1"
|
||||
"@radix-ui/react-primitive": "npm:1.0.3"
|
||||
"@radix-ui/react-slot": "npm:1.0.2"
|
||||
"@radix-ui/react-use-controllable-state": "npm:1.0.1"
|
||||
aria-hidden: "npm:^1.1.1"
|
||||
react-remove-scroll: "npm:2.5.5"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 10/adbd7301586db712616a0f8dd54a25e7544853cbf61b5d6e279215d479f57ac35157847ee424d54a7e707969a926ca0a7c28934400c9ac224bd0c7cc19229aca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-dismissable-layer@npm:1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "@radix-ui/react-dismissable-layer@npm:1.0.5"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/primitive": "npm:1.0.1"
|
||||
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||
"@radix-ui/react-primitive": "npm:1.0.3"
|
||||
"@radix-ui/react-use-callback-ref": "npm:1.0.1"
|
||||
"@radix-ui/react-use-escape-keydown": "npm:1.0.3"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 10/f1626d69bb50ec226032bb7d8c5abaaf7359c2d7660309b0ed3daaedd91f30717573aac1a1cb82d589b7f915cf464b95a12da0a3b91b6acfefb6fbbc62b992de
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-focus-guards@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-focus-guards@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/1f8ca8f83b884b3612788d0742f3f054e327856d90a39841a47897dbed95e114ee512362ae314177de226d05310047cabbf66b686ae86ad1b65b6b295be24ef7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-focus-scope@npm:1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "@radix-ui/react-focus-scope@npm:1.0.4"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||
"@radix-ui/react-primitive": "npm:1.0.3"
|
||||
"@radix-ui/react-use-callback-ref": "npm:1.0.1"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 10/3590e74c6b682737c7ac4bf8db41b3df7b09a0320f3836c619e487df9915451e5dafade9923a74383a7366c59e9436f5fff4301d70c0d15928e0e16b36e58bc9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-id@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-id@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect": "npm:1.0.1"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/446a453d799cc790dd2a1583ff8328da88271bff64530b5a17c102fa7fb35eece3cf8985359d416f65e330cd81aa7b8fe984ea125fc4f4eaf4b3801d698e49fe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-portal@npm:1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "@radix-ui/react-portal@npm:1.0.4"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/react-primitive": "npm:1.0.3"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 10/c4cf35e2f26a89703189d0eef3ceeeb706ae0832e98e558730a5e929ca7c72c7cb510413a24eca94c7732f8d659a1e81942bec7b90540cb73ce9e4885d040b64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-presence@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-presence@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||
"@radix-ui/react-use-layout-effect": "npm:1.0.1"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 10/406f0b5a54ea4e7881e15bddc3863234bb14bf3abd4a6e56ea57c6df6f9265a9ad5cfa158e3a98614f0dcbbb7c5f537e1f7158346e57cc3f29b522d62cf28823
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-primitive@npm:1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "@radix-ui/react-primitive@npm:1.0.3"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/react-slot": "npm:1.0.2"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
"@types/react-dom":
|
||||
optional: true
|
||||
checksum: 10/bedb934ac07c710dc5550a7bfc7065d47e099d958cde1d37e4b1947ae5451f1b7e6f8ff5965e242578bf2c619065e6038c3a3aa779e5eafa7da3e3dbc685799f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-slot@npm:1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@radix-ui/react-slot@npm:1.0.2"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/react-compose-refs": "npm:1.0.1"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/734866561e991438fbcf22af06e56b272ed6ee8f7b536489ee3bf2f736f8b53bf6bc14ebde94834aa0aceda854d018a0ce20bb171defffbaed1f566006cbb887
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-use-callback-ref@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-use-callback-ref@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/b9fd39911c3644bbda14a84e4fca080682bef84212b8d8931fcaa2d2814465de242c4cfd8d7afb3020646bead9c5e539d478cea0a7031bee8a8a3bb164f3bc4c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-use-controllable-state@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-use-controllable-state@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref": "npm:1.0.1"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/dee2be1937d293c3a492cb6d279fc11495a8f19dc595cdbfe24b434e917302f9ac91db24e8cc5af9a065f3f209c3423115b5442e65a5be9fd1e9091338972be9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-use-escape-keydown@npm:1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "@radix-ui/react-use-escape-keydown@npm:1.0.3"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref": "npm:1.0.1"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/c6ed0d9ce780f67f924980eb305af1f6cce2a8acbaf043a58abe0aa3cc551d9aa76ccee14531df89bbee302ead7ecc7fce330886f82d4672c5eda52f357ef9b8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/react-use-layout-effect@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/react-use-layout-effect@npm:1.0.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.13.10"
|
||||
peerDependencies:
|
||||
"@types/react": "*"
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/bed9c7e8de243a5ec3b93bb6a5860950b0dba359b6680c84d57c7a655e123dec9b5891c5dfe81ab970652e7779fe2ad102a23177c7896dde95f7340817d47ae5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rdil/parallel-prettier@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@rdil/parallel-prettier@npm:3.0.0"
|
||||
@ -1328,6 +1651,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"aria-hidden@npm:^1.1.1":
|
||||
version: 1.2.4
|
||||
resolution: "aria-hidden@npm:1.2.4"
|
||||
dependencies:
|
||||
tslib: "npm:^2.0.0"
|
||||
checksum: 10/df4bc15423aaaba3729a7d40abcbf6d3fffa5b8fd5eb33d3ac8b7da0110c47552fca60d97f2e1edfbb68a27cae1da499f1c3896966efb3e26aac4e3b57e3cc8b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"array-find-index@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "array-find-index@npm:1.0.2"
|
||||
@ -1858,6 +2190,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"detect-node-es@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "detect-node-es@npm:1.1.0"
|
||||
checksum: 10/e46307d7264644975b71c104b9f028ed1d3d34b83a15b8a22373640ce5ea630e5640b1078b8ea15f202b54641da71e4aa7597093bd4b91f113db520a26a37449
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"diff-sequences@npm:^29.6.3":
|
||||
version: 29.6.3
|
||||
resolution: "diff-sequences@npm:29.6.3"
|
||||
@ -2603,6 +2942,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-nonce@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "get-nonce@npm:1.0.1"
|
||||
checksum: 10/ad5104871d114a694ecc506a2d406e2331beccb961fe1e110dc25556b38bcdbf399a823a8a375976cd8889668156a9561e12ebe3fa6a4c6ba169c8466c2ff868
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-stream@npm:^8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "get-stream@npm:8.0.1"
|
||||
@ -2907,6 +3253,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"invariant@npm:^2.2.4":
|
||||
version: 2.2.4
|
||||
resolution: "invariant@npm:2.2.4"
|
||||
dependencies:
|
||||
loose-envify: "npm:^1.0.0"
|
||||
checksum: 10/cc3182d793aad82a8d1f0af697b462939cb46066ec48bbf1707c150ad5fad6406137e91a262022c269702e01621f35ef60269f6c0d7fd178487959809acdfb14
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ip@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "ip@npm:2.0.0"
|
||||
@ -3349,7 +3704,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"loose-envify@npm:^1.1.0":
|
||||
"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0":
|
||||
version: 1.4.0
|
||||
resolution: "loose-envify@npm:1.4.0"
|
||||
dependencies:
|
||||
@ -4235,6 +4590,41 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-remove-scroll-bar@npm:^2.3.3":
|
||||
version: 2.3.6
|
||||
resolution: "react-remove-scroll-bar@npm:2.3.6"
|
||||
dependencies:
|
||||
react-style-singleton: "npm:^2.2.1"
|
||||
tslib: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/5ab8eda61d5b10825447d11e9c824486c929351a471457c22452caa19b6898e18c3af6a46c3fa68010c713baed1eb9956106d068b4a1058bdcf97a1a9bbed734
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-remove-scroll@npm:2.5.5":
|
||||
version: 2.5.5
|
||||
resolution: "react-remove-scroll@npm:2.5.5"
|
||||
dependencies:
|
||||
react-remove-scroll-bar: "npm:^2.3.3"
|
||||
react-style-singleton: "npm:^2.2.1"
|
||||
tslib: "npm:^2.1.0"
|
||||
use-callback-ref: "npm:^1.3.0"
|
||||
use-sidecar: "npm:^1.1.2"
|
||||
peerDependencies:
|
||||
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/f0646ac384ce3852d1f41e30a9f9e251b11cf3b430d1d114c937c8fa7f90a895c06378d0d6b6ff0b2d00cbccf15e845921944fd6074ae67a0fb347a718106d88
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-router-dom@npm:^6.22.3":
|
||||
version: 6.22.3
|
||||
resolution: "react-router-dom@npm:6.22.3"
|
||||
@ -4259,6 +4649,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-style-singleton@npm:^2.2.1":
|
||||
version: 2.2.1
|
||||
resolution: "react-style-singleton@npm:2.2.1"
|
||||
dependencies:
|
||||
get-nonce: "npm:^1.0.0"
|
||||
invariant: "npm:^2.2.4"
|
||||
tslib: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/80c58fd6aac3594e351e2e7b048d8a5b09508adb21031a38b3c40911fe58295572eddc640d4b20a7be364842c8ed1120fe30097e22ea055316b375b88d4ff02a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:^18.2.0":
|
||||
version: 18.2.0
|
||||
resolution: "react@npm:18.2.0"
|
||||
@ -4294,6 +4701,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"regenerator-runtime@npm:^0.14.0":
|
||||
version: 0.14.1
|
||||
resolution: "regenerator-runtime@npm:0.14.1"
|
||||
checksum: 10/5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"remove-trailing-separator@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "remove-trailing-separator@npm:1.1.0"
|
||||
@ -5068,7 +5482,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.2":
|
||||
"tslib@npm:^2.0.0, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.2":
|
||||
version: 2.6.2
|
||||
resolution: "tslib@npm:2.6.2"
|
||||
checksum: 10/bd26c22d36736513980091a1e356378e8b662ded04204453d353a7f34a4c21ed0afc59b5f90719d4ba756e581a162ecbf93118dc9c6be5acf70aa309188166ca
|
||||
@ -5200,6 +5614,37 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-callback-ref@npm:^1.3.0":
|
||||
version: 1.3.2
|
||||
resolution: "use-callback-ref@npm:1.3.2"
|
||||
dependencies:
|
||||
tslib: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/3be76eae71b52ab233b4fde974eddeff72e67e6723100a0c0297df4b0d60daabedfa706ffb314d0a52645f2c1235e50fdbd53d99f374eb5df68c74d412e98a9b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-sidecar@npm:^1.1.2":
|
||||
version: 1.1.2
|
||||
resolution: "use-sidecar@npm:1.1.2"
|
||||
dependencies:
|
||||
detect-node-es: "npm:^1.1.0"
|
||||
tslib: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
"@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
checksum: 10/ec99e31aefeb880f6dc4d02cb19a01d123364954f857811470ece32872f70d6c3eadbe4d073770706a9b7db6136f2a9fbf1bb803e07fbb21e936a47479281690
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-sync-external-store@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "use-sync-external-store@npm:1.2.0"
|
||||
|
Loading…
Reference in New Issue
Block a user