Commit 1ca4c2bc authored by nanahira's avatar nanahira

Merge branch 'master' of git.mycard.moe:mycard/console

parents 421db8af 018fdd4c
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "console-web",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@angular/animations": "~12.2.0", "@angular/animations": "~12.2.0",
...@@ -16,6 +15,8 @@ ...@@ -16,6 +15,8 @@
"@angular/platform-browser": "~12.2.0", "@angular/platform-browser": "~12.2.0",
"@angular/platform-browser-dynamic": "~12.2.0", "@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~12.2.0", "@angular/router": "~12.2.0",
"@fortawesome/fontawesome-free": "^5.15.4",
"@json-editor/json-editor": "^2.5.4",
"bootstrap": "^5.1.0", "bootstrap": "^5.1.0",
"feather-icons": "^4.28.0", "feather-icons": "^4.28.0",
"rxjs": "~6.6.0", "rxjs": "~6.6.0",
...@@ -2128,6 +2129,15 @@ ...@@ -2128,6 +2129,15 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/@fortawesome/fontawesome-free": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz",
"integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@istanbuljs/schema": { "node_modules/@istanbuljs/schema": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
...@@ -2159,6 +2169,17 @@ ...@@ -2159,6 +2169,17 @@
"schema-utils": "^2.7.0" "schema-utils": "^2.7.0"
} }
}, },
"node_modules/@json-editor/json-editor": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@json-editor/json-editor/-/json-editor-2.5.4.tgz",
"integrity": "sha512-P1T7ixZ1vaS5pdQlkaLXH0B8f2AQOpHEOE3oKjMcQaLHkdGHEc225sknbRk+RLNUeG/zvCuMGUaxbE5WPPzAXg==",
"dependencies": {
"core-js": "^3.6.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/@ngtools/webpack": { "node_modules/@ngtools/webpack": {
"version": "12.2.1", "version": "12.2.1",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.1.tgz", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.1.tgz",
...@@ -17452,6 +17473,11 @@ ...@@ -17452,6 +17473,11 @@
"integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==",
"dev": true "dev": true
}, },
"@fortawesome/fontawesome-free": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz",
"integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg=="
},
"@istanbuljs/schema": { "@istanbuljs/schema": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
...@@ -17477,6 +17503,14 @@ ...@@ -17477,6 +17503,14 @@
"schema-utils": "^2.7.0" "schema-utils": "^2.7.0"
} }
}, },
"@json-editor/json-editor": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@json-editor/json-editor/-/json-editor-2.5.4.tgz",
"integrity": "sha512-P1T7ixZ1vaS5pdQlkaLXH0B8f2AQOpHEOE3oKjMcQaLHkdGHEc225sknbRk+RLNUeG/zvCuMGUaxbE5WPPzAXg==",
"requires": {
"core-js": "^3.6.5"
}
},
"@ngtools/webpack": { "@ngtools/webpack": {
"version": "12.2.1", "version": "12.2.1",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.1.tgz", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.1.tgz",
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
"@angular/platform-browser": "~12.2.0", "@angular/platform-browser": "~12.2.0",
"@angular/platform-browser-dynamic": "~12.2.0", "@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~12.2.0", "@angular/router": "~12.2.0",
"@fortawesome/fontawesome-free": "^5.15.4",
"@json-editor/json-editor": "^2.5.4",
"bootstrap": "^5.1.0", "bootstrap": "^5.1.0",
"feather-icons": "^4.28.0", "feather-icons": "^4.28.0",
"rxjs": "~6.6.0", "rxjs": "~6.6.0",
......
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1> <h1 class="h2">编辑应用</h1>
<div class="btn-toolbar mb-2 mb-md-0"> <div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2"> <div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-secondary">提交</button> <button type="button" class="btn btn-sm btn-outline-secondary" (click)="submit()" [disabled]="!app">提交</button>
<button type="button" class="btn btn-sm btn-outline-secondary">重置</button> <button type="button" class="btn btn-sm btn-outline-secondary" (click)="reset()" [disabled]="!app">重置</button>
</div> </div>
<!-- <button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle">-->
<!-- <span data-feather="calendar"></span>-->
<!-- This week-->
<!-- </button>-->
</div> </div>
</div> </div>
<div #form></div>
{{app | async | json}}
import { Component, OnInit } from '@angular/core'; import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { mergeMap } from 'rxjs/operators'; import { mergeMap, tap } from 'rxjs/operators';
import { AppService } from './app.service'; import { AppService, AppsJson } from './app.service';
// @ts-ignore
import { JSONEditor } from '@json-editor/json-editor';
const schema = {
title: 'Person',
type: 'object',
required: ['name', 'age', 'date', 'favorite_color', 'gender', 'location', 'pets'],
properties: {
name: {
type: 'string',
description: 'First and Last name',
minLength: 4,
default: 'Jeremy Dorn',
},
age: {
type: 'integer',
default: 25,
minimum: 18,
maximum: 99,
},
favorite_color: {
type: 'string',
format: 'color',
title: 'favorite color',
default: '#ffa500',
},
gender: {
type: 'string',
enum: ['male', 'female', 'other'],
},
date: {
type: 'string',
format: 'date',
options: {
flatpickr: {},
},
},
location: {
type: 'object',
title: 'Location',
properties: {
city: {
type: 'string',
default: 'San Francisco',
},
state: {
type: 'string',
default: 'CA',
},
citystate: {
type: 'string',
description: 'This is generated automatically from the previous two fields',
template: '{{city}}, {{state}}',
watch: {
city: 'location.city',
state: 'location.state',
},
},
},
},
pets: {
type: 'array',
format: 'table',
title: 'Pets',
uniqueItems: true,
items: {
type: 'object',
title: 'Pet',
properties: {
type: {
type: 'string',
enum: ['cat', 'dog', 'bird', 'reptile', 'other'],
default: 'dog',
},
name: {
type: 'string',
},
},
},
default: [
{
type: 'dog',
name: 'Walter',
},
],
},
},
};
JSONEditor.defaults.language = 'zh-CN';
JSONEditor.defaults.languages['zh-CN'] = {
error_notset: 'Property must be set',
error_notempty: 'Value required',
error_enum: 'Value must be one of the enumerated values',
error_const: 'Value must be the constant value',
error_anyOf: 'Value must validate against at least one of the provided schemas',
error_oneOf: 'Value must validate against exactly one of the provided schemas. It currently validates against {{0}} of the schemas.',
error_not: 'Value must not validate against the provided schema',
error_type_union: 'Value must be one of the provided types',
error_type: 'Value must be of type {{0}}',
error_disallow_union: 'Value must not be one of the provided disallowed types',
error_disallow: 'Value must not be of type {{0}}',
error_multipleOf: 'Value must be a multiple of {{0}}',
error_maximum_excl: 'Value must be less than {{0}}',
error_maximum_incl: 'Value must be at most {{0}}',
error_minimum_excl: 'Value must be greater than {{0}}',
error_minimum_incl: 'Value must be at least {{0}}',
error_maxLength: 'Value must be at most {{0}} characters long',
error_minLength: 'Value must be at least {{0}} characters long',
error_pattern: 'Value must match the pattern {{0}}',
error_additionalItems: 'No additional items allowed in this array',
error_maxItems: 'Value must have at most {{0}} items',
error_minItems: 'Value must have at least {{0}} items',
error_uniqueItems: 'Array must have unique items',
error_maxProperties: 'Object must have at most {{0}} properties',
error_minProperties: 'Object must have at least {{0}} properties',
error_required: "Object is missing the required property '{{0}}'",
error_additional_properties: 'No additional properties allowed, but property {{0}} is set',
error_property_names_exceeds_maxlength: 'Property name {{0}} exceeds maxLength',
error_property_names_enum_mismatch: 'Property name {{0}} does not match any enum values',
error_property_names_const_mismatch: 'Property name {{0}} does not match the const value',
error_property_names_pattern_mismatch: 'Property name {{0}} does not match pattern',
error_property_names_false: 'Property name {{0}} fails when propertyName is false',
error_property_names_maxlength: 'Property name {{0}} cannot match invalid maxLength',
error_property_names_enum: 'Property name {{0}} cannot match invalid enum',
error_property_names_pattern: 'Property name {{0}} cannot match invalid pattern',
error_property_names_unsupported: 'Unsupported propertyName {{0}}',
error_dependency: 'Must have property {{0}}',
error_date: 'Date must be in the format {{0}}',
error_time: 'Time must be in the format {{0}}',
error_datetime_local: 'Datetime must be in the format {{0}}',
error_invalid_epoch: 'Date must be greater than 1 January 1970',
error_ipv4: 'Value must be a valid IPv4 address in the form of 4 numbers between 0 and 255, separated by dots',
error_ipv6: 'Value must be a valid IPv6 address',
error_hostname: 'The hostname has the wrong format',
button_save: 'Save',
button_copy: 'Copy',
button_cancel: 'Cancel',
button_add: 'Add',
button_delete_all: 'All',
button_delete_all_title: 'Delete All',
button_delete_last: 'Last {{0}}',
button_delete_last_title: 'Delete Last {{0}}',
button_add_row_title: 'Add {{0}}',
button_move_down_title: 'Move down',
button_move_up_title: 'Move up',
button_properties: 'Properties',
button_object_properties: '属性',
button_copy_row_title: 'Copy {{0}}',
button_delete_row_title: 'Delete {{0}}',
button_delete_row_title_short: 'Delete',
button_copy_row_title_short: 'Copy',
button_collapse: '折叠',
button_expand: '展开',
button_edit_json: '编辑 JSON',
button_upload: 'Upload',
flatpickr_toggle_button: 'Toggle',
flatpickr_clear_button: 'Clear',
choices_placeholder_text: 'Start typing to add value',
default_array_item_title: 'item',
button_delete_node_warning: 'Are you sure you want to remove this node?',
};
@Component({ @Component({
selector: 'app-app', selector: 'app-app',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'], styleUrls: ['./app.component.css'],
}) })
export class AppComponent implements OnInit { export class AppComponent implements AfterViewInit {
app = this.route.params.pipe(mergeMap((params) => this.appService.getApp(params['id']))); app?: AppsJson.App;
editor: JSONEditor;
@ViewChild('form')
form!: ElementRef<HTMLElement>;
constructor(private route: ActivatedRoute, private appService: AppService) {} constructor(private route: ActivatedRoute, private appService: AppService) {}
ngOnInit(): void {} ngAfterViewInit(): void {
this.route.params.pipe(mergeMap((params) => this.appService.getApp(params['id']))).subscribe((app) => {
this.app = app;
this.editor ??= new JSONEditor(this.form.nativeElement, {
schema,
theme: 'bootstrap4',
iconlib: 'fontawesome5',
disable_properties: true,
});
this.editor.setValue(this.app);
});
}
submit() {
console.log(this.editor.getValue());
}
reset() {
location.reload();
}
} }
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {Observable} from "rxjs"; import { Observable } from 'rxjs';
import {HttpClient} from "@angular/common/http"; import { HttpClient } from '@angular/common/http';
import {map} from "rxjs/operators"; import { map } from 'rxjs/operators';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class AppService { export class AppService {
apps: Observable<AppsJson.App[]> = this.http.get<AppsJson.App[]>('https://api.mycard.moe/apps.json'); apps: Observable<AppsJson.App[]> = this.http.get<AppsJson.App[]>('https://api.mycard.moe/apps.json');
constructor(private http: HttpClient) { constructor(private http: HttpClient) {}
}
getApp(id: string) { getApp(id: string): Observable<AppsJson.App> {
return this.apps.pipe(map(apps => apps.find(app => app.id == id))) return this.apps.pipe(map((apps) => apps.find((app) => app.id == id)!));
} }
} }
export namespace AppsJson { export namespace AppsJson {
export const enum Locale { export const enum Locale {
zh_CN = 'zh-CN', zh_CN = 'zh-CN',
en_US = 'en-US', en_US = 'en-US',
...@@ -119,5 +116,4 @@ export namespace AppsJson { ...@@ -119,5 +116,4 @@ export namespace AppsJson {
data?: any; data?: any;
price?: Price; price?: Price;
} }
} }
...@@ -115,3 +115,7 @@ ...@@ -115,3 +115,7 @@
#sidebarMenu { #sidebarMenu {
overflow-y: auto; overflow-y: auto;
} }
/*.nav-link {*/
/* padding-bottom: 0;*/
/*}*/
<header class='navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow'> <header class='navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow'>
<a class='navbar-brand col-md-3 col-lg-2 me-0 px-3' href='#'>MyCard Partner Console</a> <a class='navbar-brand col-md-3 col-lg-2 me-0 px-3' href='#'>MyCard Partner</a>
<button class='navbar-toggler position-absolute d-md-none collapsed' type='button' data-bs-toggle='collapse' <button class='navbar-toggler position-absolute d-md-none collapsed' type='button' data-bs-toggle='collapse'
data-bs-target='#sidebarMenu' aria-controls='sidebarMenu' aria-expanded='false' data-bs-target='#sidebarMenu' aria-controls='sidebarMenu' aria-expanded='false'
aria-label='Toggle navigation'> aria-label='Toggle navigation'>
...@@ -17,17 +17,17 @@ ...@@ -17,17 +17,17 @@
<div class='row'> <div class='row'>
<nav id='sidebarMenu' class='col-md-3 col-lg-2 d-md-block bg-light sidebar collapse'> <nav id='sidebarMenu' class='col-md-3 col-lg-2 d-md-block bg-light sidebar collapse'>
<div class='position-sticky pt-3'> <div class='position-sticky pt-3'>
<h6 class='sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted'> <h6 class='sidebar-heading d-flex justify-content-between align-items-center px-3 mb-1 text-muted'>
<span>Apps</span> <span>应用</span>
<a class='link-secondary' href='#' aria-label='Add a new report'> <a class='link-secondary' href='#' aria-label='Add a new report'>
<span data-feather='plus-circle'></span> <span data-feather='plus-circle'></span>
</a> </a>
</h6> </h6>
<ul class='nav flex-column'> <ul class='nav flex-column'>
<li class='nav-item' *ngFor='let app of apps | async'> <li class='nav-item' *ngFor='let app of apps | async'>
<a class='nav-link' [routerLink]="['apps', app.id]"> <a class='nav-link' [routerLink]="['apps', app.id]" routerLinkActive="active">
<img class='feather' [src]='app.icon'> <img class='feather' [src]='app.icon' *ngIf="app.icon">
{{app.id}} {{showName(app)}}
</a> </a>
</li> </li>
</ul> </ul>
......
import {AfterViewInit, Component} from '@angular/core'; import { AfterViewInit, Component } from '@angular/core';
import feather from 'feather-icons'; import feather from 'feather-icons';
import {AppService} from "./app/app.service"; import { AppService, AppsJson } from './app/app.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './mycard.component.html', templateUrl: './mycard.component.html',
styleUrls: ['./mycard.component.css'] styleUrls: ['./mycard.component.css'],
}) })
export class MyCardComponent implements AfterViewInit { export class MyCardComponent implements AfterViewInit {
title = 'console-web'; title = 'console-web';
apps = this.appService.apps; apps = this.appService.apps;
constructor(private appService: AppService) { constructor(private appService: AppService) {}
}
ngAfterViewInit(): void { ngAfterViewInit(): void {
feather.replace(); feather.replace();
} }
showName(app: AppsJson.App) {
// getI18nName(app: AppsJson.App){ if (!app.name) return app.id;
// return app.name[''] return app.name['zh-CN'];
// } }
} }
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
@import "~bootstrap"; @import "~bootstrap";
@import "~@fortawesome/fontawesome-free/css/all.css";
body { body {
font-size: .875rem; font-size: .875rem;
......
declare interface SchemaOptions {
properties: {
fontSize: {
options: {
choices_options: any;
};
};
};
}
declare type CSSIntegrationTypes =
| "barebones"
| "html"
| "bootstrap2"
| "bootstrap3"
| "bootstrap4"
| "foundation3"
| "foundation4"
| "foundation5"
| "foundation6"
| "jqueryui"
| "materialize";
declare type IconLibraries =
| "bootstrap2"
| "bootstrap3"
| "foundation2"
| "foundation3"
| "jqueryui"
| "fontawesome3"
| "fontawesome4"
| "fontawesome5"
| "materialicons";
declare interface JSONEditorOptions {
// If true, JSON Editor will load external URLs in $ref via ajax. false
ajax?: boolean;
// Allows schema references to work either with or without cors;
// set to protocol://host:port when api is served by different host.
ajaxBase?: string;
// If true, JSON Editor will make ajax call with
// [credentials](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials). false
ajaxCredentials?: boolean;
// If true, the label will not be displayed/added. false
compact?: boolean;
// If true, remove all "add row" buttons from arrays. false
disable_array_add?: boolean;
// If true, remove all "delete row" buttons from arrays. false
disable_array_delete?: boolean;
// If true, remove all "move up" and "move down" buttons from arrays. false
disable_array_reorder?: boolean;
// If true, add copy buttons to arrays. false
enable_array_copy?: boolean;
// If true, remove all collapse buttons from objects and arrays. false
disable_collapse?: boolean;
// If true, remove all Edit JSON buttons from objects. false
disable_edit_json?: boolean;
// If true, remove all Edit Properties buttons from objects. false
disable_properties?: boolean;
// If true, array controls (add, delete etc) will be displayed at top of list. false
array_controls_top?: boolean;
// The first part of the `name` attribute of form inputs in the editor.
// An full example name is`root[person][name]` where "root" is the form_name_root.root
form_name_root?: string;
// The icon library to use for the editor. See the CSS Integration section below for more info. null
iconlib?: IconLibraries | null;
// Display only icons in buttons. This works only if iconlib is set. false
remove_button_labels?: boolean;
// If true, objects can only contain properties defined with the properties keyword. false
no_additional_properties?: boolean;
// An object containing schema definitions for URLs. Allows you to pre-define external schemas. {}
refs?: any;
// If true, all schemas that don't explicitly set the required property will be required. false
required_by_default?: boolean;
// If true, makes oneOf copy properties over when switching. true
keep_oneof_values?: boolean;
// A valid JSON Schema to use for the editor. Version 3 and Version 4 of the draft specification are supported. {}
schema?: any;
// When to show validation errors in the UI. Valid values are interaction, change, always, and never. "interaction"
show_errors?: "interaction" | "change" | "always" | "never";
// Seed the editor with an initial value. This should be valid against the editor's schema. null
startval?: any;
// The JS template engine to use. See the Templates and Variables section below for more info. default
template?:
| "ejs"
| "handlebars"
| "hogan"
| "markup"
| "mustache"
| "swig"
| "underscore";
// The CSS theme to use. See the CSS Integration section below for more info. html
theme?: CSSIntegrationTypes;
// If true, only required properties will be included by default. false
display_required_only?: boolean;
// If true, NON required properties will have an extra toggable checkbox near the title that determines if the value must be included or not in the editor´s value false
show_opt_in?: boolean;
// If true, displays a dialog box with a confirmation message before node deletion. true
prompt_before_delete?: boolean;
// The default value of `format` for objects. If set to table for example, objects will use table layout if `format` is not specified. normal
object_layout?: "normal" | "table" | "grid";
}
declare class JSONEditor {
constructor(element: Element, options?: JSONEditorOptions);
static defaults: {
options: JSONEditorOptions;
};
getValue(): any;
on(event: string, callback: () => void): void;
validate(): string[];
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment