Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

### Added
- Support for `ccmod.json` mods [#323](https://github.com/CCDirectLink/crosscode-map-editor/issues/323)

### Changed
- The settings menu now display the proper mod name [#324](https://github.com/CCDirectLink/crosscode-map-editor/pull/324)

## [1.6.2] 2024-07-19
### Changed
- Updated tilesets.json to include all available tilesets for Height Map generation
Expand Down
47 changes: 40 additions & 7 deletions common/src/controllers/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { fsPromise, pathPromise } from '../require.js';
import { saveFile as save } from './saveFile.js';

const mods: string[] = [];
let packagesCache: Map<string, { folderName: string, ccmodDependencies?: Map<string, string> }>;
let packagesCache: Record<string, { folderName: string, displayName: string, ccmodDependencies?: Map<string, string> }>;

async function listAllFiles(dir: string, filelist: string[], ending: string, root?: string): Promise<string[]> {
if (root === undefined) {
Expand Down Expand Up @@ -79,8 +79,8 @@ async function searchSubFolder(dir: string, file: string): Promise<string[]> {
return result;
}

function selectMod(name: string, packages: Map<string, { folderName: string, ccmodDependencies?: Map<string, string> }>, result: string[]) {
const pkg = packages.get(name);
function selectMod(name: string, packages: Record<string, { folderName: string, ccmodDependencies?: Map<string, string> }>, result: string[]) {
const pkg = packages[name];
if (!pkg) {
return;
}
Expand Down Expand Up @@ -131,23 +131,54 @@ async function readMods(dir: string) {

const modFolder = path.join(dir, 'mods/');
const files = await searchSubFolder(modFolder, 'package.json');
const filesCCMod = await searchSubFolder(modFolder, 'ccmod.json');

const ccmodFolderNames = new Set(filesCCMod.map(file => path.basename(path.dirname(file))));

const promises: Promise<[string, Buffer]>[] = [];
for (const file of files) {
const folderName = path.basename(path.dirname(file));
// Skip mods that have a ccmod.json file
if (ccmodFolderNames.has(folderName)) {
continue;
}
promises.push((async (): Promise<[string, Buffer]> => [path.basename(path.dirname(file)), await fs.promises.readFile(file)])());
}
const rawPackages = await Promise.all(promises);
const packages = new Map<string, { folderName: string, ccmodDependencies?: Map<string, string> }>();
const packages: Record<string, { folderName: string, displayName: string, ccmodDependencies?: Map<string, string> }> = {};

for (const [name, pkg] of rawPackages) {
try {
const parsed = JSON.parse(pkg as unknown as string);
parsed.folderName = name;
packages.set(parsed.name, parsed);
packages[parsed.name] = {
folderName: name,
displayName: parsed.displayName ?? parsed.ccmodHumanName ?? parsed.name,
ccmodDependencies: parsed.ccmodDependencies ?? parsed.dependencies ?? {},
};
} catch (err) {
console.error('Invalid json data in package.json of mod: ' + name, err);
}
}

const promisesCCMod: Promise<[string, Buffer]>[] = [];
for (const file of filesCCMod) {
promisesCCMod.push((async (): Promise<[string, Buffer]> => [path.basename(path.dirname(file)), await fs.promises.readFile(file)])());
}
const rawCCMods = await Promise.all(promisesCCMod);

for (const [name, pkg] of rawCCMods) {
try {
const parsed = JSON.parse(pkg as unknown as string);
packages[parsed.id] = {
folderName: name,
displayName: parsed.title?.['en_US'] ?? parsed.title ?? parsed.id,
ccmodDependencies: parsed.ccmodDependencies ?? parsed.dependencies ?? {},
};
} catch (err) {
console.error('Invalid json data in ccmod.json of mod: ' + name, err);
}
}

packagesCache = packages;
return packages;
}
Expand Down Expand Up @@ -214,7 +245,9 @@ export async function getAllFilesInFolder(dir: string, folder: string, extension

export async function getAllMods(dir: string) {
const packages = await readMods(dir);
return Array.from(packages.keys()).sort();
return Object.entries(packages)
.map(([id, pkg]) => ({ id, displayName: pkg.displayName as string }))
.sort((a, b) => a.displayName.localeCompare(b.displayName));
}

export async function selectedMod(dir: string, modName: string) {
Expand Down
6 changes: 4 additions & 2 deletions webapp/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { LayersComponent } from './components/layers/layers.component';
import { PhaserComponent } from './components/phaser/phaser.component';
import { SidenavComponent } from './components/sidenav/sidenav.component';
import { SplitPaneComponent } from './components/split-pane/split-pane.component';
import { GridMenuComponent } from './components/toolbar/grid-menu/grid-menu.component';
import { ToolbarDividerComponent } from './components/toolbar/toolbar-divider/toolbar-divider.component';
import { ToolbarComponent } from './components/toolbar/toolbar.component';
import { BooleanWidgetComponent } from './components/widgets/boolean-widget/boolean-widget.component';
import { CharacterWidgetComponent } from './components/widgets/character-widget/character-widget.component';
Expand Down Expand Up @@ -61,15 +63,14 @@ import { AutocompletedTextboxComponent } from './components/widgets/string-widge
import { StringWidgetComponent } from './components/widgets/string-widget/string-widget.component';
import { Vec2WidgetComponent } from './components/widgets/vec2-widget/vec2-widget.component';
import { AutofocusDirective } from './directives/autofocus.directive';
import { ColoredTextDirective } from './directives/colored-text.directive';
import { HighlightDirective } from './directives/highlight.directive';
import { HostDirective } from './directives/host.directive';
import { ModalDirective } from './directives/modal.directive';
import { ResizedDirective } from './directives/resized.directive';
import { MaterialModule } from './external-modules/material.module';
import { CombinedTooltipPipe } from './pipes/combined-tooltip.pipe';
import { KeepHtmlPipe } from './pipes/keep-html.pipe';
import { ToolbarDividerComponent } from './components/toolbar/toolbar-divider/toolbar-divider.component';
import { GridMenuComponent } from './components/toolbar/grid-menu/grid-menu.component';

const WIDGETS = [
StringWidgetComponent,
Expand Down Expand Up @@ -146,6 +147,7 @@ const WIDGETS = [
ImageSelectCardComponent,
ImageSelectListComponent,
HighlightDirective,
ColoredTextDirective,
AutofocusDirective,
CombinedTooltipPipe,
InputWithButtonComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<mat-label>Mod</mat-label>
<mat-select [(value)]="mod" (selectionChange)="modSelectEvent($event.value)">
<mat-option>None</mat-option>
<mat-option *ngFor="let mod of mods" [value]="mod">{{ mod }}</mat-option>
<mat-option *ngFor="let mod of mods" [value]="mod.id" [appColoredText]="mod.displayName"></mat-option>
</mat-select>
<mat-hint>Maps will be stored and loaded from the selected mod</mat-hint>
</mat-form-field>
Expand Down
38 changes: 19 additions & 19 deletions webapp/src/app/components/dialogs/settings/settings.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,37 @@ import { Globals } from '../../../services/globals';
import { HttpClientService } from '../../../services/http-client.service';
import { AppSettings, SettingsService } from '../../../services/settings.service';
import { SharedService } from '../../../services/shared-service';
import { OverlayRefControl } from '../overlay/overlay-ref-control';
import { PropListCard } from '../../widgets/shared/image-select-overlay/image-select-card/image-select-card.component';
import { OverlayRefControl } from '../overlay/overlay-ref-control';

@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent implements OnInit {

isElectron = Globals.isElectron;
folderFormControl = new FormControl();
icon = 'help_outline';
iconCss = 'icon-undefined';
mods: string[] = [];
mods: { id: string, displayName: string }[] = [];
mod = '';
settings: AppSettings;
isIncludeVanillaMapsDisabled: boolean;

cardLight: PropListCard = {
name: 'Light',
imgSrc: 'assets/selection-light.png',
};

cardDark: PropListCard = {
name: 'Dark',
imgSrc: 'assets/selection-dark.png',
};

private readonly sharedService: SharedService;

constructor(
private ref: OverlayRefControl,
private electron: ElectronService,
Expand All @@ -52,27 +52,27 @@ export class SettingsComponent implements OnInit {
} else {
this.sharedService = browser;
}

http.getMods().subscribe(mods => this.mods = mods);
this.mod = this.sharedService.getSelectedMod();
this.isIncludeVanillaMapsDisabled = !this.mod;
this.settings = JSON.parse(JSON.stringify(this.settingsService.getSettings()));
}

ngOnInit() {
if (this.isElectron) {
this.folderFormControl.setValue(this.electron.getAssetsPath());
this.folderFormControl.valueChanges.subscribe(() => this.resetIcon());
}

this.check();
}

private resetIcon() {
this.icon = 'help_outline';
this.iconCss = 'icon-undefined';
}

private setIcon(valid: boolean) {
if (valid) {
this.icon = 'check';
Expand All @@ -82,14 +82,14 @@ export class SettingsComponent implements OnInit {
this.iconCss = 'icon-invalid';
}
}

select() {
const path = this.electron.selectCcFolder();
if (path) {
this.folderFormControl.setValue(path);
}
}

check() {
const valid = this.electron.checkAssetsPath(this.folderFormControl.value);
this.setIcon(valid);
Expand All @@ -101,11 +101,11 @@ export class SettingsComponent implements OnInit {
});
}
}

modSelectEvent(selectedMod: string) {
this.isIncludeVanillaMapsDisabled = !selectedMod;
}

save() {
if (this.isElectron) {
this.electron.saveAssetsPath(this.folderFormControl.value);
Expand All @@ -116,12 +116,12 @@ export class SettingsComponent implements OnInit {
const ref = this.snackBar.open('Changing the path requires to restart the editor', 'Restart', {
duration: 6000
});

ref.onAction().subscribe(() => this.sharedService.relaunch());
}

close() {
this.ref.close();
}

}
Loading