Compare commits

...

19 Commits

Author SHA1 Message Date
martin e7ef05d683 merge: PR #1613 custom entries (Neintonine) — homebrew-eintraege
Upstream: https://github.com/elyukai/optolith-client/pull/1613
Basis develop-V1, mergeable. Fuegt eigene Sonderregeln/Eintraege hinzu.
Ref: Alex-Wunsch 'man kann nichts selbst ergaenzen'.
2026-07-16 17:57:33 +02:00
martin 3706a4f78e build(fork): publish explizit null — kein updater-fallback auf upstream-github 2026-07-16 14:53:27 +02:00
martin f4cf03966c build(fork): update-kanal entfernt — releases nur noch privat, kein auto-update 2026-07-16 14:47:59 +02:00
martin 630ec88c7e build(fork): electron-builder-konfigurationen für fork-releases 2026-07-16 13:52:09 +02:00
martin 8484265c2a fix(fork): updater nur aktivieren wenn app-update.yml existiert 2026-07-16 13:40:37 +02:00
martin dbdb75a888 feat(fork): v1.6.0 mit Fork-Kennzeichnung (Fenstertitel + NavBar-Badge) 2026-07-16 13:18:58 +02:00
Lukas Obermann 0dc6316b5b docs(github): use task list for new book data sources 2025-09-06 17:08:53 +02:00
Lukas Obermann c7d1735e4b docs(github): change new book data template to hide instructions as comments 2025-09-06 17:03:13 +02:00
Lukas Obermann aa93dab7fd build(release): 1.5.2 2025-01-02 18:43:46 +01:00
Lukas Obermann 5a9ae86cf9 build(git): merge master 2025-01-02 18:43:14 +01:00
Michel Fedde 7f5f94491e fix: throws during startup due to changed devtools identifiers
Refs: #1634
Co-authored-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-01-02 18:26:57 +01:00
Lukas Obermann a2979650f2 build(deps): fix auto-fixable vulnerabilities in packages 2025-01-02 18:17:24 +01:00
Michel Fedde 3e2f55b9a2 feat(custom-rule): add translation interfaces 2023-08-20 13:58:17 +02:00
Michel Fedde 8d02399238 feat(custom-rule): add custom rule-page to PDF 2023-08-19 23:24:34 +02:00
Michel Fedde f0d46a9d73 feat(custom-rule): fixes the bug, where all elements where selected individually 2023-08-19 19:18:49 +02:00
Michel Fedde c3f7f4faa8 feat(custom-rules): moved the inline textarea to a dialog to enter the rule 2023-08-19 19:07:48 +02:00
Michel Fedde ed97c55c4e feat: add option to add and save a custom rule to a custom activatable 2023-08-19 15:09:00 +02:00
Lukas Obermann 8e8932cd83 build(release): bump version to 1.5.1 2022-03-20 16:05:05 +01:00
Lukas Obermann c15505062d build(release): 1.5.1
Refs: #1360
2022-03-20 15:26:11 +01:00
45 changed files with 3001 additions and 2196 deletions
+20 -10
View File
@@ -9,19 +9,27 @@ assignees: ''
## Sources
> List here what is included in this part of the book to implement. E.g., for special abilities, you need to list every source of entries from the book. Example (in German):
>
> - S. 133150 (Berufsgeheimnisse)
<!-- List here what is included in this part of the book to implement. E.g., for special abilities, you need to list every source of entries from the book. Use page ranges if entries span multiple pages. Example:
- S. X (Section/Chapter Name)
- [ ] S. 133 (Entry A)
- [ ] S. 133134 (Entry B)
Alternatively, provide an overview of the different sections in the book where one or more relevant entries occur. Example (in German):
- [ ] S. 133150 (Berufsgeheimnisse)
-->
- [ ] S. X (Entry/Section/Chapter Name)
## Special Notes
> List here any special hints to implement the sources listed above.
>
> - …
<!-- List here any special hints to implement the sources listed above. Example:
- All entries are already present, only add source references
- Pay attention to the layout of entry A, there is a gap that should not be in Optolith
Replace the dash below this comment if there are any notes. -->
@@ -29,8 +37,10 @@ assignees: ''
## Special Implementations
> List here what cannot be solved only using the database. Example (in German):
>
> - [ ] Voraussetzungen Vorliebe I-II
<!-- List issues that track what cannot be solved only using the database. Example:
- #123
Replace the dash below this comment if there are any notes. -->
+7
View File
@@ -2,6 +2,13 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [1.5.2](https://github.com/elyukai/optolith-client/compare/v1.5.2-alpha.4...v1.5.2) (2025-01-02)
### Bug Fixes
* throws during startup due to changed devtools identifiers ([7f5f944](https://github.com/elyukai/optolith-client/commit/7f5f94491e1432d66add1315889075fb5369f700)), closes [#1634](https://github.com/elyukai/optolith-client/issues/1634)
### [1.5.2-alpha.4](https://github.com/elyukai/optolith-client/compare/v1.5.2-alpha.3...v1.5.2-alpha.4) (2022-07-22)
+1
View File
@@ -22,6 +22,7 @@ export interface RawConfig {
equipmentGroupVisibilityFilter: number
sheetCheckAttributeValueVisibility?: boolean
sheetUseParchment?: boolean
sheetShowRules?: boolean
sheetZoomFactor?: number
enableActiveItemHints: boolean
locale?: string
+3
View File
@@ -76,6 +76,9 @@
"sheetUseParchment": {
"type": "boolean"
},
"sheetShowRules": {
"type": "boolean"
},
"sheetZoomFactor": {
"type": "number"
},
+18
View File
@@ -0,0 +1,18 @@
{
"appId": "com.lukasobermann.optolith",
"productName": "Optolith",
"directories": { "output": "dist" },
"files": ["app/**/*", "LICENSE"],
"npmRebuild": false,
"mac": {
"category": "public.app-category.role-playing-games",
"icon": "app/icon.icns",
"target": [
{ "target": "dmg", "arch": ["arm64", "x64"] },
{ "target": "zip", "arch": ["arm64", "x64"] }
],
"artifactName": "Optolith_${version}_${arch}.${ext}",
"identity": null
},
"dmg": { "sign": false }
}
+49
View File
@@ -0,0 +1,49 @@
{
"appId": "com.lukasobermann.optolith",
"productName": "Optolith",
"directories": {
"output": "dist"
},
"files": [
"app/**/*",
"LICENSE"
],
"npmRebuild": false,
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"icon": "app/icon.ico",
"artifactName": "OptolithSetup_${version}.${ext}"
},
"nsis": {
"perMachine": false,
"deleteAppDataOnUninstall": false
},
"linux": {
"category": "RolePlaying",
"executableName": "Optolith",
"icon": "app",
"target": [
{
"target": "AppImage",
"arch": [
"x64"
]
},
{
"target": "tar.gz",
"arch": [
"x64"
]
}
],
"artifactName": "Optolith_${version}.${ext}"
},
"publish": null
}
+1869 -2107
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "optolith",
"productName": "Optolith",
"version": "1.5.2-alpha.4",
"version": "1.6.0",
"description": "A generator for heroes for the P&P-RPG 'The Dark Eye'.",
"author": {
"name": "Lukas Obermann",
@@ -14,7 +14,7 @@
"@electron/remote": "^2.0.8",
"adm-zip": "^0.5.1",
"ajv": "6.12.6",
"electron-devtools-installer": "^3.2.0",
"electron-devtools-installer": "^3.2.1",
"electron-log": "^4.4.8",
"electron-updater": "^5.0.5",
"electron-window-state": "5.0.3",
@@ -103,4 +103,4 @@
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
}
+17
View File
@@ -34,6 +34,7 @@ import { translate, translateP } from "../Utilities/I18n"
import { pipe, pipe_ } from "../Utilities/pipe"
import { misNumberM } from "../Utilities/typeCheckUtils"
import { getWikiEntry } from "../Utilities/WikiUtils"
import { WikiInfoSelector } from "../Views/InlineWiki/WikiInfo"
import { ReduxAction, ReduxDispatch } from "./Actions"
import { addAlert, addNotEnoughAPAlert, AlertOptions } from "./AlertActions"
@@ -466,6 +467,14 @@ export const switchRatingVisibility = (): SwitchDisAdvRatingVisibilityAction =>
type: ActionTypes.SWITCH_DISADV_RATING_VISIBILITY,
})
export const setRule = (selector: WikiInfoSelector, rule: string): SaveRuleAction => ({
type: ActionTypes.SET_CUSTOM_RULE,
payload: {
rule,
selector,
},
})
export interface SetActiveAdvantagesFilterTextAction {
type: ActionTypes.SET_ADVANTAGES_FILTER_TEXT
payload: {
@@ -525,3 +534,11 @@ export const setInactiveDisadvantagesFilterText =
filterText,
},
})
export interface SaveRuleAction {
type: ActionTypes.SET_CUSTOM_RULE
payload: {
rule: string
selector: WikiInfoSelector
}
}
+2
View File
@@ -53,6 +53,8 @@ export const requestConfigSave: ReduxAction<Promise<boolean>> =
Just (UISSA.sheetCheckAttributeValueVisibility (uiSettingsState)),
sheetUseParchment:
Just (UISSA.sheetUseParchment (uiSettingsState)),
sheetShowRules:
Just (UISSA.sheetShowRules (uiSettingsState)),
sheetZoomFactor:
UISSA.sheetZoomFactor (uiSettingsState),
theme: Just (UISSA.theme (uiSettingsState)),
+13 -1
View File
@@ -1,4 +1,9 @@
import { SWITCH_SHEET_ATTR_VALUE_VISIBILITY, SWITCH_SHEET_USE_PARCHMENT, SET_SHEET_ZOOM_FACTOR } from "../Constants/ActionTypes"
import {
SWITCH_SHEET_ATTR_VALUE_VISIBILITY,
SWITCH_SHEET_USE_PARCHMENT,
SET_SHEET_ZOOM_FACTOR,
SWITCH_SHEET_SHOW_RULES
} from "../Constants/ActionTypes"
export interface SwitchSheetAttributeValueVisibilityAction {
type: SWITCH_SHEET_ATTR_VALUE_VISIBILITY
@@ -16,6 +21,13 @@ export const switchUseParchment = (): SwitchSheetUseParchmentAction => ({
type: SWITCH_SHEET_USE_PARCHMENT,
})
export interface SwitchSheetShowRules {
type: SWITCH_SHEET_SHOW_RULES
}
export const switchShowRules = (): SwitchSheetShowRules => ({
type: SWITCH_SHEET_SHOW_RULES,
})
export interface SetSheetZoomFactor {
type: SET_SHEET_ZOOM_FACTOR
payload: {
+6
View File
@@ -321,6 +321,9 @@ export type SET_DISADV_TIER = "SET_DISADV_TIER"
export const SWITCH_DISADV_RATING_VISIBILITY = "SWITCH_DISADV_RATING_VISIBILITY"
export type SWITCH_DISADV_RATING_VISIBILITY = "SWITCH_DISADV_RATING_VISIBILITY"
export const SET_CUSTOM_RULE = "SET_CUSTOM_RULE"
export type SET_CUSTOM_RULE = "SET_CUSTOM_RULE"
export const ADD_TALENT_POINT = "ADD_TALENT_POINT"
export type ADD_TALENT_POINT = "ADD_TALENT_POINT"
export const REMOVE_TALENT_POINT = "REMOVE_TALENT_POINT"
@@ -615,6 +618,9 @@ export type SWITCH_SHEET_ATTR_VALUE_VISIBILITY = "SWITCH_SHEET_ATTR_VALUE_VISIBI
export const SWITCH_SHEET_USE_PARCHMENT = "SWITCH_SHEET_USE_PARCHMENT"
export type SWITCH_SHEET_USE_PARCHMENT = "SWITCH_SHEET_USE_PARCHMENT"
export const SWITCH_SHEET_SHOW_RULES = "SWITCH_SHEET_SHOW_RULES"
export type SWITCH_SHEET_SHOW_RULES = "SWITCH_SHEET_SHOW_RULES"
export const SET_SHEET_ZOOM_FACTOR = "SET_SHEET_ZOOM_FACTOR"
export type SET_SHEET_ZOOM_FACTOR = "SET_SHEET_ZOOM_FACTOR"
+10 -1
View File
@@ -22,7 +22,12 @@ import { getConditions, getSkillPages, getSkillsByGroup, getStates } from "../Se
import { getAllSkills } from "../Selectors/skillsSelectors"
import { getCantripsForSheet, getSpellsForSheet } from "../Selectors/spellsSelectors"
import { getAvatar, getCurrentHeroName, getCurrentSex, getProfile, getPurse, getSpecialAbilities, getWikiBooks, getWikiSpecialAbilities } from "../Selectors/stateSelectors"
import { getSheetCheckAttributeValueVisibility, getSheetUseParchment, getSheetZoomFactor } from "../Selectors/uisettingsSelectors"
import {
getSheetCheckAttributeValueVisibility,
getSheetShowRules,
getSheetUseParchment,
getSheetZoomFactor
} from "../Selectors/uisettingsSelectors"
import { requestExportHeroAsRptok } from "../Utilities/MapToolExporter"
import { pipe } from "../Utilities/pipe"
import { mapGetToMaybeSlice, mapGetToSlice } from "../Utilities/SelectorsUtils"
@@ -56,6 +61,7 @@ const mapStateToProps = (state: AppStateRecord, ownProps: SheetsOwnProps): Sheet
items: getAllItems (state),
pet: getPet (state),
useParchment: getSheetUseParchment (state),
showRules: getSheetShowRules (state),
zoomFactor: getSheetZoomFactor (state),
purse: getPurse (state),
totalPrice: getTotalPrice (state),
@@ -98,6 +104,9 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, ownProps: SheetsOwnProps) =
switchUseParchment () {
dispatch (SheetActions.switchUseParchment ())
},
switchShowRules () {
dispatch (SheetActions.switchShowRules ())
},
setSheetZoomFactor (zoomFactor: number) {
dispatch (SheetActions.setSheetZoomFactor (zoomFactor))
},
+2
View File
@@ -167,6 +167,7 @@ export interface Config {
equipmentGroupVisibilityFilter: EquipmentGroup
sheetCheckAttributeValueVisibility: Maybe<boolean>
sheetUseParchment: Maybe<boolean>
sheetShowRules: Maybe<boolean>
sheetZoomFactor: number
enableActiveItemHints: boolean
locale: Maybe<string>
@@ -202,6 +203,7 @@ export const Config =
equipmentGroupVisibilityFilter: EquipmentGroup.MeleeWeapons,
sheetCheckAttributeValueVisibility: Just (false),
sheetUseParchment: Just (false),
sheetShowRules: Just (false),
sheetZoomFactor: 100,
enableActiveItemHints: false,
locale: Nothing,
+3 -2
View File
@@ -1,3 +1,4 @@
import React from "react"
import { List } from "../../../Data/List"
import { Maybe } from "../../../Data/Maybe"
import { OrderedMap } from "../../../Data/OrderedMap"
@@ -145,8 +146,8 @@ export interface HistoryPrevState {
"@@name": "HistoryPrevState"
}
export type InputTextEvent = React.ChangeEvent<HTMLInputElement>
export type InputKeyEvent = React.KeyboardEvent<HTMLInputElement>
export type InputTextEvent = React.ChangeEvent<HTMLInputElement> | React.KeyboardEvent<HTMLTextAreaElement>
export type InputKeyEvent = React.KeyboardEvent<HTMLInputElement> | React.KeyboardEvent<HTMLTextAreaElement>
export interface SubTab {
id: TabId
+2
View File
@@ -28,6 +28,7 @@ export interface UISettingsState {
enableActiveItemHints: boolean
sheetCheckAttributeValueVisibility: boolean
sheetUseParchment: boolean
sheetShowRules: boolean
sheetZoomFactor: number
theme: Theme
enableEditingHeroAfterCreationPhase: boolean
@@ -61,6 +62,7 @@ export const UISettingsState =
enableActiveItemHints: false,
sheetCheckAttributeValueVisibility: false,
sheetUseParchment: false,
sheetShowRules: false,
sheetZoomFactor: 100,
theme: Theme.Dark,
enableEditingHeroAfterCreationPhase: false,
+14
View File
@@ -255,6 +255,7 @@ export const L10n =
"sheets.dialogs.rptoksaveerror.message": "",
"sheets.showattributevalues": "",
"sheets.useparchment": "",
"sheets.usecustomrules": "",
"sheets.zoomfactor": "",
"sheets.charactersheet": "",
"sheets.attributemodifiers.title": "",
@@ -467,6 +468,13 @@ export const L10n =
"sheets.chantssheet.blessedspecialabilities": "",
"sheets.chantssheet.blessings": "",
"sheets.rulessheet.title": "",
"sheets.rulessheet.header.name": "",
"sheets.rulessheet.header.rule": "",
"sheets.rulessheet.advantages": "",
"sheets.rulessheet.disadvantages": "",
"sheets.rulessheet.specialabilities": "",
"pacts.pactcategory": "",
"pacts.nopact": "",
"pacts.pactlevel": "",
@@ -565,6 +573,12 @@ export const L10n =
"inlinewiki.variants": "",
"inlinewiki.insteadof": "",
"inlinewiki.custom.inactive": "",
"inlinewiki.custom.norule": "",
"inlinewiki.custom.dialog.title": "",
"inlinewiki.custom.dialog.markdownInfo": "",
"inlinewiki.custom.dialog.saveBtn": "",
"rcpselectoptions.race": "",
"rcpselectoptions.culture": "",
"rcpselectoptions.profession": "",
+17 -1
View File
@@ -15,13 +15,19 @@ import { ActivatableDependentL } from "../Models/ActiveEntries/ActivatableDepend
import { ActiveObjectL } from "../Models/ActiveEntries/ActiveObject"
import { SkillDependentL } from "../Models/ActiveEntries/SkillDependent"
import { HeroModelL, HeroModelRecord } from "../Models/Hero/HeroModel"
import { activate, deactivate, setLevel } from "../Utilities/Activatable/activatableActivationUtils"
import {
activate,
deactivate,
saveRule,
setLevel,
} from "../Utilities/Activatable/activatableActivationUtils"
import { addOtherSpecialAbilityDependenciesOnActivation, removeOtherSpecialAbilityDependenciesOnDeletion } from "../Utilities/Activatable/SpecialAbilityUtils"
import { pipe, pipe_ } from "../Utilities/pipe"
type Action = DisAdvActions.ActivateDisAdvAction
| DisAdvActions.DeactivateDisAdvAction
| DisAdvActions.SetDisAdvLevelAction
| DisAdvActions.SaveRuleAction
| SpecialAbilitiesActions.ActivateSpecialAbilityAction
| SpecialAbilitiesActions.DeactivateSpecialAbilityAction
| SpecialAbilitiesActions.SetSpecialAbilityTierAction
@@ -97,6 +103,16 @@ export const activatableReducer =
(SpecialAbilityId.TraditionGuildMages as string))
}
case ActionTypes.SET_CUSTOM_RULE: {
return hero => saveRule (
action.payload.rule,
hero,
action.payload.selector.index,
action.payload.selector.currentId
)
}
default:
return ident
}
+2
View File
@@ -37,6 +37,7 @@ type Action = AttributesActions.AddAttributePointAction
| DisAdvActions.ActivateDisAdvAction
| DisAdvActions.DeactivateDisAdvAction
| DisAdvActions.SetDisAdvLevelAction
| DisAdvActions.SaveRuleAction
| SpecialAbilitiesActions.ActivateSpecialAbilityAction
| SpecialAbilitiesActions.DeactivateSpecialAbilityAction
| SpecialAbilitiesActions.SetSpecialAbilityTierAction
@@ -71,6 +72,7 @@ export const dependentReducer =
case ActionTypes.ACTIVATE_SPECIALABILITY:
case ActionTypes.DEACTIVATE_DISADV:
case ActionTypes.DEACTIVATE_SPECIALABILITY:
case ActionTypes.SET_CUSTOM_RULE:
case ActionTypes.SET_DISADV_TIER:
case ActionTypes.SET_SPECIALABILITY_TIER:
case ActionTypes.SET_TRAD_GUILD_MAGE_UNFAM_SPELL_ID:
+11 -1
View File
@@ -13,7 +13,12 @@ import { ReceiveInitialDataAction } from "../Actions/InitializationActions"
import { SetLiturgicalChantsSortOrderAction } from "../Actions/LiturgicalChantActions"
import { SetProfessionsGroupVisibilityFilterAction, SetProfessionsSortOrderAction, SetProfessionsVisibilityFilterAction } from "../Actions/ProfessionActions"
import { SetRacesSortOrderAction } from "../Actions/RaceActions"
import { SwitchSheetAttributeValueVisibilityAction, SwitchSheetUseParchmentAction, SetSheetZoomFactor } from "../Actions/SheetActions"
import {
SwitchSheetAttributeValueVisibilityAction,
SwitchSheetUseParchmentAction,
SetSheetZoomFactor,
SwitchSheetShowRules
} from "../Actions/SheetActions"
import { SetSkillsSortOrderAction, SwitchSkillRatingVisibilityAction } from "../Actions/SkillActions"
import { SetSpecialAbilitiesSortOrderAction } from "../Actions/SpecialAbilitiesActions"
import { SetSpellsSortOrderAction } from "../Actions/SpellsActions"
@@ -47,6 +52,7 @@ type Action = ReceiveInitialDataAction
| SetMeleeItemTemplatesCombatTechniqueFilterAction
| SetRangedItemTemplatesCombatTechniqueFilterAction
| SwitchEnableAnimationsAction
| SwitchSheetShowRules
const CA = Config.A
@@ -120,6 +126,8 @@ export const uiSettingsReducer =
fromMaybe (false) (CA.sheetCheckAttributeValueVisibility (config)),
sheetUseParchment:
fromMaybe (false) (CA.sheetUseParchment (config)),
sheetShowRules:
fromMaybe (false) (CA.sheetShowRules (config)),
sheetZoomFactor:
CA.sheetZoomFactor (config),
theme: fromMaybe (Theme.Dark) (CA.theme (config)),
@@ -140,6 +148,8 @@ export const uiSettingsReducer =
return over (UISettingsStateL.sheetCheckAttributeValueVisibility) (not)
case ActionTypes.SWITCH_SHEET_USE_PARCHMENT:
return over (UISettingsStateL.sheetUseParchment) (not)
case ActionTypes.SWITCH_SHEET_SHOW_RULES:
return over (UISettingsStateL.sheetShowRules) (not)
case ActionTypes.SET_SHEET_ZOOM_FACTOR:
return set (UISettingsStateL.sheetZoomFactor) (action.payload.zoomFactor)
@@ -1,7 +1,19 @@
import { notEquals } from "../../Data/Eq"
import { fmap, fmapF } from "../../Data/Functor"
import { elem, foldr, List } from "../../Data/List"
import { bindF, catMaybes, ensure, fromMaybe, Just, liftM2, listToMaybe, maybe, Maybe, Nothing } from "../../Data/Maybe"
import {
bindF,
catMaybes,
ensure,
fromMaybe,
isJust, isNothing,
Just,
liftM2,
listToMaybe,
maybe,
Maybe,
Nothing,
} from "../../Data/Maybe"
import { add, multiply, negate, subtract } from "../../Data/Num"
import { lookup, mapMaybe } from "../../Data/OrderedMap"
import { Record } from "../../Data/Record"
@@ -15,13 +27,18 @@ import { AttributeDependent } from "../Models/ActiveEntries/AttributeDependent"
import { PermanentEnergyLoss } from "../Models/Hero/PermanentEnergyLoss"
import { PermanentEnergyLossAndBoughtBack } from "../Models/Hero/PermanentEnergyLossAndBoughtBack"
import { Rules } from "../Models/Hero/Rules"
import { ActiveActivatable } from "../Models/View/ActiveActivatable"
import { AttributeCombined } from "../Models/View/AttributeCombined"
import { DerivedCharacteristicValues } from "../Models/View/DerivedCharacteristicCombined"
import { Advantage } from "../Models/Wiki/Advantage"
import { DerivedCharacteristic } from "../Models/Wiki/DerivedCharacteristic"
import { Disadvantage } from "../Models/Wiki/Disadvantage"
import { MagicalTradition } from "../Models/Wiki/MagicalTradition"
import { Race } from "../Models/Wiki/Race"
import { SpecialAbility } from "../Models/Wiki/SpecialAbility"
import { StaticData } from "../Models/Wiki/WikiModel"
import { getModifierByIsActive, getModifierByIsActives, modifyByLevelM } from "../Utilities/Activatable/activatableModifierUtils"
import { isCustomActivatableId } from "../Utilities/Activatable/checkActivatableUtils"
import { getActiveSelections } from "../Utilities/Activatable/selectionUtils"
import { createMaybeSelector } from "../Utilities/createMaybeSelector"
import { getAttributeValueWithDefault } from "../Utilities/Increasable/attributeUtils"
@@ -426,3 +443,77 @@ export const getDerivedCharacteristics = createMaybeSelector (
lookup ("WT") (mp),
))
)
export interface RuleDictionary {
[key: string]: string
}
export interface RuleContainer {
total: number
advantages: RuleDictionary
disadvantages: RuleDictionary
specialAbilities: RuleDictionary
}
export const getCustomRules = (
advantagesActive: Maybe<List<Record<ActiveActivatable<Advantage>>>>,
disadvantagesActive: Maybe<List<Record<ActiveActivatable<Disadvantage>>>>,
generalsaActive: Maybe<List<Record<ActiveActivatable<SpecialAbility>>>>,
combatSpecialAbilities: Maybe<List<Record<ActiveActivatable<SpecialAbility>>>>
): RuleContainer => {
const customRules: RuleContainer = {
total: 0,
advantages: {},
disadvantages: { },
specialAbilities: { },
}
const addToCustomRules = (
list: List<Record<ActiveActivatable<Advantage>>>
| List<Record<ActiveActivatable<Disadvantage>>>
| List<Record<ActiveActivatable<SpecialAbility>>>,
target: RuleDictionary
) => {
for (const activatable of list) {
const { heroEntry } = activatable.values
if (!isCustomActivatableId (heroEntry.values.id)) {
continue
}
for (const activeObject of heroEntry.values.active) {
if (!activeObject.values.sid2) {
continue
}
if (
isNothing (activeObject.values.sid)
|| isNothing (activeObject.values.sid2)
) {
continue
}
if (activeObject.values.sid.value in target) {
continue
}
target[activeObject.values.sid.value.toString ()] = activeObject.values.sid2.value.toString ()
customRules.total++
}
}
}
if (isJust(advantagesActive)) {
addToCustomRules (advantagesActive.value, customRules.advantages)
}
if (isJust(disadvantagesActive)) {
addToCustomRules (disadvantagesActive.value, customRules.disadvantages)
}
if (isJust(generalsaActive)) {
addToCustomRules (generalsaActive.value, customRules.specialAbilities)
}
if (isJust(combatSpecialAbilities)) {
addToCustomRules (combatSpecialAbilities.value, customRules.specialAbilities)
}
return customRules
}
+2
View File
@@ -72,6 +72,8 @@ export const getSheetCheckAttributeValueVisibility =
export const getSheetUseParchment =
pipe (ASA.ui, UISA.settings, UISSA.sheetUseParchment)
export const getSheetShowRules =
pipe (ASA.ui, UISA.settings, UISSA.sheetShowRules)
export const getSheetZoomFactor =
pipe (ASA.ui, UISA.settings, UISSA.sheetZoomFactor)
@@ -208,3 +208,17 @@ export const setLevel =
return hero
}
}
export function saveRule (
rule: string,
hero: HeroModelRecord,
index: number,
itemId: string
): HeroModelRecord {
return adjustEntryDef (over (ActivatableDependentL.active)
(modifyAt (index)
(set (ActiveObjectL.sid2)
(Just (rule)))))
(itemId)
(hero)
}
@@ -1,7 +1,7 @@
import { any } from "../../../Data/List"
import { member, Record } from "../../../Data/Record"
import { Pair } from "../../../Data/Tuple"
import { AdvantageId } from "../../Constants/Ids"
import { AdvantageId, DisadvantageId, SpecialAbilityId } from "../../Constants/Ids"
import { ActiveActivatable } from "../../Models/View/ActiveActivatable"
import { InactiveActivatable } from "../../Models/View/InactiveActivatable"
import { Advantage } from "../../Models/Wiki/Advantage"
@@ -44,3 +44,13 @@ type ViewActivatable = Record<ActiveActivatable> | Record<InactiveActivatable>
export const isActiveViewObject =
(obj: ViewActivatable): obj is Record<ActiveActivatable> =>
member ("index") (obj)
export const isCustomActivatable =
(obj: Activatable): boolean =>
isCustomActivatableId(obj.values.id)
export const isCustomActivatableId =
(activatableId: string): boolean =>
activatableId === AdvantageId.CustomAdvantage
|| activatableId === DisadvantageId.CustomDisadvantage
|| activatableId === SpecialAbilityId.CustomSpecialAbility
+3
View File
@@ -145,6 +145,8 @@ export const parseConfig = async () => {
Maybe (validated_data.value.sheetUseParchment),
sheetZoomFactor:
validated_data.value.sheetZoomFactor ?? 100,
sheetShowRules:
Maybe(validated_data.value.sheetShowRules),
enableActiveItemHints:
validated_data.value.enableActiveItemHints,
locale:
@@ -181,6 +183,7 @@ export const writeConfig = (x: Record<Config>): string => {
...obj,
sheetCheckAttributeValueVisibility: maybeToUndefined (obj.sheetCheckAttributeValueVisibility),
sheetUseParchment: maybeToUndefined (obj.sheetUseParchment),
sheetShowRules: maybeToUndefined (obj.sheetShowRules),
sheetZoomFactor: obj.sheetZoomFactor,
locale: maybeToUndefined (obj.locale),
fallbackLocale: maybeToUndefined (obj.fallbackLocale),
@@ -11,6 +11,7 @@ import { ActiveActivatable, ActiveActivatableA_ } from "../../Models/View/Active
import { StaticDataRecord } from "../../Models/Wiki/WikiModel"
import { pipe_ } from "../../Utilities/pipe"
import { isActiveRated } from "../../Utilities/ratingUtils"
import { WikiInfoSelector } from "../InlineWiki/WikiInfo"
import { ListView } from "../Universal/List"
import { ListPlaceholder } from "../Universal/ListPlaceholder"
import { Scroll } from "../Universal/Scroll"
@@ -24,10 +25,10 @@ export interface ActivatableRemoveListProps {
isRemovingEnabled: boolean
rating?: Maybe<OrderedMap<string, EntryRating>>
showRating?: boolean
selectedForInfo: Maybe<string>
selectedForInfo: Maybe<WikiInfoSelector>
setLevel (id: string, index: number, level: number): void
removeFromList (args: Record<ActivatableDeactivationOptions>): void
selectForInfo (id: string): void
selectForInfo (selector: WikiInfoSelector): void
}
const AAA_ = ActiveActivatableA_
@@ -15,11 +15,15 @@ import { ActiveActivatable, ActiveActivatableA_ } from "../../Models/View/Active
import { DropdownOption } from "../../Models/View/DropdownOption"
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
import {
isCustomActivatableId
} from "../../Utilities/Activatable/checkActivatableUtils"
import { classListMaybe } from "../../Utilities/CSS"
import { translate } from "../../Utilities/I18n"
import { getLevelElementsWithMin } from "../../Utilities/levelUtils"
import { pipe, pipe_ } from "../../Utilities/pipe"
import { renderMaybe } from "../../Utilities/ReactUtils"
import { WikiInfoSelector } from "../InlineWiki/WikiInfo"
import { Dropdown } from "../Universal/Dropdown"
import { IconButton } from "../Universal/IconButton"
import { ListItem } from "../Universal/ListItem"
@@ -38,10 +42,10 @@ export interface ActivatableRemoveListItemProps {
isImportant?: boolean
isTypical?: boolean
isUntypical?: boolean
selectedForInfo: Maybe<string>
selectedForInfo: Maybe<WikiInfoSelector>
setLevel (id: string, index: number, level: number): void
removeFromList (args: Record<ActivatableDeactivationOptions>): void
selectForInfo (id: string): void
selectForInfo (selector: WikiInfoSelector): void
}
const AAA = ActiveActivatable.A
@@ -64,16 +68,17 @@ const ActivatableRemoveListItem: React.FC<ActivatableRemoveListItemProps> = prop
} = props
const id = AAA_.id (item)
const index = AAA_.index (item)
const handleSelectTier = React.useCallback (
(mlevel: Maybe<number>) => {
if (isJust (mlevel)) {
const level = fromJust (mlevel)
setLevel (id, AAA_.index (item), level)
setLevel (id, index, level)
}
},
[ item, setLevel, id ]
[ item, setLevel, id, index ]
)
const handleRemove = React.useCallback (
@@ -82,17 +87,21 @@ const ActivatableRemoveListItem: React.FC<ActivatableRemoveListItemProps> = prop
AAA_.finalCost,
cost => ActivatableDeactivationOptions ({
id,
index: AAA_.index (item),
index,
cost,
})
)),
[ item, removeFromList, id ]
[ item, removeFromList, id, index ]
)
const handleSelectForInfo =
React.useCallback (
() => selectForInfo (id),
[ selectForInfo, id ]
() => selectForInfo ({
currentId: id,
index,
item,
}),
[ selectForInfo, id, index, item ]
)
const mlevel_element =
@@ -138,12 +147,21 @@ const ActivatableRemoveListItem: React.FC<ActivatableRemoveListItemProps> = prop
const baseName = AAA_.baseName (item)
let active = false
if (isJust (selectedForInfo)) {
active = id === selectedForInfo.value.currentId
if (isCustomActivatableId (id)) {
active &&= index === selectedForInfo.value.index
}
}
return (
<ListItem
important={isImportant}
recommended={isTypical}
unrecommended={isUntypical}
active={Maybe.elem (id) (selectedForInfo)}
active={active}
>
<ListItemName
name={
+3 -2
View File
@@ -8,6 +8,7 @@ import { EntryRating } from "../../Models/Hero/heroTypeHelpers"
import { ActiveActivatable } from "../../Models/View/ActiveActivatable"
import { StaticDataRecord } from "../../Models/Wiki/WikiModel"
import { ActivatableRemoveList } from "../Activatable/ActivatableRemoveList"
import { WikiInfoSelector } from "../InlineWiki/WikiInfo"
export interface ActiveListProps {
filterText: string
@@ -16,10 +17,10 @@ export interface ActiveListProps {
rating: Maybe<OrderedMap<string, EntryRating>>
showRating: boolean
isRemovingEnabled: boolean
selectedForInfo: Maybe<string>
selectedForInfo: Maybe<WikiInfoSelector>
removeFromList (args: Record<ActivatableDeactivationOptions>): void
setLevel (id: string, index: number, level: number): void
selectForInfo (id: string): void
selectForInfo (selector: WikiInfoSelector): void
}
export const ActiveList: React.FC<ActiveListProps> = props => {
@@ -13,6 +13,7 @@ import { AdventurePointsCategories } from "../../Models/View/AdventurePointsCate
import { InactiveActivatable } from "../../Models/View/InactiveActivatable"
import { StaticDataRecord } from "../../Models/Wiki/WikiModel"
import { translate } from "../../Utilities/I18n"
import { WikiInfoSelector } from "../InlineWiki/WikiInfo"
import { BorderButton } from "../Universal/BorderButton"
import { Checkbox } from "../Universal/Checkbox"
import { ListHeader } from "../Universal/ListHeader"
@@ -76,7 +77,7 @@ export const AdvantagesDisadvantages: React.FC<AdvantagesDisadvantagesProps> = p
} = props
const [ showAddSlidein, setShowAddSlidein ] = React.useState (false)
const [ currentId, setCurrenId ] = React.useState<Maybe<string>> (Nothing)
const [ currentWikiSelector, setCurrentWikiSelector ] = React.useState<Maybe<WikiInfoSelector>> (Nothing)
const [ currentSlideinId, setCurrentSlideinId ] = React.useState<Maybe<string>> (Nothing)
const handleShowSlidein = React.useCallback (
@@ -94,8 +95,10 @@ export const AdvantagesDisadvantages: React.FC<AdvantagesDisadvantagesProps> = p
)
const handleShowInfo = React.useCallback (
(id: string) => setCurrenId (Just (id)),
[ setCurrenId ]
(selector: WikiInfoSelector) => {
setCurrentWikiSelector (Just (selector))
},
[ setCurrentWikiSelector ]
)
const handleShowSlideinInfo = React.useCallback (
@@ -219,13 +222,13 @@ export const AdvantagesDisadvantages: React.FC<AdvantagesDisadvantagesProps> = p
rating={rating}
showRating={showRating}
isRemovingEnabled={isRemovingEnabled}
selectedForInfo={currentId}
selectedForInfo={currentWikiSelector}
removeFromList={removeFromList}
setLevel={setLevel}
selectForInfo={handleShowInfo}
/>
</MainContent>
<WikiInfoContainer currentId={currentId} />
<WikiInfoContainer currentSelector={currentWikiSelector} />
</Page>
)
}
@@ -32,6 +32,7 @@ import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
import { Activatable, AllRequirements } from "../../Models/Wiki/wikiTypeHelpers"
import { getNameCostForWiki } from "../../Utilities/Activatable/activatableActiveUtils"
import { getName } from "../../Utilities/Activatable/activatableNameUtils"
import { isCustomActivatable } from "../../Utilities/Activatable/checkActivatableUtils"
import { isExtendedSpecialAbility } from "../../Utilities/Activatable/checkStyleUtils"
import { isBlessedTradId, isMagicalTradId } from "../../Utilities/Activatable/traditionUtils"
import { putLevelName } from "../../Utilities/AdventurePoints/activatableCostUtils"
@@ -48,6 +49,8 @@ import { Markdown } from "../Universal/Markdown"
import { WikiCombatTechniques } from "./Elements/WikiCombatTechniques"
import { WikiSource } from "./Elements/WikiSource"
import { WikiBoxTemplate } from "./WikiBoxTemplate"
import { WikiInfoSelector } from "./WikiInfo"
import { WikiInfoCustomRuleContainer } from "./WikiInfoCustomRuleContainer"
import { WikiProperty } from "./WikiProperty"
const CIA = CategorizedPrerequisites.A
@@ -741,10 +744,13 @@ export function PrerequisitesText (props: PrerequisitesTextProps) {
export interface WikiActivatableInfoProps {
staticData: StaticDataRecord
x: Activatable
selector: Maybe<WikiInfoSelector>
}
export const WikiActivatableInfo: React.FC<WikiActivatableInfoProps> = props => {
const { x, staticData } = props
const { x, staticData, selector } = props
const isCustom = isCustomActivatable (x)
const specialAbilities = SDA.specialAbilities (staticData)
@@ -782,6 +788,24 @@ export const WikiActivatableInfo: React.FC<WikiActivatableInfoProps> = props =>
(bind (SAA.subgr (x))
(lookupF (SDA.combatSpecialAbilityGroups (staticData))))
if (isCustom) {
return (
<WikiBoxTemplate className="disadv" title={header_name}>
<WikiInfoCustomRuleContainer
staticData={staticData}
selector={selector}
/>
<PrerequisitesText
staticData={staticData}
x={x}
/>
{cost_elem}
{source_elem}
</WikiBoxTemplate>
)
}
switch (SAA.gr (x)) {
case SpecialAbilityGroup.StaffEnchantments:
case SpecialAbilityGroup.Bannschwert:
@@ -1343,6 +1367,24 @@ export const WikiActivatableInfo: React.FC<WikiActivatableInfoProps> = props =>
const header_name = `${header_full_name}${header_name_levels}${header_rcp}`
if (isCustom) {
return (
<WikiBoxTemplate className="disadv" title={header_name}>
<WikiInfoCustomRuleContainer
staticData={staticData}
selector={selector}
/>
<PrerequisitesText
staticData={staticData}
x={x}
/>
{cost_elem}
{source_elem}
</WikiBoxTemplate>
)
}
return (
<WikiBoxTemplate className="disadv" title={header_name}>
<Markdown source={`**${translate (staticData) ("inlinewiki.rule")}:** ${AAL.rules (x)}`} />
+35 -5
View File
@@ -1,11 +1,20 @@
import * as React from "react"
import { Maybe, orN } from "../../../Data/Maybe"
import { isJust, Maybe, Nothing, orN } from "../../../Data/Maybe"
import { Record } from "../../../Data/Record"
import { ActiveActivatable } from "../../Models/View/ActiveActivatable"
import { Aside } from "../Universal/Aside"
import { ErrorMessage } from "../Universal/ErrorMessage"
import { WikiInfoContent, WikiInfoContentStateProps } from "./WikiInfoContent"
export interface WikiInfoSelector {
currentId: string
index: number
item: Record<ActiveActivatable>
}
export interface WikiInfoOwnProps {
currentId: Maybe<string>
currentId?: Maybe<string>
currentSelector?: Maybe<WikiInfoSelector>
noWrapper?: boolean
}
@@ -28,9 +37,9 @@ export class WikiInfo extends React.Component<WikiInfoProps, WikiInfoState> {
}
shouldComponentUpdate (nextProps: WikiInfoProps) {
const { currentId } = this.props
const { currentId, currentSelector } = this.props
return nextProps.currentId !== currentId
return nextProps.currentId !== currentId || nextProps.currentSelector !== currentSelector
}
componentDidUpdate (prevProps: WikiInfoProps, prevState: WikiInfoState) {
@@ -44,6 +53,7 @@ export class WikiInfo extends React.Component<WikiInfoProps, WikiInfoState> {
render () {
const {
currentId,
currentSelector,
noWrapper,
combinedRaces,
combinedCultures,
@@ -70,9 +80,29 @@ export class WikiInfo extends React.Component<WikiInfoProps, WikiInfoState> {
return orN (noWrapper) ? currentElement : <Aside>{currentElement}</Aside>
}
const id = (() => {
if (currentId && isJust (currentId)) {
return currentId
}
if (currentSelector && isJust (currentSelector)) {
return Maybe (currentSelector.value.currentId)
}
return Nothing
}) ()
const selector = (() => {
if (currentSelector) {
return currentSelector
}
return Nothing
}) ()
return (
<WikiInfoContent
currentId={currentId}
currentId={id}
currentSelector={selector}
noWrapper={noWrapper}
combinedRaces={combinedRaces}
combinedCultures={combinedCultures}
@@ -31,6 +31,7 @@ import { WikiCantripInfo } from "./WikiCantripInfo"
import { WikiCombatTechniqueInfo } from "./WikiCombatTechniqueInfo"
import { WikiCultureInfo } from "./WikiCultureInfo"
import { WikiEquipmentInfo } from "./WikiEquipmentInfo"
import { WikiInfoSelector } from "./WikiInfo"
import { WikiInfoContentWrapper } from "./WikiInfoContentWrapper"
import { WikiLiturgicalChantInfo } from "./WikiLiturgicalChantInfo"
import { WikiProfessionInfo } from "./WikiProfessionInfo"
@@ -110,6 +111,7 @@ const getEntry =
export interface WikiInfoContentOwnProps {
currentId: Maybe<string>
currentSelector: Maybe<WikiInfoSelector>
noWrapper?: boolean
}
@@ -136,6 +138,7 @@ export type WikiInfoContentProps =
export const WikiInfoContent: React.FC<WikiInfoContentProps> = props => {
const {
currentId: mid,
currentSelector,
noWrapper,
languages,
liturgicalChantExtensions,
@@ -165,6 +168,7 @@ export const WikiInfoContent: React.FC<WikiInfoContentProps> = props => {
return (
<WikiInfoContentWrapper noWrapper={noWrapper}>
<WikiActivatableInfo
selector={currentSelector}
staticData={staticData}
x={x}
/>
@@ -0,0 +1,120 @@
import React, { useState } from "react"
import { isNothing, Maybe } from "../../../Data/Maybe"
import { StaticDataRecord } from "../../Models/Wiki/WikiModel"
import { translate } from "../../Utilities/I18n"
import { Dialog } from "../Universal/Dialog"
import { Markdown } from "../Universal/Markdown"
import { TextArea } from "../Universal/TextArea"
import { WikiInfoSelector } from "./WikiInfo"
export interface WikiInfoCustomRuleStateProps {
savedRule: string
}
export interface WikiInfoCustomRuleOwnProps {
staticData: StaticDataRecord
selector: Maybe<WikiInfoSelector>
}
export interface WikiInfoCustomRuleDispatchProps {
saveRule (selector: WikiInfoSelector, rule: string): void
}
export type WikiInfoCustomRuleProps = WikiInfoCustomRuleStateProps & WikiInfoCustomRuleDispatchProps & WikiInfoCustomRuleOwnProps
export const WikiInfoCustomRule: React.FC<WikiInfoCustomRuleProps> = props => {
const { staticData, selector, saveRule, savedRule } = props
const [ isEditing, setIsEditing ] = useState<boolean> (false)
const [ rule, setRule ] = useState<string> (savedRule)
if (isNothing (selector)) {
return (
<p>
<strong>{`${translate (staticData) ("inlinewiki.rule")}: `}</strong>
<i>{translate (staticData) ("inlinewiki.custom.inactive")}</i>
</p>
)
}
const ruleDisplay = () => {
if (savedRule === "") {
return `*${translate (staticData) ("inlinewiki.custom.norule")}*`
}
return savedRule
}
const startEditing = () => {
if (isEditing) {
return
}
setRule (savedRule)
setIsEditing (true)
}
const saveEdit = () => {
if (!isEditing) {
return
}
saveRule (selector.value, rule)
}
const closeDialog = () => {
if (!isEditing) {
return
}
if (!rule) {
setRule (savedRule)
}
setIsEditing (false)
}
return (
<div>
<div>
<i
className="icon"
style={{ float: "right", cursor: "pointer", marginTop: ".6rem", userSelect: "none" }}
onClick={startEditing}
>
{"\uE90c"}
</i>
<p>
<Markdown
source={`**${translate (staticData) ("inlinewiki.rule")}:** ${ruleDisplay ()}`}
noWrapper
/>
</p>
</div>
<Dialog
id="edit-custom-rule"
close={closeDialog}
isOpen={isEditing}
title={translate (staticData) ("inlinewiki.custom.dialog.title")}
buttons={[
{
autoWidth: true,
label: translate (staticData) ("inlinewiki.custom.dialog.saveBtn"),
disabled: false,
onClick: saveEdit,
},
]}
>
<small>{translate (staticData) ("inlinewiki.custom.dialog.markdownInfo")}</small>
<TextArea
value={rule}
onChange={setRule}
fullWidth
autoFocus
resize="vertical"
/>
</Dialog>
</div>
)
}
@@ -0,0 +1,96 @@
import { connect } from "react-redux"
import { fmap } from "../../../Data/Functor"
import { subscriptF } from "../../../Data/List"
import { bind, bindF, ensure, isNothing } from "../../../Data/Maybe"
import { lookup } from "../../../Data/OrderedMap"
import { Record } from "../../../Data/Record"
import { ReduxDispatch } from "../../Actions/Actions"
import { setRule } from "../../Actions/DisAdvActions"
import { ActivatableDependent } from "../../Models/ActiveEntries/ActivatableDependent"
import { toActiveObjectWithId } from "../../Models/ActiveEntries/ActiveObjectWithId"
import { AppStateRecord } from "../../Models/AppState"
import { HeroModel } from "../../Models/Hero/HeroModel"
import { Advantage, isAdvantage } from "../../Models/Wiki/Advantage"
import { Disadvantage, isDisadvantage } from "../../Models/Wiki/Disadvantage"
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
import { getCurrentHeroPresent, getWiki } from "../../Selectors/stateSelectors"
import { pipe } from "../../Utilities/pipe"
import { getWikiEntry } from "../../Utilities/WikiUtils"
import { WikiInfoSelector } from "./WikiInfo"
import {
WikiInfoCustomRule, WikiInfoCustomRuleDispatchProps,
WikiInfoCustomRuleOwnProps,
WikiInfoCustomRuleStateProps,
} from "./WikiInfoCustomRule"
const mapStateToProps =
(state: AppStateRecord, ownProps: WikiInfoCustomRuleOwnProps): WikiInfoCustomRuleStateProps => ({
savedRule: ((): string => {
if (isNothing (ownProps.selector)) {
return ""
}
const selectorVal = ownProps.selector.value
const mhero = getCurrentHeroPresent (state)
if (isNothing (mhero)) {
return ""
}
const hero = mhero.value
const current_id = selectorVal.currentId
const current_index = selectorVal.index
const mwiki_entry =
bind (getWikiEntry (getWiki (state)) (current_id))
(ensure ((x): x is Record<Advantage> | Record<Disadvantage> | Record<SpecialAbility> =>
isAdvantage (x) || isDisadvantage (x) || SpecialAbility.is (x)))
console.log (mwiki_entry)
const mhero_entry =
bind (mwiki_entry)
(x => lookup (current_id)
(isAdvantage (x)
? HeroModel.AL.advantages (hero)
: isDisadvantage (x)
? HeroModel.AL.disadvantages (hero)
: HeroModel.AL.specialAbilities (hero)))
console.log (mhero_entry)
const mactive_entry =
pipe (
bindF (pipe (
ActivatableDependent.A.active,
subscriptF (current_index)
)),
fmap (toActiveObjectWithId (current_index) (current_id))
)
(mhero_entry)
if (isNothing (mactive_entry)) {
return ""
}
if (isNothing (mactive_entry.value.values.sid2)) {
return ""
}
return mactive_entry.value.values.sid2.value.toString ()
}) (),
})
const mapDispatchToProps = (dispatch: ReduxDispatch): WikiInfoCustomRuleDispatchProps => ({
saveRule (selector: WikiInfoSelector, rule: string) {
dispatch (setRule (selector, rule))
},
})
const connectWikiInfo =
connect<WikiInfoCustomRuleStateProps, WikiInfoCustomRuleDispatchProps, WikiInfoCustomRuleOwnProps, AppStateRecord> (
mapStateToProps,
mapDispatchToProps
)
export const WikiInfoCustomRuleContainer = connectWikiInfo (WikiInfoCustomRule)
+14
View File
@@ -3,3 +3,17 @@
@import "NavigationBarRight";
@import "NavigationBarTabs";
@import "NavigationBarWrapper";
.fork-badge {
align-self: center;
background-color: #b33636;
border-radius: 3px;
color: #fff;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.05em;
line-height: 1;
margin: 0 6px;
padding: 4px 7px;
white-space: nowrap;
}
+4
View File
@@ -14,6 +14,7 @@ import { translate, translateP } from "../../Utilities/I18n"
import { TabId } from "../../Utilities/LocationUtils"
import { signNeg } from "../../Utilities/NumberUtils"
import { pipe, pipe_ } from "../../Utilities/pipe"
import { current_version } from "../../Selectors/envSelectors"
import { AvatarWrapper } from "../Universal/AvatarWrapper"
import { BorderButton } from "../Universal/BorderButton"
import { IconButton } from "../Universal/IconButton"
@@ -167,6 +168,9 @@ export const NavigationBar: React.FC<NavigationBarProps> = props => {
</>
)
: null}
<Text className="fork-badge">
{`FORK ${current_version}`}
</Text>
<IconButton
icon="&#xE906;"
onClick={openSettings}
@@ -0,0 +1,34 @@
#rule-sheet {
table.rules {
max-width: 100%;
white-space: break-spaces;
tbody {
tr:not(.category-display) {
td {
padding-top: 0.2rem;
}
td:first-child {
vertical-align: baseline;
padding-right: .1rem;
}
td:last-child {
word-break: break-word;
}
}
tr.category-display {
td {
height: 6mm;
border-width: 0.75pt 0;
font: bold 10px/1 "Alegreya SC";
border-color: #424242;
}
}
}
}
}
@@ -0,0 +1,242 @@
import { createRef, RefObject } from "react"
import * as React from "react"
import { List } from "../../../../Data/List"
import { Record } from "../../../../Data/Record"
import { AttributeCombined } from "../../../Models/View/AttributeCombined"
import { StaticDataRecord } from "../../../Models/Wiki/WikiModel"
import {
RuleContainer,
RuleDictionary,
} from "../../../Selectors/derivedCharacteristicsSelectors"
import { translate } from "../../../Utilities/I18n"
import { Markdown } from "../../Universal/Markdown"
import { Sheet } from "../Sheet"
import { SheetWrapper } from "../SheetWrapper"
interface Props {
attributes: List<Record<AttributeCombined>>
staticData: StaticDataRecord
useParchment: boolean
rules: RuleContainer
}
export class RulesSheet extends React.Component<Props> {
private readonly tableRef: RefObject<HTMLTableElement>
private createdWrappers: HTMLDivElement[]
constructor (props: Props) {
super (props)
this.tableRef = createRef<HTMLTableElement> ()
this.createdWrappers = []
}
componentDidMount () {
this.clearCreatedWrappers ()
const table = this.tableRef.current
if (!table) {
return
}
const maxHeight = this.getMaxHeightForTableBody (table)
const tablebody = table.querySelector ("tbody")
if (!tablebody) {
return
}
const height = tablebody.clientHeight
if (height < maxHeight) {
return
}
const rows = this.getRows (tablebody)
this.recreateTableOnMultipleSheets (table, tablebody, rows, maxHeight)
}
componentWillUnmount () {
this.clearCreatedWrappers ()
}
private recreateTableOnMultipleSheets (
table: HTMLTableElement,
tablebody: HTMLTableSectionElement,
rows: { row: HTMLTableRowElement; height: number }[],
maxHeight: number
) {
const originalWrapper = table.closest (".sheet-wrapper")
if (!originalWrapper) {
return
}
const wrapperParent = originalWrapper.parentElement
if (!wrapperParent) {
return
}
const cloneWrapper = originalWrapper.cloneNode (true)
let currentHeight = 0
let currentTableBody = tablebody
for (const row of rows) {
const newHeight = currentHeight + row.height
if (newHeight > maxHeight) {
// @ts-ignore
const newWrapper: HTMLDivElement = cloneWrapper.cloneNode (true)
const newTableBody: HTMLTableSectionElement | null = newWrapper.querySelector ("table.rules tbody")
if (!newTableBody) {
throw new Error ("No tbody found.")
}
wrapperParent.append (newWrapper)
this.createdWrappers.push (newWrapper)
currentHeight = 0
currentTableBody = newTableBody
}
currentHeight += row.height
currentTableBody.append (row.row)
}
}
private getRows (tablebody: HTMLTableSectionElement) {
const rows: {
row: HTMLTableRowElement
height: number
}[] = []
for (let i = tablebody.rows.length; i >= 0; i--) {
const row = tablebody.rows.item (i)
if (!row) {
continue
}
rows.push ({
row,
height: row.clientHeight,
})
row.remove ()
}
return rows.reverse ()
}
render () {
const {
attributes,
staticData,
useParchment,
rules,
} = this.props
return (
<SheetWrapper>
<Sheet
id="rule-sheet"
title={translate (staticData) ("sheets.rulessheet.title")}
attributes={attributes}
staticData={staticData}
useParchment={useParchment}
>
<table className="rules" ref={this.tableRef}>
<thead>
<tr>
<th>{translate (staticData) ("sheets.rulessheet.header.name")}</th>
<th>{translate (staticData) ("sheets.rulessheet.header.rule")}</th>
</tr>
</thead>
<tbody>
{this.displayRules ("advantages", translate (staticData) ("sheets.rulessheet.advantages"), rules.advantages)}
{this.displayRules ("disadvantages", translate (staticData) ("sheets.rulessheet.disadvantages"), rules.disadvantages)}
{this.displayRules ("specialAbilities", translate (staticData) ("sheets.rulessheet.specialabilities"), rules.specialAbilities)}
</tbody>
</table>
</Sheet>
</SheetWrapper>
)
}
displayRules (
ruleKey: string,
displayName: string,
dictionary: RuleDictionary
) {
if (!Object.keys (dictionary).length) {
return null
}
const rows = []
for (const dictionaryKey in dictionary) {
const name = dictionaryKey
const rule = dictionary[dictionaryKey]
rows.push (
<tr key={`${ruleKey}_${dictionaryKey}`}>
<td>{name}</td>
<td>
<Markdown
source={rule}
noWrapper
/>
</td>
</tr>
)
}
return [
(
<tr className="category-display" key={`${ruleKey}_displayName`}>
<td colSpan={2}>{displayName}</td>
</tr>
),
...rows,
]
}
private getMaxHeightForTableBody (table: HTMLTableElement): number {
// This is a number, chosen to account for margins, etc.
const ADDITIONAL_OFFSET = 40
const sheetWrapper = table.closest (".sheet")
if (!sheetWrapper) {
return table.clientHeight
}
const sheet = table.closest (".sheet")
if (!sheet) {
return table.clientHeight
}
const sheetPadding = parseFloat (window.getComputedStyle (sheet, null).getPropertyValue ("padding-left"))
const sheetHeight = sheetWrapper.clientHeight - (sheetPadding * 2)
const header = sheetWrapper.querySelector (".sheet-header")
if (!header) {
return table.clientHeight
}
const headerHeight = header.clientHeight
if (!table.tHead) {
return table.clientHeight
}
const tableHeaderHeight = table.tHead.clientHeight
const result = sheetHeight - headerHeight - tableHeaderHeight - ADDITIONAL_OFFSET
console.log (sheetHeight, headerHeight, tableHeaderHeight, result)
return result
}
private clearCreatedWrappers () {
this.createdWrappers.forEach (elem => {
elem.remove ()
})
this.createdWrappers = []
}
}
+43 -3
View File
@@ -3,7 +3,12 @@ import { DerivedCharacteristicId } from "../../../../app/Database/Schema/Derived
import { equals } from "../../../Data/Eq"
import { fmapF } from "../../../Data/Functor"
import { find, List } from "../../../Data/List"
import { bindF, Just, Maybe, maybeRNull } from "../../../Data/Maybe"
import {
bindF,
Just,
Maybe,
maybeRNull,
} from "../../../Data/Maybe"
import { OrderedMap } from "../../../Data/OrderedMap"
import { Record } from "../../../Data/Record"
import { fst, Pair, snd } from "../../../Data/Tuple"
@@ -41,7 +46,10 @@ import { Race } from "../../Models/Wiki/Race"
import { SpecialAbility } from "../../Models/Wiki/SpecialAbility"
import { State } from "../../Models/Wiki/State"
import { StaticData, StaticDataRecord } from "../../Models/Wiki/WikiModel"
import { DCPair } from "../../Selectors/derivedCharacteristicsSelectors"
import {
DCPair,
getCustomRules,
} from "../../Selectors/derivedCharacteristicsSelectors"
import { classListMaybe } from "../../Utilities/CSS"
import { translate } from "../../Utilities/I18n"
import { Maybe as NewMaybe } from "../../Utilities/Maybe"
@@ -55,6 +63,7 @@ import { Scroll } from "../Universal/Scroll"
import { BelongingsSheet } from "./BelongingsSheet/BelongingsSheet"
import { CombatSheet } from "./CombatSheet/CombatSheet"
import { CombatSheetZones } from "./CombatSheet/CombatSheetZones"
import { RulesSheet } from "./RulesSheet/RulesSheet"
import { LiturgicalChantsSheet } from "./LiturgicalChantsSheet/LiturgicalChantsSheet"
import { MainSheet } from "./MainSheet/MainSheet"
import { SkillsSheet } from "./SkillsSheet/SkillsSheet"
@@ -85,6 +94,7 @@ export interface SheetsStateProps {
name: Maybe<string>
professionName: Maybe<string>
useParchment: boolean
showRules: boolean
zoomFactor: number
// profession: Maybe<Record<Profession>>
@@ -128,12 +138,12 @@ export interface SheetsDispatchProps {
exportAsRptok (): void
switchAttributeValueVisibility (): void
switchUseParchment (): void
switchShowRules (): void
setSheetZoomFactor (zoomFactor: number): void
}
type Props = SheetsStateProps & SheetsDispatchProps & SheetsOwnProps
export const Sheets: React.FC<Props> = props => {
const {
derivedCharacteristics,
@@ -199,6 +209,8 @@ export const Sheets: React.FC<Props> = props => {
blessedTradition,
blessings,
liturgicalChants,
showRules,
switchShowRules,
} = props
const maybeArcaneEnergy =
@@ -212,6 +224,15 @@ export const Sheets: React.FC<Props> = props => {
const nArmorZones = armorZones.map (xs => xs.length).sum ()
const nArmors = Maybe.sum (fmapF (armors) (xs => xs.length))
const customRules = getCustomRules (
advantagesActive,
disadvantagesActive,
generalsaActive,
combatSpecialAbilities
)
const hasRules = customRules.total > 0
const displayRulePage = hasRules && showRules
return (
<Page id="sheets">
<Options>
@@ -247,6 +268,13 @@ export const Sheets: React.FC<Props> = props => {
>
{translate (staticData) ("sheets.showattributevalues")}
</Checkbox>
<Checkbox
checked={showRules}
onClick={switchShowRules}
disabled={!hasRules}
>
{translate (staticData) ("sheets.usecustomrules")}
</Checkbox>
<Dropdown
label={translate (staticData) ("sheets.zoomfactor")}
value={Just (zoomFactor)}
@@ -401,6 +429,18 @@ export const Sheets: React.FC<Props> = props => {
/>
))
)}
{
displayRulePage
? (
<RulesSheet
attributes={attributes}
staticData={staticData}
useParchment={useParchment}
rules={customRules}
/>
)
: null
}
</Scroll>
</Page>
)
@@ -14,6 +14,7 @@ import { StaticDataRecord } from "../../Models/Wiki/WikiModel"
import { translate } from "../../Utilities/I18n"
import { ActivatableAddList } from "../Activatable/ActivatableAddList"
import { ActivatableRemoveList } from "../Activatable/ActivatableRemoveList"
import { WikiInfoSelector } from "../InlineWiki/WikiInfo"
import { BorderButton } from "../Universal/BorderButton"
import { Checkbox } from "../Universal/Checkbox"
import { ListHeader } from "../Universal/ListHeader"
@@ -76,7 +77,7 @@ export const SpecialAbilities: React.FC<Props> = props => {
} = props
const [ isSlideinOpen, setIsSlideinOpen ] = React.useState (false)
const [ currentId, setCurrentId ] = React.useState<Maybe<string>> (Nothing)
const [ currentWikiSelector, setCurrentWikiSelector ] = React.useState<Maybe<WikiInfoSelector>> (Nothing)
const [ currentSlideinId, setCurrentSlideinId ] = React.useState<Maybe<string>> (Nothing)
const handleShowSlidein = React.useCallback (
@@ -91,10 +92,11 @@ export const SpecialAbilities: React.FC<Props> = props => {
},
[ setIsSlideinOpen, setInactiveFilterText ]
)
const handleInfo = React.useCallback (
(id: string) => setCurrentId (Just (id)),
[ setCurrentId ]
(selector: WikiInfoSelector) => {
setCurrentWikiSelector (Just (selector))
},
[ setCurrentWikiSelector ]
)
const handleSlideinInfo = React.useCallback (
@@ -195,10 +197,10 @@ export const SpecialAbilities: React.FC<Props> = props => {
removeFromList={removeFromList}
setLevel={setLevel}
selectForInfo={handleInfo}
selectedForInfo={currentId}
selectedForInfo={currentWikiSelector}
/>
</MainContent>
<WikiInfoContainer currentId={currentId} />
<WikiInfoContainer currentSelector={currentWikiSelector} />
</Page>
)
}
+4
View File
@@ -20,3 +20,7 @@
}
}
}
.icon {
font: 20px/2pt TDE Heroes;
font-size: 2rem;
}
+93
View File
@@ -0,0 +1,93 @@
import * as React from "react"
import { Either } from "../../../Data/Either"
import { Maybe, orN } from "../../../Data/Maybe"
import { InputKeyEvent } from "../../Models/Hero/heroTypeHelpers"
import { TextFieldContainer } from "./TextFieldContainer"
export interface TextAreaProps {
autoFocus?: boolean | Maybe<boolean>
className?: string
countCurrent?: number
countMax?: number
disabled?: boolean
error?: Maybe<string> | Either<string, any>
fullWidth?: boolean
hint?: Maybe<string> | string
label?: Maybe<string> | string
type?: string
value?: string
valid?: boolean
resize?: "both"|"vertical"|"horizontal"
initialHeight?: string|number
onChange (newText: string): void
onKeyDown? (event: InputKeyEvent): void
onKeyUp? (event: InputKeyEvent): void
}
export const TextArea: React.FC<TextAreaProps> = props => {
const {
autoFocus,
className,
countCurrent,
countMax,
disabled,
error,
fullWidth,
label,
onChange,
onKeyDown,
onKeyUp,
valid,
value = "",
resize = "both",
initialHeight = "150px",
hint,
} = props
const inputRef = React.useRef<HTMLTextAreaElement | null> (null)
React.useEffect (
() => {
if (Maybe.elem (true) (Maybe.normalize (autoFocus)) && inputRef.current !== null) {
inputRef.current.focus ()
}
},
[ autoFocus ]
)
const handleChange =
React.useCallback (
(e: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>) => {
if (!orN (disabled)) {
onChange (e.target.value)
}
},
[ disabled, onChange ]
)
return (
<TextFieldContainer
className={className}
countCurrent={countCurrent}
countMax={countMax}
disabled={disabled}
error={error}
fullWidth={fullWidth}
hint={hint}
isFieldEmpty={value.length === 0}
label={label}
valid={valid}
>
<textarea
value={value}
onChange={handleChange}
onKeyPress={orN (disabled) ? undefined : onKeyDown}
onKeyUp={orN (disabled) ? undefined : onKeyUp}
readOnly={disabled}
ref={inputRef}
style={{ height: initialHeight, resize }}
/>
</TextFieldContainer>
)
}
+2 -26
View File
@@ -30,10 +30,10 @@
width: 100%;
height: 32px;
border: 1px solid var(--accent-color);
padding: 0 11px;
padding: 5px 11px;
margin: 0;
color: var(--headings-color);
font: 500 13px/30px Alegreya Sans;
font: 500 13px/20px Alegreya Sans;
display: block;
outline: 0 none;
background: none;
@@ -79,28 +79,4 @@
color: var(--uncommon-bright-color);
}
}
// hr {
// width: 100%;
// height: 0px;
// border: 0 none;
// margin: 0;
//
// &.default {
// border-bottom: 1px solid #182424;
// }
//
// &.accent {
// border-bottom: 2px solid $highlight;
// transform: scaleX(0);
// margin-top: -2px;
// @include defaultEasing (all);
// }
// }
// &.focused {
// hr.accent {
// transform: scaleX(1);
// }
// }
}
+7
View File
@@ -44,6 +44,12 @@ p {
.flex {
display: flex;
}
.hide {
display: none !important;
}
.d-block {
display: block;
}
%label {
label {
@@ -126,6 +132,7 @@ p {
@import "App/Views/Sheets/CombatSheet/CombatSheet";
@import "App/Views/Sheets/BelongingsSheet/BelongingsSheet";
@import "App/Views/Sheets/MainSheet/MainSheet";
@import "App/Views/Sheets/RulesSheet/RulesSheet";
@import "App/Views/Sheets/Sheets";
@import "App/Views/Sheets/SpellsLiturgiesSheet";
@import "App/Views/Sheets/SkillsSheet/SkillsSheet";
+20 -9
View File
@@ -3,7 +3,7 @@ import { app, BrowserWindow, ipcMain, shell } from "electron"
import * as log from "electron-log"
import { autoUpdater, CancellationToken, UpdateInfo } from "electron-updater"
import windowStateKeeper from "electron-window-state"
import { promises } from "fs"
import { existsSync, promises } from "fs"
import * as path from "path"
import { prerelease } from "semver"
import * as url from "url"
@@ -14,7 +14,12 @@ remote.initialize ()
app.setAppUserModelId ("lukasobermann.optolith")
const isUpdaterEnabled = autoUpdater.isUpdaterActive ()
// Ohne app-update.yml (lokale Test-Builds, unsignierte macOS-Builds) schlägt
// checkForUpdates immer fehl und zeigt beim Start einen Fehlerdialog —
// isUpdaterActive() prüft nur isPackaged, nicht ob eine Update-Konfig existiert.
const hasUpdateConfig = existsSync (path.join (process.resourcesPath, "app-update.yml"))
const isUpdaterEnabled = hasUpdateConfig && autoUpdater.isUpdaterActive ()
ipcMain.on (IPCChannels.IsUpdaterEnabled, event => {
event.returnValue = isUpdaterEnabled
@@ -55,7 +60,7 @@ const createWindow = async () => {
icon: path.join (app.getAppPath (), "app", "icon.png"),
frame: false,
center: true,
title: "Optolith",
title: "Optolith Fork",
acceptFirstMouse: true,
backgroundColor: "#111111",
show: false,
@@ -182,14 +187,20 @@ const main = async () => {
console.log ("main: Install extensions ...")
const installExtension = require ("electron-devtools-installer")
const installedExtensions = await installExtension.default ([
installExtension.REACT_DEVELOPER_TOOLS,
installExtension.REDUX_DEVTOOLS,
])
try {
const installExtension = require ("electron-devtools-installer")
console.log (`main: Installed extensions: ${installedExtensions}`)
const installedExtensions = await installExtension.default ([
installExtension.REACT_DEVELOPER_TOOLS,
installExtension.REDUX_DEVTOOLS,
])
console.log (`main: Installed extensions: ${installedExtensions}`)
}
catch (error) {
console.error ("main: Installing extensions failed! Skipping extensions...")
}
console.log ("main: Create Window ...")