修改了大量代码

This commit is contained in:
Xu Chang 2022-04-28 19:10:52 +08:00
parent 26887d71e0
commit 7e123a3f15
27 changed files with 457 additions and 62 deletions

View File

@ -1,5 +1,5 @@
import { UniversalContext } from 'oak-domain/lib/store/UniversalContext';
import { EntityDict } from 'oak-app-domain/EntityDict';
import { EntityDict } from 'oak-app-domain';
import { RowStore } from 'oak-domain/lib/types';
export declare abstract class GeneralRuntimeContext<ED extends EntityDict> extends UniversalContext<ED> {
applicationId: string;
@ -10,6 +10,7 @@ export declare abstract class GeneralRuntimeContext<ED extends EntityDict> exten
name: 1;
config: 1;
type: 1;
systemId: 1;
}>>;
getToken(): Promise<import("oak-domain/lib/types").SelectRowShape<ED["token"]["Schema"], {
id: 1;

View File

@ -17,13 +17,12 @@ class GeneralRuntimeContext extends UniversalContext_1.UniversalContext {
name: 1,
config: 1,
type: 1,
systemId: 1,
},
filter: {
id: this.applicationId,
}
}, this);
application.type;
const t = 'type';
return application;
}
async getToken() {

View File

@ -1,5 +1,6 @@
import { GeneralRuntimeContext } from '../RuntimeContext';
import { EntityDict } from 'oak-app-domain/EntityDict';
import { EntityDict } from 'oak-app-domain';
import { WechatMpEnv } from 'oak-app-domain/Token/Schema';
export declare function loginMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(params: {
code: string;
}, context: Cxt): Promise<string>;
@ -7,4 +8,7 @@ export declare function loginByPassword<ED extends EntityDict, Cxt extends Gener
password: string;
mobile: string;
}, context: Cxt): Promise<string>;
export declare function loginWechatMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(code: string, context: Cxt): Promise<string>;
export declare function loginWechatMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>({ code, env }: {
code: string;
env: WechatMpEnv;
}, context: Cxt): Promise<string>;

View File

@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.loginWechatMp = exports.loginByPassword = exports.loginMp = void 0;
const assert_1 = __importDefault(require("assert"));
const oak_wechat_sdk_1 = __importDefault(require("oak-wechat-sdk"));
const lodash_1 = require("lodash");
async function loginMp(params, context) {
const { rowStore } = context;
throw new Error('method not implemented!');
@ -22,12 +24,163 @@ async function loginByPassword(params, context) {
throw new Error('method not implemented!');
}
exports.loginByPassword = loginByPassword;
async function loginWechatMp(code, context) {
async function loginWechatMp({ code, env }, context) {
const { rowStore } = context;
const application = await context.getApplication();
const { type, config } = application;
(0, assert_1.default)(type === 'wechatMp' || config.type === 'wechatMp');
const config2 = config;
new Error('method not implemented!');
const { appId, appSecret } = config2;
const wechatInstance = oak_wechat_sdk_1.default.getInstance(appId, appSecret, 'wechatMp');
const { sessionKey, openId, unionId } = await wechatInstance.code2Session(code);
const { result: [wechatUser] } = await rowStore.select('wechatUser', {
data: {
id: 1,
userId: 1,
unionId: 1,
},
filter: {
applicationId: application.id,
openId,
}
}, context);
const id = await generateNewId();
if (wechatUser) {
const wechatUser2 = wechatUser;
const wechatUserUpdateData = {
sessionKey,
};
if (unionId !== wechatUser.unionId) {
(0, lodash_1.assign)(wechatUserUpdateData, {
unionId,
});
}
if (wechatUser2.userId) {
await rowStore.operate('token', {
action: 'disable',
data: {},
filter: {
applicationId: application.id,
ableState: 'enabled',
userId: wechatUser2.userId,
playerId: wechatUser2.userId,
},
}, context);
}
else {
// 创建user
(0, lodash_1.assign)(wechatUserUpdateData, {
user: {
action: 'create',
data: {
id: await generateNewId(),
name: '',
nickname: ''
},
},
});
await rowStore.operate('token', {
action: 'create',
data: {
id,
userId: wechatUser2.userId,
playerId: wechatUser2.userId,
applicationId: application.id,
entity: 'wechatUser',
entityId: wechatUser2.id,
wechatUser: {
action: 'update',
data: wechatUserUpdateData,
},
env
}
}, context);
}
return id;
}
else if (unionId) {
// 如果有unionId查找同一个system下有没有相同的unionId
const { result: [wechatUser2] } = await rowStore.select('wechatUser', {
data: {
id: 1,
userId: 1,
unionId: 1,
},
filter: {
application: {
systemId: application.systemId,
},
unionId,
}
}, context);
if (wechatUser2 && wechatUser2.userId) {
await rowStore.operate('token', {
action: 'disable',
data: {},
filter: {
applicationId: application.id,
ableState: 'enabled',
userId: wechatUser2.userId,
playerId: wechatUser2.userId,
},
}, context);
const wechatUserCreateData = {
id: await generateNewId(),
sessionKey,
unionId,
origin: 'mp',
openId,
applicationId: application.id,
userId: wechatUser2.userId,
};
await rowStore.operate('token', {
action: 'create',
data: {
id,
userId: wechatUser2.userId,
playerId: wechatUser2.userId,
applicationId: application.id,
wechatUser: {
action: 'create',
data: wechatUserCreateData,
},
env,
}
}, context);
return id;
}
}
// 到这里都是要同时创建wechatUser和user对象了
const userData = {
id: await generateNewId(),
};
const wechatUserCreateData = {
id: await generateNewId(),
sessionKey,
unionId,
origin: 'mp',
openId,
applicationId: application.id,
user: {
action: 'create',
data: userData,
}
};
await rowStore.operate('token', {
action: 'create',
data: {
id,
userId: userData.id,
playerId: userData.id,
applicationId: application.id,
wechatUser: {
action: 'create',
data: wechatUserCreateData,
},
env,
}
}, context);
return id;
}
exports.loginWechatMp = loginWechatMp;
/* export type AspectDict<ED extends EntityDict> = {

View File

@ -1,5 +1,5 @@
import { Checker } from "oak-domain/lib/types";
import { EntityDict } from 'oak-app-domain/EntityDict';
import { EntityDict } from 'oak-app-domain';
import { GeneralRuntimeContext } from '../RuntimeContext';
declare const checkers: Checker<EntityDict, 'address', GeneralRuntimeContext<EntityDict>>[];
export default checkers;

File diff suppressed because one or more lines are too long

View File

@ -2,10 +2,32 @@ import { String } from 'oak-domain/lib/types/DataType';
import { Schema as User } from './User';
import { Schema as Application } from './Application';
import { EntityShape } from 'oak-domain/lib/types/Entity';
export declare type WechatMpEnv = {
type: 'wechatMp';
brand: string;
model: string;
pixelRatio: number;
screenWidth: number;
screenHeight: number;
windowWidth: number;
windowHeight: number;
statusBarHeight: number;
language: string;
version: string;
system: string;
platform: string;
fontSizeSetting: number;
SDKVersion: string;
};
export declare type WebEnv = {
type: 'web';
};
export declare type Environment = WechatMpEnv | WebEnv;
export interface Schema extends EntityShape {
application: Application;
entity: String<32>;
entityId: String<64>;
user?: User;
player?: User;
env: Environment;
}

View File

@ -2,7 +2,7 @@ import { String, Text, Image, Datetime } from 'oak-domain/lib/types/DataType';
import { Schema as ExtraFile } from './ExtraFile';
import { EntityShape } from 'oak-domain/lib/types/Entity';
export interface Schema extends EntityShape {
name: String<16>;
name?: String<16>;
nickname?: String<64>;
password?: Text;
birth?: Datetime;

View File

@ -7,7 +7,7 @@ export interface Schema extends EntityShape {
origin: 'mp' | 'public';
openId?: String<32>;
unionId?: String<32>;
accessToken: String<32>;
accessToken?: String<32>;
sessionKey?: String<64>;
subscribed?: Boolean;
subscribedAt?: Datetime;

View File

@ -1,10 +1,11 @@
import { EntityDict } from 'oak-app-domain';
import { Feature } from 'oak-frontend-base';
import { Aspect, Context } from 'oak-domain/lib/types';
import { WechatMpEnv } from 'oak-app-domain/Token/Schema';
export declare class Token<ED extends EntityDict, Cxt extends Context<ED>, AD extends Record<string, Aspect<ED, Cxt>>> extends Feature<ED, Cxt, AD> {
private token?;
loginByPassword(mobile: string, password: string): Promise<void>;
loginWechatMp(code: string): Promise<void>;
loginWechatMp(code: string, env: WechatMpEnv): Promise<void>;
logout(): Promise<void>;
getToken(): string | undefined;
}

View File

@ -13,8 +13,11 @@ class Token extends oak_frontend_base_1.Feature {
async loginByPassword(mobile, password) {
this.token = await this.getAspectProxy().loginByPassword({ password, mobile });
}
async loginWechatMp(code) {
this.token = await this.getAspectProxy().loginWechatMp(code);
async loginWechatMp(code, env) {
this.token = await this.getAspectProxy().loginWechatMp({
code,
env,
});
}
async logout() {
this.token = undefined;

View File

@ -6,7 +6,8 @@
"lodash": "^4.17.21",
"oak-domain": "file:../oak-domain",
"oak-frontend-base": "file:../oak-frontend-base",
"oak-memory-tree-store": "file:../oak-memory-tree-store"
"oak-memory-tree-store": "file:../oak-memory-tree-store",
"oak-wechat-sdk": "file:../oak-wechat-sdk"
},
"devDependencies": {
"@babel/cli": "^7.12.13",

View File

@ -2,7 +2,7 @@ import { unset } from 'lodash';
import { buildSchema, analyzeEntities } from 'oak-domain/src/compiler/schemalBuilder';
process.env.NODE_ENV = 'development';
process.env.COMPLING_BASE_ENTITY_DICT = 'yes';
process.env.COMPLING_AS_LIB = 'yes';
analyzeEntities('src/entities');
buildSchema('src/base-app-domain/');
unset(process.env, 'COMPLING_BASE_ENTITY_DICT');
unset(process.env, 'COMPLING_AS_LIB');

View File

@ -82,9 +82,9 @@ function processRepeatedAdCode(districts: any[], parentAdCode: string) {
async function main() {
const areasss: FormCreateData<Area>[] = [];
const streetsss: FormCreateData<Area>[] = [];
function saveAreas(areas: any[], parentId: string | null, depth: 0 | 1 | 2 | 3 | 4, dest: FormCreateData<Area>[] = areasss) {
const areaDebug: FormCreateData<Area>[] = [];
const areaTotal: FormCreateData<Area>[] = [];
function saveAreas(areas: any[], parentId: string | null, depth: 0 | 1 | 2 | 3 | 4, dest: FormCreateData<Area>[] = areaDebug) {
areas.forEach(
(ele) => {
const { adcode, center, citycode, level, name } = ele;
@ -109,32 +109,40 @@ async function main() {
const { districts } = result;
const country = districts[0];
saveAreas([country], null, 0);
saveAreas([country], null, 0, areaTotal);
const { districts: provinces, adcode: countryCode } = country;
saveAreas(provinces, countryCode, 1);
for (const dist of provinces) {
const result2 = await acquireAmap(dist.name, 3);
const { districts: cities, adcode: provinceCode } = result2.districts[0];
// cities.forEach((ele: any) => assert(ele.level === 'city'));
saveAreas(cities, provinceCode, 2);
for (const city of cities) {
const { districts, adcode: cityCode } = city;
const districts2 = processRepeatedAdCode(districts, cityCode);
// districts2.forEach((ele: any) => assert(ele.level === 'district'));
saveAreas(districts2, cityCode, 3);
districts2.forEach(
(district) => {
const { districts: streets, adcode: districtCode } = district;
const streets2 = processRepeatedAdCode(streets, districtCode);
// streets2.forEach((ele: any) => assert(ele.level === 'street'));
saveAreas(streets2, districtCode, 4, streetsss);
}
);
async function saveBelowProvinces(dest: FormCreateData<Area>[], limit?: number) {
for (const dist of provinces) {
const result2 = await acquireAmap(dist.name, 3);
const { districts: cities, adcode: provinceCode } = result2.districts[0];
// cities.forEach((ele: any) => assert(ele.level === 'city'));
const cities2 = limit ? cities.slice(0, limit) : cities;
saveAreas(cities2, provinceCode, 2, dest);
for (const city of cities2) {
const { districts, adcode: cityCode } = city;
const districts2 = processRepeatedAdCode(districts, cityCode);
const districts3 = limit ? districts2.slice(0, limit) : districts2;
// districts2.forEach((ele: any) => assert(ele.level === 'district'));
saveAreas(districts3, cityCode, 3, dest);
districts2.forEach(
(district) => {
const { districts: streets, adcode: districtCode } = district;
const streets2 = processRepeatedAdCode(streets, districtCode);
const streets3 = limit ? streets2.slice(0, limit) : streets2;
// streets2.forEach((ele: any) => assert(ele.level === 'street'));
saveAreas(streets3, districtCode, 4, dest);
}
);
}
}
}
writeFileSync(`${__dirname}/../src/data/area-debug.json`, JSON.stringify(areasss));
writeFileSync(`${__dirname}/../src/data/area.json`, JSON.stringify(areasss.concat(streetsss)));
await saveBelowProvinces(areaDebug, 1);
await saveBelowProvinces(areaTotal);
writeFileSync(`${__dirname}/../src/data/area-debug.json`, JSON.stringify(areaDebug));
writeFileSync(`${__dirname}/../src/data/area.json`, JSON.stringify(areaTotal));
}

View File

@ -1,7 +1,7 @@
import { SelectionResult } from 'oak-domain/lib/types';
import { UniversalContext } from 'oak-domain/lib/store/UniversalContext';
import { EntityDict } from 'oak-app-domain/EntityDict';
import { EntityDict } from 'oak-app-domain';
import { RowStore } from 'oak-domain/lib/types';
@ -21,11 +21,12 @@ export abstract class GeneralRuntimeContext<ED extends EntityDict> extends Unive
name: 1,
config: 1,
type: 1,
systemId: 1,
},
filter: {
id: this.applicationId,
}
}, this) as SelectionResult<EntityDict['application']['Schema'], {id: 1, name: 1, config: 1, type: 1}>;
}, this) as SelectionResult<EntityDict['application']['Schema'], {id: 1, name: 1, config: 1, type: 1, systemId: 1}>;
return application;
}

View File

@ -1,7 +1,13 @@
import { GeneralRuntimeContext } from '../RuntimeContext';
import { EntityDict } from 'oak-app-domain/EntityDict';
import { EntityDict } from 'oak-app-domain';
import assert from 'assert';
import { WechatMpConfig } from 'oak-app-domain/Application/Schema';
import WechatSDK from 'oak-wechat-sdk';
import { CreateOperationData as CreateToken, WechatMpEnv } from 'oak-app-domain/Token/Schema';
import { CreateOperationData as CreateWechatUser } from 'oak-app-domain/WechatUser/Schema';
import { CreateOperationData as CreateUser } from 'oak-app-domain/User/Schema';
import { assign } from 'lodash';
import { SelectRowShape } from 'oak-domain/lib/types';
export async function loginMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(params: { code: string }, context: Cxt): Promise<string> {
const { rowStore } = context;
@ -11,7 +17,7 @@ export async function loginMp<ED extends EntityDict, Cxt extends GeneralRuntimeC
export async function loginByPassword<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(params: { password: string, mobile: string }, context: Cxt): Promise<string> {
const { rowStore } = context;
const { result: [mobile]} = await rowStore.select('mobile', {
const { result: [mobile] } = await rowStore.select('mobile', {
data: {
id: 1,
mobile: 1,
@ -22,19 +28,182 @@ export async function loginByPassword<ED extends EntityDict, Cxt extends General
throw new Error('method not implemented!');
}
export async function loginWechatMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>(code: string, context: Cxt): Promise<string> {
export async function loginWechatMp<ED extends EntityDict, Cxt extends GeneralRuntimeContext<ED>>({ code, env }: {
code: string;
env: WechatMpEnv;
}, context: Cxt): Promise<string> {
const { rowStore } = context;
const application = await context.getApplication();
const { type, config } = application;
assert (type === 'wechatMp' || config.type === 'wechatMp');
assert(type === 'wechatMp' || config.type === 'wechatMp');
const config2 = config as WechatMpConfig;
const { appId, appSecret } = config2;
const wechatInstance = WechatSDK.getInstance(appId, appSecret, 'wechatMp');
const result = await fetch(`https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code`);
const json = await result.json();
console.log(json);
const { sessionKey, openId, unionId } = await wechatInstance.code2Session(code);
throw new Error('method not implemented!');
const { result: [wechatUser] } = await rowStore.select('wechatUser', {
data: {
id: 1,
userId: 1,
unionId: 1,
},
filter: {
applicationId: application.id,
openId,
}
}, context);
const id = await generateNewId();
if (wechatUser) {
const wechatUser2 = wechatUser as SelectRowShape<EntityDict['wechatUser']['Schema'], {
id: 1,
userId: 1,
unionId: 1,
}>;
const wechatUserUpdateData = {
sessionKey,
};
if (unionId !== wechatUser.unionId as any) {
assign(wechatUserUpdateData, {
unionId,
});
}
if (wechatUser2.userId) {
await rowStore.operate('token', {
action: 'disable',
data: {
},
filter: {
applicationId: application.id,
ableState: 'enabled',
userId: wechatUser2.userId,
playerId: wechatUser2.userId,
},
}, context);
}
else {
// 创建user
assign(wechatUserUpdateData, {
user: {
action: 'create',
data: {
id: await generateNewId(),
name: '',
nickname: ''
},
},
});
await rowStore.operate('token', {
action: 'create',
data: {
id,
userId: wechatUser2.userId as string,
playerId: wechatUser2.userId as string,
applicationId: application.id,
entity: 'wechatUser',
entityId: wechatUser2.id as string,
wechatUser: {
action: 'update',
data: wechatUserUpdateData,
},
env
} as CreateToken
}, context);
}
return id;
}
else if (unionId) {
// 如果有unionId查找同一个system下有没有相同的unionId
const { result: [wechatUser2] } = await rowStore.select('wechatUser', {
data: {
id: 1,
userId: 1,
unionId: 1,
},
filter: {
application: {
systemId: application.systemId,
},
unionId,
}
}, context);
if (wechatUser2 && wechatUser2.userId) {
await rowStore.operate('token', {
action: 'disable',
data: {
},
filter: {
applicationId: application.id,
ableState: 'enabled',
userId: wechatUser2.userId,
playerId: wechatUser2.userId,
},
}, context);
const wechatUserCreateData: CreateWechatUser = {
id: await generateNewId(),
sessionKey,
unionId,
origin: 'mp',
openId,
applicationId: application.id,
userId: wechatUser2.userId,
};
await rowStore.operate('token', {
action: 'create',
data: {
id,
userId: wechatUser2.userId,
playerId: wechatUser2.userId,
applicationId: application.id,
wechatUser: {
action: 'create',
data: wechatUserCreateData,
},
env,
}
}, context);
return id;
}
}
// 到这里都是要同时创建wechatUser和user对象了
const userData : CreateUser = {
id: await generateNewId(),
};
const wechatUserCreateData: CreateWechatUser = {
id: await generateNewId(),
sessionKey,
unionId,
origin: 'mp',
openId,
applicationId: application.id,
user: {
action: 'create',
data: userData,
}
};
await rowStore.operate('token', {
action: 'create',
data: {
id,
userId: userData.id,
playerId: userData.id,
applicationId: application.id,
wechatUser: {
action: 'create',
data: wechatUserCreateData,
},
env,
}
}, context);
return id;
}
/* export type AspectDict<ED extends EntityDict> = {

View File

@ -1,6 +1,6 @@
import { isMobile } from 'oak-domain/lib/utils/validator';
import { OakInputIllegalException, Checker } from "oak-domain/lib/types";
import { EntityDict } from 'oak-app-domain/EntityDict';
import { EntityDict } from 'oak-app-domain';
import { checkAttributesNotNull } from '../utils/check';
import { GeneralRuntimeContext } from '../RuntimeContext';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,31 @@ import { Schema as User } from './User';
import { Schema as Application } from './Application';
import { AbleAction } from 'oak-domain/lib/actions/action';
import { EntityShape } from 'oak-domain/lib/types/Entity';
// https://developers.weixin.qq.com/miniprogram/dev/api/base/system/wx.getSystemInfoSync.html
export type WechatMpEnv = {
type: 'wechatMp',
brand: string; // 设备品牌
model: string; // 设备型号
pixelRatio: number; // 设备像素比
screenWidth: number; // 屏幕宽度
screenHeight: number; // 屏幕高度
windowWidth: number; // 窗口宽度
windowHeight: number; // 窗口高度
statusBarHeight: number; // 状态栏高度
language: string; // 语言
version: string; // 微信版本号
system: string; // 操作系统及版本
platform: string; // 平台
fontSizeSetting: number; // 字体大小
SDKVersion: string; // 基础库版本
};
export type WebEnv = {
type: 'web',
};
export type Environment = WechatMpEnv | WebEnv;
export interface Schema extends EntityShape {
application: Application;
@ -10,6 +35,7 @@ export interface Schema extends EntityShape {
entityId: String<64>;
user?: User;
player?: User;
env: Environment;
};
type Action = AbleAction;

View File

@ -5,7 +5,7 @@ import { Schema as ExtraFile } from './ExtraFile';
import { EntityShape } from 'oak-domain/lib/types/Entity';
export interface Schema extends EntityShape {
name: String<16>;
name?: String<16>;
nickname?: String<64>;
password?: Text;
birth?: Datetime;

View File

@ -8,7 +8,7 @@ export interface Schema extends EntityShape {
origin: 'mp' | 'public';
openId?: String<32>;
unionId?: String<32>;
accessToken: String<32>;
accessToken?: String<32>;
sessionKey?: String<64>;
subscribed?: Boolean;
subscribedAt?: Datetime;

View File

@ -1,6 +1,7 @@
import { EntityDict } from 'oak-app-domain';
import { Action, Feature } from 'oak-frontend-base';
import { Aspect, Context } from 'oak-domain/lib/types';
import { WechatMpEnv } from 'oak-app-domain/Token/Schema';
export class Token<ED extends EntityDict, Cxt extends Context<ED>, AD extends Record<string, Aspect<ED, Cxt>>> extends Feature<ED, Cxt, AD> {
@ -12,8 +13,11 @@ export class Token<ED extends EntityDict, Cxt extends Context<ED>, AD extends Re
}
@Action
async loginWechatMp(code: string) {
this.token = await this.getAspectProxy().loginWechatMp(code);
async loginWechatMp(code: string, env: WechatMpEnv) {
this.token = await this.getAspectProxy().loginWechatMp({
code,
env,
});
}
@Action

View File

@ -7,5 +7,6 @@ import { initialize } from '../features';
declare global {
const OakPage: MakeOakPage<EntityDict, GeneralRuntimeContext<EntityDict>, typeof aspectDict, ReturnType<typeof initialize>>;
const OakComponent: MakeOakComponent<EntityDict, GeneralRuntimeContext<EntityDict>, typeof aspectDict, ReturnType<typeof initialize>>;
const generateNewId: () => Promise<string>;
}
export {}

View File

@ -75,6 +75,7 @@
"node_modules",
"**/*.spec.ts",
"test",
"scripts"
"scripts",
"wechatMp"
]
}

View File

@ -1,3 +1,4 @@
import { WechatMpEnv } from "oak-app-domain/Token/Schema";
OakPage({
path: 'token-login',
@ -26,7 +27,8 @@ OakPage({
methods: {
async onLoginClicked(options: WechatMiniprogram.Touch) {
const { code } = await wx.login();
await this.features.token.loginWechatMp(code);
const env = await wx.getSystemInfo();
await this.features.token.loginWechatMp(code, Object.assign(env, { type: 'wechatMp' }) as WechatMpEnv);
},
onReturnClicked() {