uicore-ts
Version:
UICore is a library to build native-like user interfaces using pure Typescript. No HTML is needed at all. Components are described as TS classes and all user interactions are handled explicitly. This library is strongly inspired by the UIKit framework tha
332 lines (248 loc) • 11.5 kB
text/typescript
import { UIColor } from "./UIColor"
import { UICore } from "./UICore"
import { UIDialogView } from "./UIDialogView"
import { EXTEND, FIRST, FIRST_OR_NIL, IS, IS_NOT, LAZY_VALUE, nil, NO, UIObject, wrapInNil, YES } from "./UIObject"
import { UIRectangle } from "./UIRectangle"
import { UIRoute } from "./UIRoute"
import { UIView } from "./UIView"
import { UIViewController } from "./UIViewController"
export interface UIRootViewControllerLazyViewControllerObject<T extends typeof UIViewController> {
instance: InstanceType<T>;
class: T;
shouldShow: () => (Promise<boolean> | boolean);
isInitialized: boolean
}
export interface UIRootViewControllerLazyViewControllersObject {
[x: string]: UIRootViewControllerLazyViewControllerObject<typeof UIViewController>
}
export interface UIRootViewControllerLazyContentViewControllersObject extends UIRootViewControllerLazyViewControllersObject {
mainViewController: UIRootViewControllerLazyViewControllerObject<typeof UIViewController>
}
export class UIRootViewController extends UIViewController {
topBarView?: UIView
backgroundView: UIView = new UIView(this.view.elementID + "BackgroundView").configuredWithObject({
style: {
background: "linear-gradient(" + UIColor.whiteColor.stringValue + ", " + UIColor.blueColor.stringValue + ")",
backgroundSize: "cover",
backgroundRepeat: "no-repeat"
}
})
bottomBarView?: UIView
_contentViewController?: UIViewController
contentViewControllers: UIRootViewControllerLazyContentViewControllersObject = {
mainViewController: this.lazyViewControllerObjectWithClass(UIViewController)
}
_detailsDialogView: UIDialogView = new UIDialogView(this.view.elementID + "DetailsDialogView")
.configuredWithObject({
dismiss: EXTEND(() => {
const route = UIRoute.currentRoute
this.detailsViewControllers.allValues.forEach(
value => route.routeByRemovingComponentNamed(value.class.routeComponentName)
)
route.apply()
}
)
})
_detailsViewController?: UIViewController
detailsViewControllers: UIRootViewControllerLazyViewControllersObject = {}
constructor(view: UIView) {
super(view)
this.view.addSubview(this.backgroundView)
}
lazyViewControllerObjectWithClass<T extends typeof UIViewController>(
classObject: T,
shouldShow: () => (Promise<boolean> | boolean) = () => YES
): UIRootViewControllerLazyViewControllerObject<T> {
const result: UIRootViewControllerLazyViewControllerObject<T> = {
class: classObject,
instance: nil,
shouldShow: shouldShow,
isInitialized: NO
}
UIObject.configureWithObject(result, {
// @ts-ignore
instance: LAZY_VALUE(
() => {
result.isInitialized = YES
return new classObject(
new UIView(classObject.name.replace("ViewController", "View"))
)
}
)
})
return result
}
override async handleRoute(route: UIRoute) {
await super.handleRoute(route)
UICore.languageService.updateCurrentLanguageKey()
// Show content view
await this.setContentViewControllerForRoute(route)
await this.setDetailsViewControllerForRoute(route)
}
async setContentViewControllerForRoute(route: UIRoute) {
const contentViewControllerObject = FIRST(
await this.contentViewControllers.allValues.findAsyncSequential(
async value => IS(route.componentWithViewController(value.class)) && await value.shouldShow()
),
this.contentViewControllers.mainViewController
)
this.contentViewController = contentViewControllerObject.instance
}
async setDetailsViewControllerForRoute(route: UIRoute) {
const detailsViewControllerObject = FIRST_OR_NIL(
await this.detailsViewControllers.allValues.findAsyncSequential(
async value => IS(route.componentWithViewController(value.class)) && await value.shouldShow()
)
)
if (IS(route) && IS(this.detailsViewController) && IS_NOT(detailsViewControllerObject)) {
this.detailsViewController = undefined
this._detailsDialogView.dismiss()
this.view.setNeedsLayout()
return
}
this.detailsViewController = detailsViewControllerObject.instance
}
get contentViewController(): UIViewController | undefined {
return this._contentViewController
}
set contentViewController(controller: UIViewController) {
if (this.contentViewController == controller) {
return
}
if (this.contentViewController) {
this.removeChildViewController(this.contentViewController)
}
this._contentViewController = controller
this.addChildViewControllerInContainer(controller, this.backgroundView)
this._triggerLayoutViewSubviews()
if (this.contentViewController) {
this.contentViewController.view.style.boxShadow = "0 3px 6px 0 rgba(0, 0, 0, 0.1)"
}
this.view.setNeedsLayout()
}
get detailsViewController(): UIViewController | undefined {
return this._detailsViewController
}
set detailsViewController(controller: UIViewController | undefined) {
if (this.detailsViewController == controller) {
return
}
if (IS(this.detailsViewController)) {
this.removeChildViewController(this.detailsViewController)
}
this._detailsViewController = controller
if (!IS(controller)) {
return
}
this.addChildViewControllerInDialogView(controller, this._detailsDialogView)
this._triggerLayoutViewSubviews()
FIRST_OR_NIL(this.detailsViewController).view.style.borderRadius = "5px"
this._detailsDialogView.showInView(this.view, YES)
}
updatePageScale(
{
minScaleWidth = 700,
maxScaleWidth = 1500,
minScale = 0.7,
maxScale = 1
} = {}
) {
const actualPageWidth = window.innerWidth ?? (UIView.pageWidth * UIView.pageScale).integerValue
let scale = minScale + (maxScale - minScale) *
((actualPageWidth - minScaleWidth) / (maxScaleWidth - minScaleWidth))
scale = Math.min(scale, maxScale)
scale = Math.max(scale, minScale)
UIView.pageScale = scale
}
performDefaultLayout(
{
paddingLength = 20,
contentViewMaxWidth = 1000,
topBarHeight = 65,
bottomBarMinHeight = 100
} = {}
) {
// View bounds
const bounds = this.view.bounds
if (this.topBarView) {
this.topBarView.frame = new UIRectangle(0, 0, topBarHeight, bounds.width)
}
this.backgroundView.style.top = "" + (this.topBarView?.frame.height.integerValue ?? 0) + "px"
this.backgroundView.style.width = "100%"
this.backgroundView.style.height = "fit-content"
this.backgroundView.style.minHeight = "" +
(bounds.height - (this.topBarView?.frame.height ?? 0) - (this.bottomBarView?.frame.height ?? 0)).integerValue + "px"
const contentView: UIView = this.contentViewController?.view ?? nil
//var contentViewStyleString = contentView.viewHTMLElement.style.cssText
contentView.style.position = "relative"
contentView.style.bottom = "0"
contentView.style.top = "0"
contentView.style.width = "100%"
contentView.setPaddings(nil, nil, paddingLength, nil)
if (contentViewMaxWidth < this.backgroundView.bounds.width) {
contentView.style.marginBottom = "" +
Math.min(
(this.backgroundView.bounds.width - contentViewMaxWidth) * 0.5,
paddingLength
).integerValue + "px"
contentView.style.marginTop = "" +
Math.min(
(this.backgroundView.bounds.width - contentViewMaxWidth) * 0.5,
paddingLength
).integerValue + "px"
contentView.style.maxWidth = contentViewMaxWidth + "px"
contentView.style.left = "" +
((this.backgroundView.bounds.width - contentView.bounds.width) * 0.5).integerValue +
"px"
}
else {
contentView.style.margin = ""
contentView.style.left = ""
contentView.style.maxWidth = ""
}
// if (contentViewStyleString != contentView.style.cssText) {
//
// contentView.setNeedsLayout()
//
// }
// Triggering immediate layout to ensure that the intrinsicContentHeight calculations work well
this.contentViewController?._triggerLayoutViewSubviews()
let contentViewControllerViewHeight = contentView.intrinsicContentHeight(
contentView.bounds.width
)
const detailsViewControllerViewHeight = FIRST_OR_NIL(this.detailsViewController).view.intrinsicContentHeight(
contentView.bounds.width)
if (detailsViewControllerViewHeight > contentViewControllerViewHeight) {
contentViewControllerViewHeight = detailsViewControllerViewHeight
}
contentView.style.height = "" + contentViewControllerViewHeight.integerValue + "px"
//contentView.setNeedsLayout()
if (IS(this.detailsViewController)) {
contentView.style.transform = "translateX(" + 0 + "px)"
this.detailsViewController.view.frame = this.backgroundView.frame.rectangleWithInset(
paddingLength
).rectangleWithWidth(
contentView.bounds.width,
0.5
).rectangleWithHeight(
Math.max(
this.detailsViewController.view.intrinsicContentHeight(
this.detailsViewController.view.bounds.width
),
contentView.bounds.height
)
)
}
else {
contentView.style.transform = "translateX(" + 0 + "px)"
}
if (this.bottomBarView) {
this.bottomBarView.frame = this.backgroundView.frame.rectangleWithY(
this.backgroundView.frame.max.y
).rectangleWithHeight(
Math.max(bottomBarMinHeight, this.bottomBarView.intrinsicContentHeight(this.backgroundView.frame.width))
)
}
wrapInNil(this._detailsDialogView).setMaxSizes(this.bottomBarView?.frame.max.y ?? 0)
}
}