Skip to content

Commit

Permalink
Merge pull request #20 from dragonrealms-phoenix/grid-layout
Browse files Browse the repository at this point in the history
New Grid System, Sidebars, PubSub, and much more
  • Loading branch information
KatoakDR authored Sep 24, 2024
2 parents 0c6c891 + c37cdff commit 14e4aa3
Show file tree
Hide file tree
Showing 96 changed files with 7,511 additions and 4,311 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
# next.js
/electron/build
/electron/renderer/.next
/electron/renderer/public
/electron/renderer/out
/electron/renderer/public/themes

# source maps
*.js.map
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ Ignite your [DragonRealms](http://play.net/dr) journey with Phoenix, a cross-pla

Phoenix is a community-supported frontend for Simutronic's text-based multiplayer game DragonRealms.

![phoenix-logo](./resources/phoenix.png)

🚧 Currently in development, stay tuned!

![phoenix-logo](./resources/phoenix.png)

## Developing Phoenix

1. Install [nodejs](https://nodejs.org/en/download).
Expand Down
3 changes: 3 additions & 0 deletions electron/common/__mocks__/electron-log.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ export const clearElectronLoggerMockProps = (
console: {},
file: {},
};
// This is a private property defined by `initializeLogging` method.
// Reset it so that the logger can be re-initialized in each test.
(logger as any).__phoenix_initialized = false;
};

export { mockElectronLogMain, mockElectronLogRenderer };
10 changes: 10 additions & 0 deletions electron/common/account/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface Account {
accountName: string;
accountPassword: string;
}

export interface Character {
accountName: string;
characterName: string;
gameCode: string;
}
2 changes: 1 addition & 1 deletion electron/common/data/__tests__/urls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('URLs', () => {
});

it('PLAY_NET_URL', () => {
expect(urls.PLAY_NET_URL).toBe('http://play.net/dr');
expect(urls.PLAY_NET_URL).toBe('https://www.play.net/dr');
});

it('ELANTHIPEDIA_URL', () => {
Expand Down
2 changes: 1 addition & 1 deletion electron/common/data/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export const PHOENIX_LICENSE_URL = `${GITHUB_BASE_URL}/blob/main/LICENSE.md`;
export const PHOENIX_PRIVACY_URL = `${GITHUB_BASE_URL}/blob/main/PRIVACY.md`;
export const PHOENIX_SECURITY_URL = `${GITHUB_BASE_URL}/blob/main/SECURITY.md`;

export const PLAY_NET_URL = `http://play.net/dr`;
export const PLAY_NET_URL = `https://www.play.net/dr`;
export const ELANTHIPEDIA_URL = `https://elanthipedia.play.net`;
58 changes: 58 additions & 0 deletions electron/common/game/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,61 @@
/**
* Simutronics has multiple games and instances per game.
* Only interested in DragonRealms, though.
*/
export enum GameCode {
PRIME = 'DR',
PLATINUM = 'DRX',
FALLEN = 'DRF',
TEST = 'DRT',
DEVELOPMENT = 'DRD',
}

export interface GameCodeMeta {
/**
* The game code.
* Example: 'DR' or 'DRX'.
*/
code: GameCode;
/**
* The code name.
* Example: 'Prime' or 'Platinum'.
*/
name: string;
/**
* The game name.
* Example: 'DragonRealms'.
*/
game: string;
}

export const GameCodeMetaMap: Record<GameCode, GameCodeMeta> = {
DR: {
code: GameCode.PRIME,
name: 'Prime',
game: 'DragonRealms',
},
DRX: {
code: GameCode.PLATINUM,
name: 'Platinum',
game: 'DragonRealms',
},
DRF: {
code: GameCode.FALLEN,
name: 'Fallen',
game: 'DragonRealms',
},
DRT: {
code: GameCode.TEST,
name: 'Test',
game: 'DragonRealms',
},
DRD: {
code: GameCode.DEVELOPMENT,
name: 'Development',
game: 'DragonRealms',
},
};

/**
* Events emitted by the game parser of data received from the game socket.
*/
Expand Down
15 changes: 4 additions & 11 deletions electron/common/logger/create-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,19 @@ import type {
LogFunctions as ElectronLogFunctions,
Logger as ElectronLogger,
} from 'electron-log';
import { includesIgnoreCase } from '../string/includes-ignore-case.js';
import { initializeLogging } from './initialize-logging.js';
import type { LogFunction, Logger } from './types.js';
import { LogLevel } from './types.js';

// TODO: is caching these necessary?
// Cache loggers for the same scope.
const scopedLoggers: Record<string, ElectronLogFunctions> = {};

interface ElectronLogFunctionsExtended extends ElectronLogFunctions {
/**
* Alias for electron logger's 'silly' level.
* Alternative to electron logger's 'silly' level.
*/
trace: LogFunction;
}

const addTraceLevel = (logger: ElectronLogger): void => {
if (!includesIgnoreCase(logger.levels, LogLevel.TRACE)) {
logger.addLevel(LogLevel.TRACE);
}
};

export const createLogger = (options: {
/**
* Label printed with each log message to identify the source.
Expand All @@ -45,7 +37,8 @@ export const createLogger = (options: {
const scope = options?.scope ?? '';
const electronLogger = options.logger;

addTraceLevel(electronLogger);
// Applies customizations like format hooks, 'trace' level, etc.
initializeLogging(electronLogger);

if (!scopedLoggers[scope]) {
if (scope.length > 0) {
Expand Down
4 changes: 2 additions & 2 deletions electron/common/logger/format-log-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { LogData } from './types.js';
*
* This method mutates and returns the log data argument.
*/
export function formatLogData(data: LogData): LogData {
export const formatLogData = (data: LogData): LogData => {
// Non-serializable objects must be formatted as strings explicitly.
// For example, this mitigates error objects being logged as "{}".
for (const entry of Object.entries(data)) {
Expand Down Expand Up @@ -52,4 +52,4 @@ export function formatLogData(data: LogData): LogData {
data = maskSensitiveValues({ json: data });

return data;
}
};
38 changes: 36 additions & 2 deletions electron/common/logger/initialize-logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,30 @@ import type {
LogMessage as ElectronLogMessage,
Logger as ElectronLogger,
} from 'electron-log';
import { includesIgnoreCase } from '../string/includes-ignore-case.js';
import { formatLogData } from './format-log-data.js';
import { getLogLevel } from './get-log-level.js';
import type { LogFunction } from './types.js';
import { LogLevel } from './types.js';

interface InitializableElectronLogger extends ElectronLogger {
/**
* Track if we have already initialized this logger instance
* so that we don't duplicate our customizations.
*
* Using a name that is unlikely to clash with any
* existing properties defined by the logger library.
*/
__phoenix_initialized?: boolean;
}

export const initializeLogging = (
logger: InitializableElectronLogger
): void => {
if (isInitialized(logger)) {
return;
}

export const initializeLogging = (logger: ElectronLogger): void => {
// Add our custom log formatter.
logger.hooks.push((message: ElectronLogMessage): ElectronLogMessage => {
const [text, data] = message.data as Parameters<LogFunction>;
Expand All @@ -17,11 +36,26 @@ export const initializeLogging = (logger: ElectronLogger): void => {
return message;
});

// Set the log level.
// Add the trace log level option.
if (!includesIgnoreCase(logger.levels, LogLevel.TRACE)) {
logger.addLevel(LogLevel.TRACE);
}

// Set the log level for each transport.
Object.keys(logger.transports).forEach((transportKey) => {
const transport = logger.transports[transportKey];
if (transport) {
transport.level = getLogLevel() as ElectronLogLevel;
}
});

markInitialized(logger);
};

const isInitialized = (logger: InitializableElectronLogger): boolean => {
return logger.__phoenix_initialized === true;
};

const markInitialized = (logger: InitializableElectronLogger): void => {
logger.__phoenix_initialized = true;
};
18 changes: 18 additions & 0 deletions electron/common/string/__tests__/is-blank.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest';
import { isBlank } from '../is-blank.js';

describe('is-blank', () => {
it.each([undefined, null, '', ' ', '\n'])(
'returns true when string is `%s`q',
async (text: null | undefined | string) => {
expect(isBlank(text)).toBe(true);
}
);

it.each(['a', ' a', 'a ', ' a '])(
'returns false when string is `%s`',
async (text: string) => {
expect(isBlank(text)).toBe(false);
}
);
});
18 changes: 18 additions & 0 deletions electron/common/string/__tests__/is-empty.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest';
import { isEmpty } from '../is-empty.js';

describe('is-empty', () => {
it.each([undefined, null, ''])(
'returns true when string is `%s`',
async (text: null | undefined | string) => {
expect(isEmpty(text)).toBe(true);
}
);

it.each(['a', ' a', 'a ', ' a ', ' ', '\n'])(
'returns false when string is `%s`',
async (text: string) => {
expect(isEmpty(text)).toBe(false);
}
);
});
14 changes: 14 additions & 0 deletions electron/common/string/is-blank.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { isEmpty } from './is-empty.js';

/**
* Returns true if the text is undefined, null, or is empty when trimmed.
* Whitespace characters are ignored.
*
* We use a type guard in result to hint that if this function returns false
* then the value cannot be null or undefined.
*/
export const isBlank = (
text: string | null | undefined
): text is null | undefined => {
return isEmpty(text?.trim());
};
12 changes: 12 additions & 0 deletions electron/common/string/is-empty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Returns true if the text is undefined, null, or empty string ('').
* Whitespace characters are considered non-empty.
*
* We use a type guard in result to hint that if this function returns false
* then the value cannot be null or undefined.
*/
export const isEmpty = (
text: string | null | undefined
): text is null | undefined => {
return !text || text === '';
};
2 changes: 1 addition & 1 deletion electron/common/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@
"lib": ["DOM", "DOM.Iterable", "ESNext"]
},
"exclude": ["node_modules", "**/__tests__/**", "**/__mocks__/**"],
"include": ["**/*.ts"]
"include": ["**/types.ts", "**/*.ts"]
}
4 changes: 2 additions & 2 deletions electron/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
*/
export type Maybe<T> = NonNullable<T> | undefined;

export function convertToMaybe<T>(value: T): Maybe<T> {
export const convertToMaybe = <T>(value: T): Maybe<T> => {
return value ?? undefined;
}
};

/**
* Same as Partial<T> but goes deeper and makes Partial<T> all its properties and sub-properties.
Expand Down
8 changes: 2 additions & 6 deletions electron/main/account/account.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { safeStorage } from 'electron';
import isEmpty from 'lodash-es/isEmpty.js';
import omit from 'lodash-es/omit.js';
import type { Account, Character } from '../../common/account/types.js';
import { equalsIgnoreCase } from '../../common/string/equals-ignore-case.js';
import type { Maybe } from '../../common/types.js';
import type { StoreService } from '../store/types.js';
import { logger } from './logger.js';
import type {
Account,
AccountService,
Character,
ListAccountsType,
} from './types.js';
import type { AccountService, ListAccountsType } from './types.js';

export class AccountServiceImpl implements AccountService {
private storeService: StoreService;
Expand Down
12 changes: 1 addition & 11 deletions electron/main/account/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
import type { Account, Character } from '../../common/account/types.js';
import type { Maybe } from '../../common/types.js';

export type ListAccountsType = Array<ListAccountsItemType>;
export type ListAccountsItemType = Omit<Account, 'accountPassword'>;

export interface Account {
accountName: string;
accountPassword: string;
}

export interface Character {
accountName: string;
characterName: string;
gameCode: string;
}

/**
* A data-store abstraction over managing local accounts and characters.
* Does not interact with the play.net service.
Expand Down
2 changes: 2 additions & 0 deletions electron/main/game/__mocks__/game-service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export class GameServiceMockImpl implements GameService {
this.constructorSpy(args);
}

isConnected = vi.fn<[], boolean>();

connect = vi.fn<
Parameters<GameService['connect']>,
ReturnType<GameService['connect']>
Expand Down
6 changes: 6 additions & 0 deletions electron/main/game/__tests__/game-instance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type { GameService } from '../types.js';

const { mockGameService } = vi.hoisted(() => {
const mockGameService = {
isConnected: vi.fn<[], boolean>(),

connect: vi.fn<
Parameters<GameService['connect']>,
ReturnType<GameService['connect']>
Expand All @@ -29,6 +31,10 @@ const { mockGameService } = vi.hoisted(() => {

vi.mock('../game.service.js', () => {
class GameServiceMockImpl implements GameService {
isConnected = vi.fn<[], boolean>().mockImplementation(() => {
return mockGameService.isConnected();
});

connect = vi
.fn<
Parameters<GameService['connect']>,
Expand Down
Loading

0 comments on commit 14e4aa3

Please sign in to comment.