Coder Social home page Coder Social logo

flyter's Introduction

Softweng project

This project was developed for the Softweng MSE master at HES-SO. It's a small blog made with Vuepress automatically built with github actions on push.

You can check it out here!

Technologies

The purpose of the project was to familiarize with frontend framework technologies like Vue/React or other. I chose to go on using Vuepress to build the blog. As such, we use:

  • Vuejs
  • Vuex
  • Vuepress

As main technologies.

Building

In order to play with this:

  1. Clone this repository and cd in it
  2. Run npm install to install all dependencies as well as npm i -g vuepress to load the vuepress builder
  3. Run npm run dev to start the blog locally on your machine

Running in docker

If you want to run this small site in docker, do the following:

  1. Run docker build --tag vpovesco:1.0 . to build an image from the given dockerfile
  2. Run docker run -p 8080:8080 -d vpovesco:1.0 to run it on port 8080

It internally uses live-server which is not the best choice to expose a website, but it works for a proof-of-concept.

Running tests

A small test suite was developed to check the behavior of the Vuex store, you can run it with npm test. It's built using Jest and babel-jest for transpilation.

Something cool about this blog

Every visitor can have his own set of favorite article. When on an article simply click the heart button which will add it to vuex and automatically persist it to local storage using vuex-persist, a plugin for Vuex.

Difficulties encountered

Vuepress is quite tough to get started with. Theming is complex and extending an existing theme is more complex than it seems. Loading external libraries like Vuex is also troublesome and doesn't follow the standard registration flow. But all in all it was a nice experiment.

Making vuex persist work with SSR

Vuepress couldn't build the project because we're using window.localStorage in the store to persist it automatically. In order for everything to work, after a bit of digging, I ended up with the following solution:

if (typeof process === 'undefined') {
    // We're in browser!
}

This also allows us to run jest tests without problems.

flyter's People

Contributors

ovesco avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flyter's Issues

date type

Hi @ovesco

If I want to create a date type, how to start?

Thanks
Eric Xin

Incorrect types file: multiple default exports

The distributed types file 'main.d.ts' have multiple default exports harming intelisense in vscode

Seems parcel does a poor job creating a distribution type file

Here is the main.d.ts content:

import { Instance as _Instance1 } from "@popperjs/core";
export const parseTemplate: (markup: string | HTMLElement | (() => string)) => HTMLElement;
export class Instance {
    flyterElement: HTMLElement;
    constructor(domTarget: HTMLElement, config: Config, typeGetter: (name: string) => typeData, rendererGetter: (name: string) => rendererData);
    getDomTarget(): HTMLElement;
    getConfig(key: string, callback?: boolean): any;
    getRawConfig(): Config;
    updateConfig(config: DeepPartial<Config>): void;
    getCurrentSession(): EditionSession | null;
    getFlyterElement(): HTMLElement;
    open(): Promise<EditionSession | undefined>;
    close(): Promise<void>;
    getValue(): any;
    setValue(val: any): Promise<void>;
    refresh(): Promise<void>;
    buildStandardReadableValue(val: any | null): Promise<string>;
    deleteSession(): void;
    disable(): Promise<void>;
    enable(): Promise<void>;
    destroy(): Promise<void>;
    buildType(): import("types").FlyterType<import("types").anyConfigObject>;
    buildRenderer(): import("types").FlyterRenderer<import("types").anyConfigObject>;
}
export default Instance;
interface Config {
    themes: anyConfigObject;
    trigger: "click" | "hover" | "none" | ((instance: Instance) => "click" | "hover" | "none");
    submitOnEnter: boolean | ((instance: Instance) => boolean);
    triggerOnTarget: boolean | ((instance: Instance) => boolean);
    emptyValue: any | ((instance: Instance) => any);
    initialValue: any | ((instance: Instance) => any);
    emptyValueDisplay: string | ((instance: Instance) => string);
    valueFormatter: (value: any, instance: Instance) => string | Promise<string>;
    onOpen: (instance: Instance) => Promise<any> | any;
    onDisabled: (instance: Instance) => Promise<any> | any;
    onEnabled: (Instance: Instance) => Promise<any> | any;
    onClose: (instance: Instance) => Promise<any> | any;
    onDestroy: (instance: Instance) => Promise<any> | any;
    onSubmit: (value: any, instance: Instance) => Promise<any> | any;
    onLoading: (status: boolean, instance: Instance) => void;
    onRendererLoading: (status: boolean, instance: Instance) => any;
    onError: (error: Error, instance: Instance) => Promise<any> | any;
    onCancel: (instance: Instance) => Promise<any> | any;
    validate: (value: any, instance: Instance) => Promise<Error | boolean> | Error | boolean;
    server: {
        url: string | null | ((instance: Instance) => string);
        queryParams: anyConfigObject | ((instance: Instance) => anyConfigObject);
        method: string | ((instance: Instance) => string);
        resultFormatter: (data: any, value: any) => any;
    };
    type: {
        name: string | ((instance: Instance) => string);
        config: any | ((instance: Instance) => any);
    };
    renderer: {
        name: string | ((instance: Instance) => string);
        config: any | ((instance: Instance) => any);
    };
    okButton: {
        enabled: boolean | ((instance: Instance) => boolean);
        text: string | ((instance: Instance) => string);
    };
    cancelButton: {
        enabled: boolean | ((instance: Instance) => boolean);
        text: string | ((instance: Instance) => string);
    };
    template: {
        edit: string | ((instance: Instance) => string);
        buttons: string | ((instance: Instance) => string);
        read: string | ((instance: Instance) => string);
        loading: string | ((instance: Instance) => string);
    };
}
declare class EditionSession {
    constructor(instance: Instance, type: FlyterType<any>, renderer: FlyterRenderer<any>);
    getType(): FlyterType<any>;
    getInstance(): Instance;
    getRenderer(): FlyterRenderer<any>;
    getFlyterElement(): HTMLElement;
    getSessionMarkup(): HTMLElement;
    initialize(): Promise<void>;
    openEdition(): Promise<void>;
    cancel(): Promise<void>;
    closeSession(): Promise<void>;
    submit(): Promise<void>;
}
type anyConfigObject = {
    [key: string]: any;
};
type DeepPartial<T> = {
    [P in keyof T]?: DeepPartial<T[P]> | T[P];
};
type TypeConstructor<C extends anyConfigObject = anyConfigObject> = new (getSession: () => EditionSession, config: C) => FlyterType<C>;
type RendererConstructor = new (getSession: () => EditionSession, config: anyConfigObject) => FlyterRenderer<anyConfigObject>;
type rendererData = {
    rendererConfig: anyConfigObject;
    renderer: RendererConstructor;
};
type typeData = {
    typeConfig: anyConfigObject;
    type: TypeConstructor;
};
export abstract class FlyterType<Config> {
    protected getSession: () => EditionSession;
    protected config: Config;
    constructor(getSession: () => EditionSession, config: Config);
    getTypeConfig(): Config;
    abstract init(): Promise<any> | any;
    abstract show(container: HTMLElement, value: any): Promise<any> | any;
    abstract getCurrentValue(): any;
    abstract getReadableValue(val: any): Promise<string> | string;
    abstract disable(status: boolean): any;
    abstract onDestroy(): Promise<any> | any;
}
export abstract class FlyterRenderer<Config> {
    protected getSession: () => EditionSession;
    protected config: Config;
    constructor(getSession: () => EditionSession, config: Config);
    getRendererConfig(): Config;
    abstract getMarkup(): HTMLElement | null;
    abstract init(): Promise<any> | any;
    abstract error(error: Error): any;
    abstract show(markup: HTMLElement): Promise<any> | any;
    abstract hide(): Promise<any> | any;
    abstract destroy(): Promise<any> | any;
    abstract setLoading(loading: boolean): any;
}
export class ManyInstance {
    constructor(instances: Instance[]);
    getInstances(): Instance[];
    updateAllConfig(config: DeepPartial<Config>): void;
    getCurrentSessions(): import("EditionSession").ManyInstance[];
    openAll(): Promise<any[]>;
    closeAll(): Promise<any[]>;
    refreshAll(): Promise<any[]>;
    destroyAll(): Promise<any[]>;
    enableAll(): Promise<void>;
    disableAll(): void;
}
export default ManyInstance;
export default Theme;
declare class Flyter {
    registerRenderer(name: string, renderer: any, baseRendererConfig: anyConfigObject): void;
    registerType<C extends anyConfigObject>(name: string, type: TypeConstructor<C>, baseTypeConfig: C): void;
    registerTheme(name: string, theme: any, baseThemeConfig: anyConfigObject): void;
    attach(target: HTMLElement | string | HTMLElement[] | NodeList, givenConfig?: DeepPartial<Config> | string): Instance | ManyInstance;
}
export default Flyter;
type valOrAsync<T> = T | ((renderer: PopupRenderer) => T) | ((renderer: PopupRenderer) => Promise<T>);
type PopupConfigType = {
    popper: (...args: any[]) => _Instance1 | _Instance1;
    popperConfig: valOrAsync<{
        placement: string | anyConfigObject;
    }>;
    transitionDuration: valOrAsync<number>;
    title: valOrAsync<string | null>;
    closeOnClickOutside: valOrAsync<boolean>;
    popupTemplate: valOrAsync<string>;
    popupClass: valOrAsync<string>;
    onInit: ((renderer: PopupRenderer) => any) | ((renderer: PopupRenderer) => Promise<any>);
    onShow: ((renderer: PopupRenderer) => any) | ((renderer: PopupRenderer) => Promise<any>);
    onHide: ((renderer: PopupRenderer) => any) | ((renderer: PopupRenderer) => Promise<any>);
};
export class PopupRenderer extends FlyterRenderer<PopupConfigType> {
    getPopperInstance(): _Instance1 | null;
    getMarkup(): HTMLElement | null;
    init(): Promise<void>;
    error(error: Error): void;
    show(markup: HTMLElement): Promise<void>;
    hide(): Promise<void>;
    destroy(): Promise<void>;
    setLoading(loading: boolean): void;
}
export default PopupRenderer;
interface BootstrapThemeConfig {
    size: "xs" | "md" | "lg" | "xl" | "sm";
    okBtntnTheme: string;
    cancelBtnTheme: string;
    spinnerTheme: string;
    inline: boolean;
}
type _valOrAsync1<T> = T | ((renderer: InlineRenderer) => T) | ((renderer: InlineRenderer) => Promise<T>);
type InlineConfigType = {
    closeOnClickOutside: _valOrAsync1<boolean>;
    inlineTemplate: _valOrAsync1<string>;
    containerClass: _valOrAsync1<string>;
    onInit: ((renderer: InlineRenderer) => any) | ((renderer: InlineRenderer) => Promise<any>);
    onShow: ((renderer: InlineRenderer) => any) | ((renderer: InlineRenderer) => Promise<any>);
    onHide: ((renderer: InlineRenderer) => any) | ((renderer: InlineRenderer) => Promise<any>);
};
export class InlineRenderer extends FlyterRenderer<InlineConfigType> {
    getMarkup(): HTMLElement | null;
    init(): Promise<void>;
    error(error: Error): void;
    show(markup: HTMLElement): Promise<void>;
    hide(): Promise<void>;
    destroy(): Promise<void>;
    setLoading(loading: boolean): void;
}
export default InlineRenderer;
export type TextTypeConfig = {
    class: string;
    type: string;
    attributes: string;
    treatEmptyAsNull: boolean;
};
export default TextType;
type DataSource = Array<{
    value: any;
    label: string;
}>;
interface BaseChoiceConfig {
    dataSource: DataSource | (() => DataSource) | (() => Promise<DataSource>);
    class: string;
}
export interface SelectTypeConfig extends BaseChoiceConfig {
    multiple: boolean;
    displaySeparator: string;
    showEmptyValue: boolean;
}
export default SelectType;
export interface RadioTypeConfig extends BaseChoiceConfig {
    labelClass: string;
    radioClass: string;
    inputContainerClass: string;
}
export default RadioType;
export interface CheckboxTypeConfig extends BaseChoiceConfig {
    displaySeparator: string;
    checkboxClass: string;
    labelClass: string;
    inputContainerClass: string;
}
export default CheckboxType;
declare const flyter: FlyterBuilder;
export const withBootstrapTheme: (config?: DeepPartial<BootstrapThemeConfig>) => void;
export const withPopupRenderer: (config?: DeepPartial<PopupConfigType>) => void;
export const withInlineRenderer: (config?: DeepPartial<InlineConfigType>) => void;
export const withTextType: () => void;
export const withSelectType: () => void;
export const withCheckboxType: () => void;
export const withRadioType: () => void;
export default flyter;

//# sourceMappingURL=main.d.ts.map

Feature Request: allow to configure popover container

When using the popover renderer, the popover markup is unconditionally added to body element: https://github.com/ovesco/flyter/blob/master/src/renderer/PopupRenderer.ts#L143

When using the popover renderer in a Bootstrap Modal, the Modal prevents the popover to receive focus.

Bootstrap allow to configure the container of the Popover: https://getbootstrap.com/docs/5.3/components/popovers/#custom-container

I managed to fix by moving the popover element in renderer.config.onInit:

   const flyterInstance = flyter.attach(this.querySelector('[data-flyter]'), {
      initialValue: this.value,   
      renderer: {
        config: {
          onInit: (renderer) => {
            if (this.container) {
              const el = renderer.getMarkup()
              document.querySelector(this.container)?.appendChild(el)
            }
          },
        },
      },
    })

It works but required to hook into internals of flyter which may break in the future

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.